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

test("Expanded work stays above the composer", WEB, async ({t3, app, screen, browser}) => {
  const fixture = `${APP_DIR}/apps/server/scripts/dev-replay/showcase_transcript.ndjson`;
  const original = await readFile(fixture, "utf8");
  const records: any[] = [{type:"expect_outbound",frame:{kind:"request",method:"session/prompt",params:"<any>"}}];
  const emit = (update: any) => records.push({type:"emit_inbound",frame:{kind:"notification",method:"session/update",params:{sessionId:"demo",update}}});
  const message = (text: string) => emit({sessionUpdate:"agent_message_chunk",content:{type:"text",text}});
  const command = (id: string, title: string) => {
    emit({sessionUpdate:"tool_call",toolCallId:id,kind:"execute",title,status:"in_progress",rawInput:{command:title}});
    emit({sessionUpdate:"tool_call_update",toolCallId:id,status:"completed",content:[{type:"content",content:{type:"text",text:Array.from({length:14},(_,i)=>`Check ${i+1}: synthetic output is ready`).join("\n")+`\n${id.toUpperCase()}_END`}}]});
  };
  message("## Earlier context\n\n"+"A completed inspection provides context for the next step.\n\n".repeat(20));
  emit({sessionUpdate:"agent_thought_chunk",content:{type:"text",text:"Reviewing the approach\n\n"+"This synthetic reasoning explains the next operation and its expected result.\n\n".repeat(8)+"THINKING_END"}});
  message("## Standalone command\n\nThe next command has completed and its output is available on expansion.");
  command("standalone", "printf standalone-check");
  message("## Grouped commands\n\nThese two operations belong to one work group.");
  command("nested", "printf nested-check");
  command("companion", "printf companion-check");
  message("## Later messages\n\n"+"Later context keeps this completed work above the end of the conversation.\n\n".repeat(22));
  records.push({type:"emit_inbound",frame:{kind:"response",method:"session/prompt",result:{stopReason:"end_turn"}}});
  await writeFile(fixture, records.map(r=>JSON.stringify(r)).join("\n")+"\n");
  try {
    const project=await t3.project("Composer reveal demo");
    await t3.thread({project,title:"Completed inspection",messages:["Inspect the synthetic sample"],model:"showcase"});
  } finally { await writeFile(fixture,original); }
  await browser.addInitScript(() => {
    const originalSend = WebSocket.prototype.send;
    const pending: Array<() => void> = [];
    (window as any).holdOutput = false;
    (window as any).releaseOutput = () => { (window as any).holdOutput = false; pending.splice(0).forEach(send => send()); };
    WebSocket.prototype.send = function(data) {
      const text = typeof data === "string" ? data : "";
      if ((window as any).holdOutput && /subscribeCommandOutput|getTurnItem/.test(text)) {
        pending.push(() => originalSend.call(this, data));
        return;
      }
      originalSend.call(this,data);
    };
  });
  await t3.open();
  await screen.getByRole("button", "Completed inspection, Done, Composer reveal demo").tap({ timeout: 120_000 });
  await browser.emulateMedia({ reducedMotion: "reduce" });
  await screen.getByRole("button", "Toggle thread details panel").tap();
  await browser.locator('[data-timeline-row-kind="turn-fold"] button').first().tap();
  await browser.locator('[data-v2-item-type="reasoning"]').waitFor();
  await browser.evaluate(() => {
    const w = window as any;
    w.revealMetrics = (selector: string) => {
      const row = document.querySelector(selector) as HTMLElement;
      if (!row) throw new Error(`Missing work row: ${selector}`);
      let viewport = row.parentElement!;
      while (viewport && !(viewport.scrollHeight > viewport.clientHeight && /auto|scroll/.test(getComputedStyle(viewport).overflowY))) viewport = viewport.parentElement!;
      if (!viewport) throw new Error("Missing timeline viewport");
      const bounds = row.getBoundingClientRect();
      const view = viewport.getBoundingClientRect();
      const composer = document.querySelector('[data-chat-composer-surface]')!.getBoundingClientRect();
      return { top:bounds.top, bottom:bounds.bottom, viewportTop:view.top, composerTop:composer.top, scrollTop:viewport.scrollTop, viewport };
    };
    w.placeNearComposer = (selector: string) => {
      const m = w.revealMetrics(selector);
      m.viewport.dispatchEvent(new WheelEvent("wheel",{deltaY:-1,bubbles:true}));
      m.viewport.scrollTop += m.bottom - (m.composerTop - 12);
      m.viewport.dispatchEvent(new Event("scroll"));
    };
    w.measureReveal = (selector: string) => {
      const m = w.revealMetrics(selector);
      return {top:m.top,bottom:m.bottom,viewportTop:m.viewportTop,composerTop:m.composerTop,scrollTop:m.scrollTop,unrevealed:Math.max(0,Math.min(m.bottom-m.composerTop,m.top-(m.viewportTop+24)))};
    };
  });
  const results: Array<{label:string;unrevealed:number}> = [];
  const thinking = '[data-timeline-row-kind="work"] [data-v2-item-type="reasoning"]';
  const standalone = '[data-timeline-row-kind="work"] [role="button"][aria-label="printf standalone-check"]';
  const nested = '[role="button"][aria-label="printf nested-check"]';
  const companion = '[role="button"][aria-label="printf companion-check"]';
  const group = '[data-timeline-row-kind="work-toggle"] button';
  const place = async (selector:string) => {
    await browser.locator(selector).scrollIntoView();
    await browser.evaluate(selector => (window as any).placeNearComposer(selector),selector);
  };
  const capture = async (label:string,selector:string) => {
    await app.screenshot(label);
    const metrics = await browser.evaluate(selector => (window as any).measureReveal(selector),selector);
    console.log(label, metrics);
    results.push({label,unrevealed:metrics.unrevealed});
  };
  await place(thinking);
  await app.screenshot("01-thinking-collapsed");
  await browser.locator(thinking).tap();
  await capture("02-thinking-expanded",thinking);
  await browser.locator(thinking).tap();

  await place(standalone);
  await browser.evaluate(() => { (window as any).holdOutput = true; });
  await browser.locator(standalone).tap();
  await screen.getByText("Loading output…",{exact:true}).waitFor();
  await app.screenshot("03-standalone-loading");
  await browser.evaluate(() => (window as any).releaseOutput());
  await screen.getByText(/STANDALONE_END/).waitFor();
  await capture("04-standalone-output-ready",standalone);
  await browser.locator(standalone).tap();

  await place(group);
  await browser.locator(group).tap();
  await browser.locator(nested).waitFor();
  await place(nested);
  await browser.evaluate(() => { (window as any).holdOutput = true; });
  await browser.locator(nested).tap();
  await screen.getByText("Loading output…",{exact:true}).waitFor();
  await app.screenshot("05-nested-loading");
  await browser.evaluate(() => (window as any).releaseOutput());
  await screen.getByText(/NESTED_END/).waitFor();
  await capture("06-nested-output-ready",nested);
  await browser.locator(nested).tap();

  await place(companion);
  await browser.evaluate(() => { (window as any).holdOutput = true; });
  await browser.locator(companion).tap();
  await screen.getByText("Loading output…",{exact:true}).waitFor();
  await browser.locator(group).tap();
  await place(group);
  await browser.locator(group).tap();
  await browser.locator(companion).waitFor();
  const navigation = await browser.evaluate(selector => {
    const m=(window as any).revealMetrics(selector);
    const r=m.viewport.getBoundingClientRect();
    return {x:r.left+r.width/2,y:r.top+r.height/2};
  },companion);
  await browser.mouse.move(navigation.x,navigation.y);
  await browser.mouse.wheel(0,-240);
  await app.screenshot("07-manual-navigation-before-output");
  const before=await browser.evaluate(selector=>(window as any).measureReveal(selector),companion);
  await browser.evaluate(() => (window as any).releaseOutput());
  await screen.getByText(/COMPANION_END/).waitFor({state:"attached"});
  await app.screenshot("08-manual-navigation-after-output");
  const after=await browser.evaluate(selector=>(window as any).measureReveal(selector),companion);
  console.log("manual navigation",{before,after});
  await expect(results.map(r=>({label:r.label,revealed:r.unrevealed<=3}))).toEqual(results.map(r=>({label:r.label,revealed:true})));
  await expect(Math.abs(after.top-before.top)).toBeLessThanOrEqual(3);
});
