///|
let _request_cookie_key : TypedKey[Map[String, String]] = TypedKey(
  box=cookies => Cookies(cookies),
  unbox=err => {
    match err {
      Cookies(cookies) => Some(cookies)
      _ => None
    }
  },
)

///|
let _request_query_key : TypedKey[QueryParams] = TypedKey(
  box=query => QueryParams(query),
  unbox=err => {
    match err {
      QueryParams(query) => Some(query)
      _ => None
    }
  },
)

///|
/// Represents an incoming HTTP request handled by the application.
pub(all) struct Request {
  /// The request context, which holds per-request state and configuration.
  ctx : Context
  /// The HTTP method of the request (e.g. GET, POST).
  meth : @http.RequestMethod
  /// The request path, excluding the query string.
  path : StringView
  /// The raw query string, if any. This is the part after `?` in the URL.
  search : StringView?
  /// The request headers. This is an immutable view of the headers, and should be used for reading header values.
  headers : Headers
  /// The parsed path parameters for this request, if any. This is populated by the router when a route is matched.
  params : Map[StringView, StringView]
  /// The request body, which can be read as a stream or buffered in memory.
  body : RequestBody
}

///|
/// Returns the app instance handling this request. This can be used to access app-level configuration and state.
pub fn Request::app(self : Request) -> App {
  self.ctx.app
}

///|
/// Returns the client IP chain for this request.
/// When `trust_proxy` is enabled, this prefers `X-Forwarded-For`.
pub fn Request::ips(self : Request) -> ArrayView[StringView] {
  if self.ctx.app.config().trust_proxy &&
    self.headers.get_all("X-Forwarded-For") is x &&
    !x.is_empty() {
    x
  } else {
    match self.ctx.conn_info {
      Mock => []
      Normal(_, _, peer_addr) | Upgraded(_, peer_addr) =>
        [extract_host(peer_addr.to_string())]
    }
  }
}

///|
/// Returns the decoded query parameters for this request.
///
/// The raw query string is available as `request.search`. Query parameters are
/// parsed on demand and cached in the request context. Repeated names are
/// preserved; `get` returns the first value and `get_all` returns all values.
/// Decoding follows `application/x-www-form-urlencoded`: `+` and `%20` decode
/// to a space; encode a literal plus sign as `%2B`.
pub fn Request::query(self : Request) -> QueryParams {
  match self.ctx.get_userdata(_request_query_key) {
    Some(query) => query
    None => {
      let query = parse_query_params(self.search)
      self.ctx.set_userdata(_request_query_key, query)
      query
    }
  }
}

///|
/// Returns the first client IP address for this request, if one is available.
pub fn Request::ip(self : Request) -> StringView? {
  self.ips().get(0)
}

///|
/// Returns the cookies sent with this request as a map of cookie name to value.
/// Cookies are parsed on demand and cached in the request context.
pub fn Request::cookie(self : Request) -> Map[String, String] {
  match self.ctx.get_userdata(_request_cookie_key) {
    Some(cookies) => cookies
    None => {
      let cookies = match self.headers.get("Cookie") {
        Some(raw) => parse_cookie(raw)
        None => {}
      }
      self.ctx.set_userdata(_request_cookie_key, cookies)
      cookies
    }
  }
}