///|
/// One HTTP field line. Names are normalized to lowercase ASCII while values
/// remain byte-for-byte equivalent as MoonBit strings.
pub(all) struct HeaderField {
  name : String
  value : String
} derive(Debug, Eq)

///|
/// Ordered and duplicate-preserving field collection. Protocol validation can
/// therefore reject repeated singleton fields without a framework hiding them.
pub(all) struct Headers {
  entries : Array[HeaderField]
} derive(Debug, Eq)

///|
pub fn header(name : String, value : String) -> HeaderField {
  { name: ascii_lower(name), value, }
}

///|
pub fn Headers::empty() -> Headers {
  { entries: [], }
}

///|
pub fn Headers::from_array(entries : Array[HeaderField]) -> Headers {
  let copied : Array[HeaderField] = []
  for entry in entries {
    copied.push(header(entry.name, entry.value))
  }
  { entries: copied, }
}

///|
pub fn Headers::add(self : Headers, name : String, value : String) -> Headers {
  let entries = self.entries.copy()
  entries.push(header(name, value))
  { entries, }
}

///|
pub fn Headers::replace(
  self : Headers,
  name : String,
  value : String,
) -> Headers {
  self.without(name).add(name, value)
}

///|
pub fn Headers::without(self : Headers, name : String) -> Headers {
  let normalized = ascii_lower(name)
  let entries : Array[HeaderField] = []
  for entry in self.entries {
    if entry.name != normalized {
      entries.push(entry)
    }
  }
  { entries, }
}

///|
pub fn Headers::values(self : Headers, name : String) -> Array[String] {
  let normalized = ascii_lower(name)
  let values : Array[String] = []
  for entry in self.entries {
    if entry.name == normalized {
      values.push(entry.value)
    }
  }
  values
}

///|
pub fn Headers::first(self : Headers, name : String) -> String? {
  let normalized = ascii_lower(name)
  for entry in self.entries {
    if entry.name == normalized {
      return Some(entry.value)
    }
  }
  None
}

///|
pub fn Headers::last(self : Headers, name : String) -> String? {
  let normalized = ascii_lower(name)
  let mut found : String? = None
  for entry in self.entries {
    if entry.name == normalized {
      found = Some(entry.value)
    }
  }
  found
}

///|
pub fn Headers::count(self : Headers, name : String) -> Int {
  self.values(name).length()
}

///|
pub fn Headers::contains(self : Headers, name : String) -> Bool {
  self.first(name) is Some(_)
}

///|
pub fn Headers::singleton(
  self : Headers,
  name : String,
) -> Result[String?, TusError] {
  let values = self.values(name)
  match values.length() {
    0 => Ok(None)
    1 => Ok(Some(values[0]))
    _ => Err(duplicate_header(ascii_lower(name)))
  }
}

///|
pub fn Headers::required_singleton(
  self : Headers,
  name : String,
  missing_kind : TusErrorKind,
) -> Result[String, TusError] {
  match self.singleton(name) {
    Err(error) => Err(error)
    Ok(None) => Err(missing_header(ascii_lower(name), missing_kind))
    Ok(Some(value)) => Ok(value)
  }
}

///|
pub fn Headers::combined(self : Headers, name : String) -> String? {
  let values = self.values(name)
  if values.is_empty() {
    return None
  }
  let builder = StringBuilder()
  for index, value in values {
    if index > 0 {
      builder.write_string(", ")
    }
    builder.write_string(value)
  }
  Some(builder.to_string())
}

///|
pub fn Headers::len(self : Headers) -> Int {
  self.entries.length()
}

///|
pub fn Headers::as_array(self : Headers) -> Array[HeaderField] {
  self.entries.copy()
}

///|
pub fn Headers::wire_size(self : Headers) -> Int {
  let mut size = 0
  for entry in self.entries {
    size = size + entry.name.length() + 2 + entry.value.length() + 2
  }
  size
}

///|
/// Validate generic HTTP field safety before tus-specific interpretation.
pub fn Headers::validate(
  self : Headers,
  limits : TusLimits,
) -> Result[Unit, TusError] {
  if self.entries.length() > limits.max_header_count {
    return Err(
      tus_error(
        HeaderCountExceeded,
        "TUS_HEADER_COUNT_EXCEEDED",
        "request contains more field lines than configured",
        status=431,
        expected=Some(limits.max_header_count.to_string()),
        actual=Some(self.entries.length().to_string()),
      ),
    )
  }
  let bytes = self.wire_size()
  if bytes > limits.max_header_bytes {
    return Err(
      tus_error(
        HeaderBytesExceeded,
        "TUS_HEADER_BYTES_EXCEEDED",
        "request field section exceeds the configured byte budget",
        status=431,
        expected=Some(limits.max_header_bytes.to_string()),
        actual=Some(bytes.to_string()),
      ),
    )
  }
  for entry in self.entries {
    if !is_http_token(entry.name) {
      return Err(
        tus_error(
          InvalidHeaderName,
          "TUS_HEADER_NAME_INVALID",
          "HTTP field name is not a token",
          header_name=Some(entry.name),
        ),
      )
    }
    if contains_unsafe_field_byte(entry.value) {
      return Err(
        tus_error(
          UnsafeHeaderValue,
          "TUS_HEADER_VALUE_UNSAFE",
          "HTTP field value contains a line break, NUL, or control byte",
          header_name=Some(entry.name),
        ),
      )
    }
  }
  Ok(())
}

///|
pub fn trim_ows(value : String) -> String {
  value.trim(chars=" \t").to_owned()
}

///|
pub fn ascii_equal_ignore_case(left : String, right : String) -> Bool {
  ascii_lower(left) == ascii_lower(right)
}

///|
pub fn ascii_lower(value : String) -> String {
  let builder = StringBuilder()
  for character in value {
    if character >= 'A' && character <= 'Z' {
      builder.write_char((character.to_int() + 32).unsafe_to_char())
    } else {
      builder.write_char(character)
    }
  }
  builder.to_string()
}

///|
pub fn is_http_token(value : String) -> Bool {
  if value.length() == 0 {
    return false
  }
  for character in value {
    if !is_token_character(character) {
      return false
    }
  }
  true
}

///|
fn is_token_character(character : Char) -> Bool {
  if character >= 'a' && character <= 'z' {
    return true
  }
  if character >= 'A' && character <= 'Z' {
    return true
  }
  if character >= '0' && character <= '9' {
    return true
  }
  match character {
    '!'
    | '#'
    | '$'
    | '%'
    | '&'
    | '\''
    | '*'
    | '+'
    | '-'
    | '.'
    | '^'
    | '_'
    | '`'
    | '|'
    | '~' => true
    _ => false
  }
}

///|
fn contains_unsafe_field_byte(value : String) -> Bool {
  for character in value {
    let code = character.to_int()
    if code == 0 ||
      code == 10 ||
      code == 13 ||
      (code < 32 && code != 9) ||
      code == 127 {
      return true
    }
  }
  false
}