import { expect } from "e2e";
import { writeFile } from "node:fs/promises";
import { run } from "./shell.ts";
import { runPromise, test, WEB } from "./t3.ts";

test("A selected-file commit preserves an excluded stash conflict and Commit all refuses it", WEB,
  async ({ t3, app, screen, browser }) => {
    const project = await t3.project("Shopping", {
      "shopping-list.md": "# Shopping\n- milk\n",
      "notes.md": "# Notes\nOriginal notes.\n",
    });
    const git = (args: string[], allowExit = [0]) => runPromise(run("git", [
      "-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", ...args,
    ], { cwd: project, allowExit }));
    await git(["checkout", "-b", "shopping"]);
    await writeFile(`${project}/shopping-list.md`, "# Shopping\n- oat milk\n");
    await git(["stash", "push", "-m", "Oat milk"]);
    await writeFile(`${project}/shopping-list.md`, "# Shopping\n- almond milk\n");
    await git(["commit", "-am", "Use almond milk"]);
    await git(["stash", "pop"], [1]);
    await writeFile(`${project}/notes.md`, "# Notes\nUpdated notes.\n");
    const thread = await t3.thread({ project, title: "Preserve shopping conflict", messages: ["Ready to commit the notes update."] });
    await t3.open(thread);
    // Redact only the synthetic repository's temporary path from public recordings.
    await browser.evaluate((repo: string) => {
      const redact = () => {
        for (const description of document.querySelectorAll('[data-slot="toast-description"]')) {
          const walker = document.createTreeWalker(description, NodeFilter.SHOW_TEXT);
          while (walker.nextNode()) {
            const node = walker.currentNode;
            if (node.textContent?.includes(repo)) node.textContent = node.textContent.replaceAll(repo, "[synthetic repository]");
          }
        }
      };
      new MutationObserver(redact).observe(document.body, { childList: true, subtree: true, characterData: true });
    }, project);
    await screen.getByRole("button", "Git action options").tap();
    await screen.getByRole("menuitem", "Commit").tap();
    await screen.getByRole("button", "Edit", { exact: true }).tap();
    // The header checkbox is first, followed by notes.md and shopping-list.md.
    await screen.getByRole("checkbox").nth(2).uncheck();
    await screen.getByPlaceholder("Leave empty to auto-generate").fill("Update notes");
    await app.screenshot("selected-notes-excluded-conflict");
    await screen.getByRole("button", "Commit", { exact: true }).tap();
    await screen.getByText(/^Committed [0-9a-f]{7}$/).waitFor();
    await screen.getByRole("button", "Git action options").tap();
    await screen.getByRole("menuitem", "Commit").tap();
    await screen.getByPlaceholder("Leave empty to auto-generate").fill("Commit all");
    await app.screenshot("commit-all-shopping-conflict");
    await screen.getByRole("button", "Commit", { exact: true }).tap();
    await screen.getByText(/^(Committed [0-9a-f]{7}|Action failed)$/).waitFor();
    await browser.evaluate(async () => {
      await Promise.all(document.getAnimations().filter((animation) =>
        Number.isFinite(Number(animation.effect?.getTiming().iterations))
      ).map((animation) => animation.finished.catch(() => {})));
    });
    await app.screenshot("commit-all-result");
    await expect(screen.getByText("Cannot commit with unresolved merge conflicts in shopping-list.md. Resolve them, then commit.", { exact: false })).toBeVisible({ timeout: 3000 });
    expect((await git(["ls-files", "--unmerged"])).stdout.trim().split("\n")).toHaveLength(3);
    expect((await git(["log", "-1", "--format=%s"])).stdout.trim()).toBe("Update notes");
  });
