import { afterEach, describe, expect, it, vi } from "vitest";
import type { ToolResult } from "../io/output-types";
import type { PlimiPlugin, ToolContext } from "./plugin-types";
import { disposePersistentWorker, runPlugin } from "./plugin-runner";

class FakeWorker extends EventTarget {
  terminate = vi.fn();
  postMessage = vi.fn((request: { id: string }) => {
    queueMicrotask(() => {
      const result: ToolResult = { type: "text", value: "done" };
      this.dispatchEvent(
        new MessageEvent("message", {
          data: { type: "success", id: request.id, result },
        })
      );
    });
  });
}

function makePlugin(
  id: string,
  persistentWorker: boolean,
  workerFactory: () => Worker
): PlimiPlugin<Record<string, never>> {
  return {
    manifest: {
      id,
      name: id,
      description: id,
      category: "image",
      version: "1.0.0",
      input: { type: "text" },
      output: { type: "text" },
      offlineReady: true,
    },
    capabilities: { worker: true, persistentWorker },
    worker: workerFactory,
  };
}

function makeContext(): ToolContext {
  return {
    signal: new AbortController().signal,
    reportProgress: vi.fn(),
    logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
  };
}

describe("runPlugin worker lifecycle", () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("reuses a persistent worker between runs", async () => {
    const worker = new FakeWorker();
    const factory = vi.fn(() => worker as unknown as Worker);
    const plugin = makePlugin("persistent-test", true, factory);

    await runPlugin(plugin, { text: "one" }, {}, makeContext());
    await runPlugin(plugin, { text: "two" }, {}, makeContext());

    expect(factory).toHaveBeenCalledTimes(1);
    expect(worker.postMessage).toHaveBeenCalledTimes(2);
    expect(worker.terminate).not.toHaveBeenCalled();

    expect(disposePersistentWorker("persistent-test")).toBe(true);
    expect(worker.terminate).toHaveBeenCalledOnce();

    await runPlugin(plugin, { text: "three" }, {}, makeContext());
    expect(factory).toHaveBeenCalledTimes(2);
    disposePersistentWorker("persistent-test");
  });

  it("terminates a non-persistent worker after success", async () => {
    const worker = new FakeWorker();
    const plugin = makePlugin(
      "temporary-test",
      false,
      () => worker as unknown as Worker
    );

    await runPlugin(plugin, { text: "one" }, {}, makeContext());

    expect(worker.terminate).toHaveBeenCalledOnce();
  });
});
