import { expect } from "e2e";
import { existsSync, writeFileSync, utimesSync, readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { join } from "node:path";
import { test, WEB } from "./t3.ts";

// The identity filter extends an existing lock window; it never creates index.lock.
test("Changes totals leave the index available to a concurrent git add", WEB, async ({ t3, app, screen, browser }) => {
  const repo = await t3.project("index-lock-demo", {
    "tracked.txt": "same content\n",
    ".gitattributes": "tracked.txt filter=lockprobe\n",
  });
  await t3.thread({ project: repo, title: "Changes totals lock probe", messages: ["Start"] });
  await t3.open();
  await screen.getByRole("button", "Changes totals lock probe, Done, index-lock-demo").tap();
  await screen.getByRole("button", "Toggle terminal drawer").tap();
  await new Promise(resolve => setTimeout(resolve, 2500));
  await browser.keyboard.type("unset PROMPT_COMMAND; PS1='$ '; printf '\\033c'\n");
  await new Promise(resolve => setTimeout(resolve, 1500));
  const git = (...args: string[]) => spawnSync("git", ["-C", repo, ...args], { encoding: "utf8" });
  const metadata = join(repo, ".git");
  writeFileSync(join(metadata, "lockprobe.sh"), `#!/bin/sh
if [ -f .git/index.lock ] && [ ! -f .git/probe-release ]; then
  python3 -c 'import os; p=os.getppid(); print(open("/proc/%d/cmdline"%p,"rb").read().replace(b"\\0",b" ").decode())' >> .git/lock-owner
  touch .git/probe-blocked
  n=0
  while [ ! -f .git/probe-release ] && [ "$n" -lt 3000 ]; do sleep .01; n=$((n+1)); done
fi
cat
`, { mode: 0o755 });
  const configured = git("config", "filter.lockprobe.clean", "sh .git/lockprobe.sh");
  if (configured.status !== 0) throw Error(configured.stderr);
  writeFileSync(join(metadata, "probe-add.sh"), `#!/bin/sh
printf 'Linux | Chromium | 1280x800 | synthetic repository\\n'
printf 'tracked.txt: mtime changed; contents unchanged\\n'
if [ -f .git/index.lock ]; then echo 'Background refresh holds index.lock: true'; else echo 'Background refresh holds index.lock: false'; fi
printf '$ git add tracked.txt\\n'
git -c filter.lockprobe.clean=cat add tracked.txt 2>.git/add-error
code=$?
printf 'git add exit: %s\\n' "$code"
if [ "$code" != 0 ]; then sed -E "s@'.*/index.lock'@'.git/index.lock'@" .git/add-error | head -1; else echo 'Git add succeeded.'; fi
printf '%s' "$code" > .git/add-exit
`);
  utimesSync(join(repo, "tracked.txt"), 1_000_000_000, 1_000_000_000);
  // Opening Git action options explicitly requests the same background status refresh.
  await screen.getByRole("button", "Git action options").tap();
  await browser.keyboard.press("Escape");
  const deadline = Date.now() + 8000;
  while (!existsSync(join(metadata, "probe-blocked")) && Date.now() < deadline) {
    await new Promise(resolve => setTimeout(resolve, 50));
  }
  await browser.evaluate(() => (document.querySelector('[aria-label="Terminal input"]') as HTMLTextAreaElement).focus());
  await browser.keyboard.type("sh .git/probe-add.sh\n");
  const addDeadline = Date.now() + 5000;
  while (!existsSync(join(metadata, "add-exit")) && Date.now() < addDeadline) {
    await new Promise(resolve => setTimeout(resolve, 50));
  }
  const exit = Number(readFileSync(join(metadata, "add-exit"), "utf8"));
  if (existsSync(join(metadata, "lock-owner"))) console.log(readFileSync(join(metadata, "lock-owner"), "utf8"));
  await new Promise(resolve => setTimeout(resolve, 600));
  await app.screenshot("concurrent-git-add");
  writeFileSync(join(metadata, "probe-release"), "");
  await new Promise(resolve => setTimeout(resolve, 1500));
  await browser.keyboard.type("git add tracked.txt; printf 'Git add after refresh exit: %s\\n' $?\n");
  await new Promise(resolve => setTimeout(resolve, 600));
  await app.screenshot("after-refresh");
  expect(exit).toBe(0);
});
