import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { ListPanel } from "./ListPanel";
import { ReplacementPanel } from "./ReplacementPanel";
import {
  DEFAULT_REGEX_LIMITS,
  TEMPLATE_PRESENTATION_LIMITS,
} from "../regex/execution/request-limits";
import type { EditorMark } from "../editors/editor.types";
import type { ReplacementSyntaxResult } from "../regex/model/syntax";
import type { RegexMatchResult } from "../regex/model/match";

const editorHarness = vi.hoisted(() => ({
  marks: [] as readonly EditorMark[],
}));

vi.mock("../editors/CodeEditor", () => ({
  CodeEditor({
    value,
    label,
    marks = [],
  }: {
    readonly value: string;
    readonly label: string;
    readonly marks?: readonly EditorMark[];
  }) {
    if (label === "Replacement template") editorHarness.marks = marks;
    return <textarea aria-label={label} value={value} readOnly />;
  },
}));

afterEach(() => {
  cleanup();
  editorHarness.marks = [];
});

describe("template presentation limits", () => {
  it("bounds replacement marks, rows and diagnostics while reporting exact totals", () => {
    const tokenCount = TEMPLATE_PRESENTATION_LIMITS.replacementEditorMarks + 10;
    const syntax: ReplacementSyntaxResult = {
      accepted: true,
      tokens: Array.from({ length: tokenCount }, (_, index) => ({
        id: `replacement-${index}`,
        kind: "full-match" as const,
        raw: "$&",
        range: { startUtf16: index * 2, endUtf16: index * 2 + 2 },
        explanation: "Insert the complete match",
      })),
      diagnostics: [],
      totalDiagnostics: 1_000,
      diagnosticsTruncated: true,
    };

    const { container } = render(
      <ReplacementPanel
        replacement={"$&".repeat(tokenCount)}
        onReplacementChange={() => undefined}
        syntax={syntax}
        onSelectToken={() => undefined}
        onSelectContribution={() => undefined}
      />,
    );

    expect(editorHarness.marks).toHaveLength(
      TEMPLATE_PRESENTATION_LIMITS.replacementEditorMarks,
    );
    expect(container.querySelectorAll(".token-list li")).toHaveLength(
      TEMPLATE_PRESENTATION_LIMITS.replacementTokenRows,
    );
    expect(
      screen.getByTestId("replacement-mark-render-limit"),
    ).toHaveTextContent(tokenCount.toLocaleString());
    expect(
      screen.getByTestId("replacement-token-render-limit"),
    ).toHaveTextContent(tokenCount.toLocaleString());
    expect(
      screen.getByTestId("replacement-diagnostic-render-limit"),
    ).toHaveTextContent("1,000");
  });

  it("shows an explicit replacement input-limit error", () => {
    render(
      <ReplacementPanel
        replacement={"$&".repeat(
          DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16 / 2 + 1,
        )}
        onReplacementChange={() => undefined}
        syntax={{
          accepted: false,
          tokens: [],
          diagnostics: [],
          totalDiagnostics: 1,
          diagnosticsTruncated: false,
        }}
        onSelectToken={() => undefined}
        onSelectContribution={() => undefined}
      />,
    );

    expect(screen.getByTestId("replacement-input-limit")).toHaveTextContent(
      "parsing and execution stop",
    );
  });

  it("bounds list token chips and refuses oversized-template export", () => {
    const { container, rerender } = render(
      <ListPanel
        templateSource={"$0".repeat(
          TEMPLATE_PRESENTATION_LIMITS.listTokenChips + 10,
        )}
        onTemplateChange={() => undefined}
        matches={[]}
        subject=""
      />,
    );

    expect(container.querySelectorAll(".token-chips span")).toHaveLength(
      TEMPLATE_PRESENTATION_LIMITS.listTokenChips,
    );
    expect(screen.getByTestId("list-token-render-limit")).toHaveTextContent(
      (TEMPLATE_PRESENTATION_LIMITS.listTokenChips + 10).toLocaleString(),
    );

    rerender(
      <ListPanel
        templateSource={"$0".repeat(
          DEFAULT_REGEX_LIMITS.maximumListTemplateUtf16 / 2 + 1,
        )}
        onTemplateChange={() => undefined}
        matches={[]}
        subject=""
      />,
    );

    expect(screen.getByTestId("list-template-input-limit")).toHaveTextContent(
      "Exact rendering and export are disabled",
    );
    expect(
      screen.getByRole("button", { name: "Download TEXT" }),
    ).toBeDisabled();
  });

  it("disables list export when the engine returned only a match prefix", () => {
    const matches: readonly RegexMatchResult[] = [
      {
        matchNumber: 1,
        value: "a",
        valueStatus: "complete",
        range: { startUtf16: 0, endUtf16: 1 },
        nativeRange: { start: 0, end: 1, unit: "utf16" },
        captures: [],
      },
    ];
    render(
      <ListPanel
        templateSource="$0"
        onTemplateChange={() => undefined}
        matches={matches}
        subject="a"
        sourceResultTruncated
      />,
    );

    expect(screen.getByText(/engine limit reached/u)).toBeInTheDocument();
    expect(
      screen.getByText(/engine stopped collecting matches/u),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: "Download TEXT" }),
    ).toBeDisabled();
  });
});
