import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type {
  SubjectMinimizationProgress,
  SubjectMinimizationRequest,
  SubjectMinimizationResult,
} from "../regex/minimization/minimization.types";
import { MinimizePanel, type SubjectMinimizerClient } from "./MinimizePanel";

function result(): SubjectMinimizationResult {
  const observation = {
    status: "complete" as const,
    reproduced: true,
    fingerprint: ["expected-no-match:present"],
    summary: "The forbidden match is still present.",
    sides: [
      {
        flavour: "ecmascript" as const,
        status: "complete" as const,
        engineIdentity: "ecmascript · Fixture · 1 · utf16",
      },
    ],
  };
  return {
    schemaVersion: 1,
    targetIdentity: "minimization-fixture",
    status: "complete",
    stopReason: "locally-minimal",
    locallyMinimal: true,
    originalSubject: "abXcd",
    minimizedSubject: "X",
    originalScalars: 5,
    minimizedScalars: 1,
    originalUtf8Bytes: 5,
    minimizedUtf8Bytes: 1,
    evaluations: 12,
    acceptedReductions: 3,
    elapsedMs: 4,
    budgets: {
      maximumEvaluations: 500,
      maximumWallTimeMs: 15_000,
      candidateTimeoutMs: 2_000,
    },
    baseline: observation,
    final: observation,
    retainedHistory: [],
    historyTruncated: false,
  };
}

function renderPanel(client: SubjectMinimizerClient) {
  const onUseSubject = vi.fn();
  const onClose = vi.fn();
  render(
    <MinimizePanel
      activeFlavour="ecmascript"
      activeFlavourVersion="ECMAScript 2025 syntax / current browser runtime"
      activeFlags={["g", "u"]}
      activeOptions={{}}
      pattern="X"
      subject="abXcd"
      replacement="!"
      scanAll
      timeoutMs={2_000}
      onUseSubject={onUseSubject}
      onClose={onClose}
      createMinimizer={() => client}
    />,
  );
  return { onUseSubject, onClose };
}

function completingClient(
  requests: SubjectMinimizationRequest[],
): SubjectMinimizerClient {
  return {
    minimize: vi.fn(
      async (
        request: SubjectMinimizationRequest,
        onProgress?: (progress: SubjectMinimizationProgress) => void,
      ) => {
        requests.push(request);
        onProgress?.({
          phase: "local-sweep",
          evaluations: 12,
          maximumEvaluations: 500,
          acceptedReductions: 3,
          currentScalars: 1,
          currentUtf8Bytes: 1,
          elapsedMs: 4,
          maximumWallTimeMs: 15_000,
        });
        return result();
      },
    ),
    cancel: vi.fn(),
    dispose: vi.fn(),
  };
}

describe("MinimizePanel", () => {
  it("runs a failing unit-test predicate and can apply the verified result", async () => {
    const requests: SubjectMinimizationRequest[] = [];
    const { onUseSubject } = renderPanel(completingClient(requests));

    expect(
      screen.getByText(/does not claim a globally or semantically minimal/u),
    ).toBeVisible();
    fireEvent.click(screen.getByRole("button", { name: "Verify & minimize" }));

    expect(
      await screen.findByRole("heading", {
        name: "Locally minimal under the documented transforms",
      }),
    ).toBeVisible();
    expect(screen.getByLabelText("Minimized subject")).toHaveValue("X");
    expect(requests[0]?.target).toMatchObject({
      kind: "engine",
      oracle: {
        kind: "unit-test-failure",
        expectation: { kind: "should-not-match" },
      },
    });
    fireEvent.click(
      screen.getByRole("button", { name: "Use minimized subject" }),
    );
    expect(onUseSubject).toHaveBeenCalledWith("X");
  });

  it("exposes capture-range, timeout and two-engine mismatch targets", async () => {
    const requests: SubjectMinimizationRequest[] = [];
    renderPanel(completingClient(requests));
    const kind = screen.getByLabelText("Minimization failure kind");

    expect(kind).toContainHTML("capture-range-mismatch");
    expect(kind).toContainHTML("engine-timeout");
    expect(kind).toContainHTML("comparison-mismatch");

    fireEvent.change(kind, { target: { value: "engine-timeout" } });
    fireEvent.click(screen.getByRole("button", { name: "Verify & minimize" }));
    await waitFor(() => expect(requests).toHaveLength(1));
    expect(requests[0]?.target).toMatchObject({
      kind: "engine",
      oracle: { kind: "engine-timeout" },
    });

    fireEvent.change(kind, {
      target: { value: "comparison-mismatch" },
    });
    fireEvent.click(screen.getByRole("button", { name: "Verify & minimize" }));
    await waitFor(() => expect(requests).toHaveLength(2));
    expect(requests[1]?.target).toMatchObject({
      kind: "comparison-mismatch",
      operation: "match",
      sides: [
        { flavour: "ecmascript", pattern: "X" },
        { flavour: "pcre2", pattern: "X" },
      ],
    });
  });

  it("cancels the active worker set and closes independently", async () => {
    const client: SubjectMinimizerClient = {
      minimize: vi.fn(
        () => new Promise<SubjectMinimizationResult>(() => undefined),
      ),
      cancel: vi.fn(),
      dispose: vi.fn(),
    };
    const { onClose } = renderPanel(client);

    fireEvent.click(screen.getByRole("button", { name: "Verify & minimize" }));
    fireEvent.click(await screen.findByRole("button", { name: "Cancel" }));
    expect(client.cancel).toHaveBeenCalledOnce();
    expect(
      screen.getByText(/active syntax and engine workers were terminated/u),
    ).toBeVisible();

    fireEvent.click(
      screen.getByRole("button", { name: "Close subject minimizer" }),
    );
    expect(onClose).toHaveBeenCalledOnce();
  });
});
