///|
/// Dispatch permissions domain.
fn BidiProtocol::dispatch_permissions(
  self : BidiProtocol,
  request : BidiRequest,
  action : String,
) -> Result[Unit, String] {
  match action {
    "setPermission" =>
      match
        self.resolve_permissions_set_permission(request.id, request.params) {
        Some(_) => {
          self.send_success(request.id, Some(make_object({})))
          Ok(())
        }
        None => Ok(())
      }
    _ => {
      self.send_error(
        request.id,
        "unknown command",
        "Unknown method: " + request.method_name,
      )
      Ok(())
    }
  }
}

///|
fn BidiProtocol::resolve_permissions_set_permission(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Bool? {
  let map = match params {
    Some(Object(map)) => map
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      return None
    }
  }

  let descriptor = match map.get("descriptor") {
    Some(Object(descriptor)) => descriptor
    _ => {
      self.send_error(
        request_id, "invalid argument", "descriptor must be an object",
      )
      return None
    }
  }
  let permission_name = match descriptor.get("name") {
    Some(String(permission_name)) =>
      if @browser_domain.is_supported_permission_name(permission_name) {
        permission_name
      } else {
        self.send_error(
          request_id, "invalid argument", "descriptor.name is invalid",
        )
        return None
      }
    _ => {
      self.send_error(
        request_id, "invalid argument", "descriptor.name must be a supported string",
      )
      return None
    }
  }

  let state = match map.get("state") {
    Some(String(state)) =>
      if state == "granted" || state == "denied" || state == "prompt" {
        state
      } else {
        self.send_error(request_id, "invalid argument", "state is invalid")
        return None
      }
    _ => {
      self.send_error(request_id, "invalid argument", "state must be a string")
      return None
    }
  }

  let origin = match map.get("origin") {
    Some(String(origin)) => origin
    _ => {
      self.send_error(request_id, "invalid argument", "origin must be a string")
      return None
    }
  }

  let user_context_id = match
    get_map_field_with_alias(map, "userContext", "user_context") {
    Some(String(user_context_id)) =>
      if !self.user_contexts.contains(user_context_id) {
        self.send_error(
          request_id,
          "no such user context",
          "Unknown user context: " + user_context_id,
        )
        return None
      } else {
        user_context_id
      }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "userContext must be a string",
      )
      return None
    }
    None => "default"
  }

  let embedded_origin = match
    get_map_field_with_alias(map, "embeddedOrigin", "embedded_origin") {
    Some(String(embedded_origin)) => embedded_origin
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "embeddedOrigin must be a string",
      )
      return None
    }
    None => ""
  }

  let scope_key = @browser_domain.permission_scope_key(
    permission_name,
    user_context_id,
    @browser_domain.normalize_permission_origin(origin),
    @browser_domain.normalize_permission_origin(embedded_origin),
  )
  if state == "prompt" {
    self.permission_state_by_scope.remove(scope_key)
  } else {
    self.permission_state_by_scope[scope_key] = state
  }
  Some(true)
}

///|
fn BidiProtocol::remove_permission_state_for_user_context(
  self : BidiProtocol,
  user_context_id : String,
) -> Unit {
  let keys : Array[String] = []
  for key, _ in self.permission_state_by_scope {
    if @browser_domain.permission_scope_belongs_to_user_context(
        key, user_context_id,
      ) {
      keys.push(key)
    }
  }
  for key in keys {
    self.permission_state_by_scope.remove(key)
  }
}

///|
fn BidiProtocol::try_handle_synthetic_permissions_query_call(
  self : BidiProtocol,
  request_id : Int,
  ctx_id : String,
  realm_id : String,
  function_declaration : String,
  unwrap_result : Bool,
) -> Bool {
  if !function_declaration.contains("navigator.permissions.query(") {
    return false
  }
  let permission_name = match
    @browser_domain.extract_permissions_query_name(function_declaration) {
    Some(permission_name) =>
      if @browser_domain.is_supported_permission_name(permission_name) {
        permission_name
      } else {
        return false
      }
    None => return false
  }

  let state = self.resolve_permission_state_for_context(permission_name, ctx_id)
  let remote_value = make_object({
    "type": Json::string("string"),
    "value": Json::string(state),
  })
  if unwrap_result {
    self.send_script_synthetic_value_response(request_id, remote_value)
  } else {
    self.send_script_remote_value_response(
      request_id, realm_id, remote_value, false,
    )
  }
  true
}

///|
fn BidiProtocol::try_handle_synthetic_window_location_origin_call(
  self : BidiProtocol,
  request_id : Int,
  ctx_id : String,
  realm_id : String,
  function_declaration : String,
  unwrap_result : Bool,
) -> Bool {
  if !(function_declaration.contains("window.location.origin") ||
    function_declaration.contains("location.origin")) {
    return false
  }
  let origin = self.permission_query_origin_for_context(ctx_id)
  self.send_script_remote_value_response(
    request_id,
    realm_id,
    make_object({
      "type": Json::string("string"),
      "value": Json::string(origin),
    }),
    unwrap_result,
  )
  true
}

///|
fn BidiProtocol::permission_query_origin_for_context(
  self : BidiProtocol,
  ctx_id : String,
) -> String {
  @browser_domain.normalize_permission_origin(
    derive_realm_origin(self.requested_navigation_url(ctx_id)),
  )
}

///|
fn BidiProtocol::resolve_permission_state_for_context(
  self : BidiProtocol,
  permission_name : String,
  ctx_id : String,
) -> String {
  let user_context_id = self.context_user_context
    .get(ctx_id)
    .unwrap_or("default")
  let origin = if permission_name == "storage-access" {
    match self.context_parent.get(ctx_id) {
      Some(parent_ctx_id) =>
        self.permission_query_origin_for_context(parent_ctx_id)
      None => self.permission_query_origin_for_context(ctx_id)
    }
  } else {
    self.permission_query_origin_for_context(ctx_id)
  }
  let embedded_origin = if permission_name == "storage-access" &&
    self.context_parent.contains(ctx_id) {
    self.permission_query_origin_for_context(ctx_id)
  } else {
    ""
  }
  let scope_key = @browser_domain.permission_scope_key(
    permission_name, user_context_id, origin, embedded_origin,
  )
  self.permission_state_by_scope.get(scope_key).unwrap_or("prompt")
}