///|
fn synthetic_navigation_blocked_request_payload(
  input : @crater_network.SyntheticNetworkEventInput,
  phase : String,
  response_event : Json?,
) -> Map[String, Json] {
  let blocked_request : Map[String, Json] = {}
  blocked_request["phase"] = Json::string(phase)
  blocked_request["context_id"] = Json::string(input.context_id)
  blocked_request["url"] = Json::string(input.url)
  blocked_request["method"] = Json::string(input.http_method)
  blocked_request["request_headers"] = synthetic_navigation_header_object_from_entries(
    input.request_headers,
  )
  match input.request_value {
    Some(request_value) => blocked_request["request_value"] = request_value
    None => ()
  }
  blocked_request["redirect_count"] = Json::number(
    input.redirect_count.to_double(),
  )
  blocked_request["navigation"] = match input.navigation {
    Some(navigation) => Json::string(navigation)
    None => Json::null()
  }
  blocked_request["destination"] = Json::string(input.destination)
  blocked_request["initiator_type"] = match input.initiator_type {
    Some(initiator_type) => Json::string(initiator_type)
    None => Json::null()
  }
  blocked_request["is_navigation"] = Json::boolean(true)
  match response_event {
    Some(event) => {
      blocked_request["response_headers"] = Json::array(
        synthetic_navigation_response_headers_from_event(event),
      )
      blocked_request["response_status"] = Json::number(
        synthetic_navigation_response_status_from_event(event).to_double(),
      )
      blocked_request["response_status_text"] = Json::string(
        synthetic_navigation_response_status_text_from_event(event),
      )
    }
    None => ()
  }
  blocked_request
}

///|
fn synthetic_navigation_requires_auth_event(
  response_overrides : @crater_network.SyntheticNetworkResponseOverrides,
) -> Bool {
  response_overrides.status == 401 ||
  response_overrides.status == 407 ||
  response_overrides.auth_challenges != None
}

///|
fn synthetic_navigation_is_unreachable_test_url(url : String) -> Bool {
  url.has_prefix("http://not_a_valid_url.test") ||
  url.has_prefix("https://not_a_valid_url.test")
}

///|
fn synthetic_navigation_header_object_from_entries(
  headers : Array[Json],
) -> Json {
  let object : Map[String, Json] = {}
  for header in headers {
    let name = match synthetic_network_header_entry_name(header) {
      Some(name) => name
      None => continue
    }
    let value = synthetic_network_header_entry_string_value(header).unwrap_or(
      "",
    )
    object[name] = Json::string(value)
  }
  make_object(object)
}

///|
fn synthetic_navigation_response_headers_from_event(
  event : Json,
) -> Array[Json] {
  match get_field(event, "response") {
    Some(Object(response)) =>
      match response.get("headers") {
        Some(Array(headers)) => headers
        _ => []
      }
    _ => []
  }
}

///|
fn synthetic_navigation_response_status_from_event(event : Json) -> Int {
  match get_field(event, "response") {
    Some(Object(response)) =>
      match response.get("status") {
        Some(Number(status, ..)) => status.to_int()
        _ => 200
      }
    _ => 200
  }
}

///|
fn synthetic_navigation_response_status_text_from_event(event : Json) -> String {
  match get_field(event, "response") {
    Some(Object(response)) =>
      match response.get("statusText") {
        Some(String(status_text)) => status_text
        _ => "OK"
      }
    _ => "OK"
  }
}

///|
fn synthetic_navigation_synthesize_response_value(url : String) -> Json {
  if url.has_prefix("data:") {
    let payload = url.unsafe_substring(start=5, end=url.length())
    match find_substring(payload, ",", 0) {
      Some(idx) => {
        let meta = payload.unsafe_substring(start=0, end=idx)
        let encoded = payload.unsafe_substring(
          start=idx + 1,
          end=payload.length(),
        )
        let mime = match find_substring(meta, ";", 0) {
          Some(semi_idx) => meta.unsafe_substring(start=0, end=semi_idx)
          None => meta
        }
        if meta.contains(";base64") {
          if mime.has_prefix("image/") {
            return make_object({
              "type": Json::string("base64"),
              "value": Json::string(encoded),
            })
          }
          return make_object({
            "type": Json::string("string"),
            "value": Json::string(decode_base64(encoded)),
          })
        }
        let decoded = js_decode_query_component(encoded)
        return make_object({
          "type": Json::string("string"),
          "value": Json::string(decoded),
        })
      }
      None => ()
    }
  }
  let path = match @crater_network.canonicalize_network_http_url(url) {
    Some(parts) => parts.path
    None => ""
  }
  if path.has_suffix("/empty.txt") {
    return make_object({
      "type": Json::string("string"),
      "value": Json::string("empty\n"),
    })
  }
  if path.has_suffix("/other.txt") {
    return make_object({
      "type": Json::string("string"),
      "value": Json::string("other\n"),
    })
  }
  if path.has_suffix("/empty.png") {
    return make_object({
      "type": Json::string("base64"),
      "value": Json::string(
        "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2P4DwQACfsD/Z8fLAAAAAAASUVORK5CYII=",
      ),
    })
  }
  if path.has_suffix("/cached.py") {
    let response_body = synthetic_network_first_query_value(url, "response").unwrap_or(
      "",
    )
    let raw_response = synthetic_navigation_first_raw_query_value(
      url, "response",
    ).unwrap_or("")
    let content_type = synthetic_network_first_query_value(url, "contenttype")
      .unwrap_or("text/plain")
      .to_lower()
    if content_type.has_prefix("image/") || content_type == "img/png" {
      return make_object({
        "type": Json::string("base64"),
        "value": Json::string(
          if raw_response != "" {
            synthetic_navigation_raw_query_value_to_base64(raw_response)
          } else {
            encode_base64_utf8(response_body)
          },
        ),
      })
    }
    return make_object({
      "type": Json::string("string"),
      "value": Json::string(response_body),
    })
  }
  if path.has_suffix("/headers.py") || path.has_suffix("/charset.py") {
    return make_object({
      "type": Json::string("string"),
      "value": Json::string(
        synthetic_network_first_query_value(url, "content").unwrap_or(""),
      ),
    })
  }
  make_object({ "type": Json::string("string"), "value": Json::string("") })
}

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

///|
fn synthetic_navigation_raw_query_values(
  url : String,
  key : String,
) -> Array[String] {
  let values : Array[String] = []
  let query = @crater_network.extract_network_url_query(url)
  if query == "" {
    return values
  }
  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(raw_value)
      }
      None => if raw == key { values.push("") }
    }
  }
  values
}

///|
extern "js" fn synthetic_navigation_raw_query_value_to_base64(
  value : String,
) -> String =
  #| (value) => {
  #|   const raw = String(value ?? "").replace(/\+/g, " ");
  #|   const bytes = [];
  #|   for (let i = 0; i < raw.length; ) {
  #|     const ch = raw[i];
  #|     if (ch === "%" && i + 2 < raw.length) {
  #|       const hex = raw.slice(i + 1, i + 3);
  #|       const parsed = Number.parseInt(hex, 16);
  #|       if (!Number.isNaN(parsed)) {
  #|         bytes.push(parsed);
  #|         i += 3;
  #|         continue;
  #|       }
  #|     }
  #|     bytes.push(ch.charCodeAt(0));
  #|     i += 1;
  #|   }
  #|   let binary = "";
  #|   for (const byte of bytes) {
  #|     binary += String.fromCharCode(byte);
  #|   }
  #|   return btoa(binary);
  #| }