/// <reference lib="webworker" />
import {
  RubyEngineAdapter,
  RUBY_ENGINE_VERSION,
} from "../regex/execution/adapters/ruby/RubyEngineAdapter";
import { File, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
import { DefaultRubyVM } from "../regex/execution/adapters/ruby/ruby-module";
import {
  WORKER_PROTOCOL_VERSION,
  workerError,
  type EngineWorkerOperation,
  type EngineWorkerResult,
  type WorkerRequest,
  type WorkerResponse,
} from "../regex/execution/worker-protocol";

const scope = self as DedicatedWorkerGlobalScope;
let adapterPromise: Promise<RubyEngineAdapter> | undefined;

function applicationRoot(): URL {
  const worker = new URL(scope.location.href);
  const assets = worker.pathname.lastIndexOf("/assets/");
  if (assets >= 0) {
    worker.pathname = worker.pathname.slice(0, assets + 1);
  } else {
    const sourceWorker = worker.pathname.lastIndexOf("/src/workers/");
    worker.pathname =
      sourceWorker >= 0 ? worker.pathname.slice(0, sourceWorker + 1) : "/";
  }
  worker.search = "";
  worker.hash = "";
  return worker;
}

async function compileRuntime(url: URL): Promise<WebAssembly.Module> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(
      `Ruby runtime request failed with HTTP ${response.status}.`,
    );
  }
  if (typeof WebAssembly.compileStreaming === "function") {
    return WebAssembly.compileStreaming(Promise.resolve(response));
  }
  return WebAssembly.compile(await response.arrayBuffer());
}

async function loadAdapter(): Promise<RubyEngineAdapter> {
  const runtimeUrl = new URL("engines/ruby/ruby.wasm", applicationRoot());
  const module = await compileRuntime(runtimeUrl);
  const { vm, wasi } = await DefaultRubyVM(module, { consolePrint: false });
  const requestFile = new File([]);
  const root = wasi.fds[3];
  if (!(root instanceof PreopenDirectory)) {
    throw new Error("The Ruby WASI runtime has no writable root filesystem.");
  }
  root.dir.contents.set("regex-tools-request.bin", requestFile);
  const outputFile = new File([]);
  root.dir.contents.set("regex-tools-output.bin", outputFile);
  const adapter = new RubyEngineAdapter(
    vm,
    `WebAssembly · ${scope.navigator.userAgent}`,
    (payload) => {
      requestFile.data = payload;
    },
    () => outputFile.data,
  );
  const info = await adapter.load();
  if (info.engineVersion !== RUBY_ENGINE_VERSION) {
    throw new Error(
      "Loaded CRuby runtime identity does not match the adapter.",
    );
  }
  return adapter;
}

scope.onmessage = async (
  event: MessageEvent<WorkerRequest<EngineWorkerOperation>>,
) => {
  const request = event.data;
  if (request.protocolVersion !== WORKER_PROTOCOL_VERSION) return;
  let response: WorkerResponse<EngineWorkerResult>;
  try {
    adapterPromise ??= loadAdapter();
    const adapter = await adapterPromise;
    const payload: EngineWorkerResult =
      request.payload.kind === "load"
        ? {
            kind: "load",
            info: await adapter.load(),
          }
        : request.payload.kind === "execute"
          ? {
              kind: "execute",
              result: await adapter.execute(request.payload.request),
            }
          : {
              kind: "replace",
              result: await adapter.replace(request.payload.request),
            };
    response = {
      protocolVersion: WORKER_PROTOCOL_VERSION,
      requestId: request.requestId,
      generation: request.generation,
      ok: true,
      payload,
    };
  } catch (error) {
    response = {
      protocolVersion: WORKER_PROTOCOL_VERSION,
      requestId: request.requestId,
      generation: request.generation,
      ok: false,
      error: workerError(error),
    };
  }
  scope.postMessage(response);
};
