import { execFile, spawn } from 'node:child_process';
import { promisify } from 'node:util';
import { readFile, writeFile, symlink, mkdir } from 'node:fs/promises';
import { expect } from 'e2e';
import { test, WEB, runPromise, makeState } from './t3.ts';
import { APP_DIR, T3_HOME } from '../e2e.config.ts';

// The replay model supplies conversation events; this fixture supplies its disk edits.
test('Diff titles open the exact POSIX file and resolve a nested no-remote project', WEB, async ({t3, app, screen, browser}) => {
  const root = process.env.PROOF_DESKTOP_ROOT;
  const exec = promisify(execFile);
  if (root) await symlink(root+'/code', APP_DIR+'/node_modules/.bin/code').catch(() => {});
  const desktopRun = root ? root+'/desktop-'+Date.now() : '';
  if(root) await mkdir(desktopRun, {recursive:true});
  const menuActions = async (kind: string, source: string, file: string, workspace: string) => {
    if(!root) return;
    await browser.evaluate(() => {
      const w = window as any;
      w.proofRequests = [];
      if(w.proofInstrumented) return;
      w.proofInstrumented = true;
      const send = WebSocket.prototype.send;
      WebSocket.prototype.send = function(data) {
        if(typeof data === 'string' && data.includes('shell.openInEditor')) w.proofRequests.push(JSON.parse(data));
        return send.call(this,data);
      };
    });
    for(const action of ['Open', 'Reveal in Files', 'Open with']) {
      const key = kind+'-'+source+'-'+action.replaceAll(' ','-');
      const target = source === 'header'
        ? browser.locator('[data-title]').filter({hasText:file})
        : screen.getByRole('button').filter({hasText:kind==='literal' ? 'back\\slash.txt' : /^a\.ts/}).first();
      await target.secondaryTap();
      if(action==='Open with') {
        await screen.getByRole('menuitem', 'Open with', {exact:true}).hover();
        await screen.getByRole('menuitem', 'VS Code', {exact:true}).waitFor({state:'visible'});
      }
      const nativeVideo = action==='Reveal in Files' ? spawn('ffmpeg', ['-y','-loglevel','error','-f','x11grab','-video_size','1920x1080','-framerate','8','-draw_mouse','0','-i',process.env.DISPLAY!, '-vf',kind==='nested' ? 'drawbox=x=0:y=0:w=1920:h=65:color=white:t=fill,drawbox=x=0:y=500:w=1920:h=100:color=white:t=fill' : 'drawbox=x=0:y=0:w=1920:h=65:color=white:t=fill', '-c:v','libx264','-preset','ultrafast','-crf','22',desktopRun+'/'+key+'.mp4'], {stdio:['pipe','ignore','ignore']}) : null;
      await app.screenshot(key+'-menu');
      await screen.getByRole('menuitem', action==='Open with' ? 'VS Code' : action, {exact:true}).tap();
      await exec('python3',[root+'/desktop-capture.py',desktopRun+'/'+key+'.png']);
      if(nativeVideo) {
        const ended = new Promise<void>((resolve,reject) => nativeVideo.once('exit',code=>code===0 ? resolve() : reject(new Error('native recorder exit '+code))));
        nativeVideo.stdin.write('q');
        await ended;
      }
      const requests = await browser.evaluate(() => (window as any).proofRequests);
      await writeFile(desktopRun+'/'+key+'.json',JSON.stringify({key, workspace, requests},null,2));
      await browser.evaluate(() => {
        const walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT); const nodes=[];
        while(walker.nextNode()) nodes.push(walker.currentNode);
        for(const node of nodes) if(node.nodeValue?.includes('/workspaces/')) node.nodeValue=node.nodeValue.replace(/\/home\/[^ ]*?\/workspaces\/w-[^/]+/g,'/fixture');
      });
      await app.screenshot(key+'-result');
    }
  };
  const agentPath = `${APP_DIR}/apps/server/scripts/dev-replay-agent.ts`;
  const original = await readFile(agentPath, 'utf8');
  await writeFile(agentPath, original.replace('const turns = loadTurns(session.model);', `
    for (const name of ['back\\\\slash.txt', 'back/slash.txt', 'a.ts']) {
      const file = session.cwd + '/' + name;
      if (NodeFS.existsSync(file)) NodeFS.appendFileSync(file, '\\nCHANGED\\n');
    }
    const turns = loadTurns(session.model);`));
  try {
    const literal = await t3.project('literal-files', {'back\\slash.txt': 'literal backslash file\n', 'back/slash.txt': 'nested file\n'});
    const parent = await t3.project('nested-repository', {'project/a.ts': 'export const nestedProject = true;\n'});
    const {cli} = makeState(T3_HOME);
    await runPromise(cli(['project','add',`${parent}/project`,'--title','nested-project']));
    const first = await t3.thread({project:literal,title:'Literal backslash changes',messages:['Append CHANGED to both files'],model:'multi-turn'});
    const second = await t3.thread({project:`${parent}/project`,title:'Nested project changes',messages:['Append CHANGED to a.ts'],model:'multi-turn'});
    await t3.open(first);
    await screen.getByRole('button','Open diff', {exact:true}).tap();
    await browser.locator('[data-title]').filter({hasText:'back\\slash.txt'}).waitFor({state:'visible'});
    await app.screenshot('literal-diff');
    await menuActions('literal','header','back\\slash.txt',literal);
    await menuActions('literal','card','back\\slash.txt',literal);
    await browser.locator('[data-title]').filter({hasText:'back\\slash.txt'}).tap();
    await screen.getByText('CHANGED', {exact:true}).first().waitFor({state:'visible'});
    await app.screenshot('literal-title-open');
    const literalText = await screen.getByText(/^(literal backslash file|nested file)$/).textContent();
    await t3.open(second);
    await screen.getByRole('button','Open diff', {exact:true}).tap();
    await browser.locator('[data-title]').filter({hasText:'project/a.ts'}).waitFor({state:'visible'});
    await app.screenshot('nested-diff');
    if(root) {
      await screen.getByRole('button', 'Expand all folders', {exact:true}).tap();
      await menuActions('nested','header','project/a.ts',`${parent}/project`);
      await menuActions('nested','card','project/a.ts',`${parent}/project`);
    }
    await browser.locator('[data-title]').filter({hasText:'project/a.ts'}).tap();
    await screen.getByText(/^(CHANGED|The file could not be accessed or read.*)$/).first().waitFor({state:'visible'});
    await browser.evaluate(() => {
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
      const nodes: Text[] = [];
      while (walker.nextNode()) nodes.push(walker.currentNode as Text);
      for (const node of nodes) {
        if (node.nodeValue?.includes('/workspaces/')) {
          node.nodeValue = node.nodeValue.replace(/\/home\/[^ ]*?\/workspaces\/w-[^/]+/g, '/fixture');
        }
      }
    });
    await app.screenshot('nested-title-open');
    const nestedText = await screen.getByText(/^(export const nestedProject = true;|The file could not be accessed or read.*)$/).textContent();
    expect(literalText).toContain('literal backslash file');
    expect(nestedText).toContain('export const nestedProject = true;');
    expect(nestedText).not.toContain('The file could not be accessed or read');
  } finally {
    await writeFile(agentPath, original);
  }
});
