/// <reference lib="dom" />
import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, watch } from "node:fs";
import { T3_HOME } from "../e2e.config.ts";
import { makeState, runPromise, test, WEB } from "./t3.ts";

const waitForFile = async (path: string) => {
  if (existsSync(path)) return;
  await new Promise<void>((resolve, reject) => {
    const timer = setTimeout(() => { watcher.close(); reject(new Error("Capture shell did not become ready")); }, 15000);
    const watcher = watch(path.slice(0, path.lastIndexOf("/")), () => {
      if (existsSync(path)) { watcher.close(); clearTimeout(timer); resolve(); }
    });
    if (existsSync(path)) { watcher.close(); clearTimeout(timer); resolve(); }
  });
};

const git = (cwd: string, ...args: string[]) => execFileSync("git", ["-c", "protocol.file.allow=always", "-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", ...args], { cwd, encoding: "utf8" }).trim();

test("Revert files restores populated and nested submodules without moving HEAD", WEB, async ({t3, app, screen, browser}) => {
  const inner = await t3.project("inner-source", {"inner.txt":"inner original\n"});
  const sub = await t3.project("sub-source", {"sub.txt":"sub original\n"});
  git(sub, "submodule", "add", inner, "inner");
  git(sub, "commit", "-am", "Add inner");
  const project = await t3.project("checkpoint-demo", {"parent.txt":"parent original\n"});
  git(project, "submodule", "add", sub, "sub");
  git(project, "commit", "-am", "Add submodule");
  const {cli} = makeState(T3_HOME);
  const launched = JSON.parse((await runPromise(cli(["thread", "start", project, "Initialize checkpoint demonstration", "--worktree", "main", "--title", "Submodule restore", "--provider", "devReplay", "--model", "multi-turn", "--wait", "--json"]))).stdout);
  const state = JSON.parse((await runPromise(cli(["thread", "show", launched.threadId, "--json"]))).stdout);
  const workspace = state.thread.worktreePath;
  if (!workspace) throw new Error("No thread worktree");
  git(workspace, "submodule", "update", "--init", "--recursive");
  const levels = [["", "parent"], ["sub/", "sub"], ["sub/inner/", "inner"]];
  for (const [dir, name] of levels) {
    writeFileSync(`${workspace}/${dir}${name}-base.txt`, `${name} base\n`);
    writeFileSync(`${workspace}/${dir}${name}.txt`, `${name} v2 EDITED\n`);
    writeFileSync(`${workspace}/${dir}${name}-added.txt`, `${name} added\n`);
  }
  const heads = levels.map(([dir]) => git(`${workspace}/${dir}`, "rev-parse", "HEAD"));
  await runPromise(cli(["thread", "send", launched.threadId, "Capture v2 at all three levels", "--wait", "--json"]));
  await t3.open({threadId:launched.threadId});
  await screen.getByRole("textbox").fill("Turn 3: later edits at all three levels");
  for (const [dir, name] of levels) {
    writeFileSync(`${workspace}/${dir}${name}.txt`, `${name} v3 TURN3\n`);
    writeFileSync(`${workspace}/${dir}${name}-turn3.txt`, `${name} turn3\n`);
  }
  await screen.getByRole("textbox").press("Enter");
  await screen.getByText("Turn 3: later edits at all three levels", {exact:true}).hover();
  // The button becomes enabled when the real checkpoint worker completes.
  await screen.getByRole("button", "Edit from here", {exact:true}).tap();
  await screen.getByRole("button", "Revert files too", {exact:true}).tap();
  await expect.poll(() => browser.locator('[role="textbox"]').textContent()).toContain("Turn 3: later edits");
  const observed = levels.map(([dir,name], index) => ({ file:`${dir}${name}.txt`, content:readFileSync(`${workspace}/${dir}${name}.txt`, "utf8").trim(), laterFile:existsSync(`${workspace}/${dir}${name}-turn3.txt`), headUnchanged:git(`${workspace}/${dir}`, "rev-parse", "HEAD") === heads[index] }));
  // Read-only disk inspection in the app's terminal makes the result visible in the recording.
  const rc = process.env.T3_EVIDENCE_RC;
  if (!rc) throw new Error("T3_EVIDENCE_RC is required for the capture shell");
  const ready = `${T3_HOME}/terminal-ready`;
  const printed = `${T3_HOME}/terminal-printed`;
  writeFileSync(rc, `PS1='$ '\nproof() {\n  clear\n  echo 'CHECKPOINT RESTORE — actual working-tree files'\n${levels.map(([dir,name],index) => `  printf '${dir}${name}.txt: '; cat '${dir}${name}.txt'\n  if test -f '${dir}${name}-turn3.txt'; then added=PRESENT; else added=absent; fi\n  if test "$(git -C '${dir || "."}' rev-parse HEAD)" = '${heads[index]}'; then head=unchanged; else head=MOVED; fi\n  echo "  turn3 file: $added | HEAD: $head"`).join("\n")}\n  touch '${printed}'\n}\ntouch '${ready}'\n`);
  await screen.getByRole("button", "Toggle terminal drawer", {exact:true}).tap();
  await waitForFile(ready);
  await browser.locator("canvas.cursor-text").tap();
  await browser.keyboard.type("proof\n");
  await waitForFile(printed);
  await browser.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
  let previous = await app.screenshot("restore-stability-0");
  let stable = false;
  for (let attempt = 1; attempt <= 6; attempt++) {
    await new Promise(resolve => setTimeout(resolve, 500));
    const current = await app.screenshot(`restore-stability-${attempt}`);
    if (readFileSync(previous).equals(readFileSync(current))) {
      stable = true;
      break;
    }
    previous = current;
  }
  if (!stable) throw new Error("Restore capture did not settle");
  await app.screenshot("restored-submodule-files");
  await expect(observed).toEqual(levels.map(([dir,name]) => ({file:`${dir}${name}.txt`, content:`${name} v2 EDITED`, laterFile:false, headUnchanged:true})));
});
