///|
/// What a mount prefix routes to: a moonapi sub-application, whose routes, security schemes and
/// lifespan hooks fold into the parent's, or any other ASGI application, which the parent can
/// only forward to.
enum Mount {
  Sub(App)
  Asgi(@moonasgi.AsgiApp)
}

///|
/// A lifespan hook: it gets the lifespan state, where what it stores reaches every request as
/// `ctx.state()`.
pub type Hook = async (Map[String, Json]) -> Unit

///|
/// A request-level middleware (← FastAPI's `@app.middleware("http")`): the request and
/// `call_next`, which runs the rest of the application, in; the response out. The response comes
/// back whole, so a stream behind it is gathered first; a middleware that must keep streams
/// streaming is a `@mid.Layer`.
pub type Middleware = async (
  @moonasgi.Request,
  async (@moonasgi.Request) -> @moonasgi.Response,
) -> @moonasgi.Response

///|
/// A moonapi application: routes and WebSocket routes, the layers around them, exception and
/// per-status handlers, mounts, the security schemes the OpenAPI document surfaces (with their
/// runtime enforcers), lifespan hooks, and the verification clock. Compiles to an ASGI
/// application any server (mooncat) can run.
pub struct App {
  router : @route.Router
  layers : Array[&@mid.Layer]
  exception_handlers : Array[@exceptions.ExceptionHandler]
  security_schemes : Array[@security.DeclaredScheme]
  enforcers : Map[String, @security.SecurityEnforcer]
  status_handlers : Map[Int32, (@ctx.Context) -> @moonasgi.Response]
  mounts : Array[(String, Mount)]
  // Run in registration order when the server starts, and in reverse when it stops,
  // so a resource is torn down before whatever it was built on.
  startup_hooks : Array[Hook]
  shutdown_hooks : Array[Hook]
  // What the emitted document says about itself. `enable_docs` serves the same
  // values, so the page a reader opens is not a different API from the one
  // `App::openapi` describes.
  mut info : @openapi.ApiInfo
  mut clock : () -> Int64
  // The container a route's declared `dependencies` are resolved through.
  mut deps : @di.Deps?
}

///|
/// Create an empty application. The verification clock defaults to `0` (Unix
/// epoch); a server sets a real one with `App::set_clock`, and tests inject a
/// fixed time so token expiry is deterministic.
pub fn App::new() -> App {
  {
    router: @route.Router::new(),
    layers: [],
    exception_handlers: [],
    security_schemes: [],
    enforcers: {},
    status_handlers: {},
    mounts: [],
    startup_hooks: [],
    shutdown_hooks: [],
    info: @openapi.ApiInfo::new(),
    clock: () => 0L,
    deps: None,
  }
}

///|
/// Give the app the dependency container its routes resolve their declared
/// `dependencies` through (← FastAPI's `Depends` wiring, which a route names and
/// the application supplies). The container's value type is erased on the way
/// in, because a route-level dependency runs for its effect and its value is
/// never handed to the handler — the same thing FastAPI's `dependencies=[...]`
/// does with the values it builds.
///
/// A route that declares a dependency the container does not provide is answered
/// with a `500`: the setup the route promised did not happen, and running the
/// handler as though it had is worse than saying so.
pub fn App::depends(self : App, deps : @di.Deps) -> Unit {
  self.deps = Some(deps)
}

///|
/// Set what the OpenAPI document says about this API (← FastAPI's `FastAPI(title=…,
/// description=…, contact=…, license_info=…, servers=…)`). `App::openapi` and the
/// `/openapi.json` route `enable_docs` registers both read it, so the document a
/// client fetches and the one a test builds cannot describe different APIs.
pub fn App::describe(
  self : App,
  title? : String,
  api_version? : String,
  description? : String,
  terms_of_service? : String,
  contact? : @openapi.Contact?,
  license? : @openapi.License?,
  servers? : Array[@openapi.Server],
) -> Unit {
  let cur = self.info
  self.info = {
    title: title.unwrap_or(cur.title),
    api_version: api_version.unwrap_or(cur.api_version),
    description: description.unwrap_or(cur.description),
    terms_of_service: terms_of_service.unwrap_or(cur.terms_of_service),
    contact: contact.unwrap_or(cur.contact),
    license: license.unwrap_or(cur.license),
    servers: servers.unwrap_or(cur.servers),
  }
}

///|
/// Run `hook` when the server starts, before it accepts the first request (← FastAPI's
/// `on_event("startup")`). Hooks run in the order they were registered; one that raises aborts
/// startup, and the server is told why.
pub fn App::on_startup(self : App, hook : Hook) -> Unit {
  self.startup_hooks.push(hook)
}

///|
/// Run `hook` when the server shuts down, after the last request has been served (← FastAPI's
/// `on_event("shutdown")`). Hooks run in reverse registration order, so a resource is released
/// before whatever it was opened from. A hook that raises does not stop the others — shutdown
/// reports the first failure once the rest have run.
pub fn App::on_shutdown(self : App, hook : Hook) -> Unit {
  self.shutdown_hooks.push(hook)
}

///|
/// Set the clock the app reads to verify token expiry when enforcing route
/// security (Unix seconds). A native server passes the wall clock; a test passes
/// a fixed function so expiry is deterministic.
pub fn App::set_clock(self : App, clock : () -> Int64) -> Unit {
  self.clock = clock
}

///|
/// Wrap the application in `layer`, a moonasgi ASGI middleware (← Starlette's
/// `add_middleware`): moonasgi's `Cors`, `Gzip`, `TrustedHosts`, `HttpsRedirect` and
/// `ServerErrors`, or one of your own. The first registered is the outermost, layers and
/// `middleware` alike, and they wrap mounted applications too.
pub fn App::layer(self : App, layer : &@mid.Layer) -> Unit {
  self.layers.push(layer)
}

///|
/// Add a request-level middleware (← FastAPI's `@app.middleware("http")`), placed among the
/// layers in registration order.
pub fn App::middleware(self : App, mw : Middleware) -> Unit {
  self.layers.push(@mid.Wrap(inner => lift(mw, inner)))
}

///|
/// The ASGI application that runs `mw` around `inner` for HTTP requests. `call_next` hands
/// `inner` the request it is given; `inner`'s `receive`, once the body is out, waits for the
/// response to finish before saying `http.disconnect`, as a server does.
fn lift(mw : Middleware, inner : @moonasgi.AsgiApp) -> @moonasgi.AsgiApp {
  async fn(scope, receive, send) {
    guard scope is Http(hs) else { inner(scope, receive, send) }
    let request = request_of(hs, read_body(receive))
    let call_next = async fn(req : @moonasgi.Request) -> @moonasgi.Response {
      let scope = @moonasgi.Scope::Http({
        ..hs,
        http_method: req.http_method,
        path: req.path,
        query_string: req.query_string,
        headers: req.headers,
      })
      let delivered = Ref(false)
      let done = Ref(false)
      let finished = @cond_var.Cond()
      let status = Ref(500)
      let headers : Ref[Array[(String, String)]] = Ref([])
      let body = Buffer()
      let inner_receive = async fn() {
        if !delivered.val {
          delivered.val = true
          return @moonasgi.Event::HttpRequest(body=req.body, more_body=false)
        }
        while !done.val {
          finished.wait()
        }
        @moonasgi.Event::HttpDisconnect
      }
      let inner_send : @moonasgi.Send = event => {
        match event {
          HttpResponseStart(status=s, headers=h, ..) => {
            status.val = s
            headers.val = h
          }
          HttpResponseBody(body=b, more_body~) => {
            body.write_bytes(b)
            if !more_body {
              done.val = true
              finished.broadcast()
            }
          }
          _ => ()
        }
      }
      inner(scope, inner_receive, inner_send)
      done.val = true
      finished.broadcast()
      @moonasgi.Response::new(status.val, headers.val, body.to_bytes())
    }
    send_response(send, mw(request, call_next))
  }
}

///|
/// Register an exception handler. On a raised error the handlers are tried in
/// registration order; the first to return `Some(response)` wins. An
/// unhandled error falls through to the built-in mapping — an `HttpException`
/// becomes its own `status` / `detail`, anything else a `500`.
pub fn App::exception_handler(
  self : App,
  h : @exceptions.ExceptionHandler,
) -> Unit {
  self.exception_handlers.push(h)
}

///|
/// Register a per-status exception handler (← FastAPI's `add_exception_handler`
/// keyed by an HTTP status code). Whenever an error path yields `status` — a
/// routing `404` / `405`, or a raised `HttpException` (or the fallback `500`) —
/// the handler's response replaces the default, so an app can serve a custom
/// error page. Successful handler returns are never rewritten.
pub fn App::add_status_handler(
  self : App,
  status : Int32,
  handler : (@ctx.Context) -> @moonasgi.Response,
) -> Unit {
  self.status_handlers[status] = handler
}

///|
/// Declare a security scheme under `name`, surfaced in the emitted OpenAPI
/// document (`components/securitySchemes` in 3.x, `securityDefinitions` in
/// Swagger 2.0) so the generated spec describes how to authenticate.
/// `description` is the prose shown beside it in the documentation UI.
///
/// This declares only. A route naming this scheme in its `security` is
/// documented as protected and left unguarded, exactly as a FastAPI scheme that
/// no path operation depends on is — the `secure_*` functions are the ones that
/// wire an enforcer.
pub fn App::add_security_scheme(
  self : App,
  name : String,
  scheme : @security.SecurityScheme,
  description~ : String = "",
) -> Unit {
  self.security_schemes.push({ name, kind: scheme, description, })
}