/// <reference lib="dom" />
import { expect } from "e2e";
import { readFile, writeFile } from "node:fs/promises";
import { APP_DIR } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

const oldText = 'export function sum(a: number, b: number) {\n  return a + b;\n}\n';
const newText = oldText + '\nexport function clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max);\n}\n';
const patch = 'diff --git a/src/math.ts b/src/math.ts\n--- a/src/math.ts\n+++ b/src/math.ts\n@@ -1,3 +1,7 @@\n export function sum(a: number, b: number) {\n   return a + b;\n }\n+\n+export function clamp(value: number, min: number, max: number) {\n+  return Math.min(Math.max(value, min), max);\n+}\n';

test('Repeated diff blocks preserve occurrence actions and render without crashing', {...WEB, video: 'off'}, async ({ t3, app, browser, screen }) => {
  const fixture = `${APP_DIR}/apps/server/scripts/dev-replay/showcase_transcript.ndjson`;
  const original = await readFile(fixture, 'utf8');
  const update = (update: unknown) => ({ type: 'emit_inbound', frame: { kind: 'notification', method: 'session/update', params: { sessionId: 'replay', update } } });
  await writeFile(fixture, [
    {type:'expect_outbound',frame:{kind:'request',method:'session/prompt',params:'<any>'}},
    update({sessionUpdate:'tool_call',toolCallId:'edit',kind:'edit',title:'Edit src/math.ts',status:'completed',rawInput:{path:'src/math.ts'},content:[{type:'diff',path:'src/math.ts',oldText,newText},{type:'diff',path:'src/math.ts',oldText:newText,newText:newText+'// repeated edit\n'},{type:'diff',path:'src/math.ts',oldText:newText,newText:newText+'// repeated edit\n'}]}),
    update({sessionUpdate:'agent_message_chunk',content:{type:'text',text:'Added clamp. The edit contains three blocks for src/math.ts.'}}),
    {type:'emit_inbound',frame:{kind:'response',method:'session/prompt',result:{stopReason:'end_turn'}}}
  ].map(x=>JSON.stringify(x)).join('\n')+'\n');
  try {
    const project = await t3.project('Repeated blocks', {'src/math.ts': oldText});
    await writeFile(`${project}/src/math.ts`,newText);
    const thread = await t3.thread({project,title:'Repeated same-file diffs',messages:['Add clamp with repeated edit blocks'],model:'showcase'});
    await browser.addInitScript((patch: string) => {
      const Original = window.WebSocket;
      window.WebSocket = class extends Original {
        constructor(...args: ConstructorParameters<typeof WebSocket>) {
          super(...args);
          const handled = new WeakSet<Event>();
          super.addEventListener('message', (event) => {
            if (handled.has(event)) return;
            try {
              const data = JSON.parse(event.data);
              let changed = false;
              const visit = (v: any) => {
                if (!v || typeof v !== 'object') return;
                if (Array.isArray(v.sources) && typeof v.cwd === 'string') {
                  v.incomplete = false;
                  for (const source of v.sources) {
                    source.diff = patch.repeat(3); source.truncated = false; source.patchesDeferred = false;
                    source.files = [0,1,2].map(()=>({path:'src/math.ts',previousPath:null,changeType:'change',additions:4,deletions:0}));
                    changed = true;
                  }
                }
                for (const value of Object.values(v)) visit(value);
              };
              visit(data);
              if (changed) {
                event.stopImmediatePropagation();
                const replacement = new MessageEvent('message',{data:JSON.stringify(data)});
                handled.add(replacement); this.dispatchEvent(replacement);
              }
            } catch {}
          });
        }
      };
    }, patch);
    await browser.emulateMedia({reducedMotion:'reduce'});
    await browser.setViewport({width:1600,height:1000});
    await t3.open(thread);
    await screen.getByRole('button','Toggle right panel').tap();
    await screen.getByText('Diff',{exact:true}).tap();
    const blocks = () => browser.evaluate(() => Array.from(document.querySelectorAll('*')).filter(h=>h.shadowRoot?.querySelector('[data-diffs-header]')).map(h=>({text:h.shadowRoot!.textContent,lines:h.shadowRoot!.querySelectorAll('[data-line]').length})));
    const click = async (kind: string) => {
      const point = await browser.evaluate((kind: string) => {
        const hosts = Array.from(document.querySelectorAll('*')).filter(h=>h.shadowRoot?.querySelector('[data-diffs-header]'));
        const header = hosts[1]?.shadowRoot?.querySelector<HTMLElement>('[data-diffs-header]');
        if (!header) throw new Error('Second repeated diff header missing');
        const target = kind === 'filename' ? header.querySelector<HTMLElement>('[data-title]') : header;
        if (!target) throw new Error('Diff filename missing');
        const r = target.getBoundingClientRect();
        return {x:r.x+r.width*(kind==='filename'?0.5:0.65),y:r.y+r.height/2};
      },kind);
      await browser.mouse.move(point.x,point.y);
      await browser.mouse.down();
      await browser.mouse.up();
    };
    await expect.poll(async()=> (await blocks()).length).toBe(3);
    // Expand all through the existing controls before exercising blank header space.
    const expand = screen.getByRole('button','Expand all files');
    if (await expand.isVisible()) await expand.tap();
    await expect.poll(async()=> (await blocks()).every(b=>b.lines>0)).toBe(true);
    await app.startRecording({name:'repeated-diff-interactions'});
    await app.screenshot('01-git-three-expanded');
    await click('header');
    await app.screenshot('02-click-second-header');
    const collapsed = await blocks();
    const correctCollapse = collapsed[0].lines>0 && collapsed[1].lines===0 && collapsed[2].lines>0;
    // Restore all, then exercise the filename action and return to Diff.
    await screen.getByRole('button','Collapse all files').tap();
    await screen.getByRole('button','Expand all files').tap();
    await click('filename');
    const fileReady = () => browser.evaluate(() => {
      const active = document.querySelector('[data-active-tab="true"]')?.textContent?.includes('math.ts');
      const surface = document.querySelector('[data-right-panel-surface-content]');
      const text = (surface?.textContent ?? '') + Array.from(surface?.querySelectorAll('*') ?? []).map(h=>h.shadowRoot?.textContent ?? '').join('');
      return !!active && text.includes('Math.min');
    });
    await expect.poll(fileReady).toBe(true);
    const fileOpened = await fileReady();
    await app.screenshot('03-second-filename-open');
    await screen.getByText('Diff',{exact:true}).tap();
    await screen.getByRole('button','Collapse src/math.ts').nth(1).tap();
    await app.screenshot('04-second-chevron');
    const chevron = await blocks();
    const correctChevron = chevron[0].lines>0 && chevron[1].lines===0 && chevron[2].lines>0;
    // Tool-call path: the real ACP adapter joins three entries into one repeated-file patch.
    await screen.getByRole('button',/Worked for/).tap();
    await screen.getByRole('button',/Changed 1 file/).tap();
    await screen.getByRole('button','Open diff').tap();
    // Wait for either the crash screen or all three repeated blocks.
    await expect.poll(async()=>await browser.evaluate(()=>document.body.textContent?.includes('Something went wrong') || Array.from(document.querySelectorAll('*')).filter(h=>h.shadowRoot?.querySelector('[data-diffs-header]')).length===3)).toBe(true);
    if (!(await screen.getByText('Something went wrong',{exact:false}).isVisible())) {
      const expandTool = screen.getByRole('button','Expand all files');
      if (await expandTool.isVisible()) await expandTool.tap();
      await expect.poll(async()=> (await blocks()).length===3 && (await blocks()).every(b=>b.lines>0)).toBe(true);
    }
    await app.screenshot('05-tool-call-repeated-blocks');
    await app.stopRecording();
    const crashed = await screen.getByText('Something went wrong',{exact:false}).isVisible();
    expect({correctCollapse,fileOpened,correctChevron,crashed}).toEqual({correctCollapse:true,fileOpened:true,correctChevron:true,crashed:false});
  } finally {
    await writeFile(fixture,original);
  }
});
