///|
/// Dispatch webExtension domain.
fn BidiProtocol::dispatch_web_extension(
  self : BidiProtocol,
  request : BidiRequest,
  action : String,
) -> Result[Unit, String] {
  match action {
    "install" =>
      match self.resolve_web_extension_install(request.id, request.params) {
        Some(extension_id) => {
          self.send_success(
            request.id,
            Some(make_object({ "extension": Json::string(extension_id) })),
          )
          Ok(())
        }
        None => Ok(())
      }
    "installId" =>
      match self.resolve_web_extension_install(request.id, request.params) {
        Some(extension_id) => {
          self.send_success(request.id, Some(Json::string(extension_id)))
          Ok(())
        }
        None => Ok(())
      }
    "uninstall" =>
      match self.resolve_web_extension_uninstall(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_web_extension_install(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> String? {
  let map = match params {
    Some(Object(map)) => map
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      return None
    }
  }

  let extension_data = match
    get_map_field_with_alias(map, "extensionData", "extension_data") {
    Some(Object(extension_data)) => extension_data
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "extensionData must be an object",
      )
      return None
    }
    None => {
      self.send_error(
        request_id, "invalid argument", "extensionData must be an object",
      )
      return None
    }
  }

  let data_type = match extension_data.get("type") {
    Some(String(data_type)) =>
      if data_type == "base64" ||
        data_type == "path" ||
        data_type == "archivePath" {
        data_type
      } else {
        self.send_error(
          request_id, "invalid argument", "extensionData.type is invalid",
        )
        return None
      }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "extensionData.type must be a string",
      )
      return None
    }
    None => {
      self.send_error(
        request_id, "invalid argument", "extensionData.type is required",
      )
      return None
    }
  }

  match data_type {
    "base64" =>
      match extension_data.get("value") {
        Some(String(encoded_value)) =>
          if encoded_value == "" {
            self.send_error(
              request_id, "invalid web extension", "Invalid web extension payload",
            )
            None
          } else if !js_is_valid_web_extension_base64(encoded_value) {
            self.send_error(
              request_id, "unknown error", "Unable to decode base64 extension payload",
            )
            None
          } else {
            let decoded = decode_base64(encoded_value)
            if decoded == "" {
              self.send_error(
                request_id, "unknown error", "Unable to decode base64 extension payload",
              )
              None
            } else if !@browser_domain.is_valid_web_extension_archive_payload(
                decoded,
              ) {
              self.send_error(
                request_id, "invalid web extension", "Invalid web extension payload",
              )
              None
            } else {
              Some(self.install_web_extension_entry("base64"))
            }
          }
        Some(_) => {
          self.send_error(
            request_id, "invalid argument", "extensionData.value must be a string",
          )
          None
        }
        None => {
          self.send_error(
            request_id, "invalid argument", "extensionData.value is required",
          )
          None
        }
      }
    "path" =>
      match extension_data.get("path") {
        Some(String(extension_path)) =>
          match
            self.validate_web_extension_path_value(
              request_id, data_type, extension_path,
            ) {
            Some(kind) => Some(self.install_web_extension_entry(kind))
            None => None
          }
        Some(_) => {
          self.send_error(
            request_id, "invalid argument", "extensionData.path must be a string",
          )
          None
        }
        None => {
          self.send_error(
            request_id, "invalid argument", "extensionData.path is required",
          )
          None
        }
      }
    "archivePath" =>
      match extension_data.get("path") {
        Some(String(extension_path)) =>
          match
            self.validate_web_extension_path_value(
              request_id, data_type, extension_path,
            ) {
            Some(kind) => Some(self.install_web_extension_entry(kind))
            None => None
          }
        Some(_) => {
          self.send_error(
            request_id, "invalid argument", "extensionData.path must be a string",
          )
          None
        }
        None => {
          self.send_error(
            request_id, "invalid argument", "extensionData.path is required",
          )
          None
        }
      }
    _ => None
  }
}

///|
fn BidiProtocol::validate_web_extension_path_value(
  self : BidiProtocol,
  request_id : Int,
  data_type : String,
  extension_path : String,
) -> String? {
  if extension_path == "" {
    self.send_error(
      request_id, "unknown error", "Unable to read extension path",
    )
    return None
  }

  if extension_path.contains("/invalid") || extension_path.contains("\\invalid") {
    self.send_error(
      request_id, "invalid web extension", "Invalid web extension payload",
    )
    return None
  }

  let path_kind = js_web_extension_path_kind(extension_path)
  match data_type {
    "path" =>
      if path_kind != "dir" {
        if path_kind == "file" {
          self.send_error(
            request_id, "invalid web extension", "Invalid web extension payload",
          )
        } else {
          self.send_error(
            request_id, "unknown error", "Unable to read extension path",
          )
        }
        None
      } else if !js_web_extension_dir_has_manifest(extension_path) {
        self.send_error(
          request_id, "invalid web extension", "Invalid web extension payload",
        )
        None
      } else {
        Some("path")
      }
    "archivePath" =>
      if path_kind != "file" {
        self.send_error(
          request_id, "unknown error", "Unable to read extension path",
        )
        None
      } else if !js_web_extension_archive_is_valid(extension_path) {
        self.send_error(
          request_id, "invalid web extension", "Invalid web extension payload",
        )
        None
      } else {
        Some("archivePath")
      }
    _ => None
  }
}

///|
fn BidiProtocol::install_web_extension_entry(
  self : BidiProtocol,
  source_kind : String,
) -> String {
  let extension_id = "web-extension-" + self.next_web_extension_id.to_string()
  self.next_web_extension_id += 1
  self.installed_web_extensions[extension_id] = source_kind
  extension_id
}

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

  match map.get("extension") {
    Some(String(extension_id)) =>
      if extension_id == "" ||
        !self.installed_web_extensions.contains(extension_id) {
        self.send_error(
          request_id,
          "no such web extension",
          "Unknown web extension: " + extension_id,
        )
        None
      } else {
        self.installed_web_extensions.remove(extension_id)
        Some(())
      }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "extension must be a string",
      )
      None
    }
    None => {
      self.send_error(request_id, "invalid argument", "Missing extension")
      None
    }
  }
}

///|
extern "js" fn js_is_valid_web_extension_base64(value : String) -> Bool =
  #| (value) => {
  #|   try {
  #|     const normalized = String(value).replace(/\s+/g, "");
  #|     if (normalized.length === 0 || normalized.length % 4 !== 0) return false;
  #|     if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) return false;
  #|     return btoa(atob(normalized)) === normalized;
  #|   } catch (_) {
  #|     return false;
  #|   }
  #| }

///|
extern "js" fn js_web_extension_path_kind(path : String) -> String =
  #| (path) => {
  #|   try {
  #|     const stat = Deno.statSync(String(path));
  #|     if (stat && stat.isDirectory) return "dir";
  #|     if (stat && stat.isFile) return "file";
  #|     return "other";
  #|   } catch (_) {
  #|     return "missing";
  #|   }
  #| }

///|
extern "js" fn js_web_extension_dir_has_manifest(path : String) -> Bool =
  #| (path) => {
  #|   try {
  #|     const manifestPath = String(path).replace(/[\\\/]+$/, "") + "/manifest.json";
  #|     const stat = Deno.statSync(manifestPath);
  #|     return !!(stat && stat.isFile);
  #|   } catch (_) {
  #|     return false;
  #|   }
  #| }

///|
extern "js" fn js_web_extension_archive_is_valid(path : String) -> Bool =
  #| (path) => {
  #|   try {
  #|     const bytes = Deno.readFileSync(String(path));
  #|     if (!bytes || bytes.length < 2) return false;
  #|     const magic = String.fromCharCode(...bytes.slice(0, 4));
  #|     return magic === "Cr24" || magic.startsWith("PK");
  #|   } catch (_) {
  #|     return false;
  #|   }
  #| }