///|
fn first_uri_delimiter(value : String) -> Int {
  let mut result = value.length()
  for index = 0; index < value.length(); index = index + 1 {
    let code = value.code_unit_at(index)
    if code == '/' || code == '?' || code == '#' {
      result = index
      break
    }
  }
  result
}

///|
fn count_code_unit(value : String, expected : UInt16) -> Int {
  let mut count = 0
  for index = 0; index < value.length(); index = index + 1 {
    if value.code_unit_at(index) == expected {
      count = count + 1
    }
  }
  count
}

///|
fn normalized_port(value : String) -> String? {
  if value == "" {
    return None
  }
  let parsed = parse_decimal_int(value)
  match parsed {
    Some(port) =>
      if port >= 0 && port <= 65_535 {
        Some(port.to_string())
      } else {
        None
      }
    None => None
  }
}

///|
fn normalize_authority(authority : String, scheme : String) -> String? {
  if authority == "" || authority.contains("@") {
    return None
  }
  if authority.has_prefix("[") {
    guard authority.find("]") is Some(close) else { return None }
    let host = authority.sub(start=0, end=close + 1).to_lower().to_owned()
    let suffix = authority.sub(start=close + 1).to_owned()
    if suffix == "" {
      return Some(host)
    }
    if !suffix.has_prefix(":") {
      return None
    }
    let raw_port = suffix.sub(start=1).to_owned()
    guard normalized_port(raw_port) is Some(port) else { return None }
    if (scheme == "http" && port == "80") ||
      (scheme == "https" && port == "443") {
      Some(host)
    } else {
      Some("\{host}:\{port}")
    }
  } else {
    if count_code_unit(authority, ':') > 1 {
      return None
    }
    let (raw_host, raw_port) = match authority.rev_split_once(":") {
      Some((host, port)) => (host.to_owned(), Some(port.to_owned()))
      None => (authority, None)
    }
    let host = raw_host.to_lower()
    if host == "" {
      return None
    }
    match raw_port {
      Some(raw) => {
        guard normalized_port(raw) is Some(port) else { return None }
        if (scheme == "http" && port == "80") ||
          (scheme == "https" && port == "443") {
          Some(host)
        } else {
          Some("\{host}:\{port}")
        }
      }
      None => Some(host)
    }
  }
}

///|
fn remove_dot_segments(path : String) -> String {
  let trailing = path.has_suffix("/") ||
    path.has_suffix("/.") ||
    path.has_suffix("/..")
  let segments : Array[String] = []
  for view in path.split("/") {
    let segment = view.to_owned()
    match segment {
      "." => ()
      ".." => if segments.length() > 1 { ignore(segments.pop()) }
      _ => segments.push(segment)
    }
  }
  let mut result = segments.join("/")
  if result == "" {
    result = "/"
  } else if !result.has_prefix("/") {
    result = "/\{result}"
  }
  if trailing && result != "/" && !result.has_suffix("/") {
    result = "\{result}/"
  }
  result
}

///|
/// Normalize an absolute HTTP(S) URI for primary cache lookup.
///
/// Scheme and host are lower-cased, default ports and fragments are removed,
/// an empty path becomes `/`, and literal dot segments are resolved. Query text
/// and percent-encoded octets are otherwise preserved.
pub fn normalize_cache_uri(uri : String) -> String? {
  let trimmed = uri.trim(chars=" \t").to_owned()
  guard trimmed.split_once("://") is Some((raw_scheme, remainder_view)) else {
    return None
  }
  let scheme = raw_scheme.to_lower().to_owned()
  if scheme != "http" && scheme != "https" {
    return None
  }
  let remainder = remainder_view.to_owned()
  let boundary = first_uri_delimiter(remainder)
  let authority_text = remainder.sub(start=0, end=boundary).to_owned()
  guard normalize_authority(authority_text, scheme) is Some(authority) else {
    return None
  }
  let suffix_with_fragment = remainder.sub(start=boundary).to_owned()
  let suffix = match suffix_with_fragment.split_once("#") {
    Some((before, _)) => before.to_owned()
    None => suffix_with_fragment
  }
  let (raw_path, query) = match suffix.split_once("?") {
    Some((path, query)) => (path.to_owned(), Some(query.to_owned()))
    None => (suffix, None)
  }
  if raw_path != "" && !raw_path.has_prefix("/") {
    return None
  }
  let path = remove_dot_segments(if raw_path == "" { "/" } else { raw_path })
  let query_suffix = match query {
    Some(value) => "?\{value}"
    None => ""
  }
  Some("\{scheme}://\{authority}\{path}\{query_suffix}")
}

///|
pub fn cache_origin(uri : String) -> String? {
  guard normalize_cache_uri(uri) is Some(normalized) else { return None }
  guard normalized.split_once("://") is Some((scheme, remainder)) else {
    return None
  }
  let rest = remainder.to_owned()
  let boundary = first_uri_delimiter(rest)
  Some("\{scheme.to_owned()}://\{rest.sub(start=0, end=boundary).to_owned()}")
}

///|
pub fn same_cache_origin(left : String, right : String) -> Bool {
  match (cache_origin(left), cache_origin(right)) {
    (Some(left_origin), Some(right_origin)) => left_origin == right_origin
    _ => false
  }
}

///|
/// Construct the primary key. HEAD shares the GET lookup namespace while
/// retaining its request method in `RequestMeta`.
pub fn primary_cache_key(request : RequestMeta) -> PrimaryCacheKey? {
  guard normalize_cache_uri(request.uri) is Some(uri) else { return None }
  let key_method = if request.http_method == "HEAD" {
    "GET"
  } else {
    request.http_method
  }
  Some(PrimaryCacheKey::new(key_method, uri))
}

///|
pub fn PrimaryCacheKey::from_request(request : RequestMeta) -> PrimaryCacheKey? {
  primary_cache_key(request)
}