import { readFile } from "node:fs/promises";
import { expect } from "e2e";
import { test, WEB } from "./t3.ts";

test("Minimap previews track the strip and fit the timeline", { ...WEB, timeout: 1200000 }, async ({t3, app, screen, browser}) => {
  const project = await t3.project("Minimap demo");
  const message = (i: number) => `Turn ${String(i+1).padStart(2,"0")}: Review the timeline interaction and explain the next step.`;
  await t3.thread({ project, title: "Six turns", messages: Array.from({length:6}, (_, i) => message(i)) });
  await t3.thread({ project, title: "Two turns", messages: ["First short question", "Second short question"] });
  await t3.thread({ project, title: "Seventy turns", messages: Array.from({length:70}, (_, i) => message(i)) });
  await t3.open();
  await screen.getByRole("button", /^Six turns,/).waitFor({state:"visible", timeout:120000});
  await app.open("/settings/appearance");
  await screen.getByRole("combobox", "Chat width").tap();
  await screen.getByRole("option", "Wide").tap();
  await browser.keyboard.press("Escape");
  await screen.getByRole("button", /^Six turns,/).tap();
  await screen.getByTestId("timeline-minimap").waitFor({state:"visible"});
  const details=screen.getByRole("button", "Toggle thread details panel");
  if (await details.getAttribute("aria-pressed") === "true") await details.tap();
  await browser.evaluate(() => {
    const style = document.createElement("style");
    style.textContent = '* { caret-color: transparent !important; } [data-chat-provider-model-picker-label] { visibility:hidden !important; }';
    document.head.append(style);
    const cursor = document.createElement("div");
    cursor.id="evidence-pointer";
    cursor.style.cssText="position:fixed;width:12px;height:12px;border:2px solid #f43f5e;border-radius:50%;transform:translate(-50%,-50%);pointer-events:none;z-index:2147483647";
    document.body.append(cursor);
    const stage = document.createElement("div"); stage.id="evidence-stage"; stage.style.cssText="position:fixed;top:8px;left:600px;padding:4px 8px;background:#fff;color:#111;border:1px solid #ddd;border-radius:4px;font:12px sans-serif;pointer-events:none;z-index:2147483646";document.body.append(stage);
    document.addEventListener("mousemove", e => {cursor.style.left=e.clientX+"px";cursor.style.top=e.clientY+"px";}, {capture:true});
  });
  const pause = (ms=600) => new Promise(resolve => setTimeout(resolve, ms));
  const capture = async (label: string) => {
    await browser.evaluate(label => {document.getElementById("evidence-stage")!.textContent=label;},label);
    await pause();
    for (let attempt=0; attempt<6; attempt++) {
      const a = await app.screenshot(label+"-settle-"+attempt);
      await pause(500);
      const b = await app.screenshot(label+(attempt?"-stable-"+attempt:""));
      const same = (await readFile(a)).equals(await readFile(b));
      console.log("STABILITY", label, attempt, same);
      if (same) return;
      await pause(600);
    }
    throw new Error(`Capture ${label} was not stable`);
  };
  const geometry = () => browser.evaluate(() => {
    const map = document.querySelector('[data-testid="timeline-minimap"]')!;
    const button = map.querySelector('button[aria-label^="Jump to message:"]')!;
    const r = button.getBoundingClientRect();
    const m = map.getBoundingClientRect();
    return { x:r.x,y:r.y,width:r.width,height:r.height,top:m.top,bottom:m.bottom,count:map.querySelectorAll('[data-minimap-strip]').length };
  });
  const preview = () => browser.evaluate(() => {
    const map = document.querySelector('[data-testid="timeline-minimap"]')!;
    const p = map.querySelector('span.w-80');
    const r = p?.getBoundingClientRect();
    return {visible:!!p,text:p?.textContent??"",y:r?.y??0,height:r?.height??0};
  });
  const move = async (x:number,y:number) => {await browser.mouse.move(x,y); await pause(250);};
  for (const title of ["Six turns", "Two turns"]) {
    await screen.getByRole("button", new RegExp(`^${title},`)).tap();
    if (await details.getAttribute("aria-pressed") === "true") await details.tap();
    await screen.getByTestId("timeline-minimap").waitFor({state:"visible"});
    await move(1100,100);
    const rail=await geometry();
    for (const index of [...new Set([0,1,rail.count-2,rail.count-1])]) {
      const y=rail.y+rail.height*index/(rail.count-1);
      await move(rail.x+14,y);
      await capture(`${title}-tick-${index+1}`);
      const p=await preview();
      expect.soft(p.visible,`${title} tick ${index+1} activates`).toBe(true);
      expect.soft(Math.abs(p.y+p.height/2-y),`${title} tick ${index+1} stays centered`).toBeLessThan(2);
    }
    await move(1100,100);
    await move(rail.x+4,rail.y-24);
    await move(rail.x+4,rail.y-3);
    await capture(`${title}-approach-first-from-above`);
    expect.soft((await preview()).visible,`${title}: first tick approach from above`).toBe(true);
    await move(1100,100);
    await move(rail.x+4,rail.y+rail.height+24);
    await move(rail.x+4,rail.y+rail.height+3);
    await capture(`${title}-approach-last-from-below`);
    expect.soft((await preview()).visible,`${title}: last tick approach from below`).toBe(true);
  }
  await screen.getByRole("button", /^Six turns,/).tap();
  await move(1100,100);
  const rail=await geometry();
  await move(rail.x+14,rail.y+rail.height/2);
  await capture("hover-on-strip");
  for (const fraction of [0,.2,.4,.6,.8,.99,.5]) await move(rail.x+14,rail.y+rail.height*fraction);
  await move(rail.x+90,rail.y+rail.height/2);
  await capture("leave-strip-onto-preview");
  expect.soft((await preview()).visible,"Leaving strip dismisses preview").toBe(false);
  await move(rail.x+90,rail.y-40);
  await capture("move-vertically-beyond-preview");
  expect.soft((await preview()).visible,"Moving beyond preview leaves messages interactive").toBe(false);
  await browser.evaluate(() => {
    const p = [...document.querySelectorAll("p")].filter(p=>p.textContent==="second fixture turn complete").at(1);
    p?.scrollIntoView({block:"center"});
    window.getSelection()?.removeAllRanges();
  });
  await pause();
  const response = await browser.evaluate(() => {
    const p = [...document.querySelectorAll("p")].filter(p=>p.textContent==="second fixture turn complete").at(1)!;
    const r=p.getBoundingClientRect();return {x:r.x,y:r.y+r.height/2};
  });
  await move(rail.x+14,rail.y+rail.height/2);
  await move(response.x+2,response.y);
  await browser.mouse.down();
  await move(response.x+190,response.y);
  await browser.mouse.up();
  await capture("select-underlying-response");
  const selected=await browser.evaluate(()=>window.getSelection()?.toString()??"");
  console.log("SELECTION",selected);
  expect.soft(selected,"Response text can be selected after leaving the strip").toContain("fixture turn");
  expect.soft(await browser.evaluate(() => {
    const selection=window.getSelection();
    return !!selection?.anchorNode && !document.querySelector('[data-testid="timeline-minimap"]')!.contains(selection.anchorNode);
  }),"The selection belongs to the underlying response").toBe(true);
  await move(1100,100);
  await move(rail.x+14,rail.y);
  await browser.mouse.down();await browser.mouse.up();
  await capture("click-first-tick-navigation");
  await screen.getByRole("button", /^Seventy turns,/).tap();
  if (await details.getAttribute("aria-pressed") === "true") await details.tap();
  for (let page=0;page<5;page++) {
    const load=screen.getByRole("button","Load earlier turns");
    if (await load.count()===0) break;
    await load.tap();
    await pause(700);
  }
  await move(1100,100);
  const endBefore=screen.getByRole("button", /^Scroll to end/);
  if (await endBefore.count()) await endBefore.tap();
  await pause(700);
  console.log("FULL_TIMELINE_RAIL",await geometry());
  await capture("seventy-turns-rail-before-terminal");
  await browser.keyboard.press("Control+j");
  await browser.locator('[data-thread-terminal-drawer]').waitFor({state:"visible"});
  await browser.keyboard.type("clear; printf '%s\\n' 'Terminal ready' 'Visible output: timeline clipping check' 'Line 3: synthetic demo data' 'Line 4: terminal stays visible'");
  await browser.keyboard.press("Enter");
  await pause(1500);
  await browser.evaluate(() => {if(document.activeElement instanceof HTMLElement)document.activeElement.blur();});
  await move(1100,100);
  const endAfter=screen.getByRole("button", /^Scroll to end/);
  if (await endAfter.count()) await endAfter.tap();
  await pause(700);
  await screen.getByRole("button", /^Jump to message:/).press("End");
  await screen.getByRole("button", /^Jump to message:/).press("Enter");
  await move(1100,26); await browser.mouse.down(); await browser.mouse.up();
  await pause(1000);
  const capped=await geometry();
  console.log("CAPPED_RAIL",capped);
  for (const index of [0,1,capped.count-2,capped.count-1]) {
    const y=capped.y+capped.height*index/(capped.count-1);
    await move(capped.x+14,y);
    await capture(`terminal-open-capped-rail-tick-${index+1}`);
    const p=await preview();
    expect.soft(p.visible,"Capped rail endpoint activates").toBe(true);
    expect.soft(p.y,"Preview fits above timeline boundary").toBeGreaterThanOrEqual(capped.top);
    expect.soft(p.y+p.height,"Preview fits below timeline boundary").toBeLessThanOrEqual(capped.bottom);
  }
  expect.soft(capped.height,"70-turn rail fits reduced timeline").toBeLessThanOrEqual(capped.bottom-capped.top-200);
});
