/// <reference types="node" />

import { expect, test, type Page } from "@playwright/test";
import { readFile } from "node:fs/promises";
import { unzipSync } from "fflate";

async function setEditor(page: Page, testId: string, value: string) {
  const content = page.getByTestId(testId).locator(".cm-content");
  await content.fill(value);
}

async function waitForReady(page: Page) {
  await expect(page.locator(".run-status")).toHaveClass(/status-ready/u, {
    timeout: 15_000,
  });
}

test("standalone ECMAScript workbench parses, explains and extracts", async ({
  page,
}) => {
  const pageErrors: string[] = [];
  page.on("pageerror", (error) => pageErrors.push(error.message));
  await page.goto("./");

  await expect(
    page.getByRole("heading", { name: "Regex Tools" }),
  ).toBeVisible();
  await waitForReady(page);
  await expect(
    page.getByRole("heading", { name: "Explanation tree" }),
  ).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Extraction tree" }),
  ).toBeVisible();
  await expect(page.locator(".extraction-row")).toHaveCount(8);
  await expect(page.getByText("alice", { exact: true }).first()).toBeVisible();
  await expect(
    page.getByText("Bérénice", { exact: true }).first(),
  ).toBeVisible();
  await expect(
    page.getByRole("columnheader", { name: "Native start" }),
  ).toBeVisible();
  await expect(
    page.getByRole("checkbox", { name: "Global (g)" }),
  ).toBeChecked();
  await expect(page.locator(".cm-capture-1").first()).toHaveAttribute(
    "title",
    /group 1 · date/u,
  );
  await expect(
    page
      .locator('[aria-live="polite"]')
      .filter({ hasText: "Completed locally" }),
  ).toHaveCount(1);
  expect(pageErrors).toEqual([]);
});

test("PCRE2 WebAssembly executes flavour-only syntax and native substitution", async ({
  page,
}) => {
  const pageErrors: string[] = [];
  page.on("pageerror", (error) => pageErrors.push(error.message));
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
  await expect(
    page.getByRole("spinbutton", { name: "Match steps" }),
  ).toHaveValue("1000000");
  await setEditor(
    page,
    "editor-regular-expression-pattern",
    "(?|(a)|(b))-(?<number>\\d+)",
  );
  await setEditor(page, "editor-test-text", "b-42");
  const run = page.getByRole("button", { name: "Run", exact: true });
  await expect(run).toBeEnabled();
  await run.click();
  await waitForReady(page);

  await expect(page.locator(".run-status")).toContainText(
    "native offsets utf8-byte",
  );
  await expect(page.locator(".extraction-row").first()).toContainText("b-42");
  await expect(page.getByText("42", { exact: true }).first()).toBeVisible();

  await page.getByRole("button", { name: "Replace", exact: true }).click();
  await setEditor(page, "editor-replacement-template", "${number}:$1");
  await expect(run).toBeEnabled();
  await run.click();
  await waitForReady(page);
  await expect(
    page.getByTestId("editor-replacement-output").locator(".cm-content"),
  ).toHaveText("42:b");

  await page.getByRole("button", { name: "Quick reference" }).click();
  await expect(
    page.getByRole("dialog", { name: "Quick reference" }),
  ).toContainText("Branch-reset group");
  await page.keyboard.press("Escape");
  await page.getByRole("button", { name: "Capabilities" }).click();
  const capabilities = page.getByRole("dialog", { name: "Capabilities" });
  await expect(capabilities).toContainText("PCRE2 WebAssembly");
  await expect(capabilities).toContainText("10.47 2025-10-21");
  expect(pageErrors).toEqual([]);
});

test("PCRE2 trace viewer reports bounded automatic callouts in its own worker", async ({
  page,
}) => {
  const pageErrors: string[] = [];
  page.on("pageerror", (error) => pageErrors.push(error.message));
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
  await setEditor(page, "editor-regular-expression-pattern", "(a+)+$");
  await setEditor(page, "editor-test-text", "aaaa!");

  await page.getByRole("button", { name: "PCRE trace" }).click();
  const dialog = page.getByRole("dialog", { name: "Execution trace" });
  await expect(dialog).toBeVisible();
  await expect(dialog).toContainText("reported automatic callouts");
  await dialog.getByRole("spinbutton", { name: "Maximum events" }).fill("1");
  await dialog
    .getByRole("spinbutton", { name: "Maximum serialized bytes" })
    .fill("4096");
  await dialog.getByRole("button", { name: "Run trace" }).click();
  await expect(dialog.locator(".trace-status")).toHaveClass(/status-ready/u, {
    timeout: 15_000,
  });
  await expect(dialog.locator(".trace-table tbody tr")).toHaveCount(1);
  await expect(dialog).toContainText("native status -37");
  await expect(dialog).toContainText("one exact pcre2_match() invocation");
  await expect(dialog).toContainText("configured event or serialized-byte cap");
  await dialog.getByRole("button", { name: "Select trace event 1" }).click();
  await expect(dialog).not.toBeVisible();
  expect(pageErrors).toEqual([]);
});

test("ECMAScript analysis runs bounded cold and warm samples outside trace mode", async ({
  page,
}) => {
  const pageErrors: string[] = [];
  page.on("pageerror", (error) => pageErrors.push(error.message));
  await page.goto("./");
  await waitForReady(page);
  await page.getByRole("button", { name: "Analysis" }).click();
  const dialog = page.getByRole("dialog", {
    name: "Performance & risk analysis",
  });
  await expect(dialog).toBeVisible();
  await expect(dialog).toContainText("Potential risk findings");
  await dialog.getByRole("tab", { name: "Benchmark" }).click();
  await expect(dialog).toContainText("Trace mode is never used");
  await dialog.getByRole("spinbutton", { name: "Warm-up samples" }).fill("0");
  await dialog.getByRole("spinbutton", { name: "Measured samples" }).fill("2");
  await dialog.getByRole("button", { name: "Run bounded benchmark" }).click();
  await expect(
    dialog.getByRole("heading", { name: "Benchmark result" }),
  ).toBeVisible({ timeout: 15_000 });
  await expect(dialog).toContainText("2 measured warm samples");
  await expect(dialog).toContainText("Native ECMAScript RegExp");
  expect(pageErrors).toEqual([]);
});

test("serves the bundled PCRE2 runtime with WebAssembly MIME", async ({
  request,
}) => {
  const moduleResponse = await request.get("./engines/pcre2/pcre2.mjs");
  expect(moduleResponse.ok()).toBe(true);
  expect(moduleResponse.headers()["content-type"]).toContain("text/javascript");
  const response = await request.get("./engines/pcre2/pcre2.wasm");
  expect(response.ok()).toBe(true);
  expect(response.headers()["content-type"]).toContain("application/wasm");
  expect((await response.body()).subarray(0, 4)).toEqual(
    Buffer.from([0x00, 0x61, 0x73, 0x6d]),
  );
});

test("quick reference and capabilities open as populated in-viewport dialogs", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);

  await page
    .getByRole("button", { name: "Quick reference", exact: true })
    .click();
  const referenceDialog = page.getByRole("dialog", {
    name: "Quick reference",
  });
  await expect(referenceDialog).toBeVisible();
  await expect(referenceDialog).toBeInViewport();
  await expect(
    referenceDialog.getByRole("button", { name: "Close quick reference" }),
  ).toBeFocused();
  await expect(
    referenceDialog.getByRole("listitem").filter({ hasText: "Literal" }),
  ).toBeVisible();
  await referenceDialog
    .getByLabel("Filter constructs")
    .fill("not a reference entry");
  await expect(referenceDialog.getByRole("status")).toContainText(
    "No reference constructs match",
  );
  await page.keyboard.press("Escape");
  await expect(referenceDialog).not.toBeVisible();
  await expect(
    page.getByRole("button", { name: "Quick reference", exact: true }),
  ).toHaveAttribute("aria-expanded", "false");

  await page.getByRole("button", { name: "Capabilities", exact: true }).click();
  const capabilitiesDialog = page.getByRole("dialog", {
    name: "Capabilities",
  });
  await expect(capabilitiesDialog).toBeVisible();
  await expect(capabilitiesDialog).toBeInViewport();
  await expect(
    capabilitiesDialog.getByRole("button", { name: "Close capabilities" }),
  ).toBeFocused();
  await expect(capabilitiesDialog).toContainText("regexpp-community 4.12.2");
  await expect(capabilitiesDialog).toContainText("Native ECMAScript RegExp");
  await capabilitiesDialog
    .getByRole("button", { name: "Close capabilities" })
    .click();
  await expect(capabilitiesDialog).not.toBeVisible();
});

test("corpus mode scans local text, applies exact replacements, and exports safely", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page
    .getByRole("button", { name: "Corpus / Apply", exact: true })
    .click();

  await expect(
    page.getByRole("heading", { name: "Corpus documents" }),
  ).toBeVisible();
  await expect(page.getByTestId("editor-test-text")).not.toBeVisible();

  await page
    .getByRole("textbox", { name: "Pasted corpus text" })
    .fill("2026-07-24 alice\nnot a date");
  await page.getByRole("button", { name: "Add pasted text" }).click();
  await page.getByLabel("Add corpus text files").setInputFiles([
    {
      name: "second.txt",
      mimeType: "text/plain",
      buffer: Buffer.from("2025-12-31 Bérénice", "utf8"),
    },
    {
      name: "empty.txt",
      mimeType: "text/plain",
      buffer: Buffer.from("no date here", "utf8"),
    },
  ]);
  await expect(
    page.getByRole("list", { name: "Corpus documents" }).getByRole("listitem"),
  ).toHaveCount(3);

  await page.getByRole("button", { name: "Scan corpus" }).click();
  const results = page.getByRole("table", {
    name: "Corpus document results",
  });
  await expect(results).toBeVisible();
  await expect(results.locator("tbody tr")).toHaveCount(3);
  await expect(results.locator("tbody tr").nth(0)).toContainText("complete");
  await expect(results.locator("tbody tr").nth(0)).toContainText("1");
  await expect(results.locator("tbody tr").nth(2)).toContainText("0");
  await expect(
    page.getByRole("table", {
      name: "Capture summaries for pasted-text.txt",
    }),
  ).toContainText("alice");

  const reportDownload = page.waitForEvent("download");
  await page.getByRole("button", { name: "Download summary JSON" }).click();
  const report = await reportDownload;
  expect(report.suggestedFilename()).toBe("regex-corpus-report.json");
  const reportPath = await report.path();
  if (!reportPath) throw new Error("Corpus report download unavailable");
  const reportText = await readFile(reportPath, "utf8");
  expect(reportText).not.toContain("2026-07-24 alice");
  expect(JSON.parse(reportText)).toMatchObject({
    schemaVersion: 1,
    operation: "match",
    status: "complete",
    summary: { totalDocuments: 3, totalMatches: 2 },
  });

  await page.getByRole("radio", { name: /Apply replacement/u }).check();
  await page.getByRole("button", { name: "Apply to corpus" }).click();
  await expect(results.locator("tbody tr")).toHaveCount(3);
  await expect(results.locator("tbody tr").first()).toContainText("complete");
  const contentConfirmation = page.getByRole("checkbox", {
    name: "I understand applied downloads contain corpus content",
  });
  await expect(
    page.getByRole("button", { name: "Download applied ZIP" }),
  ).toBeDisabled();
  await contentConfirmation.check();
  await expect(
    page.getByRole("heading", { name: "Replacement preview" }),
  ).toBeVisible();
  await expect(page.locator(".corpus-output-preview pre")).toContainText(
    "alice — 2026-07-24",
  );

  const zipDownload = page.waitForEvent("download");
  await page.getByRole("button", { name: "Download applied ZIP" }).click();
  const archive = await zipDownload;
  expect(archive.suggestedFilename()).toBe("regex-corpus-applied.zip");
  const archivePath = await archive.path();
  if (!archivePath) throw new Error("Corpus ZIP download unavailable");
  const files = unzipSync(await readFile(archivePath));
  expect(Object.keys(files).sort()).toEqual([
    "empty.replaced.txt",
    "pasted-text.replaced.txt",
    "second.replaced.txt",
  ]);
  expect(new TextDecoder().decode(files["pasted-text.replaced.txt"])).toContain(
    "alice — 2026-07-24",
  );

  await page.getByRole("button", { name: "Project", exact: true }).click();
  const projectDownload = page.waitForEvent("download");
  await page
    .locator(".project-popover")
    .getByRole("button", { name: "Export JSON" })
    .click();
  const projectFile = await projectDownload;
  const projectPath = await projectFile.path();
  if (!projectPath) throw new Error("Project download unavailable");
  const projectText = await readFile(projectPath, "utf8");
  expect(projectText).not.toContain("2026-07-24 alice");
  expect(JSON.parse(projectText)).toMatchObject({ mode: "corpus" });
});

test("corpus independent-line mode has explicit anchor semantics and preserves separators", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(page, "editor-regular-expression-pattern", "^.+$");
  await page
    .getByRole("button", { name: "Corpus / Apply", exact: true })
    .click();
  await page
    .getByRole("textbox", { name: "Pasted corpus text" })
    .fill("alpha\r\nbeta");
  await page.getByRole("button", { name: "Add pasted text" }).click();
  await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled();

  await page.getByRole("button", { name: "Scan corpus" }).click();
  const resultRow = page
    .getByRole("table", { name: "Corpus document results" })
    .locator("tbody tr")
    .first();
  await expect(resultRow.locator("td").nth(1)).toHaveText("0");

  await page.getByRole("radio", { name: /Independent lines/u }).check();
  await expect(page.getByText(/Anchors see one line at a time/u)).toBeVisible();
  await page.getByRole("button", { name: "Scan corpus" }).click();
  await expect(resultRow.locator("td").nth(1)).toHaveText("2");

  await page.getByRole("radio", { name: /Apply replacement/u }).check();
  await page
    .getByRole("textbox", { name: "Replacement template" })
    .fill("[$&]");
  await page.getByRole("button", { name: "Apply to corpus" }).click();
  await expect(page.locator(".corpus-output-preview pre")).toContainText(
    "[alpha]",
  );
  await expect(page.locator(".corpus-output-preview pre")).toContainText(
    "[beta]",
  );
  expect(await page.locator(".corpus-output-preview pre").textContent()).toBe(
    "[alpha]\n[beta]",
  );
});

test("a running corpus job can be cancelled and labels unprocessed output", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$");
  await page.getByLabel("Manual limit").selectOption("10000");
  await page
    .getByRole("button", { name: "Corpus / Apply", exact: true })
    .click();
  await page
    .getByRole("textbox", { name: "Pasted corpus text" })
    .fill(`${"a".repeat(48)}!`);
  await page.getByRole("button", { name: "Add pasted text" }).click();
  await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled();
  await page.getByRole("button", { name: "Scan corpus" }).click();
  await page.getByRole("button", { name: "Cancel corpus run" }).click();

  await expect(
    page.getByRole("table", { name: "Corpus document results" }),
  ).toContainText("cancelled", { timeout: 15_000 });
  await expect(
    page.getByRole("button", { name: "Download summary JSON" }),
  ).toBeEnabled();
});

test("editing reports malformed syntax and recovers with exact matches", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  const patternEditor = page
    .getByTestId("editor-regular-expression-pattern")
    .locator(".cm-content");
  const originalPattern = await patternEditor.textContent();
  await patternEditor.click();
  await patternEditor.press("End");
  await patternEditor.press("x");
  await expect(patternEditor).toHaveText(`${originalPattern}x`);
  await setEditor(page, "editor-regular-expression-pattern", "(");
  await expect(
    page
      .locator(".diagnostic-error")
      .filter({ hasText: /unterminated|invalid/iu }),
  ).toBeVisible();
  await expect(page.locator(".run-status")).toContainText(
    "Execution is paused",
  );

  await setEditor(page, "editor-regular-expression-pattern", "(?<word>\\w+)");
  await setEditor(page, "editor-test-text", "hello world");
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);
  await expect(page.locator(".extraction-row").first()).toContainText("hello");
});

test("rapid pattern and replacement edits cannot run with stale syntax", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await page.getByRole("button", { name: "Replace", exact: true }).click();

  await setEditor(page, "editor-regular-expression-pattern", "(?<stale>\\w+)");
  await setEditor(page, "editor-replacement-template", "$<stale>");
  await setEditor(
    page,
    "editor-regular-expression-pattern",
    "(?<letter>[A-Z])",
  );
  await setEditor(page, "editor-replacement-template", "[$<letter>]");
  await setEditor(page, "editor-test-text", "A B");

  const runButton = page.getByRole("button", { name: "Run", exact: true });
  await expect(runButton).toBeDisabled();
  await runButton.evaluate((button: HTMLButtonElement) => button.click());
  await expect(page.locator(".run-status")).not.toHaveClass(/status-running/u);
  await expect(page.locator(".extraction-row")).toHaveCount(0);

  await expect(runButton).toBeEnabled();
  const currentNamedToken = page.locator(".token-row").filter({
    has: page.locator("code", { hasText: "$<letter>" }),
  });
  await expect(currentNamedToken.locator("code")).toHaveText("$<letter>");
  await expect(currentNamedToken.locator("small")).toHaveText("named capture");

  await page.getByRole("checkbox", { name: "Ignore case (i)" }).check();
  await expect(runButton).toBeDisabled();
  await runButton.evaluate((button: HTMLButtonElement) => button.click());
  await expect(page.locator(".run-status")).not.toHaveClass(/status-running/u);

  await expect(runButton).toBeEnabled();
  await runButton.click();
  await waitForReady(page);
  await expect(
    page.getByTestId("editor-replacement-output").locator(".cm-content"),
  ).toHaveText("[A] [B]");
});

test("manual editing clears results that no longer describe the inputs", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();

  await setEditor(page, "editor-regular-expression-pattern", "nomatch");

  await expect(page.locator(".run-status")).toHaveClass(/status-idle/u);
  await expect(page.locator(".run-status")).toContainText(
    "previous results were cleared",
  );
  await expect(page.locator(".run-status small")).toHaveCount(0);
  await expect(page.locator(".extraction-row")).toHaveCount(0);
  await expect(page.locator(".capture-table-scroll tbody tr")).toHaveCount(0);
  await expect(page.locator(".cm-subject-match")).toHaveCount(0);
});

test("capture-group resource limits block execution visibly", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();

  await setEditor(
    page,
    "editor-regular-expression-pattern",
    "()".repeat(1_001),
  );

  await expect(page.locator(".run-status")).toHaveClass(/status-error/u);
  await expect(page.locator(".run-status")).toContainText(
    "above the 1,000 group execution limit",
  );
  await expect(page.locator(".run-status small")).toHaveCount(0);
  await expect(page.locator(".extraction-row")).toHaveCount(0);
});

test("large valid patterns bound syntax tree nodes and editor decorations", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();

  await setEditor(page, "editor-regular-expression-pattern", "a".repeat(2_100));

  await expect(page.getByTestId("pattern-mark-render-limit")).toBeVisible();
  await expect(page.getByTestId("explanation-render-limit")).toContainText(
    "2,000",
  );
  await expect(
    page
      .getByRole("tree", { name: "Pattern explanation" })
      .getByRole("treeitem"),
  ).toHaveCount(2_000);
});

test("the pattern editor rejects input above its hard resource limit", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  const editor = page
    .getByTestId("editor-regular-expression-pattern")
    .locator(".cm-content");
  const previousPattern = await editor.textContent();

  await editor.fill("a".repeat(64 * 1_024 + 1));

  await expect(page.getByRole("alert")).toContainText(
    "limited to 65,536 UTF-16 units",
  );
  await expect(editor).toHaveText(previousPattern ?? "");
});

test("large match sets bound tree nodes and editor highlights explicitly", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(page, "editor-regular-expression-pattern", "a");
  await setEditor(page, "editor-test-text", "a".repeat(10_001));

  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);

  await expect(page.locator(".run-status")).toContainText("10,000 matches");
  await expect(page.locator(".extraction-row")).toHaveCount(2_000);
  await expect(page.getByTestId("extraction-render-limit")).toContainText(
    "first 2,000 of 20,000",
  );
  await expect(page.getByTestId("subject-mark-render-limit")).toContainText(
    "first 2,000 of 10,000",
  );
  await expect(
    page.locator(".diagnostic").filter({ hasText: "configured limit" }),
  ).toBeVisible();
});

test("list export stays disabled when the engine returned a limited prefix", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await page.getByRole("button", { name: "List", exact: true }).click();
  await setEditor(page, "editor-regular-expression-pattern", "a");
  await setEditor(page, "editor-test-text", "a".repeat(10_001));

  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);

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

test("subject caret selection identifies a capture without replacing it", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  const subjectEditor = page
    .getByTestId("editor-test-text")
    .locator(".cm-content");
  const originalSubject = await subjectEditor.textContent();

  await subjectEditor.click();
  await subjectEditor.press("Control+Home");
  await expect(
    page
      .getByRole("tree", { name: "Match extraction" })
      .locator(".tree-row.is-selected"),
  ).toContainText("Group 1 — date");
  await expect(
    page
      .getByRole("grid", { name: "Captured matches" })
      .locator("tbody tr")
      .first(),
  ).toHaveAttribute("aria-selected", "true");
  expect(
    await subjectEditor.evaluate((editor) => {
      const selection = globalThis.getSelection();
      return (
        selection?.isCollapsed === true && editor.contains(selection.anchorNode)
      );
    }),
  ).toBe(true);

  await subjectEditor.press("x");
  await expect(subjectEditor).toHaveText(`x${originalSubject}`);
});

test("test-text whitespace and caret position are visible on request", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(page, "editor-test-text", "a b\ncd");

  await expect(page.getByText("Ln 2, Col 3", { exact: true })).toBeVisible();
  await page.getByRole("checkbox", { name: "Visible whitespace" }).check();
  await expect(
    page.getByTestId("editor-test-text").locator(".cm-highlightSpace"),
  ).toHaveCount(1);
});

test("capture-row selection remains synchronized after editor highlighting", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  const captureRows = page
    .getByRole("grid", { name: "Captured matches" })
    .locator("tbody tr");

  await captureRows.first().focus();
  await captureRows.first().press("Enter");

  await expect(captureRows.first()).toHaveAttribute("aria-selected", "true");
  await expect(captureRows.first()).toHaveClass(/is-selected/u);
  await expect(
    page
      .getByRole("tree", { name: "Match extraction" })
      .locator(".tree-row.is-selected"),
  ).toContainText("Group 1 — date");
  await expect(
    page.getByTestId("editor-test-text").locator(".cm-selected-range"),
  ).toHaveCount(1);

  await page
    .getByRole("tree", { name: "Match extraction" })
    .getByRole("treeitem", { name: /^Match 1 —/u })
    .click();
  await expect(
    page
      .getByRole("tree", { name: "Pattern explanation" })
      .locator(".tree-row.is-selected"),
  ).toContainText("Regular-expression pattern");
});

test("pattern explanation selection synchronizes capture result views", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);

  await page
    .getByRole("tree", { name: "Pattern explanation" })
    .locator(".tree-row")
    .filter({ hasText: "group 1 · date" })
    .first()
    .click();

  await expect(
    page
      .getByRole("tree", { name: "Match extraction" })
      .locator(".tree-row.is-selected"),
  ).toContainText("Group 1 — date");
  await expect(
    page
      .getByRole("grid", { name: "Captured matches" })
      .locator("tbody tr")
      .first(),
  ).toHaveAttribute("aria-selected", "true");
  await expect(
    page.getByTestId("editor-test-text").locator(".cm-selected-range"),
  ).toHaveCount(1);
});

test("replacement and list modes use native and typed templates", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByRole("button", { name: "Replace", exact: true }).click();
  await expect(
    page.getByRole("heading", { name: "Replacement tokens" }),
  ).toBeVisible();
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);
  await expect(
    page.getByTestId("editor-replacement-output").locator(".cm-content"),
  ).toContainText("alice — 2026-07-24");
  const perMatchPreview = page.locator(".replacement-preview-panel");
  await expect(
    perMatchPreview.getByRole("heading", { name: "Per-match preview" }),
  ).toBeVisible();
  await expect(
    perMatchPreview.locator(".replacement-match-preview"),
  ).toHaveCount(2);
  await expect(
    perMatchPreview.locator(".replacement-match-preview").first(),
  ).toContainText("2026-07-24 alice");
  await expect(
    perMatchPreview.locator(".replacement-match-preview").first(),
  ).toContainText("alice — 2026-07-24");

  const userContribution = perMatchPreview
    .getByRole("button", { name: /Insert named capture “user”/u })
    .first();
  await userContribution.click();
  await expect(userContribution).toHaveAttribute("aria-pressed", "true");
  await expect(
    page
      .getByRole("tree", { name: "Match extraction" })
      .locator(".tree-row.is-selected"),
  ).toContainText("Group 2 — user");
  await expect(
    page
      .getByTestId("editor-replacement-template")
      .locator(".cm-selected-range"),
  ).toHaveCount(1);
  await expect(
    page
      .getByTestId("editor-regular-expression-pattern")
      .locator(".cm-selected-range"),
  ).toHaveCount(1);
  await expect(
    page.getByTestId("editor-test-text").locator(".cm-selected-range"),
  ).toHaveCount(1);
  await expect(
    page.getByTestId("editor-replacement-output").locator(".cm-selected-range"),
  ).toHaveCount(1);

  await page.getByRole("button", { name: "List", exact: true }).click();
  await expect(
    page.getByRole("heading", { name: "Generated rows" }),
  ).toBeVisible();
  await expect(page.locator(".list-preview li")).toHaveCount(2);
  await expect(page.locator(".list-preview li").first()).toContainText(
    "alice — 2026-07-24",
  );
});

test("sticky scan-all replacement uses every authoritative match", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);

  await page.getByRole("checkbox", { name: "Global (g)" }).press("Space");
  await page.getByRole("checkbox", { name: "Unicode (u)" }).press("Space");
  await page.getByRole("checkbox", { name: "Sticky (y)" }).press("Space");
  await page.getByRole("checkbox", { name: "Scan all" }).check();
  await setEditor(page, "editor-regular-expression-pattern", "a");
  await setEditor(page, "editor-test-text", "aa");
  await waitForReady(page);

  await page.getByRole("button", { name: "Replace", exact: true }).click();
  await setEditor(page, "editor-replacement-template", "x");
  await waitForReady(page);

  await expect(page.locator(".run-status")).toContainText("2 matches");
  await expect(
    page
      .getByRole("tree", { name: "Match extraction" })
      .getByRole("treeitem", { name: /^Match \d+ —/u }),
  ).toHaveCount(2);
  await expect(
    page.getByTestId("editor-replacement-output").locator(".cm-content"),
  ).toHaveText("xx");
});

test("unit tests run through the actual worker engine", async ({ page }) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByRole("button", { name: "Unit tests", exact: true }).click();
  await page
    .getByRole("textbox", { name: "Name", exact: true })
    .fill("Current dates");
  await page.getByRole("button", { name: "Add test" }).click();
  await page.getByRole("button", { name: "Run all" }).click();
  await expect(page.locator(".test-result")).toContainText("Pass", {
    timeout: 15_000,
  });
});

test("unit-test cases can be captured, cloned, edited, selectively run, filtered, and moved as JSON", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByRole("button", { name: "Unit tests", exact: true }).click();

  await page.getByRole("button", { name: "Add current result" }).click();
  await page
    .getByRole("button", { name: "Clone Current match result" })
    .click();
  await page
    .getByRole("button", { name: "Edit Current match result copy" })
    .click();
  await page
    .getByRole("textbox", { name: "Name", exact: true })
    .fill("Expected failure");
  await page
    .getByRole("combobox", { name: "Expectation" })
    .selectOption("should-not-match");
  await page.getByRole("button", { name: "Update test" }).click();

  await page
    .getByRole("checkbox", { name: "Select Current match result" })
    .check();
  await page.getByRole("button", { name: "Run selected" }).click();
  await expect(
    page
      .locator(".test-list li")
      .filter({ hasText: "Current match result" })
      .first()
      .locator(".test-result"),
  ).toContainText("Pass", { timeout: 15_000 });
  await expect(
    page
      .locator(".test-list li")
      .filter({ hasText: "Expected failure" })
      .locator(".test-result"),
  ).toHaveText("Not run");

  await page.getByRole("checkbox", { name: "Select Expected failure" }).check();
  await page.getByRole("button", { name: "Run selected" }).click();
  await expect(
    page
      .locator(".test-list li")
      .filter({ hasText: "Expected failure" })
      .locator(".test-result"),
  ).toContainText("Fail", { timeout: 15_000 });

  await page.getByRole("checkbox", { name: "Failures only" }).check();
  await expect(page.locator(".test-list li")).toHaveCount(1);
  await expect(page.locator(".test-list li")).toContainText("Expected failure");

  await page
    .getByRole("checkbox", { name: "Include subjects in JSON" })
    .check();
  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Export JSON" }).click();
  const download = await downloadPromise;
  expect(download.suggestedFilename()).toBe("regex-tests.regex-tests.json");
  const downloadedPath = await download.path();
  if (!downloadedPath) throw new Error("Test-suite download path unavailable");
  await page
    .getByLabel("Import test-suite JSON file")
    .setInputFiles(downloadedPath);
  await expect(page.getByText("Imported 2 tests.")).toBeVisible();
  await page.getByRole("checkbox", { name: "Failures only" }).uncheck();
  await expect(page.locator(".test-list li")).toHaveCount(4);
});

test("a running unit-test suite can be cancelled without affecting interactive execution", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$");
  await setEditor(page, "editor-test-text", `${"a".repeat(48)}!`);
  await page.getByRole("button", { name: "Unit tests", exact: true }).click();
  await page
    .getByRole("combobox", { name: "Expectation" })
    .selectOption("must-time-out");
  await page
    .getByRole("spinbutton", { name: "Per-test timeout (ms, optional)" })
    .fill("10000");
  await page.getByRole("button", { name: "Add test" }).click();
  await page.getByRole("button", { name: "Run all" }).click();
  await page.getByRole("button", { name: "Cancel" }).click();
  await expect(page.getByRole("button", { name: "Run all" })).toBeEnabled();

  await page.getByRole("button", { name: "Match", exact: true }).click();
  await setEditor(page, "editor-regular-expression-pattern", "^ok$");
  await setEditor(page, "editor-test-text", "ok");
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);
  await expect(page.locator(".run-status")).toContainText("1 matches");
});

test("invalid Toolbox context degrades to the complete standalone app", async ({
  page,
}) => {
  await page.goto(
    "./?toolbox=https%3A%2F%2Fcross-origin.invalid%2Ftoolbox.catalog.json",
  );
  await expect(
    page.getByRole("heading", { name: "Regex Tools" }),
  ).toBeVisible();
  await waitForReady(page);
  await expect(
    page.getByRole("button", { name: "Run", exact: true }),
  ).toBeEnabled();
});

test("a valid same-origin Toolbox context connects the shared app switcher", async ({
  page,
}) => {
  await page.goto("./?toolbox=/toolbox.catalog.json");
  await waitForReady(page);

  await expect(page.locator(".toolbox-shell")).toHaveAttribute(
    "data-toolbox-context",
    "connected",
  );
  await page.getByRole("button", { name: "Apps" }).click();
  const switcher = page.getByRole("navigation", {
    name: "Toolbox applications",
  });
  await expect(switcher).toBeVisible();
  await expect(
    switcher.getByRole("link", { name: "Regex Tools" }),
  ).toHaveAttribute("aria-current", "page");
});

test("the same production build loads from a deep nested path", async ({
  page,
}) => {
  const responses: string[] = [];
  page.on("response", (response) => {
    if (response.status() >= 400) responses.push(response.url());
  });
  await page.goto("/deep/nested/regex/");
  await expect(
    page.getByRole("heading", { name: "Regex Tools" }),
  ).toBeVisible();
  await waitForReady(page);
  await expect(page.locator(".extraction-row")).toHaveCount(8);
  expect(responses).toEqual([]);
});

test("responsive layout retains editors and result trees", async ({ page }) => {
  await page.setViewportSize({ width: 320, height: 844 });
  await page.goto("./");
  await waitForReady(page);
  await expect(
    page.getByTestId("editor-regular-expression-pattern"),
  ).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Explanation tree" }),
  ).toBeVisible();
  await expect(
    page.getByRole("heading", { name: "Extraction tree" }),
  ).toBeVisible();
  const overflows = await page.evaluate(
    () => document.documentElement.scrollWidth > window.innerWidth,
  );
  expect(overflows).toBe(false);
});

test("dark theme capture and success text meets normal-text contrast", async ({
  page,
}) => {
  await page.emulateMedia({ colorScheme: "dark" });
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await setEditor(
    page,
    "editor-regular-expression-pattern",
    "(a)(b)(c)(d)(e)(f)(g)(h)",
  );
  await setEditor(page, "editor-test-text", "abcdefgh");
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);
  await expect(page.locator(".cm-capture-8").first()).toBeAttached();

  const ratios = await page.evaluate(() => {
    const channels = (value: string): [number, number, number] => {
      const values = value.match(/[\d.]+/gu)?.map(Number) ?? [];
      if (value.startsWith("color(srgb")) {
        return [values[0]! * 255, values[1]! * 255, values[2]! * 255];
      }
      return [values[0]!, values[1]!, values[2]!];
    };
    const luminance = (color: [number, number, number]) => {
      const normalized = color.map((channel) => {
        const value = channel / 255;
        return value <= 0.04045
          ? value / 12.92
          : ((value + 0.055) / 1.055) ** 2.4;
      });
      return (
        0.2126 * normalized[0]! +
        0.7152 * normalized[1]! +
        0.0722 * normalized[2]!
      );
    };
    const contrast = (foreground: string, background: string) => {
      const left = luminance(channels(foreground));
      const right = luminance(channels(background));
      return (Math.max(left, right) + 0.05) / (Math.min(left, right) + 0.05);
    };
    const editor = document.querySelector<HTMLElement>(".code-editor");
    if (!editor) throw new Error("Editor is unavailable.");
    const editorBackground = getComputedStyle(editor).backgroundColor;
    const captureRatios = Array.from({ length: 8 }, (_, index) => {
      const capture = document.querySelector<HTMLElement>(
        `.cm-capture-${index + 1}`,
      );
      if (!capture) throw new Error(`Capture ${index + 1} is unavailable.`);
      return contrast(getComputedStyle(capture).color, editorBackground);
    });
    const success = document.querySelector<HTMLElement>(
      ".run-status.status-ready > span",
    );
    if (!success) throw new Error("Success status is unavailable.");
    return [
      ...captureRatios,
      contrast(
        getComputedStyle(success).color,
        getComputedStyle(success).backgroundColor,
      ),
    ];
  });
  expect(Math.min(...ratios)).toBeGreaterThanOrEqual(4.5);
});

test("project file picker is named and omitted from keyboard order", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByRole("button", { name: "Project", exact: true }).click();

  const input = page.getByLabel("Import regex project JSON file");
  await expect(input).toHaveAttribute("tabindex", "-1");
  await expect(input).toHaveAttribute("type", "file");
});

test("result trees and capture rows support keyboard navigation", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);

  const explanationItems = page
    .getByRole("tree", { name: "Pattern explanation" })
    .getByRole("treeitem");
  await explanationItems.first().focus();
  await explanationItems.first().press("ArrowDown");
  await expect(explanationItems.nth(1)).toBeFocused();

  const captureRows = page
    .getByRole("grid", { name: "Captured matches" })
    .locator("tbody tr");
  await captureRows.first().focus();
  await captureRows.first().press("ArrowDown");
  await expect(captureRows.nth(1)).toBeFocused();

  const globalFlag = page.getByRole("checkbox", { name: "Global (g)" });
  await globalFlag.focus();
  const flagFocus = await globalFlag.evaluate((input) => {
    const indicator = input.nextElementSibling;
    if (!(indicator instanceof HTMLElement)) return null;
    const style = getComputedStyle(indicator);
    return { style: style.outlineStyle, width: style.outlineWidth };
  });
  expect(flagFocus).toEqual({ style: "solid", width: "3px" });
});

test("a timeout kills the worker and the next request uses a fresh engine", async ({
  page,
}) => {
  await page.goto("./");
  await waitForReady(page);
  await page.getByLabel("Live").uncheck();
  await page.getByLabel("Manual limit").selectOption("250");
  await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$");
  await setEditor(page, "editor-test-text", `${"a".repeat(48)}!`);
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await expect(page.locator(".run-status")).toHaveClass(/status-timeout/u, {
    timeout: 15_000,
  });
  await expect(page.locator(".run-status")).toContainText(
    "worker was terminated",
  );
  await expect(page.locator(".run-status small")).toHaveCount(0);
  await expect(page.locator(".extraction-row")).toHaveCount(0);
  await expect(page.locator(".capture-table-scroll tbody tr")).toHaveCount(0);
  await expect(page.locator(".cm-subject-match")).toHaveCount(0);

  await setEditor(page, "editor-regular-expression-pattern", "^ok$");
  await setEditor(page, "editor-test-text", "ok");
  await page.getByRole("button", { name: "Run", exact: true }).click();
  await waitForReady(page);
  await expect(page.locator(".run-status")).toContainText("1 matches");
});
