import { expect } from "e2e";
import { test, WEB } from "./t3.ts";

// Adapted from the PR's supplied browser-driver.js: send the same malformed
// Chunk through the real decoder without closing or replacing its WebSocket.
test("A failed shell subscription recovers on its healthy socket", WEB, async ({t3, app, screen, browser}) => {
  const project = await t3.project("subscription-demo");
  const thread = await t3.thread({project, title: "Subscription recovery", messages: ["first"]});
  await browser.addInitScript(() => {
    const w = window as any;
    const r = w.__recovery = { sockets: [], frames: [], counter: 910000, faults: [], receipts: [] };
    const send = WebSocket.prototype.send;
    WebSocket.prototype.send = function(data) {
      if (!r.sockets.includes(this)) r.sockets.push(this);
      try { r.frames.push({socket:r.sockets.indexOf(this), ...JSON.parse(String(data))}); } catch {}
      return send.call(this, data);
    };
    r.command = (threadId: string, type: string) => new Promise((resolve, reject) => {
      const socket = r.sockets.findLast((s: WebSocket) => s.readyState === 1 && s.url.includes('/ws?'));
      if (!socket) throw Error('No OPEN app socket');
      const id = ++r.counter;
      const receive = (e: MessageEvent) => {
        const m = JSON.parse(e.data);
        if (m._tag !== 'Exit' || m.requestId !== id) return;
        socket.removeEventListener('message', receive);
        r.receipts.push(m);
        if (m.exit._tag !== 'Success') reject(Error(JSON.stringify(m)));
        else resolve(m);
      };
      socket.addEventListener('message', receive);
      socket.send(JSON.stringify({_tag:'Request',id,tag:'orchestration.dispatchCommand',headers:[],payload:{type,commandId:crypto.randomUUID(),threadId,...(type==='thread.unsettle'?{reason:'user'}:{})}}));
    });
    r.fault = () => {
      const socket = r.sockets.findLast((s: WebSocket) => s.readyState === 1 && s.url.includes('/ws?'));
      const si = r.sockets.indexOf(socket);
      const request = r.frames.findLast((f: any) => f.socket===si && f._tag==='Request' && f.tag==='orchestration.subscribeShell');
      if (!request) throw Error('No active shell subscription');
      r.faults.push({socket:si,id:request.id,readyState:socket.readyState});
      socket.dispatchEvent(new MessageEvent('message',{data:JSON.stringify({_tag:'Chunk',requestId:request.id,values:[{type:'issue-4589-invalid-stream-item'}]})}));
    };
  });
  await t3.open(thread);
  await expect(screen.getByText("first fixture turn complete", {exact:false})).toBeVisible();
  const command = (type: string) => browser.evaluate(({id,type}) => (window as any).__recovery.command(id,type), {id:thread.threadId,type});
  const shelf = browser.locator('[data-sidebar-row="shelf:settled"]');
  await command('thread.settle');
  await expect(shelf).toHaveText(/Settled\s*1/);
  await browser.evaluate(() => new Promise(resolve => setTimeout(() => resolve(true), 1000)));
  await browser.evaluate(() => (window as any).__recovery.fault());
  await command('thread.unsettle');
  // Give the same-session retry and shell projection time on both commits.
  await browser.evaluate(() => new Promise(resolve => setTimeout(() => resolve(true), 3000)));
  await app.screenshot('accepted-unsettle-after-fault');
  const outcome = await browser.evaluate(() => {
    const r = (window as any).__recovery;
    const fault = r.faults[0];
    const shell = r.frames.filter((f:any) => f.tag==='orchestration.subscribeShell');
    return {text:document.querySelector('[data-sidebar-row="shelf:settled"]')?.textContent ?? '', sameSocketOpen:r.sockets[fault.socket].readyState===1, shellRequests:shell.map((f:any)=>({id:f.id,socket:f.socket})), successfulCommands:r.receipts.length};
  });
  console.log('Controlled fault outcome', JSON.stringify(outcome));
  // Injection is one-shot and has ended. Exercise ordinary commands afterward.
  await command('thread.settle');
  await browser.evaluate(() => new Promise(resolve => setTimeout(() => resolve(true), 500)));
  await app.screenshot('normal-settle-control');
  await command('thread.unsettle');
  await browser.evaluate(() => new Promise(resolve => setTimeout(() => resolve(true), 500)));
  await app.screenshot('normal-unsettle-control');
  console.log('Normal control receipts', await browser.evaluate(() => (window as any).__recovery.receipts.map((m:any) => ({requestId:m.requestId, exit:m.exit._tag}))));
  await expect(outcome.text).toMatch(/Settled\s*0/);
  await expect(outcome.sameSocketOpen).toBe(true);
});
