import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { EcmaScriptSyntaxProvider } from "../regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider";
import type {
  PatternFormatSnapshotCheck,
  PatternFormatValidationResult,
} from "../regex/formatting/formatting.types";
import {
  PatternFormatterPanel,
  type PatternFormatValidatorClient,
  type PatternFormatterPanelProps,
} from "./PatternFormatterPanel";

const provider = new EcmaScriptSyntaxProvider();

function sameCheck(): PatternFormatSnapshotCheck {
  return {
    comparison: "same",
    before: {
      status: "complete",
      engineIdentity: "Native ECMAScript RegExp · fixture",
      message: "Source completed.",
    },
    after: {
      status: "complete",
      engineIdentity: "Native ECMAScript RegExp · fixture",
      message: "Candidate completed.",
    },
    summary: "Exact normalized match and replacement output is unchanged.",
    mismatchKinds: [],
  };
}

function validationResult(
  overrides: Partial<PatternFormatValidationResult> = {},
): PatternFormatValidationResult {
  return {
    status: "equivalent",
    canApply: true,
    sourceSyntaxAccepted: true,
    formattedSyntaxAccepted: true,
    captureShapePreserved: true,
    current: sameCheck(),
    tests: [],
    applicableTestCount: 0,
    skippedTestCount: 0,
    completedTestCount: 0,
    differences: [],
    elapsedMs: 3,
    notices: [
      "No enabled unit test has the exact active pattern; the current snapshot was compared.",
    ],
    ...overrides,
  };
}

function validator(
  result: PatternFormatValidationResult = validationResult(),
): PatternFormatValidatorClient {
  return {
    validate: vi.fn().mockImplementation(async (_input, onProgress) => {
      onProgress?.(0, 0);
      return result;
    }),
    cancel: vi.fn(),
    dispose: vi.fn(),
  };
}

async function renderFormatter(
  overrides: Partial<PatternFormatterPanelProps> = {},
  result: PatternFormatValidationResult = validationResult(),
) {
  const pattern = overrides.pattern ?? "a/b";
  const syntax =
    overrides.syntax ??
    (await provider.parsePattern({
      flavour: "ecmascript",
      flavourVersion: "2025",
      pattern,
      flags: [],
      options: {},
    }));
  const currentValidator = validator(result);
  const onApply = vi.fn();
  const rendered = render(
    <PatternFormatterPanel
      flavour="ecmascript"
      flavourVersion="2025"
      pattern={pattern}
      flags={[]}
      options={{}}
      syntax={syntax}
      subject="a/b a/b"
      replacement="[$&]"
      scanAll
      timeoutMs={2_000}
      tests={[]}
      onApply={onApply}
      createValidator={() => currentValidator}
      {...overrides}
    />,
  );
  return { ...rendered, currentValidator, onApply, syntax };
}

afterEach(cleanup);

describe("PatternFormatterPanel", () => {
  it("shows an exact preview and requires validation plus confirmation before apply", async () => {
    const user = userEvent.setup();
    const { currentValidator, onApply } = await renderFormatter();

    expect(screen.getByTestId("formatted-pattern-preview").textContent).toBe(
      "a\\/b",
    );
    expect(
      screen.queryByRole("button", { name: "Apply formatted pattern" }),
    ).not.toBeInTheDocument();

    await user.click(
      screen.getByRole("button", { name: "Validate exact snapshots" }),
    );
    await waitFor(() => {
      expect(currentValidator.validate).toHaveBeenCalledTimes(1);
    });
    expect(
      await screen.findByText(
        "All completed safety gates retained the same observable behavior.",
      ),
    ).toBeVisible();
    expect(screen.getByRole("checkbox")).toBeEnabled();
    expect(
      screen.getByRole("button", { name: "Apply formatted pattern" }),
    ).toBeDisabled();

    await user.click(screen.getByRole("checkbox"));
    await user.click(
      screen.getByRole("button", { name: "Apply formatted pattern" }),
    );

    expect(onApply).toHaveBeenCalledWith("a\\/b");
  });

  it("keeps a behavior-changing candidate blocked", async () => {
    const user = userEvent.setup();
    await renderFormatter(
      {},
      validationResult({
        status: "different",
        canApply: false,
        current: {
          ...sameCheck(),
          comparison: "different",
          summary: "Match count changed.",
          mismatchKinds: ["match-count"],
        },
        differences: [
          {
            scope: "current-subject",
            code: "current-behavior-changed",
            summary: "Match count changed.",
          },
        ],
      }),
    );

    await user.click(
      screen.getByRole("button", { name: "Validate exact snapshots" }),
    );

    expect(await screen.findByText("current-behavior-changed")).toBeVisible();
    expect(screen.getByRole("checkbox")).toBeDisabled();
    expect(
      screen.getByRole("button", { name: "Apply formatted pattern" }),
    ).toBeDisabled();
  });

  it("reports PCRE2 and stale syntax snapshots as unavailable", async () => {
    const { rerender, syntax } = await renderFormatter({
      flavour: "pcre2",
      syntax: undefined,
    });

    expect(
      screen.getByText(/currently available only for ECMAScript/),
    ).toBeVisible();
    expect(
      screen.queryByRole("button", { name: "Validate exact snapshots" }),
    ).not.toBeInTheDocument();

    rerender(
      <PatternFormatterPanel
        flavour="ecmascript"
        flavourVersion="2025"
        pattern="a/c"
        flags={[]}
        options={{}}
        syntax={syntax}
        subject=""
        replacement=""
        scanAll={false}
        timeoutMs={2_000}
        tests={[]}
        onApply={vi.fn()}
      />,
    );
    expect(
      screen.getByText(/accepted, non-recovered syntax-provider snapshot/),
    ).toBeVisible();
  });

  it("clears a validation result when an exact input snapshot changes", async () => {
    const user = userEvent.setup();
    const rendered = await renderFormatter();
    await user.click(
      screen.getByRole("button", { name: "Validate exact snapshots" }),
    );
    expect(await screen.findByRole("checkbox")).toBeVisible();

    rendered.rerender(
      <PatternFormatterPanel
        flavour="ecmascript"
        flavourVersion="2025"
        pattern="a/b"
        flags={[]}
        options={{}}
        syntax={rendered.syntax}
        subject="different"
        replacement="[$&]"
        scanAll
        timeoutMs={2_000}
        tests={[]}
        onApply={rendered.onApply}
        createValidator={() => rendered.currentValidator}
      />,
    );

    expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: "Validate exact snapshots" }),
    ).toBeEnabled();
  });
});
