import { expect } from "e2e";
import { createServer, connect } from "node:net";
import { test, WEB } from "./t3.ts";

test("Terminal reconnect transfers only its retained tail", WEB, async ({t3,app,screen,browser}) => {
  const project = await t3.project("Terminal history", {"history.py": `import random,string\nr=random.Random(42)\nfor i in range(4900):\n print('\\033[36m%04d '%i+''.join(r.choices(string.ascii_letters+string.digits,k=900))+' café 界 🚀\\033[0m')\nprint('HISTORY_READY café 界 🚀',flush=True)\n`});
  await t3.thread({project,title:"Terminal restoration",messages:["Set up terminal evidence"]});
    await browser.addInitScript(()=>{
      const Original=window.WebSocket;
      (window as any).terminalEvidence={requests:[],snapshots:[],output:''};
      window.WebSocket=class extends Original {
        constructor(url:string|URL, protocols?:string|string[]){const u=new URL(url);if(sessionStorage.relayPort)u.port=sessionStorage.relayPort;super(u,protocols);let pending='';this.addEventListener('message',event=>{try{if(event.data.startsWith('\u001e')){pending+=event.data.slice(1);return;}const msg=JSON.parse(pending+event.data);pending='';const ev=(window as any).terminalEvidence;for(const value of msg.values??[]){ev.output+=value.data??'';if(value.type==='snapshot'&&value.snapshot?.history!==undefined){const req=ev.requests.find((r:any)=>r.id===msg.requestId); ev.snapshots.push({history:value.snapshot.history,bytes:new TextEncoder().encode(value.snapshot.history).length,sequence:value.sequence??value.snapshot.sequence,ms:performance.now()-(req?.start??0),request:req});}}}catch{}});}
        send(data:any){try{const msg=JSON.parse(data);if(msg.tag==='terminal.attach'){(window as any).terminalEvidence.requests.push({...msg,start:performance.now()});}}catch{}super.send(data);}
      };
    });
  await t3.open();
  await screen.getByRole("button", "Terminal restoration, Done, Terminal history").tap();
  await screen.getByRole("button","Toggle terminal drawer").tap();
  await browser.evaluate(()=>new Promise<void>(resolve=>{const focus=()=>{const input=document.querySelector<HTMLTextAreaElement>('textarea[aria-label="Terminal input"]');if(!input)return false;input.focus();return true;};if(focus())return resolve();const observer=new MutationObserver(()=>{if(focus()){observer.disconnect();resolve();}});observer.observe(document.body,{childList:true,subtree:true});}));
  await browser.evaluate(()=>new Promise<void>(resolve=>{const id=setInterval(()=>{if((window as any).terminalEvidence.output.length>0){clearInterval(id);resolve();}},50);}));
  await browser.evaluate(()=>new Promise(resolve=>setTimeout(resolve,500)));
  await browser.keyboard.type("unset PROMPT_COMMAND; PS1='$ '; clear; python3 history.py");
  await browser.keyboard.press("Enter");
  // Wait for real PTY output to finish before reconnecting.
  await browser.evaluate(() => new Promise<void>((resolve,reject) => { const start=Date.now(); const id=setInterval(()=>{ if((window as any).terminalEvidence.output.includes('HISTORY_READY')) {clearInterval(id);resolve();} else if(Date.now()-start>60000){clearInterval(id);reject(Error('Terminal output did not finish'));}},100); }));
  const port = await browser.evaluate(() => Number(location.port));
  let throttle=false;
  const sockets = new Set<any>();
  const relay=createServer(client => {
    const upstream=connect(port,"127.0.0.1"); sockets.add(client);sockets.add(upstream);
    const pipe=(src:any,dst:any) => { let queue=Promise.resolve(); let next=0; src.on('data',(chunk:Buffer)=>{ for(let i=0;i<chunk.length;i+=16384){ const part=chunk.subarray(i,i+16384); const due=throttle ? (next=Math.max(Date.now()+60,next)+part.length/625) : Date.now(); queue=queue.then(async()=>{const delay=due-Date.now();if(delay>0)await new Promise(r=>setTimeout(r,delay));if(!dst.destroyed)dst.write(part);}); } });src.on('end',()=>queue.then(()=>dst.end()));src.on('error',()=>dst.destroy());};
    pipe(client,upstream);pipe(upstream,client);
  });
  await new Promise<void>(resolve=>relay.listen(0,"127.0.0.1",resolve));
  const proxyPort=(relay.address() as any).port;
  try {
    await browser.evaluate((port)=>{sessionStorage.relayPort=String(port);},proxyPort);
    const results=[];
    for(let i=0;i<3;i++){
      throttle=true;
      await browser.reload();
      if(i===0) { await browser.evaluate(()=>new Promise(resolve=>setTimeout(resolve,2500))); await app.screenshot("reattach-at-2500ms"); }
      await browser.evaluate(()=>new Promise<void>((resolve,reject)=>{const start=Date.now();const id=setInterval(()=>{if((window as any).terminalEvidence.snapshots.length){clearInterval(id);resolve();}else if(Date.now()-start>90000){clearInterval(id);reject(Error('Attach snapshot absent'));}},50);}));
      const result=await browser.evaluate(async()=>{const s=(window as any).terminalEvidence.snapshots.at(-1);const bytes=new TextEncoder().encode(s.history);let start=Math.max(0,bytes.length-524288);while(start<bytes.length&&(bytes[start]&192)===128)start++;const retained=bytes.slice(start);const digest=await crypto.subtle.digest('SHA-256',retained);return {bytes:s.bytes,sequence:s.sequence??null,ms:s.ms,tail:s.history.slice(-200),retainedSha256:Array.from(new Uint8Array(digest),x=>x.toString(16).padStart(2,'0')).join(''),hasReplacementCharacter:s.history.includes('\uFFFD'),maxHistoryBytes:s.request?.payload?.maxHistoryBytes??null};});
      results.push(result);
      await browser.evaluate(()=>new Promise(resolve=>setTimeout(resolve,750)));
      if(i===0) await app.screenshot("history-restored");
    }
    console.log("TERMINAL_EVIDENCE",JSON.stringify(results));
    throttle=false;
    await browser.evaluate(()=>new Promise<void>(resolve=>{const focus=()=>{const input=document.querySelector<HTMLTextAreaElement>('textarea[aria-label="Terminal input"]');if(!input)return false;input.focus();return true;};if(focus())return resolve();const observer=new MutationObserver(()=>{if(focus()){observer.disconnect();resolve();}});observer.observe(document.body,{childList:true,subtree:true});}));
    await browser.evaluate(()=>new Promise(resolve=>setTimeout(resolve,500)));
    await browser.keyboard.type("python3 -c \"print('LIVE_OK caf\\u00e9 \\u754c \\U0001f680')\"");
    await browser.keyboard.press("Enter");
    await browser.evaluate(()=>new Promise<void>((resolve,reject)=>{const start=Date.now();const id=setInterval(()=>{if((window as any).terminalEvidence.output.includes('LIVE_OK café 界 🚀')){clearInterval(id);resolve();}else if(Date.now()-start>15000){clearInterval(id);reject(Error('Live output absent'));}},100);}));
    await app.screenshot("live-output-after-reconnect");
    console.log("TERMINAL_EVIDENCE",JSON.stringify(results));

    await expect(results.every(x=>x.bytes<=524288)).toBe(true);
    await expect(results.every(x=>x.tail.includes('HISTORY_READY café 界 🚀'))).toBe(true);
  } finally { for(const socket of sockets)socket.destroy();await new Promise<void>(resolve=>relay.close(()=>resolve())); }
});
