///|
struct Context {
  app : App
  mut conn_info : ConnectionInfo
  mut committed : Bool
  mut userdata : @immut/sorted_map.SortedMap[Int, TypedBox]
}

///|
#warnings("-unused_field")
priv enum ConnectionInfo {
  Mock
  Normal(@http.Request, TrackedServerConnection, @socket.Addr)
  Upgraded(@websocket.Conn, @socket.Addr)
}

///|
fn Context::new(app : App, conn_info : ConnectionInfo) -> Context {
  {
    app,
    committed: false,
    conn_info,
    userdata: @immut/sorted_map.SortedMap::new(),
  }
}

///|
fn Context::connection_unusable(self : Context) -> Bool {
  match self.conn_info {
    Normal(_, conn, _) => conn.is_unusable()
    Mock | Upgraded(_, _) => false
  }
}

///|
/// Returns a typed userdata value previously stored under `key`.
pub fn[T] Context::get_userdata(self : Context, key : TypedKey[T]) -> T? {
  match self.userdata.get(key.val) {
    None => None
    Some(val) => (key.unbox)(val)
  }
}

///|
/// Stores a typed userdata value under `key`.
pub fn[T] Context::set_userdata(
  self : Context,
  key : TypedKey[T],
  val : T,
) -> Unit {
  self.userdata = self.userdata.add(key.val, (key.box)(val))
}

///|
/// Wraps a handler with middlewares. The middlewares will be applied in the order they are given.
/// Example:
/// ```moonbit nocheck
/// ctx.get(
///   "/",
///   ctx.with_middlewares([middleware1, middleware2]) <| ((req, res) => {
///     ...
///   })
/// )
/// ```
pub fn Context::with_middlewares(
  _self : Context,
  middlewares : ArrayView[Middleware],
  handler : Handler,
) -> Handler {
  Middleware::chain(middlewares)(handler)
}