///|
/// A responder is newtype of an asynchronous function that takes a status code
/// and headers, sends the response status line and headers, and returns a writer
/// for the response body.
///
/// **Important Notes**: The responder is only valid during request handling.
/// Do not store it or use it after the response cycle completes.
pub(all) struct Responder(async (Int, Headers) -> &WriteFlusher)

///|
fn Responder::from_conn(conn : TrackedServerConnection) -> Responder {
  Responder((status, h) => {
    // TODO: raise error if upgraded
    conn.send_response(
      status,
      status_to_reason_phrase(status),
      extra_headers=h.store.headers,
      cookies=h.store.set_cookie.map(str => parse_set_cookie(str)),
    )
    conn
  })
}

///|
fn Responder::track_committed(
  self : Responder,
  committed : Ref[Bool],
) -> Responder {
  Responder((status, headers) => {
    guard !committed.val else { fail("Response already committed") }
    let writer = (self.0)(status, headers)
    committed.val = true
    writer
  })
}

///|
/// Sends the status line and headers, then returns the body writer.
pub async fn Responder::respond(
  self : Responder,
  status? : Int = 200,
  headers? : &ToHeaders,
) -> &WriteFlusher {
  (self.0)(
    status,
    match headers {
      None => Headers::default()
      Some(h) => h.to_headers()
    },
  )
}

///|
/// Sends a response without writing a body.
pub async fn Responder::send_void(
  self : Responder,
  status? : Int = 200,
  headers? : &ToHeaders,
) -> Unit {
  let _ = self.respond(status~, headers?)
}

///|
/// Sends a JSON response with the appropriate content type.
pub async fn Responder::send_json(
  self : Responder,
  json : Json,
  status? : Int = 200,
  headers? : &ToHeaders,
  indent? : Int,
) -> Unit {
  self
  .respond(
    status~,
    headers=[
      ("Content-Type", "application/json"),
      ..iter_optional_to_headers(headers),
    ],
  )
  .write(json.stringify(indent?))
}

///|
/// Sends a plain text response with UTF-8 content type.
pub async fn Responder::send_text(
  self : Responder,
  text : String,
  status? : Int = 200,
  headers? : &ToHeaders,
) -> Unit {
  self
  .respond(
    status~,
    headers=[
      ("Content-Type", "text/plain; charset=utf-8"),
      ..iter_optional_to_headers(headers),
    ],
  )
  .write(text)
}

///|
/// Sends an HTML response with UTF-8 content type.
pub async fn Responder::send_html(
  self : Responder,
  html : Html,
  status? : Int = 200,
  headers? : &ToHeaders,
) -> Unit {
  let w = self.respond(
    status~,
    headers=[
      ("Content-Type", "text/html; charset=utf-8"),
      ..iter_optional_to_headers(headers),
    ],
  )
  (html.f)(raw => w.write(raw))
}

///|
/// Upgrades the current HTTP connection to a WebSocket connection.
///
/// Returns A WebSocket connection object for bidirectional communication.
///
/// **Important Notes**
/// - After upgrading, do not send an HTTP response. Use the WebSocket connection
///   to communicate with the client instead.
/// - The returned WebSocket connection is only valid during request handling.
///   Do not store it or use it after the response cycle completes.
pub async fn Responder::upgrade(
  _self : Responder,
  req : Request,
) -> @websocket.Conn {
  match req.ctx.conn_info {
    Mock => fail("Mock")
    Normal(raw_req, server_conn, peer_addr) => {
      let ws = server_conn.upgrade(raw_req)
      req.ctx.conn_info = Upgraded(ws, peer_addr)
      ws
    }
    Upgraded(_) => fail("Upgraded")
  }
}