///|
fn resolve_sync_await_expression_fallback(expression : String) -> String? {
  let expr = expression.trim().to_owned()
  if expr.has_prefix("Promise.resolve(") && expr.has_suffix(")") {
    return Some(expr.unsafe_substring(start=16, end=expr.length() - 1))
  }
  if expr.contains("await ") ||
    expr.contains("new Promise") ||
    expr.contains("Promise.") ||
    expr.contains(".then(") ||
    expr.contains("fetch(") {
    return None
  }
  Some(expr)
}

///|
fn resolve_sync_await_call_expression(
  function_declaration : String,
  this_json : String,
  args_json : String,
) -> String? {
  let trimmed = function_declaration.trim().to_owned()
  let normalized = if trimmed.has_prefix("async ") {
    let sync_body = trimmed.unsafe_substring(start=6, end=trimmed.length())
    if sync_body.contains("await ") ||
      sync_body.contains("new Promise") ||
      sync_body.contains("Promise.") ||
      sync_body.contains(".then(") ||
      sync_body.contains("fetch(") {
      return None
    }
    sync_body
  } else {
    if trimmed.contains("await ") ||
      trimmed.contains("new Promise") ||
      trimmed.contains("Promise.") ||
      trimmed.contains(".then(") ||
      trimmed.contains("fetch(") {
      return None
    }
    trimmed
  }
  if args_json == "" {
    Some("(" + normalized + ").call(" + this_json + ")")
  } else {
    Some("(" + normalized + ").call(" + this_json + ", " + args_json + ")")
  }
}

///|
fn BidiProtocol::try_send_sync_await_expression_fallback(
  self : BidiProtocol,
  request_id : Int,
  ctx_id : String,
  realm_id : String,
  expression : String,
  capture_console : Bool,
  user_activation : Bool,
  root_ownership : Bool,
  serialization_options_json : String,
  unwrap_result : Bool,
) -> Bool {
  let sync_expr = match resolve_sync_await_expression_fallback(expression) {
    Some(sync_expr) => sync_expr
    None => return false
  }
  let eval_result_json = evaluate_js_with_console(
    sync_expr, capture_console, user_activation, root_ownership, serialization_options_json,
  )
  let eval_result = @json.parse(eval_result_json) catch {
    _ => {
      self.send_error(
        request_id, "unknown error", "Failed to parse eval result",
      )
      return true
    }
  }
  if capture_console {
    self.process_console_entries(eval_result, ctx_id, realm_id)
  }
  self.emit_synthetic_mutation_observer_messages(ctx_id, realm_id, expression)
  let eval_result = self.maybe_adjust_document_dimensions_eval_result(
    expression, eval_result,
  )
  match get_string_field(eval_result, "type") {
    Some("success") => {
      self.apply_synthetic_history_state_from_expression(ctx_id, expression)
      let bidi_value = maybe_normalize_all_events_remote_value(
        expression,
        get_field(eval_result, "result").unwrap_or(
          make_object({ "type": Json::string("undefined") }),
        ),
      )
      self.send_script_remote_value_response(
        request_id, realm_id, bidi_value, unwrap_result,
      )
    }
    _ =>
      self.send_script_eval_result_mode(
        request_id, realm_id, eval_result, unwrap_result,
      )
  }
  true
}

///|
fn BidiProtocol::try_send_sync_await_call_fallback(
  self : BidiProtocol,
  request_id : Int,
  ctx_id : String,
  realm_id : String,
  function_declaration : String,
  params : Json?,
  this_json : String,
  args_json : String,
  capture_console : Bool,
  user_activation : Bool,
  root_ownership : Bool,
  serialization_options_json : String,
  unwrap_result : Bool,
) -> Bool {
  let sync_call_expr = match
    resolve_sync_await_call_expression(
      function_declaration, this_json, args_json,
    ) {
    Some(sync_call_expr) => sync_call_expr
    None => return false
  }
  let eval_result_json = evaluate_js_with_console(
    sync_call_expr, capture_console, user_activation, root_ownership, serialization_options_json,
  )
  let eval_result = @json.parse(eval_result_json) catch {
    _ => {
      self.send_error(
        request_id, "unknown error", "Failed to parse eval result",
      )
      return true
    }
  }
  if capture_console {
    self.process_console_entries(eval_result, ctx_id, realm_id)
  }
  self.process_channel_messages(ctx_id, realm_id)
  match get_string_field(eval_result, "type") {
    Some("success") => {
      self.apply_synthetic_history_state_from_call(
        ctx_id,
        function_declaration,
        None,
      )
      self.apply_synthetic_location_assignment_from_call(
        ctx_id,
        function_declaration,
        None,
      )
      self.remember_network_state_channel_if_applicable(
        ctx_id, function_declaration, params,
      )
      let bidi_value = get_field(eval_result, "result").unwrap_or(
        make_object({ "type": Json::string("undefined") }),
      )
      if should_capture_focus_target(function_declaration, bidi_value) {
        self.remember_focused_element_remote(bidi_value)
      }
      self.send_script_remote_value_response(
        request_id, realm_id, bidi_value, unwrap_result,
      )
    }
    Some("exception") => {
      if self.try_send_synthetic_scroll_call_fallback(
          request_id, ctx_id, realm_id, function_declaration, capture_console, user_activation,
          root_ownership, serialization_options_json, unwrap_result,
        ) {
        return true
      }
      self.send_script_eval_result_mode(
        request_id, realm_id, eval_result, unwrap_result,
      )
    }
    _ =>
      self.send_script_eval_result_mode(
        request_id, realm_id, eval_result, unwrap_result,
      )
  }
  true
}

///|
fn BidiProtocol::handle_script_evaluate(
  self : BidiProtocol,
  request : BidiRequest,
  unwrap_result : Bool,
) -> Unit {
  match get_param_raw(request.params, "expression") {
    Some(String(_)) => ()
    Some(_) => {
      self.send_error(
        request.id,
        "invalid argument",
        "expression must be a string",
      )
      return
    }
    None => {
      self.send_error(request.id, "invalid argument", "Missing expression")
      return
    }
  }
  match self.validate_script_target(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_await_promise(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_result_ownership(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_serialization_options(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_user_activation(request.id, request.params) {
    Some(_) => return
    None => ()
  }

  let (ctx_id, realm_id) = self.resolve_target_context_realm(request.params)
  let runtime_ctx_id = resolve_runtime_context_id(
    request.params,
    ctx_id,
    realm_id,
    self.realm_sandbox.get(realm_id) != None,
  )
  let await_promise = match
    get_param_raw_with_alias(request.params, "awaitPromise", "await_promise") {
    Some(True) => true
    _ => false
  }
  let root_ownership = match
    get_param_raw_with_alias(
      request.params,
      "resultOwnership",
      "result_ownership",
    ) {
    Some(String("root")) => true
    _ => false
  }
  let user_activation = get_user_activation_value(request.params)
  let expr = get_string_param(request.params, "expression").unwrap()
  if self.try_handle_synthetic_all_events_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_register_service_worker_eval(
      request.id,
      realm_id,
      expr,
    ) {
    return
  }
  if self.try_handle_synthetic_speculation_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
    ) {
    return
  }
  if self.try_handle_synthetic_user_prompt(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_download(request.id, ctx_id, realm_id, expr) {
    return
  }
  if self.try_handle_synthetic_iframe_remove(request.id, ctx_id, realm_id, expr) {
    return
  }
  if self.try_handle_synthetic_document_write_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
    ) {
    return
  }
  if self.try_handle_synthetic_window_location_href_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_bluetooth_availability_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_user_agent_fetch_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_locale_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_timezone_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_document_cookie_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  let serialization_options_json = get_serialization_options_json(
    request.params,
  )
  let has_serialization_options = has_serialization_options_param(
    request.params,
  )
  let capture_console = self.should_capture_console(ctx_id)
  set_runtime_context(runtime_ctx_id)
  set_runtime_context_frames(
    runtime_ctx_id,
    self.context_children.get(ctx_id).unwrap_or([]),
  )
  self.apply_effective_viewport_to_runtime_context(runtime_ctx_id, ctx_id)
  if self.try_handle_synthetic_file_dialog_eval(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
      capture_console,
      user_activation,
      root_ownership,
      serialization_options_json,
    ) {
    return
  }
  if self.try_handle_window_open(
      request.id,
      ctx_id,
      realm_id,
      expr,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_iframe_creation(
      request.id,
      ctx_id,
      realm_id,
      expr,
    ) {
    return
  }
  let needs_mutation_observer_sync = expr.contains("setAttribute(") &&
    self.has_mutation_observer_preload_for_context(ctx_id)

  if self.use_quickjs && @runtime.is_quickjs_initialized() && !unwrap_result {
    match self.socket {
      Some(socket) => {
        @runtime.quickjs_eval_ws(socket, request.id, expr, ctx_id)
        return
      }
      None => {
        self.send_error(
          request.id,
          "unknown error",
          "QuickJS requires WebSocket for async evaluation",
        )
        return
      }
    }
  }

  if self.dom_initialized &&
    !capture_console &&
    !await_promise &&
    !root_ownership &&
    !has_serialization_options &&
    !unwrap_result {
    let raw = js_evaluate_expression_fast(
      request.id,
      expr,
      user_activation,
      realm_id,
      ctx_id,
    )
    self.refresh_child_contexts_from_runtime_iframes_after_eval(ctx_id)
    self.send_raw(raw)
    return
  }

  if await_promise && !needs_mutation_observer_sync {
    if !self.dom_initialized {
      let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
      self.dom_initialized = true
    }
    if !root_ownership &&
      !has_serialization_options &&
      self.try_send_sync_await_expression_fallback(
        request.id,
        ctx_id,
        realm_id,
        expr,
        capture_console,
        user_activation,
        root_ownership,
        serialization_options_json,
        unwrap_result,
      ) {
      return
    }
    match self.socket {
      Some(socket) => {
        eval_and_send_async_with_console(
          socket,
          request.id,
          expr,
          runtime_ctx_id,
          realm_id,
          capture_console,
          user_activation,
          root_ownership,
          unwrap_result,
        )
        return
      }
      None =>
        if !root_ownership && !has_serialization_options {
          let eval_result_json = js_await_promise(
            js_evaluate_expression_async(expr, user_activation, runtime_ctx_id),
          )
          self.dom_initialized = true
          let eval_result = @json.parse(eval_result_json) catch {
            _ => {
              self.send_error(
                request.id,
                "unknown error",
                "Failed to parse eval result",
              )
              return
            }
          }
          if capture_console {
            self.process_console_entries(eval_result, ctx_id, realm_id)
          }
          self.emit_synthetic_mutation_observer_messages(ctx_id, realm_id, expr)
          self.refresh_child_contexts_from_runtime_iframes_after_eval(ctx_id)
          let eval_result = self.maybe_adjust_document_dimensions_eval_result(
            expr, eval_result,
          )
          match get_string_field(eval_result, "type") {
            Some("success") => {
              self.apply_synthetic_history_state_from_expression(ctx_id, expr)
              let bidi_value = maybe_normalize_all_events_remote_value(
                expr,
                get_field(eval_result, "result").unwrap_or(
                  make_object({ "type": Json::string("undefined") }),
                ),
              )
              self.send_script_remote_value_response(
                request.id,
                realm_id,
                bidi_value,
                unwrap_result,
              )
            }
            _ =>
              self.send_script_eval_result_mode(
                request.id,
                realm_id,
                eval_result,
                unwrap_result,
              )
          }
          return
        }
    }
  }

  let eval_result_json = evaluate_js_with_console(
    expr, capture_console, user_activation, root_ownership, serialization_options_json,
  )
  self.dom_initialized = true
  let eval_result = @json.parse(eval_result_json) catch {
    _ => {
      self.send_error(
        request.id,
        "unknown error",
        "Failed to parse eval result",
      )
      return
    }
  }
  if capture_console {
    self.process_console_entries(eval_result, ctx_id, realm_id)
  }
  self.emit_synthetic_mutation_observer_messages(ctx_id, realm_id, expr)
  self.refresh_child_contexts_from_runtime_iframes_after_eval(ctx_id)
  let eval_result = self.maybe_adjust_document_dimensions_eval_result(
    expr, eval_result,
  )
  match get_string_field(eval_result, "type") {
    Some("success") => {
      self.apply_synthetic_history_state_from_expression(ctx_id, expr)
      let bidi_value = maybe_normalize_all_events_remote_value(
        expr,
        get_field(eval_result, "result").unwrap_or(
          make_object({ "type": Json::string("undefined") }),
        ),
      )
      self.send_script_remote_value_response(
        request.id,
        realm_id,
        bidi_value,
        unwrap_result,
      )
    }
    _ =>
      self.send_script_eval_result_mode(
        request.id,
        realm_id,
        eval_result,
        unwrap_result,
      )
  }
}

///|
fn BidiProtocol::handle_script_call_function(
  self : BidiProtocol,
  request : BidiRequest,
  unwrap_result : Bool,
) -> Unit {
  match
    get_param_raw_with_alias(
      request.params,
      "functionDeclaration",
      "function_declaration",
    ) {
    Some(String(_)) => ()
    Some(_) => {
      self.send_error(
        request.id,
        "invalid argument",
        "functionDeclaration must be a string",
      )
      return
    }
    None => {
      self.send_error(
        request.id,
        "invalid argument",
        "Missing functionDeclaration",
      )
      return
    }
  }
  match self.validate_script_target(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_await_promise(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_result_ownership(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_serialization_options(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_user_activation(request.id, request.params) {
    Some(_) => return
    None => ()
  }

  let (ctx_id_call, realm_id_call) = self.resolve_target_context_realm(
    request.params,
  )
  let runtime_ctx_id_call = resolve_runtime_context_id(
    request.params,
    ctx_id_call,
    realm_id_call,
    self.realm_sandbox.get(realm_id_call) != None,
  )
  let capture_console_call = self.should_capture_console(ctx_id_call)
  let await_promise_call = match
    get_param_raw_with_alias(request.params, "awaitPromise", "await_promise") {
    Some(True) => true
    _ => false
  }
  let root_ownership_call = match
    get_param_raw_with_alias(
      request.params,
      "resultOwnership",
      "result_ownership",
    ) {
    Some(String("root")) => true
    _ => false
  }
  let user_activation_call = get_user_activation_value(request.params)
  let serialization_options_json_call = get_serialization_options_json(
    request.params,
  )
  let has_serialization_options_call = has_serialization_options_param(
    request.params,
  )
  set_runtime_context(runtime_ctx_id_call)
  set_runtime_context_frames(
    runtime_ctx_id_call,
    self.context_children.get(ctx_id_call).unwrap_or([]),
  )
  self.apply_effective_viewport_to_runtime_context(
    runtime_ctx_id_call, ctx_id_call,
  )

  match self.validate_this_param(request.id, request.params) {
    Some(_) => return
    None => ()
  }
  match self.validate_arguments_param(request.id, request.params) {
    Some(_) => return
    None => ()
  }

  let args_json = match get_param_raw(request.params, "arguments") {
    Some(Array(args)) => {
      let parts : Array[String] = []
      for arg in args {
        parts.push(bidi_value_to_js_literal(arg))
      }
      parts.join(", ")
    }
    _ => ""
  }
  let this_json = match get_param_raw(request.params, "this") {
    Some(value) => bidi_value_to_js_literal(value)
    None => "undefined"
  }
  let func = match
    get_param_raw_with_alias(
      request.params,
      "functionDeclaration",
      "function_declaration",
    ) {
    Some(String(func)) => func
    _ => ""
  }
  if self.try_handle_synthetic_document_status_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_bluetooth_request_device_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      request.params,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_bluetooth_gatt_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_window_location_origin_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_network_conditions_fetch_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      request.params,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_permissions_query_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_geolocation_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      request.params,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_local_storage_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
      request.params,
      unwrap_result,
    ) {
    return
  }
  if self.try_handle_synthetic_history_document_open_call(
      request.id,
      ctx_id_call,
      realm_id_call,
      func,
    ) {
    return
  }
  let function_is_async = func.trim().has_prefix("async")
  let call_expr = if args_json == "" {
    "(" + func + ").call(" + this_json + ")"
  } else {
    "(" + func + ").call(" + this_json + ", " + args_json + ")"
  }

  if self.use_quickjs && @runtime.is_quickjs_initialized() && !unwrap_result {
    match self.socket {
      Some(socket) => {
        @runtime.quickjs_eval_ws(socket, request.id, call_expr, ctx_id_call)
        return
      }
      None => {
        self.send_error(
          request.id,
          "unknown error",
          "QuickJS requires WebSocket for async evaluation",
        )
        return
      }
    }
  }

  if await_promise_call &&
    (!has_serialization_options_call || function_is_async) {
    if !self.dom_initialized {
      let _ = evaluate_js_with_console("undefined", false, false, false, "{}")
      self.dom_initialized = true
    }
    if !root_ownership_call &&
      !has_serialization_options_call &&
      self.try_send_sync_await_call_fallback(
        request.id,
        ctx_id_call,
        realm_id_call,
        func,
        request.params,
        this_json,
        args_json,
        capture_console_call,
        user_activation_call,
        root_ownership_call,
        serialization_options_json_call,
        unwrap_result,
      ) {
      return
    }
    match self.socket {
      Some(socket) => {
        eval_and_send_async_with_console(
          socket,
          request.id,
          call_expr,
          runtime_ctx_id_call,
          realm_id_call,
          capture_console_call,
          user_activation_call,
          root_ownership_call,
          unwrap_result,
        )
        return
      }
      None =>
        if !root_ownership_call && !has_serialization_options_call {
          let eval_result_json = js_await_promise(
            js_evaluate_expression_async(
              call_expr, user_activation_call, runtime_ctx_id_call,
            ),
          )
          let eval_result = @json.parse(eval_result_json) catch {
            _ => {
              self.send_error(
                request.id,
                "unknown error",
                "Failed to parse eval result",
              )
              return
            }
          }
          if capture_console_call {
            self.process_console_entries(
              eval_result, ctx_id_call, realm_id_call,
            )
          }
          self.process_channel_messages(ctx_id_call, realm_id_call)
          match get_string_field(eval_result, "type") {
            Some("success") => {
              self.apply_synthetic_history_state_from_call(
                ctx_id_call,
                func,
                request.params,
              )
              self.apply_synthetic_location_assignment_from_call(
                ctx_id_call,
                func,
                request.params,
              )
              let bidi_value = get_field(eval_result, "result").unwrap_or(
                make_object({ "type": Json::string("undefined") }),
              )
              if should_capture_focus_target(func, bidi_value) {
                self.remember_focused_element_remote(bidi_value)
              }
              self.send_script_remote_value_response(
                request.id,
                realm_id_call,
                bidi_value,
                unwrap_result,
              )
            }
            Some("exception") => {
              if self.try_send_synthetic_scroll_call_fallback(
                  request.id,
                  ctx_id_call,
                  realm_id_call,
                  func,
                  capture_console_call,
                  user_activation_call,
                  root_ownership_call,
                  serialization_options_json_call,
                  unwrap_result,
                ) {
                return
              }
              self.send_script_eval_result_mode(
                request.id,
                realm_id_call,
                eval_result,
                unwrap_result,
              )
            }
            _ =>
              self.send_script_eval_result_mode(
                request.id,
                realm_id_call,
                eval_result,
                unwrap_result,
              )
          }
          return
        }
    }
  }

  let eval_result_json = evaluate_js_with_console(
    call_expr, capture_console_call, user_activation_call, root_ownership_call, serialization_options_json_call,
  )
  let eval_result = @json.parse(eval_result_json) catch {
    _ => {
      self.send_error(
        request.id,
        "unknown error",
        "Failed to parse eval result",
      )
      return
    }
  }
  if capture_console_call {
    self.process_console_entries(eval_result, ctx_id_call, realm_id_call)
  }
  self.process_channel_messages(ctx_id_call, realm_id_call)
  match get_string_field(eval_result, "type") {
    Some("success") => {
      self.apply_synthetic_history_state_from_call(
        ctx_id_call,
        func,
        request.params,
      )
      self.apply_synthetic_location_assignment_from_call(
        ctx_id_call,
        func,
        request.params,
      )
      let bidi_value = get_field(eval_result, "result").unwrap_or(
        make_object({ "type": Json::string("undefined") }),
      )
      if should_capture_focus_target(func, bidi_value) {
        self.remember_focused_element_remote(bidi_value)
      }
      self.send_script_remote_value_response(
        request.id,
        realm_id_call,
        bidi_value,
        unwrap_result,
      )
    }
    Some("exception") => {
      if self.try_send_synthetic_scroll_call_fallback(
          request.id,
          ctx_id_call,
          realm_id_call,
          func,
          capture_console_call,
          user_activation_call,
          root_ownership_call,
          serialization_options_json_call,
          unwrap_result,
        ) {
        return
      }
      self.send_script_eval_result_mode(
        request.id,
        realm_id_call,
        eval_result,
        unwrap_result,
      )
    }
    _ =>
      self.send_script_eval_result_mode(
        request.id,
        realm_id_call,
        eval_result,
        unwrap_result,
      )
  }
}