///|
/// An inbound HTTP request in ergonomic form: the request line, headers, and the
/// fully-read body. The sugar over `HttpScope` plus a drained `Receive`, so a
/// `Handler` never touches the async transport directly.
pub(all) struct Request {
  http_method : String
  path : String
  query_string : Bytes
  headers : Array[(String, String)]
  body : Bytes
} derive(Eq)

///|
/// An outbound HTTP response: status, headers, and the full body. Mutable so
/// middleware can decorate it before the server serialises it into
/// `HttpResponseStart` + `HttpResponseBody`.
pub(all) struct Response {
  status : Int
  headers : Array[(String, String)]
  body : Bytes
} derive(Eq)

///|
/// A streamed outbound response: the status line, headers, an ordered list of
/// body `chunks` each emitted as its own `HttpResponseBody`, and optional
/// trailing `trailers`. Models ASGI response streaming (multiple body messages
/// with `more_body: true`) and the `http.response.trailers` extension without
/// an async transport. `events` lowers it to the exact event sequence a server
/// would send. `early_hints` (the `http.response.early_hint` extension) are the
/// `103 Early Hints` messages emitted ahead of the final response, each element
/// a list of `Link` header values.
pub(all) struct StreamingResponse {
  status : Int
  headers : Array[(String, String)]
  chunks : Array[Bytes]
  trailers : Array[(String, String)]
  early_hints : Array[Array[String]]
} derive(Eq)

///|
/// Build a `StreamingResponse`. `status` defaults to `200`, `headers`,
/// `trailers` and `early_hints` to empty; `chunks` is the ordered body, each
/// element becoming one `HttpResponseBody` frame.
pub fn StreamingResponse::new(
  chunks~ : Array[Bytes],
  status? : Int = 200,
  headers? : Array[(String, String)] = [],
  trailers? : Array[(String, String)] = [],
  early_hints? : Array[Array[String]] = [],
) -> StreamingResponse {
  { status, headers, chunks, trailers, early_hints }
}

///|
/// Lower a streamed response to the outbound events a server sends: one
/// `HttpResponseEarlyHint` per early-hint message (before the response starts),
/// then an `HttpResponseStart` (with `trailers` set when any are present), then
/// one `HttpResponseBody` per chunk — `more_body: true` on all but the last —
/// and, if there are trailers, a terminating `HttpResponseTrailers`. An empty
/// `chunks` still yields a single empty final body, so the stream is always
/// well-formed.
pub fn StreamingResponse::events(self : StreamingResponse) -> Array[Event] {
  let has_trailers = self.trailers.length() > 0
  let out : Array[Event] = []
  for links in self.early_hints {
    out.push(HttpResponseEarlyHint(links~))
  }
  out.push(
    HttpResponseStart(
      status=self.status,
      headers=self.headers,
      trailers=has_trailers,
    ),
  )
  if self.chunks.length() == 0 {
    out.push(HttpResponseBody(body=b"", more_body=false))
  } else {
    for i = 0; i < self.chunks.length(); i = i + 1 {
      let last = i == self.chunks.length() - 1
      out.push(HttpResponseBody(body=self.chunks[i], more_body=!last))
    }
  }
  if has_trailers {
    out.push(HttpResponseTrailers(headers=self.trailers, more_trailers=false))
  }
  out
}

///|
/// The ergonomic request→response function most handlers are written as. The
/// synchronous sugar the suite lifts onto `AsgiApp` at the server boundary.
pub type Handler = (Request) -> Response

///|
/// A streaming handler: produces a `StreamingResponse` (multi-chunk body,
/// optional trailers) instead of a single buffered `Response`. Driven by
/// `run_http_stream` and the `TestClient`.
pub type StreamHandler = (Request) -> StreamingResponse

///|
/// A handler transformer that wraps a downstream `Handler` to add cross-cutting
/// behaviour. Composed as an onion where the first registered is outermost.
pub type Middleware = (Handler) -> Handler

///|
/// Build a response from raw bytes.
pub fn Response::new(
  status : Int,
  headers : Array[(String, String)],
  body : Bytes,
) -> Response {
  { status, headers, body }
}

///|
/// Look up the first header matching `name`, following ASGI's lowercased-name
/// convention.
pub fn Request::header(self : Request, name : String) -> String? {
  for pair in self.headers {
    if pair.0 == name {
      return Some(pair.1)
    }
  }
  None
}

///|
/// Look up the first response header matching `name`, same convention as
/// `Request::header`.
pub fn Response::header(self : Response, name : String) -> String? {
  for pair in self.headers {
    if pair.0 == name {
      return Some(pair.1)
    }
  }
  None
}

///|
/// Compose middlewares over a base handler. The first element is the outermost
/// wrapper, matching registration order.
pub fn compose(middlewares : Array[Middleware], base : Handler) -> Handler {
  let mut handler = base
  for i = middlewares.length() - 1; i >= 0; i = i - 1 {
    handler = middlewares[i](handler)
  }
  handler
}

///|
/// A `text/plain; charset=utf-8` response whose body is the UTF-8 encoding of
/// `body`. The ergonomic constructor for the common string reply.
pub fn Response::text(status? : Int = 200, body : String) -> Response {
  {
    status,
    headers: [("content-type", "text/plain; charset=utf-8")],
    body: @utf8.encode(body),
  }
}

///|
/// An `application/json` response whose body is the UTF-8 encoding of `value`
/// serialised with `Json::stringify`. The ergonomic constructor for a JSON reply.
pub fn Response::json(status? : Int = 200, value : Json) -> Response {
  {
    status,
    headers: [("content-type", "application/json")],
    body: @utf8.encode(value.stringify()),
  }
}

///|
/// Fold one inbound event into the body buffer, returning whether more chunks
/// should be pulled. `HttpRequest` appends its bytes and continues while
/// `more_body`; any other event (a disconnect, a stray frame) ends the drain.
/// Shared by the async `to_asgi` loop and the synchronous `run_http` driver so
/// both drain identically.
fn absorb(buf : Buffer, event : Event) -> Bool {
  match event {
    HttpRequest(body~, more_body~) => {
      buf.write_bytes(body[:])
      more_body
    }
    _ => false
  }
}

///|
/// Assemble the ergonomic `Request` from an http scope and its fully-drained
/// body.
fn build_request(scope : HttpScope, body : Bytes) -> Request {
  {
    http_method: scope.http_method,
    path: scope.path,
    query_string: scope.query_string,
    headers: scope.headers,
    body,
  }
}

///|
/// Serialise a `Response` into the outbound event pair a server sends: an
/// `HttpResponseStart` carrying status and headers (no trailers), then a single
/// `HttpResponseBody` with the whole body and `more_body: false`. The public
/// `Response::events` for callers that want the lowered form directly.
pub fn Response::events(self : Response) -> Array[Event] {
  [
    HttpResponseStart(status=self.status, headers=self.headers, trailers=false),
    HttpResponseBody(body=self.body, more_body=false),
  ]
}

///|
/// The scope-aware sans-transport core: drain the http request body from
/// `inbound` (accumulating `HttpRequest` chunks until `more_body` is false) and
/// hand `app` the full `HttpScope` alongside the assembled body, returning the
/// outbound events `app` emits. This is the seam a framework binds to — a
/// framework reads what the ergonomic `Request` drops: `root_path` (for mounted
/// sub-apps), `extensions` (to gate a feature on what the server advertises),
/// `client`/`server` peers, the `asgi` handshake, and `state` (the map a
/// lifespan startup seeds and the server copies onto every request scope). It is
/// the synchronous analog of the ASGI `(scope, receive, send)` callable for the
/// common drain-then-handle shape; `run_http_app` is the `Request`-level wrapper
/// over it. Non-http scopes yield `[]`.
pub fn run_http_scoped(
  app : (HttpScope, Bytes) -> Array[Event],
  scope : Scope,
  inbound : Array[Event],
) -> Array[Event] {
  match scope {
    Http(hs) => {
      let buf = Buffer()
      for event in inbound {
        if !absorb(buf, event) {
          break
        }
      }
      app(hs, buf.to_bytes())
    }
    _ => []
  }
}

///|
/// The `Request`-level sans-transport core: drain the http request body from
/// `inbound`, hand the assembled `Request` to `app`, and return the outbound
/// events `app` emits. The ergonomic wrapper over `run_http_scoped` for apps
/// that only need the request line, headers, and body — every server and the
/// `TestClient` drive a `Handler` through it. Non-http scopes yield `[]`.
pub fn run_http_app(
  app : (Request) -> Array[Event],
  scope : Scope,
  inbound : Array[Event],
) -> Array[Event] {
  run_http_scoped(fn(hs, body) { app(build_request(hs, body)) }, scope, inbound)
}

///|
/// The synchronous counterpart of `to_asgi`: drive a `Handler` over an already
/// materialised inbound event sequence and return the outbound events a server
/// would send — `[HttpResponseStart, HttpResponseBody]` for an http scope, `[]`
/// otherwise. A thin `run_http_app` wrapper over `Response::events`, the
/// sans-transport core `to_asgi` mirrors with async `receive`/`send`.
pub fn run_http(
  handler : Handler,
  scope : Scope,
  inbound : Array[Event],
) -> Array[Event] {
  run_http_app(fn(req) { handler(req).events() }, scope, inbound)
}

///|
/// Drive a streaming `StreamHandler` over an inbound event sequence, returning
/// the full outbound stream — `HttpResponseStart`, one `HttpResponseBody` per
/// chunk (`more_body: true` on all but the last), and a trailing
/// `HttpResponseTrailers` when the response carries trailers. The streaming
/// counterpart of `run_http`.
pub fn run_http_stream(
  handler : StreamHandler,
  scope : Scope,
  inbound : Array[Event],
) -> Array[Event] {
  run_http_app(fn(req) { handler(req).events() }, scope, inbound)
}

///|
/// Lift a synchronous `Handler` onto the load-bearing `AsgiApp` the server binds
/// to. For an http scope it drains the request body — looping `receive()` and
/// accumulating `HttpRequest` chunks until `more_body` is false — assembles a
/// `Request`, runs the handler, then emits `HttpResponseStart` followed by a
/// single `HttpResponseBody`. Non-http scopes (websocket, lifespan) are no-ops:
/// this sugar covers request→response handlers only. Shares its drain, request
/// assembly, and response serialisation with `run_http`, which tests the same
/// logic without the async transport.
pub fn to_asgi(handler : Handler) -> AsgiApp {
  (scope, receive, send) => {
    match scope {
      Http(hs) => {
        let buf = Buffer()
        while absorb(buf, receive()) {

        }
        let resp = handler(build_request(hs, buf.to_bytes()))
        for event in resp.events() {
          send(event)
        }
      }
      _ => ()
    }
  }
}