///|
/// A normalized HTTP header. Lookup is case-insensitive, while the original
/// spelling is retained for forwarding and diagnostics.
pub(all) struct Header {
  name : String
  value : String
} derive(Debug, Eq)

///|
pub fn Header::new(name : String, value : String) -> Header {
  { name, value, }
}

///|
pub(all) struct QueryParam {
  name : String
  value : String
} derive(Debug, Eq)

///|
pub fn QueryParam::new(name : String, value : String) -> QueryParam {
  { name, value, }
}

///|
/// The transport-independent request consumed by the matcher.
pub(all) struct HttpRequest {
  verb : String
  path : String
  query : Array[QueryParam]
  headers : Array[Header]
  body : String
} derive(Debug, Eq)

///|
pub fn HttpRequest::new(
  verb : String,
  path : String,
  query : Array[QueryParam],
  headers : Array[Header],
  body : String,
) -> HttpRequest {
  { verb: verb.to_upper(), path, query, headers, body, }
}

///|
pub fn HttpRequest::header(self : HttpRequest, name : String) -> String? {
  let wanted = name.to_lower()
  for header in self.headers {
    if header.name.to_lower() == wanted {
      return Some(header.value)
    }
  }
  None
}

///|
pub fn HttpRequest::query_first(self : HttpRequest, name : String) -> String? {
  for item in self.query {
    if item.name == name {
      return Some(item.value)
    }
  }
  None
}

///|
pub(all) struct HttpResponse {
  status : Int
  headers : Array[Header]
  body : String
} derive(Debug, Eq)

///|
pub fn HttpResponse::new(
  status : Int,
  headers : Array[Header],
  body : String,
) -> HttpResponse {
  { status, headers, body, }
}

///|
pub(all) struct RequestLimits {
  max_headers : Int
  max_body_chars : Int
} derive(Debug, Eq)

///|
pub fn RequestLimits::safe_defaults() -> RequestLimits {
  { max_headers: 128, max_body_chars: 1_048_576, }
}

///|
pub(all) enum ValidationError {
  EmptyMethod
  InvalidPath(String)
  TooManyHeaders(actual~ : Int, limit~ : Int)
  BodyTooLarge(actual~ : Int, limit~ : Int)
  InvalidStatus(Int)
} derive(Debug, Eq)

///|
pub extend Header with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Header with Eq::{not_equal, equal}

///|
pub extend QueryParam with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend QueryParam with Eq::{not_equal, equal}

///|
pub extend HttpRequest with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend HttpRequest with Eq::{not_equal, equal}

///|
pub extend HttpResponse with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend HttpResponse with Eq::{not_equal, equal}

///|
pub extend RequestLimits with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend RequestLimits with Eq::{not_equal, equal}

///|
pub extend ValidationError with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend ValidationError with Eq::{not_equal, equal}

///|
pub fn HttpRequest::validate(
  self : HttpRequest,
  limits : RequestLimits,
) -> Result[Unit, ValidationError] {
  if self.verb.trim().is_empty() {
    return Err(EmptyMethod)
  }
  if !self.path.has_prefix("/") {
    return Err(InvalidPath(self.path))
  }
  if self.headers.length() > limits.max_headers {
    return Err(
      TooManyHeaders(actual=self.headers.length(), limit=limits.max_headers),
    )
  }
  if self.body.length() > limits.max_body_chars {
    return Err(
      BodyTooLarge(actual=self.body.length(), limit=limits.max_body_chars),
    )
  }
  Ok(())
}

///|
pub fn HttpResponse::validate(
  self : HttpResponse,
) -> Result[Unit, ValidationError] {
  if self.status < 100 || self.status > 599 {
    Err(InvalidStatus(self.status))
  } else {
    Ok(())
  }
}