/// <reference lib="webworker" />
import { Pcre2TraceAdapter } from "../regex/execution/adapters/pcre2/Pcre2TraceAdapter";
import { importPcre2ModuleFactory } from "../regex/execution/adapters/pcre2/pcre2-module";
import {
  WORKER_PROTOCOL_VERSION,
  workerError,
  type TraceWorkerOperation,
  type TraceWorkerResult,
  type WorkerRequest,
  type WorkerResponse,
} from "../regex/execution/worker-protocol";

const scope = self as DedicatedWorkerGlobalScope;
let adapterPromise: Promise<Pcre2TraceAdapter> | 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 loadAdapter(): Promise<Pcre2TraceAdapter> {
  const moduleUrl = new URL("engines/pcre2/pcre2.mjs", applicationRoot());
  const factory = await importPcre2ModuleFactory(moduleUrl);
  const module = await factory({
    locateFile: (name) => new URL(name, moduleUrl).href,
    print: () => undefined,
    printErr: (line) => console.warn(`PCRE2 trace ${line}`),
  });
  const adapter = new Pcre2TraceAdapter(
    module,
    `WebAssembly trace · ${scope.navigator.userAgent}`,
  );
  await adapter.load();
  return adapter;
}

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