///|
pub fn synthetic_network_header_entries_from_object(
  raw_headers : Map[String, Json],
) -> Array[Json] {
  let headers : Array[Json] = []
  for name, raw_value in raw_headers {
    headers.push(
      synthetic_network_header_string_entry(
        name,
        synthetic_network_header_value_from_json(raw_value),
      ),
    )
  }
  headers
}

///|
pub fn synthetic_network_merge_header_entries(
  headers : Array[Json],
) -> Array[Json] {
  let order : Array[String] = []
  let names : Map[String, String] = {}
  let values : Map[String, String] = {}
  for header in headers {
    let name = match synthetic_network_header_entry_name(header) {
      Some(name) => name
      None => continue
    }
    let value = match synthetic_network_header_entry_string_value(header) {
      Some(value) => value
      None => continue
    }
    let key = name.to_lower()
    if values.contains(key) {
      let merged = values.get(key).unwrap_or("")
      values[key] = merged + ", " + value
    } else {
      order.push(key)
      names[key] = name
      values[key] = value
    }
  }
  let merged : Array[Json] = []
  for key in order {
    let name = names.get(key).unwrap_or(key)
    let value = values.get(key).unwrap_or("")
    merged.push(synthetic_network_header_string_entry(name, value))
  }
  merged
}

///|
pub fn synthetic_network_header_entry_name(header : Json) -> String? {
  match header {
    Object(map) =>
      match map.get("name") {
        Some(String(name)) => Some(name)
        _ => None
      }
    _ => None
  }
}

///|
pub fn synthetic_network_header_entry_string_value(header : Json) -> String? {
  match header {
    Object(map) =>
      match map.get("value") {
        Some(Object(value_map)) =>
          match value_map.get("value") {
            Some(String(value)) => Some(value)
            _ => None
          }
        _ => None
      }
    _ => None
  }
}

///|
pub fn synthetic_network_normalize_header_entries(
  raw_headers : Json?,
  field_name : String,
) -> Result[Array[Json], String] {
  match raw_headers {
    None | Some(Null) => Ok([])
    Some(Array(items)) => {
      let headers : Array[Json] = []
      for item in items {
        match synthetic_network_validate_header_entry(item) {
          Some(header) => headers.push(header)
          None => return Err(field_name + " entry is invalid")
        }
      }
      Ok(headers)
    }
    Some(_) => Err(field_name + " must be an array")
  }
}

///|
pub fn synthetic_network_normalize_string_list(
  raw_list : Json?,
  field_name : String,
) -> Result[Array[String], String] {
  match raw_list {
    None | Some(Null) => Ok([])
    Some(Array(items)) => {
      let values : Array[String] = []
      for item in items {
        match item {
          String(value) => values.push(value)
          _ => return Err(field_name + " entries must be strings")
        }
      }
      Ok(values)
    }
    Some(_) => Err(field_name + " must be an array")
  }
}

///|
pub fn synthetic_network_validate_header_entry(entry : Json) -> Json? {
  let map = match entry {
    Object(map) => map
    _ => return None
  }
  let name = match map.get("name") {
    Some(String(name)) =>
      if synthetic_network_is_valid_header_name_token(name) {
        name
      } else {
        return None
      }
    _ => return None
  }
  let value = match map.get("value") {
    Some(value) =>
      match synthetic_network_validate_header_value(value) {
        Some(value) => value
        None => return None
      }
    None => return None
  }
  Some(Json::object({ "name": Json::string(name), "value": value }))
}

///|
pub fn synthetic_network_validate_header_value(value : Json) -> Json? {
  let map = match value {
    Object(map) => map
    _ => return None
  }
  let value_type = match map.get("type") {
    Some(String(value_type)) => value_type
    _ => return None
  }
  if value_type != "string" {
    return None
  }
  let raw_value = match map.get("value") {
    Some(String(raw_value)) => raw_value
    _ => return None
  }
  if !synthetic_network_is_valid_header_string_value(raw_value) {
    return None
  }
  Some(
    Json::object({
      "type": Json::string("string"),
      "value": Json::string(raw_value),
    }),
  )
}

///|
pub fn synthetic_network_is_valid_header_name_token(name : String) -> Bool {
  if name.length() == 0 {
    return false
  }
  for c in name.iter() {
    if !synthetic_network_is_valid_header_name_token_char(c) {
      return false
    }
  }
  true
}

///|
pub fn synthetic_network_is_valid_header_string_value(value : String) -> Bool {
  let chars = value.to_array()
  if chars.length() > 0 {
    let first = chars[0]
    let last = chars[chars.length() - 1]
    if first == ' ' || first == '\t' || last == ' ' || last == '\t' {
      return false
    }
  }
  for c in chars {
    let code = c.to_int()
    if code == 0 || code == 10 || code == 13 {
      return false
    }
  }
  true
}

///|
fn synthetic_network_header_value_from_json(raw_value : Json) -> String {
  match raw_value {
    String(value) => value
    Number(value, ..) => value.to_string()
    True => "true"
    False => "false"
    Null => ""
    Array(_) | Object(_) => raw_value.stringify()
  }
}

///|
pub fn synthetic_network_is_valid_continue_url(url : String) -> Bool {
  if url.has_prefix("data:") {
    return true
  }
  if url.has_prefix("http://") {
    return has_valid_http_host(url, 7)
  }
  if url.has_prefix("https://") {
    return has_valid_http_host(url, 8)
  }
  false
}

///|
pub fn synthetic_network_normalize_continue_url(
  raw_url : Json,
) -> Result[String, String] {
  let url = match raw_url {
    String(url) => url
    _ => return Err("url must be a string")
  }
  if synthetic_network_is_valid_continue_url(url) {
    Ok(url)
  } else {
    Err("url is invalid")
  }
}

///|
pub fn synthetic_network_cache_key(
  http_method : String,
  url : String,
) -> String {
  http_method + " " + url
}

///|
pub fn synthetic_network_request_path(url : String) -> String {
  match canonicalize_network_http_url(url) {
    Some(parts) => parts.path
    None => ""
  }
}

///|
pub fn synthetic_network_first_query_value(
  url : String,
  key : String,
) -> String? {
  let values = synthetic_network_query_values(url, key)
  if values.length() == 0 {
    None
  } else {
    Some(values[0])
  }
}

///|
pub fn synthetic_network_query_values(
  url : String,
  key : String,
) -> Array[String] {
  let values : Array[String] = []
  match find_substring(url, "?", 0) {
    Some(query_idx) => {
      let query_start = query_idx + 1
      if query_start >= url.length() {
        return values
      }
      let query = url.unsafe_substring(start=query_start, end=url.length())
      for part in query.split("&") {
        let raw = part.to_owned()
        if raw.length() == 0 {
          continue
        }
        match find_substring(raw, "=", 0) {
          Some(eq_idx) => {
            let part_key = raw.unsafe_substring(start=0, end=eq_idx)
            if part_key != key {
              continue
            }
            let value_start = eq_idx + 1
            let raw_value = if value_start <= raw.length() {
              raw.unsafe_substring(start=value_start, end=raw.length())
            } else {
              ""
            }
            values.push(decode_query_component(raw_value))
          }
          None => if raw == key { values.push("") }
        }
      }
      values
    }
    None => values
  }
}

///|
pub fn synthetic_network_int_query_param(
  url : String,
  key : String,
  default_value : Int,
) -> Int {
  match synthetic_network_first_query_value(url, key) {
    Some(value) =>
      match parse_decimal_string(value) {
        Some(parsed) => parsed
        None => default_value
      }
    None => default_value
  }
}

///|
pub fn synthetic_network_request_header_string_value(
  headers : Array[Json],
  name : String,
) -> String? {
  let lookup = name.to_lower()
  for header in headers {
    match header {
      Object(map) => {
        let header_name = match map.get("name") {
          Some(String(header_name)) => header_name.to_lower()
          _ => continue
        }
        if header_name != lookup {
          continue
        }
        match map.get("value") {
          Some(Object(value_map)) =>
            match value_map.get("value") {
              Some(String(value)) => return Some(value)
              _ => ()
            }
          _ => ()
        }
      }
      _ => ()
    }
  }
  None
}

///|
pub fn synthetic_network_header_string_entry(
  name : String,
  value : String,
) -> Json {
  Json::object({
    "name": Json::string(name),
    "value": Json::object({
      "type": Json::string("string"),
      "value": Json::string(value),
    }),
  })
}

///|
pub fn synthetic_network_infer_mime_type(url : String, path : String) -> String {
  if url.has_prefix("data:") {
    let payload_start = match find_substring(url, ",", 0) {
      Some(payload_start) => payload_start
      None => url.length()
    }
    let prefix = if payload_start > 5 {
      url.unsafe_substring(start=5, end=payload_start)
    } else {
      ""
    }
    match find_substring(prefix, ";", 0) {
      Some(idx) => {
        let mime_type = prefix.unsafe_substring(start=0, end=idx)
        if mime_type == "" {
          "text/plain"
        } else {
          mime_type.to_lower()
        }
      }
      None => if prefix == "" { "text/plain" } else { prefix.to_lower() }
    }
  } else if path.has_suffix(".html") {
    "text/html"
  } else if path.has_suffix(".txt") {
    "text/plain"
  } else if path.has_suffix(".js") {
    "text/javascript"
  } else if path.has_suffix(".png") {
    "image/png"
  } else if path.has_suffix(".svg") {
    "image/svg+xml"
  } else if path.has_suffix(".css") {
    "text/css"
  } else if path.has_suffix("/cached.py") {
    synthetic_network_first_query_value(url, "contenttype").unwrap_or(
      "text/plain",
    )
  } else {
    "text/plain"
  }
}

///|
pub fn synthetic_network_status_text_for_code(status : Int) -> String {
  match status {
    101 => "Switching Protocols"
    200 => "OK"
    201 => "Created"
    202 => "Accepted"
    203 => "Non-Authoritative Information"
    204 => "No Content"
    205 => "Reset Content"
    206 => "Partial Content"
    300 => "Multiple Choices"
    305 => "Use Proxy"
    301 => "Moved Permanently"
    302 => "Found"
    303 => "See Other"
    304 => "Not Modified"
    307 => "Temporary Redirect"
    308 => "Permanent Redirect"
    400 => "Bad Request"
    401 => "Unauthorized"
    402 => "Payment Required"
    403 => "Forbidden"
    404 => "Not Found"
    405 => "Method Not Allowed"
    406 => "Not Acceptable"
    407 => "Proxy Authentication Required"
    408 => "Request Timeout"
    409 => "Conflict"
    410 => "Gone"
    411 => "Length Required"
    412 => "Precondition Failed"
    415 => "Unsupported Media Type"
    417 => "Expectation Failed"
    500 => "Internal Server Error"
    501 => "Not Implemented"
    502 => "Bad Gateway"
    503 => "Service Unavailable"
    504 => "Gateway Timeout"
    505 => "HTTP Version Not Supported"
    _ => if status == 200 { "OK" } else { "" }
  }
}

///|
pub fn synthetic_network_normalize_response_overrides(
  raw_overrides : Json?,
) -> Result[SyntheticNetworkResponseOverrides, String] {
  let overrides = match raw_overrides {
    None | Some(Null) =>
      return Ok(synthetic_network_default_response_overrides())
    Some(Object(map)) => map
    Some(_) => return Err("responseOverrides must be an object")
  }
  let from_cache = match overrides.get("fromCache") {
    Some(True) => true
    Some(False) | None => false
    Some(_) => return Err("responseOverrides.fromCache must be a boolean")
  }
  let headers = match
    synthetic_network_normalize_header_entries(
      overrides.get("headers"),
      "requestHeaders",
    ) {
    Ok(headers) => headers
    Err(message) => return Err(message)
  }
  let mime_type = match overrides.get("mimeType") {
    Some(String(value)) => value
    None => "text/plain"
    Some(_) => return Err("responseOverrides.mimeType must be a string")
  }
  let protocol = match overrides.get("protocol") {
    Some(String(value)) => value
    None => "http/1.1"
    Some(_) => return Err("responseOverrides.protocol must be a string")
  }
  let status = match overrides.get("status") {
    Some(value) =>
      match synthetic_network_json_as_safe_int(value, 0.0, 9007199254740991.0) {
        Some(status) => status
        None =>
          return Err("responseOverrides.status must be a non-negative integer")
      }
    None => 200
  }
  let status_text = match overrides.get("statusText") {
    Some(String(value)) => value
    None => synthetic_network_status_text_for_code(status)
    Some(_) => return Err("responseOverrides.statusText must be a string")
  }
  let mut auth_challenges : Json? = match overrides.get("authChallenges") {
    None | Some(Null) => None
    Some(Array(values)) => Some(Json::array(values))
    Some(_) => return Err("responseOverrides.authChallenges must be an array")
  }
  let missing_auth_challenges = match auth_challenges {
    None => true
    Some(_) => false
  }
  if (status == 401 || status == 407) && missing_auth_challenges {
    auth_challenges = Some(Json::array([]))
  }
  Ok({
    from_cache,
    headers,
    mime_type,
    protocol,
    status,
    status_text,
    auth_challenges,
  })
}

///|
pub fn synthetic_network_response_overrides_to_json(
  overrides : SyntheticNetworkResponseOverrides,
) -> Json {
  let result : Map[String, Json] = {}
  result["fromCache"] = Json::boolean(overrides.from_cache)
  result["headers"] = Json::array(overrides.headers)
  result["mimeType"] = Json::string(overrides.mime_type)
  result["protocol"] = Json::string(overrides.protocol)
  result["status"] = Json::number(overrides.status.to_double())
  result["statusText"] = Json::string(overrides.status_text)
  match overrides.auth_challenges {
    Some(auth_challenges) => result["authChallenges"] = auth_challenges
    None => ()
  }
  Json::object(result)
}

///|
fn synthetic_network_default_response_overrides() -> SyntheticNetworkResponseOverrides {
  {
    from_cache: false,
    headers: [],
    mime_type: "text/plain",
    protocol: "http/1.1",
    status: 200,
    status_text: "OK",
    auth_challenges: None,
  }
}

///|
fn has_valid_http_host(url : String, scheme_len : Int) -> Bool {
  if url.length() <= scheme_len {
    return false
  }
  let chars = url.to_array()
  let head = chars[scheme_len]
  if head == ':' || head == '#' || head == '/' || head == '?' {
    return false
  }
  true
}

///|
fn synthetic_network_is_valid_header_name_token_char(c : Char) -> Bool {
  let code = c.to_int()
  if (code >= 48 && code <= 57) ||
    (code >= 65 && code <= 90) ||
    (code >= 97 && code <= 122) {
    return true
  }
  match code {
    33 => true
    35 => true
    36 => true
    37 => true
    38 => true
    39 => true
    42 => true
    43 => true
    45 => true
    46 => true
    94 => true
    95 => true
    96 => true
    124 => true
    126 => true
    _ => false
  }
}