///|
fn BidiProtocol::apply_navigation_provide_response_body_override(
  self : BidiProtocol,
  context_id : String,
  destination : String,
  body_text : String,
) -> Unit {
  ignore(self)
  self.prepare_runtime_context_for_browsing_command(context_id)
  match destination {
    "script" =>
      ignore(evaluate_js_with_console(body_text, false, false, false, "{}"))
    "style" => {
      let body_json = Json::string(body_text).stringify()
      let expression = (
          #|(() => {
          #|  if (
          #|    !document.body &&
          #|    document.documentElement &&
          #|    typeof document.createElement === "function"
          #|  ) {
          #|    const body = document.createElement("body");
          #|    document.documentElement.appendChild(body);
          #|  }
          #|  if (
          #|    document.body &&
          #|    !document.querySelector("div") &&
          #|    typeof document.createElement === "function"
          #|  ) {
          #|    const div = document.createElement("div");
          #|    document.body.appendChild(div);
          #|  }
          #|  const style = document.createElement("style");
        ) +
        "  style.textContent = " +
        body_json +
        ";\n" +
        (
          #|  const parent = document.head || document.documentElement;
          #|  if (parent) parent.appendChild(style);
          #|  const match = /color\s*:\s*([^;}\n]+)/i.exec(style.textContent || "");
          #|  const fallbackColor =
          #|    match && match[1] ? String(match[1]).trim() : "rgb(0, 0, 0)";
          #|  const originalGetComputedStyle =
          #|    typeof globalThis.getComputedStyle === "function"
          #|      ? globalThis.getComputedStyle.bind(globalThis)
          #|      : null;
          #|  const craterGetComputedStyle = function(el) {
          #|    const base = originalGetComputedStyle
          #|      ? originalGetComputedStyle(el)
          #|      : (el && el._style) || {};
          #|    return new Proxy(base || {}, {
          #|      get(target, prop) {
          #|        if (prop === "getPropertyValue") {
          #|          return function(name) {
          #|            const raw = target && target[name] ? target[name] : "";
          #|            if (String(name) === "color" && !raw) return fallbackColor;
          #|            return raw || "";
          #|          };
          #|        }
          #|        if (prop === "color") {
          #|          const raw = target && target[prop] ? target[prop] : "";
          #|          return raw || fallbackColor;
          #|        }
          #|        return target && target[prop] ? target[prop] : "";
          #|      },
          #|    });
          #|  };
          #|  globalThis.getComputedStyle = craterGetComputedStyle;
          #|  window.getComputedStyle = craterGetComputedStyle;
          #|})()
        )
      ignore(evaluate_js_with_console(expression, false, false, false, "{}"))
    }
    _ => {
      let body_json = Json::string(body_text).stringify()
      let expression = (
          #|(() => {
          #|  if (
          #|    !document.body &&
          #|    document.documentElement &&
          #|    typeof document.createElement === "function"
          #|  ) {
          #|    const body = document.createElement("body");
          #|    document.documentElement.appendChild(body);
          #|  }
          #|  if (document.body) {
        ) +
        "    document.body.innerHTML = " +
        body_json +
        ";\n" +
        (
          #|  }
          #|})()
        )
      ignore(evaluate_js_with_console(expression, false, false, false, "{}"))
    }
  }
}

///|
fn BidiProtocol::send_continue_blocked_response_result(
  self : BidiProtocol,
  request_id : Int,
  response_started_event : Json?,
  response_completed_event : Json?,
  auth_required_event : Json?,
  load_event : Json?,
  consumed : Bool,
  status_code : Int?,
  reason_phrase : String?,
  body_text : String?,
) -> Unit {
  let _ = self
  let result : Map[String, Json] = {}
  result["responseStartedEvent"] = match response_started_event {
    Some(event) => event
    None => Json::null()
  }
  result["responseCompletedEvent"] = match response_completed_event {
    Some(event) => event
    None => Json::null()
  }
  result["authRequiredEvent"] = match auth_required_event {
    Some(event) => event
    None => Json::null()
  }
  result["browsingContextLoad"] = match load_event {
    Some(event) => event
    None => Json::null()
  }
  result["consumed"] = Json::boolean(consumed)
  match status_code {
    Some(value) => result["statusCode"] = Json::number(value.to_double())
    None => ()
  }
  match reason_phrase {
    Some(value) => result["reasonPhrase"] = Json::string(value)
    None => ()
  }
  match body_text {
    Some(text) => result["bodyText"] = Json::string(text)
    None => result["bodyText"] = Json::null()
  }
  self.send_success(request_id, Some(make_object(result)))
}

///|
fn BidiProtocol::emit_synthetic_network_event_if_subscribed(
  self : BidiProtocol,
  event_name : String,
  event : Json?,
) -> Unit {
  match event {
    Some(Object(event_map)) =>
      match event_map.get("context") {
        Some(String(context_id)) =>
          if self.is_subscribed_for_context(event_name, context_id) {
            self.outbox.push(
              Event({ event_method: event_name, params: make_object(event_map) }),
            )
          }
        _ => ()
      }
    _ => ()
  }
}

///|
fn BidiProtocol::emit_synthetic_network_result_events(
  self : BidiProtocol,
  response_started_event : Json?,
  response_completed_event : Json?,
  auth_required_event : Json?,
  load_event : Json?,
) -> Unit {
  self.emit_synthetic_network_event_if_subscribed(
    "network.responseStarted", response_started_event,
  )
  self.emit_synthetic_network_event_if_subscribed(
    "network.responseCompleted", response_completed_event,
  )
  self.emit_synthetic_network_event_if_subscribed(
    "network.authRequired", auth_required_event,
  )
  match load_event {
    Some(Object(load_map)) =>
      self.outbox.push(
        Event({
          event_method: "browsingContext.load",
          params: make_object(load_map),
        }),
      )
    _ => ()
  }
}

///|
fn BidiProtocol::maybe_emit_followup_preflight_blocked_request(
  self : BidiProtocol,
  blocked_request : Map[String, Json],
) -> Unit {
  let http_method = match blocked_request.get("method") {
    Some(String(name)) => name.to_upper()
    _ => "GET"
  }
  let is_navigation = match
    get_map_field_with_alias(blocked_request, "isNavigation", "is_navigation") {
    Some(True) => true
    _ => false
  }
  if http_method != "OPTIONS" || is_navigation {
    return
  }
  let context_id = match
    get_map_field_with_alias(blocked_request, "contextId", "context_id") {
    Some(String(context_id)) => context_id
    _ => return
  }
  let url = match blocked_request.get("url") {
    Some(String(url)) => url
    _ => return
  }
  let event_name = "network.beforeRequestSent"
  if !self.is_subscribed_for_context(event_name, context_id) {
    return
  }
  let intercepts = self.resolve_matching_network_intercepts_internal(
    context_id, "beforeRequestSent", url,
  )
  if intercepts.length() == 0 {
    return
  }
  let request_token = self.next_synthetic_network_request_id()
  let request_headers = match blocked_request.get("request_headers") {
    Some(Object(request_headers)) =>
      synthetic_network_header_entries_from_object(request_headers)
    _ => []
  }
  let resolved_request_headers = self.resolve_network_extra_header_entries(
    context_id, request_headers,
  )
  let redirect_count = match blocked_request.get("redirect_count") {
    Some(redirect_count) =>
      storage_json_non_negative_int(redirect_count).unwrap_or(0)
    None => 0
  }
  let navigation : String? = match blocked_request.get("navigation") {
    Some(String(navigation)) => Some(navigation)
    _ => None
  }
  let destination = match blocked_request.get("destination") {
    Some(String(destination)) => destination
    _ => ""
  }
  let initiator_type : String? = match
    get_map_field_with_alias(blocked_request, "initiatorType", "initiator_type") {
    Some(String(initiator_type)) => Some(initiator_type)
    _ => Some("fetch")
  }
  let input : @crater_network.SyntheticNetworkEventInput = {
    context_id,
    url,
    http_method: "GET",
    request_token,
    request_headers: resolved_request_headers,
    request_value: None,
    request_cookies: [],
    request_cookies_override: false,
    intercepts,
    redirect_count,
    navigation,
    destination,
    initiator_type,
  }
  let event = match
    self.build_synthetic_before_request_sent_event_json(0, input) {
    Some(event) => event
    None => return
  }
  self.outbox.push(Event({ event_method: event_name, params: event }))
  let followup_blocked_request : Map[String, Json] = {}
  followup_blocked_request["phase"] = Json::string("beforeRequestSent")
  followup_blocked_request["context_id"] = Json::string(context_id)
  followup_blocked_request["url"] = Json::string(url)
  followup_blocked_request["method"] = Json::string("GET")
  followup_blocked_request["request_headers"] = match
    blocked_request.get("request_headers") {
    Some(Object(request_headers)) => make_object(request_headers)
    _ => make_object({})
  }
  followup_blocked_request["redirect_count"] = Json::number(
    redirect_count.to_double(),
  )
  followup_blocked_request["navigation"] = match navigation {
    Some(navigation) => Json::string(navigation)
    None => Json::null()
  }
  followup_blocked_request["destination"] = Json::string(destination)
  followup_blocked_request["initiator_type"] = match initiator_type {
    Some(initiator_type) => Json::string(initiator_type)
    None => Json::null()
  }
  followup_blocked_request["is_navigation"] = Json::boolean(false)
  self.remember_network_blocked_request(request_token, followup_blocked_request)
}

///|
fn BidiProtocol::build_synthetic_auth_required_event_json(
  self : BidiProtocol,
  request_id : Int,
  input : @crater_network.SyntheticNetworkEventInput,
  blocked_request : Map[String, Json],
) -> Json? {
  let resolved_overrides = self.resolve_synthetic_response_overrides_internal(
    input.context_id,
    input.url,
    input.http_method,
    input.request_headers,
    false,
  )
  let realm = synthetic_network_first_query_value(input.url, "realm").unwrap_or(
    "testrealm",
  )
  let status = synthetic_network_blocked_response_status(blocked_request)
  let response_overrides : @crater_network.SyntheticNetworkResponseOverrides = {
    from_cache: resolved_overrides.from_cache,
    headers: if synthetic_network_blocked_response_headers(blocked_request).length() >
      0 {
      synthetic_network_blocked_response_headers(blocked_request)
    } else {
      resolved_overrides.headers
    },
    mime_type: resolved_overrides.mime_type,
    protocol: resolved_overrides.protocol,
    status,
    status_text: synthetic_network_blocked_response_status_text(
      blocked_request, status,
    ),
    auth_challenges: Some(
      Json::array([
        make_object({
          "scheme": Json::string("Basic"),
          "realm": Json::string(realm),
        }),
      ]),
    ),
  }
  self.build_synthetic_response_event_json(
    request_id, input, response_overrides,
  )
}

///|
fn BidiProtocol::synthetic_network_request_lookup_id(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> String? {
  let _ = self
  match params {
    Some(Object(map)) =>
      match map.get("request") {
        Some(String(request_lookup_id)) => Some(request_lookup_id)
        _ => {
          self.send_error(
            request_id, "invalid argument", "request must be a string",
          )
          None
        }
      }
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      None
    }
  }
}