import { readFile, writeFile } from "node:fs/promises";
import { setTimeout as settle } from "node:timers/promises";
import { expect } from "e2e";
import { APP_DIR } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

test("Minimap visits adjacent user messages in both directions", WEB, async ({ t3, app, screen, browser }) => {
  // A replay turn with no assistant text creates the adjacent pair through real app commands.
  const labels = ["Turn 1 — Start", "Turn 2 — Planning", "Turn 3 — Review", "Turn 4 — Adjacent A", "Turn 5 — Adjacent B", "Turn 6 — Follow-up", "Turn 7 — Finish"];
  const frames = labels.flatMap((label, index) => [
    { type: "expect_outbound", frame: { kind: "request", method: "session/prompt" } },
    ...(index === 3 ? [] : [{ type: "emit_inbound", frame: { kind: "notification", method: "session/update", params: { sessionId: "replay", update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: `${label}: synthetic reply.\n\n` + "Context for this turn.\n\n".repeat(55) } } } } }]),
    { type: "emit_inbound", frame: { kind: "response", method: "session/prompt", result: { stopReason: "end_turn" } } },
  ]);
  await writeFile(`${APP_DIR}/apps/server/scripts/dev-replay/showcase_transcript.ndjson`, frames.map(frame => JSON.stringify(frame)).join("\n"));
  const project = await t3.project("Minimap-demo");
  await t3.thread({ project, title: "Adjacent user messages", messages: labels, model: "showcase" });
  await browser.setViewport({ width: 1280, height: 800 });
  await t3.open();
  await screen.getByRole("button", "Adjacent user messages, Done, Minimap-demo", { exact: true }).tap();
  await browser.locator('[data-testid="timeline-minimap"]').waitFor({ state: "attached" });
  await browser.evaluate(() => { document.querySelector<HTMLElement>('.messages-timeline-scroll')!.scrollTop = 0; });
  await settle(1000);
  const topTurn = () => browser.evaluate(() => {
    const viewport = document.querySelector('.messages-timeline-scroll')!.getBoundingClientRect();
    const rows = [...document.querySelectorAll('[data-message-role="user"]')];
    return rows.find(row => { const y = row.getBoundingClientRect().top - viewport.top; return y >= 0 && y < 100; })?.textContent?.match(/Turn \d — [A-Za-z -]+/)?.[0]?.trim() ?? "No user message at target";
  });
  const capture = async (label: string) => {
    let prior = await app.screenshot(`${label}-settling`);
    for (let attempt = 0; attempt < 6; attempt++) {
      await settle(500);
      const current = await app.screenshot(label);
      if ((await readFile(prior)).equals(await readFile(current))) return;
      prior = current;
    }
    throw new Error(`Capture did not stabilize: ${label}`);
  };
  const forward: string[] = [];
  const backward: string[] = [];
  await capture("starting-above-pair");
  for (let i = 1; i < labels.length; i++) {
    const next = screen.getByRole("button", "Next turn", { exact: true });
    if (await next.isEnabled()) { await next.hover(); await next.tap(); }
    await settle(1000);
    forward.push(await topTurn());
    if (i === 4) await capture("next-at-adjacent-pair");
  }
  await capture("after-six-next-clicks");
  // Start the backward pass at the final turn on both sides, independently of Next's result.
  await browser.evaluate(() => { const v = document.querySelector<HTMLElement>('.messages-timeline-scroll')!; v.scrollTop = v.scrollHeight; });
  await settle(1000);
  for (let i = 0; i < labels.length - 1; i++) {
    const previous = screen.getByRole("button", "Previous turn", { exact: true });
    if (await previous.isEnabled()) { await previous.hover(); await previous.tap(); }
    await settle(1000);
    backward.push(await topTurn());
    if (i === 2) await capture("previous-at-adjacent-pair");
  }
  await capture("after-six-previous-clicks");
  if (process.env.EVIDENCE_TRANSCRIPT) await writeFile(process.env.EVIDENCE_TRANSCRIPT, JSON.stringify({ forward, backward }, null, 2));
  expect({ forward, backward }).toEqual({ forward: labels.slice(1), backward: labels.slice(0, -1).reverse() });
});
