///|
/// Return the canonical representation used for case-insensitive HTTP field
/// names. Leading and trailing whitespace is ignored so adapter input can be
/// normalized at the boundary.
pub fn normalize_header_name(name : String) -> String {
  name.trim().to_lower().to_owned()
}

///|
/// Remove optional whitespace around an HTTP field value without changing
/// meaningful whitespace inside the value.
pub fn normalize_header_value(value : String) -> String {
  value.trim(chars=" \t").to_owned()
}

///|
fn is_ascii_alpha(ch : Char) -> Bool {
  (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}

///|
fn is_ascii_digit(ch : Char) -> Bool {
  ch >= '0' && ch <= '9'
}

///|
fn is_http_token_punctuation(ch : Char) -> Bool {
  ch == '!' ||
  ch == '#' ||
  ch == '$' ||
  ch == '%' ||
  ch == '&' ||
  ch == '\'' ||
  ch == '*' ||
  ch == '+' ||
  ch == '-' ||
  ch == '.' ||
  ch == '^' ||
  ch == '_' ||
  ch == '`' ||
  ch == '|' ||
  ch == '~'
}

///|
/// Check the RFC 9110 token grammar used by HTTP field names.
pub fn is_valid_header_name(name : String) -> Bool {
  let normalized = normalize_header_name(name)
  if normalized == "" {
    return false
  }
  for ch in normalized {
    if !is_ascii_alpha(ch) &&
      !is_ascii_digit(ch) &&
      !is_http_token_punctuation(ch) {
      return false
    }
  }
  true
}

///|
/// A case-insensitive, insertion-ordered collection of HTTP fields.
///
/// Repeated fields are retained as separate values. `contains` distinguishes a
/// missing field from a present field whose value is empty, which is required
/// for correct `Vary` matching.
pub struct HeaderMap {
  values : Map[String, Array[String]]
} derive(Eq, Debug)

///|
pub fn HeaderMap::new() -> HeaderMap {
  HeaderMap::{ values: Map([]) }
}

///|
pub fn HeaderMap::from_pairs(pairs : Array[(String, String)]) -> HeaderMap {
  let headers = HeaderMap::new()
  for pair in pairs {
    ignore(headers.append(pair.0, pair.1))
  }
  headers
}

///|
/// Create a deep copy so callers cannot mutate arrays held by the source map.
pub fn HeaderMap::copy(self : HeaderMap) -> HeaderMap {
  let result = HeaderMap::new()
  self.values.each(fn(name, values) { result.values[name] = values.copy() })
  result
}

///|
/// Replace all existing values for `name`. Invalid field names are rejected and
/// leave the map unchanged.
pub fn HeaderMap::set(self : HeaderMap, name : String, value : String) -> Bool {
  if !is_valid_header_name(name) {
    return false
  }
  self.values[normalize_header_name(name)] = [normalize_header_value(value)]
  true
}

///|
/// Add one value for `name`, retaining previous values.
pub fn HeaderMap::append(
  self : HeaderMap,
  name : String,
  value : String,
) -> Bool {
  if !is_valid_header_name(name) {
    return false
  }
  let key = normalize_header_name(name)
  let normalized = normalize_header_value(value)
  match self.values.get(key) {
    Some(values) => values.push(normalized)
    None => self.values[key] = [normalized]
  }
  true
}

///|
pub fn HeaderMap::remove(self : HeaderMap, name : String) -> Bool {
  let key = normalize_header_name(name)
  if self.values.contains(key) {
    self.values.remove(key)
    true
  } else {
    false
  }
}

///|
pub fn HeaderMap::contains(self : HeaderMap, name : String) -> Bool {
  self.values.contains(normalize_header_name(name))
}

///|
/// Return a defensive copy of every value for `name`.
pub fn HeaderMap::get_all(self : HeaderMap, name : String) -> Array[String] {
  match self.values.get(normalize_header_name(name)) {
    Some(values) => values.copy()
    None => []
  }
}

///|
pub fn HeaderMap::get_first(self : HeaderMap, name : String) -> String? {
  match self.values.get(normalize_header_name(name)) {
    Some(values) => if values.length() == 0 { None } else { Some(values[0]) }
    None => None
  }
}

///|
/// Combine repeated field values using the comma convention used by the cache
/// directives handled by MoonCache.
pub fn HeaderMap::get(self : HeaderMap, name : String) -> String? {
  let values = self.get_all(name)
  if values.length() == 0 {
    None
  } else {
    Some(values.join(", "))
  }
}

///|
pub fn HeaderMap::length(self : HeaderMap) -> Int {
  self.values.length()
}

///|
pub fn HeaderMap::is_empty(self : HeaderMap) -> Bool {
  self.values.is_empty()
}

///|
fn compare_normalized_header_names(left : String, right : String) -> Int {
  let limit = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  for index = 0; index < limit; index = index + 1 {
    let left_code = left.code_unit_at(index)
    let right_code = right.code_unit_at(index)
    if left_code < right_code {
      return -1
    }
    if left_code > right_code {
      return 1
    }
  }
  if left.length() < right.length() {
    -1
  } else if left.length() > right.length() {
    1
  } else {
    0
  }
}

///|
/// Return canonical field names in deterministic lexical order.
pub fn HeaderMap::names(self : HeaderMap) -> Array[String] {
  let result = self.values.keys().to_array()
  result.sort_by(compare_normalized_header_names)
  result
}

///|
/// Return normalized field/value pairs in deterministic name order while
/// preserving value order within each repeated field.
pub fn HeaderMap::pairs(self : HeaderMap) -> Array[(String, String)] {
  let result : Array[(String, String)] = []
  for name in self.names() {
    for value in self.get_all(name) {
      result.push((name, value))
    }
  }
  result
}

///|
fn is_sensitive_header_name(name : String) -> Bool {
  let key = normalize_header_name(name)
  key == "authorization" ||
  key == "proxy-authorization" ||
  key == "cookie" ||
  key == "set-cookie"
}

///|
/// Return a copy suitable for diagnostics. Credential and cookie values are
/// replaced while their presence remains visible.
pub fn HeaderMap::redacted(self : HeaderMap) -> HeaderMap {
  let result = HeaderMap::new()
  for name in self.names() {
    if is_sensitive_header_name(name) {
      ignore(result.set(name, ""))
    } else {
      for value in self.get_all(name) {
        ignore(result.append(name, value))
      }
    }
  }
  result
}

///|
/// Overlay every field from `updates`, replacing existing repeated values.
pub fn HeaderMap::overlay(self : HeaderMap, updates : HeaderMap) -> HeaderMap {
  let result = self.copy()
  for name in updates.names() {
    let values = updates.get_all(name)
    if values.length() > 0 {
      ignore(result.set(name, values[0]))
      for index = 1; index < values.length(); index = index + 1 {
        ignore(result.append(name, values[index]))
      }
    }
  }
  result
}