///|
/// A middleware is a newtype of a function that takes a next handler, and returns a new handler.
/// Example:
/// ```moonbit nocheck
/// let auth_middleware = Middleware(next => {
///   (req, res) => {
///     if is_authenticated(req) {
///       next(req, res)
///     } else {
///       res.send_void(status=401)
///     }
///   }
/// })
/// ```
pub(all) struct Middleware((Handler) -> Handler)

///|
/// Returns a middleware that forwards requests without modification.
pub fn Middleware::pass_through() -> Middleware {
  Middleware(next => next)
}

///|
/// Cancels the wrapped handler when the request's serving app begins shutdown.
/// Long-lived streams and WebSocket handlers can use this to stop without
/// waiting for `Config.stop_timeout`. Handler `defer` and `errdefer` blocks
/// still run; async cleanup can use `@async.protect_from_cancel`.
/// Interrupted HTTP connections are closed without finalizing the response.
/// Apply it to a single handler with `Middleware::apply`, or register it with
/// `ModuleContext::add_middleware` for every request.
pub fn cancel_on_shutdown() -> Middleware {
  next => {
    (req, res) => {
      @async.with_task_group(group => {
        group.spawn_bg(
          () => {
            req.app().wait_for_shutdown_requested()
            // return_immediately cancels next but returns normally to the caller.
            // Close the interrupted HTTP response instead of ending or reusing it.
            match req.ctx.conn_info {
              Normal(_, conn, _) => conn.notify_unusable()
              Mock | Upgraded(_, _) => ()
            }
            group.return_immediately(())
          },
          no_wait=true,
        )
        next(req, res)
      })
    }
  }
}

///|
/// Composes two middlewares into one.
/// The current middleware runs before `other`.
pub fn Middleware::compose(self : Middleware, other : Middleware) -> Middleware {
  Middleware(next => (self.0)((other.0)(next)))
}

///|
/// Chains middlewares in declaration order.
pub fn Middleware::chain(middlewares : ArrayView[Middleware]) -> Middleware {
  match middlewares {
    [] => Middleware(next => next)
    [first, .. rest] => first.compose(Middleware::chain(rest))
  }
}

///|
/// Wraps a handler with this middleware.
/// Use this to apply middleware to an individual route handler.
pub fn Middleware::apply(self : Middleware, handler : Handler) -> Handler {
  (self.0)(handler)
}