///|
/// Handle browsingContext.navigate.
fn BidiProtocol::handle_browsing_context_navigate(
  self : BidiProtocol,
  request : BidiRequest,
) -> Result[Unit, String] {
  let ctx_id = match get_param_raw(request.params, "context") {
    Some(String(value)) => value
    Some(_) => {
      self.send_error(
        request.id,
        "invalid argument",
        "context must be a string",
      )
      return Ok(())
    }
    None => {
      self.send_error(request.id, "invalid argument", "Missing context/url")
      return Ok(())
    }
  }
  let url = match get_param_raw(request.params, "url") {
    Some(String(value)) => value
    Some(_) => {
      self.send_error(request.id, "invalid argument", "url must be a string")
      return Ok(())
    }
    None => {
      self.send_error(request.id, "invalid argument", "Missing context/url")
      return Ok(())
    }
  }
  let wait_mode = match get_param_raw(request.params, "wait") {
    Some(String(value)) =>
      if value == "none" || value == "interactive" || value == "complete" {
        value
      } else {
        self.send_error(
          request.id,
          "invalid argument",
          "wait must be one of none, interactive, or complete",
        )
        return Ok(())
      }
    Some(_) => {
      self.send_error(request.id, "invalid argument", "wait must be a string")
      return Ok(())
    }
    None => "none"
  }
  if !self.manager.has_session(ctx_id) {
    self.send_error(request.id, "no such frame", "Unknown context: " + ctx_id)
    return Ok(())
  }
  let resolved_url = self.resolve_effective_navigation_target_url(ctx_id, url)
  self.input_synthetic_events_by_context.remove(ctx_id)
  self.context_synthetic_scrolled.remove(ctx_id)
  self.clear_synthetic_location_href(ctx_id)
  let _ = self.fail_in_flight_navigation(
    ctx_id, "aborted", "Navigation interrupted by another navigation",
  )
  if !is_valid_navigation_target_url(resolved_url) {
    self.send_error(request.id, "invalid argument", "url is invalid")
    return Ok(())
  }
  if self.navigation_blocked_by_network_conditions(ctx_id, resolved_url) {
    let nav_id = self.next_navigation_id.to_string()
    self.next_navigation_id += 1
    self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
    self.send_error(
      request.id,
      "unknown error",
      "Navigation failed: network offline",
    )
    return Ok(())
  }
  if should_fail_navigation_with_unknown_error(resolved_url) {
    let nav_id = self.next_navigation_id.to_string()
    self.next_navigation_id += 1
    self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
    self.send_error(
      request.id,
      "unknown error",
      "Navigation failed: navigation target is unreachable",
    )
    return Ok(())
  }
  if self.is_iframe_navigation_blocked(ctx_id, resolved_url) {
    let nav_id = self.next_navigation_id.to_string()
    self.next_navigation_id += 1
    self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
    self.emit_navigation_failed_event(ctx_id, resolved_url, nav_id)
    self.send_error(
      request.id,
      "unknown error",
      "Navigation failed: blocked by embedding policy",
    )
    return Ok(())
  }
  match self.manager.get_session(ctx_id) {
    Some(session) =>
      if self.has_beforeunload_prompt_in_scope(ctx_id) {
        let handler = self.resolve_unhandled_prompt_handler(
          ctx_id, "beforeunload",
        )
        let nav_id = self.next_navigation_id.to_string()
        self.next_navigation_id += 1
        self.clear_pending_prompt_state(ctx_id)
        self.pending_prompt_type[ctx_id] = "beforeunload"
        self.pending_beforeunload_request[ctx_id] = request.id
        self.pending_beforeunload_url[ctx_id] = resolved_url
        self.pending_beforeunload_requested_url[ctx_id] = url
        self.pending_beforeunload_wait[ctx_id] = wait_mode
        self.pending_beforeunload_navigation[ctx_id] = nav_id
        self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
        self.emit_user_prompt_opened(
          ctx_id,
          "beforeunload",
          handler,
          "beforeunload",
          None,
        )
        Ok(())
      } else {
        let previous_url = session.get_url()
        match session.navigate_to(resolved_url) {
          Ok(_) => {
            self.remember_requested_navigation_url(ctx_id, url)
            if is_fragment_navigation(previous_url, resolved_url) {
              self.complete_fragment_navigation_request(
                request.id,
                ctx_id,
                resolved_url,
              )
            } else if self.should_navigate_via_async_bridge(
                resolved_url, wait_mode,
              ) {
              // HTTP wait=complete: the sync sync_runtime_page() path returns
              // before __loadPageWithScripts resolves (MoonBit cannot await a
              // JS Promise across the FFI boundary). Hand the load over to
              // js_navigate_and_send_async, which awaits the page load in JS
              // and emits the BiDi success response via socket.send() once
              // the document is actually loaded. Fixes
              // bug.bidi.navigate-wait-complete-returns-early.
              let nav_id = self.complete_navigation_request_with_wait_deferred(
                request.id,
                ctx_id,
                resolved_url,
                wait_mode,
                None,
              )
              match self.socket {
                Some(socket) =>
                  navigate_and_send_async(
                    socket,
                    request.id,
                    ctx_id,
                    resolved_url,
                    nav_id,
                  )
                None =>
                  // No socket attached (typically wbtest harness). Fall back
                  // to the sync path so the test-only outbox observes the
                  // success response. Production server always has a socket.
                  self.complete_navigation_request_with_wait_after_deferred(
                    request.id,
                    ctx_id,
                    resolved_url,
                    nav_id,
                  )
              }
            } else {
              self.complete_navigation_request_with_wait(
                request.id,
                ctx_id,
                resolved_url,
                wait_mode,
              )
            }
            Ok(())
          }
          Err(e) => {
            let nav_id = self.next_navigation_id.to_string()
            self.next_navigation_id += 1
            self.emit_navigation_started_event(ctx_id, resolved_url, nav_id)
            self.send_error(
              request.id,
              "unknown error",
              "Navigation failed: " + e.message,
            )
            Ok(())
          }
        }
      }
    None => {
      self.send_error(request.id, "no such frame", "Unknown context: " + ctx_id)
      Ok(())
    }
  }
}