import type { WorkerLike } from "./WorkerSupervisor";

export const WORKER_STARTUP_READY_KIND =
  "regex-tools-worker-startup-ready" as const;
export const WORKER_STARTUP_PROTOCOL_VERSION = 1;

export interface WorkerStartupReady {
  readonly kind: typeof WORKER_STARTUP_READY_KIND;
  readonly protocolVersion: typeof WORKER_STARTUP_PROTOCOL_VERSION;
}

export function workerStartupReady(): WorkerStartupReady {
  return {
    kind: WORKER_STARTUP_READY_KIND,
    protocolVersion: WORKER_STARTUP_PROTOCOL_VERSION,
  };
}

function isWorkerStartupReady(value: unknown): value is WorkerStartupReady {
  if (typeof value !== "object" || value === null) return false;
  const candidate = value as Partial<WorkerStartupReady>;
  return (
    candidate.kind === WORKER_STARTUP_READY_KIND &&
    candidate.protocolVersion === WORKER_STARTUP_PROTOCOL_VERSION
  );
}

/**
 * Holds supervisor requests outside a worker until its runtime has completed
 * cold start. Some single-threaded WebAssembly reactors cannot receive any
 * message event while their host initialization is in flight.
 */
export class StartupBufferedWorker implements WorkerLike {
  onmessage: ((event: MessageEvent<unknown>) => void) | null = null;
  onerror: ((event: ErrorEvent) => void) | null = null;
  onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;

  readonly #worker: WorkerLike;
  readonly #queue: unknown[] = [];
  #ready = false;
  #terminated = false;

  constructor(worker: WorkerLike) {
    this.#worker = worker;
    worker.onmessage = (event) => {
      if (isWorkerStartupReady(event.data)) {
        if (this.#ready || this.#terminated) return;
        this.#ready = true;
        for (const message of this.#queue.splice(0)) {
          this.#worker.postMessage(message);
        }
        return;
      }
      this.onmessage?.(event);
    };
    worker.onerror = (event) => this.onerror?.(event);
    worker.onmessageerror = (event) => this.onmessageerror?.(event);
  }

  postMessage(message: unknown): void {
    if (this.#terminated) return;
    if (this.#ready) {
      this.#worker.postMessage(message);
    } else {
      this.#queue.push(message);
    }
  }

  terminate(): void {
    if (this.#terminated) return;
    this.#terminated = true;
    this.#queue.splice(0);
    this.#worker.terminate();
  }
}
