import { expect } from "e2e";
import { readdir, rm, readFile, writeFile } from "node:fs/promises";
import { setTimeout as pause } from "node:timers/promises";
import { join } from "node:path";
import { test, WEB, makeState, runPromise } from "./t3.ts";
import { T3_HOME } from "../e2e.config.ts";
import { run } from "./shell.ts";

test("Edit from here restores a worktree whose folder lost its checkout", WEB,
  async ({ t3, app, screen, browser }) => {
    const stableCapture = async (label: string) => {
      const bytes = async (relative: string) => {
        const root = process.env.EVIDENCE_OUTPUT!;
        const paths = await readdir(root, { recursive: true });
        const found = paths.find((path) => path.endsWith(relative.split("/").at(-1)!));
        if (!found) throw new Error("Capture path was not found");
        return readFile(join(root, found));
      };
      let previous = await bytes(await app.screenshot(`${label}-settling`));
      for (let attempt = 0; attempt < 10; attempt++) {
        await pause(500);
        const next = await bytes(await app.screenshot(label));
        if (previous.equals(next)) return;
        previous = next;
      }
      throw new Error("Capture did not settle");
    };
    const project = await t3.project("Rollback demo", { "README.md": "checkpoint-state\n" });
    const { cli } = makeState(T3_HOME);
    const started = JSON.parse((await runPromise(cli([
      "thread", "start", project, "First message", "--title", "Recover checkout",
      "--worktree", "main", "--provider", "devReplay", "--model", "multi-turn", "--wait", "--json",
    ]))).stdout);
    const state = JSON.parse((await runPromise(cli(["thread", "show", started.threadId, "--json"]))).stdout);
    const worktree = state.thread.worktreePath;
    if (!worktree) throw new Error("Thread did not create a managed worktree");
    await t3.open();
    await screen.getByRole("button", /^Recover checkout,/).tap();
    await screen.getByRole("textbox").fill("Second message");
    await screen.getByRole("textbox").press("Enter");
    // Wait for the real second turn and its checkpoint before damaging the checkout.
    await browser.locator('text="second fixture turn complete"').waitFor({ state: "visible" });
    await browser.locator('button[aria-label="Stop generation"]').waitFor({ state: "hidden" });
    await runPromise(cli(["thread", "show", started.threadId, "--json"]));
    for (const entry of await readdir(worktree)) await rm(join(worktree, entry), { recursive: true, force: true });
    await runPromise(run("git", ["worktree", "prune", "--expire", "now"], { cwd: project }));
    await app.open("/");
    await screen.getByRole("button", /^Recover checkout,/).tap();
    await screen.getByText("Second message", { exact: true }).hover();
    await screen.getByRole("button", "Edit from here").last().tap();
    await stableCapture("confirm-revert-files");
    await screen.getByRole("button", "Revert files too", { exact: true }).tap();
    await browser.mouse.move(700, 100);
    try {
      await expect(screen.getByText("second fixture turn complete", { exact: true })).toBeHidden();
      await expect(await readFile(join(worktree, "README.md"), "utf8")).toBe("checkpoint-state\n");
    } finally {
      await stableCapture("rollback-result");
      const contents = await readFile(join(worktree, "README.md"), "utf8").catch(() => null);
      const root = await runPromise(run("git", ["rev-parse", "--show-toplevel"], { cwd: worktree }));
      await writeFile(process.env.EVIDENCE_TRANSCRIPT!, JSON.stringify({
        setup: "Managed worktree; README.md initially contains checkpoint-state",
        damage: "All checkout contents removed while retaining the folder; git worktree prune --expire now",
        action: "Edit from here on Second message; Revert files too",
        after: { readme: contents, isOwnCheckout: root.stdout.trim() === worktree, entries: await readdir(worktree) },
      }, null, 2));;
    }
  });
