import { describe, expect, it } from "vitest";
import type {
  WorkerLike,
  WorkerRequestError,
} from "../execution/WorkerSupervisor";
import {
  WORKER_PROTOCOL_VERSION,
  type WorkerRequest,
  type WorkerResponse,
} from "../execution/worker-protocol";
import { AnalysisSupervisor } from "./AnalysisSupervisor";
import type {
  AnalysisWorkerOperation,
  AnalysisWorkerResult,
} from "./analysis.types";

class ResponsiveWorker implements WorkerLike {
  onmessage: ((event: MessageEvent<unknown>) => void) | null = null;
  onerror: ((event: ErrorEvent) => void) | null = null;
  onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
  terminated = false;

  postMessage(value: unknown): void {
    const request = value as WorkerRequest<AnalysisWorkerOperation>;
    let payload: AnalysisWorkerResult;
    if (request.payload.kind === "identity") {
      payload = {
        kind: "identity",
        result: {
          flavour: "ecmascript",
          engineName: "Native ECMAScript RegExp",
          engineVersion: "Fixture runtime",
          runtimeVersion: "Fixture runtime",
          nativeOffsetUnit: "utf16",
        },
      };
    } else if (request.payload.kind === "benchmark-sample") {
      payload = {
        kind: "benchmark-sample",
        result: {
          accepted: true,
          effectiveFlags: "dg",
          subjectBytes: 1,
          subjectUtf16: 1,
          compileMs: 1,
          firstMatchMs: 1,
          allMatchesMs: 1,
          replacementMs: 1,
          throughputBytesPerSecond: 1_000,
          matchCount: 1,
          matched: true,
          matchCollectionTruncated: false,
          replacementOutputUtf16: 1,
        },
      };
    } else {
      payload = {
        kind: "growth-probe",
        result: {
          accepted: true,
          effectiveFlags: "d",
          subjectBytes: 1,
          subjectUtf16: 1,
          executionMs: 1,
          matchCount: 0,
          matched: false,
          matchCollectionTruncated: false,
        },
      };
    }
    const response: WorkerResponse<AnalysisWorkerResult> = {
      protocolVersion: WORKER_PROTOCOL_VERSION,
      requestId: request.requestId,
      generation: request.generation,
      ok: true,
      payload,
    };
    globalThis.queueMicrotask(() => {
      if (!this.terminated) {
        this.onmessage?.(new MessageEvent("message", { data: response }));
      }
    });
  }

  terminate(): void {
    this.terminated = true;
  }
}

class SilentWorker implements WorkerLike {
  onmessage: ((event: MessageEvent<unknown>) => void) | null = null;
  onerror: ((event: ErrorEvent) => void) | null = null;
  onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
  terminated = false;

  postMessage(): void {}

  terminate(): void {
    this.terminated = true;
  }
}

describe("AnalysisSupervisor", () => {
  it("routes typed identity, benchmark and growth responses", async () => {
    const created: ResponsiveWorker[] = [];
    const supervisor = new AnalysisSupervisor(() => {
      const next = new ResponsiveWorker();
      created.push(next);
      return next;
    });

    await expect(supervisor.identity(100)).resolves.toEqual(
      expect.objectContaining({ engineVersion: "Fixture runtime" }),
    );
    await expect(
      supervisor.benchmarkSample(
        {
          flavour: "ecmascript",
          pattern: "a",
          flags: [],
          subject: "a",
          replacement: "x",
          scanAll: false,
          maximumMatches: 10,
        },
        100,
      ),
    ).resolves.toEqual(expect.objectContaining({ compileMs: 1 }));
    await expect(
      supervisor.growthProbe(
        {
          flavour: "ecmascript",
          pattern: "a",
          flags: [],
          subject: "b",
          scanAll: false,
          maximumMatches: 10,
        },
        100,
      ),
    ).resolves.toEqual(expect.objectContaining({ matched: false }));

    expect(created).toHaveLength(1);
    supervisor.dispose();
    expect(created[0]?.terminated).toBe(true);
  });

  it("terminates and rejects an active request on cancellation", async () => {
    const worker = new SilentWorker();
    const supervisor = new AnalysisSupervisor(() => worker);
    const pending = supervisor.identity(1_000);

    supervisor.cancel();

    await expect(pending).rejects.toMatchObject({
      kind: "cancelled",
    } satisfies Partial<WorkerRequestError>);
    expect(worker.terminated).toBe(true);
    supervisor.dispose();
  });
});
