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

test("Queued scheduled deliveries follow the current thread selection", WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project("Scheduled delivery demo");
  await t3.thread({ project, title: "Scheduled delivery proof", messages: ["Initial context"], model: "multi-turn" });
  await browser.addInitScript(() => {
    const redact = () => {
      const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_TEXT);
      while (walker.nextNode()) {
        const node = walker.currentNode;
        const text = node.textContent ?? "";
        const safe = text.replace(/\/(?:home|Users)\/[^\s]+/g, "[workspace]");
        if (safe !== text) node.textContent = safe;
      }
    };
    new MutationObserver(redact).observe(document, { subtree: true, childList: true, characterData: true });
  });
  const pause = () => new Promise(resolve => setTimeout(resolve, 500));
  const stableCapture = async (label: string) => {
    let previous = await app.screenshot(`${label}-stability`);
    for (let attempt = 0; attempt < 20; attempt++) {
      await pause();
      const current = await app.screenshot(label);
      if ((await readFile(previous)).equals(await readFile(current))) return;
      previous = current;
    }
    throw new Error(`Capture did not settle: ${label}`);
  };
  await t3.open();
  await screen.getByRole("button", "Scheduled delivery proof, Done, Scheduled delivery demo").tap();
  await browser.waitForURL(/mcp/);
  const threadId = decodeURIComponent((await browser.url()).split("/").pop()!);
  await browser.evaluate(async () => {
    const socket = new WebSocket(`${location.origin.replace("http", "ws")}/ws?orchestrationProtocol=2`);
    await new Promise((resolve, reject) => { socket.onopen = resolve; socket.onerror = reject; });
    let sequence = 0;
    const pending = new Map();
    socket.onmessage = async (event) => {
      const frames = JSON.parse(typeof event.data === "string" ? event.data : await event.data.text());
      for (const frame of Array.isArray(frames) ? frames : [frames]) {
        if (frame._tag === "Ping") socket.send(JSON.stringify({ _tag: "Pong" }));
        if (frame._tag === "Exit") {
          const waiter = pending.get(String(frame.requestId));
          if (waiter) { pending.delete(String(frame.requestId)); frame.exit._tag === "Success" ? waiter.resolve(frame.exit.value) : waiter.reject(new Error(JSON.stringify(frame.exit))); }
        }
      }
    };
    (window as any).proofRpc = (tag, payload) => new Promise((resolve, reject) => {
      const id = String(++sequence); pending.set(id, { resolve, reject });
      setTimeout(() => reject(new Error(`RPC timed out: ${tag}`)), 20000);
      socket.send(JSON.stringify({ _tag: "Request", id, tag, payload, headers: [] }));
    });
    return null;
  });
  const rpc = (tag: string, payload: any) => browser.evaluate(async ({ tag, payload }) => await (window as any).proofRpc(tag, payload), { tag, payload });
  const command = (input: any) => rpc("orchestration.dispatchCommand", { ...input, commandId: crypto.randomUUID(), threadId });
  const projection = () => rpc("orchestration.getThreadProjection", { threadId });
  const initial: any = await projection();
  const { task }: any = await rpc("scheduledTasks.upsert", {
    title: "Queued delivery", prompt: "Scheduled check", enabled: false,
    schedule: { type: "interval", everyMs: 86400000 }, projectId: initial.thread.projectId,
    threadId, threadMode: "fresh_session", workspaceStrategy: { type: "root" },
    modelSelection: { instanceId: "devReplay", model: "showcase" }, runtimeMode: "approval-required", interactionMode: "default",
  });
  const results: any[] = [];
  for (const [index, model] of ["multi-turn", "todo-list"].entries()) {
    await command({ type: "thread.model-selection.set", modelSelection: { instanceId: "devReplay", model: "showcase" } });
    await command({ type: "message.dispatch", createdBy: "user", creationSource: "web", messageId: crypto.randomUUID(), text: `Active work ${index + 1}`, attachments: [], dispatchMode: { type: "start_immediately" } });
    await expect(screen.getByRole("button", "Stop generation")).toBeVisible();
    await rpc("scheduledTasks.runNow", { id: task.id });
    const queued: any = await projection();
    const delivery = queued.runs.find((run: any) => run.status === "queued");
    if (!delivery) throw new Error("Scheduled delivery did not queue while the thread was busy");
    await command({ type: "thread.model-selection.set", modelSelection: { instanceId: "devReplay", model } });
    await pause();
    // Stop the active replay and hold its queue before releasing the scheduled delivery.
    const active = queued.runs.find((run: any) => ["starting", "running", "waiting"].includes(run.status));
    if (active) await command({ type: "run.interrupt", runId: active.id, holdQueue: true });
    await stableCapture(`delivery-${index + 1}-queued`);
    await command({ type: "queue.resume" });
    await expect(screen.getByRole("button", "Stop generation")).toBeHidden({ timeout: 60000 });
    const settled: any = await projection();
    const run = settled.runs.find((run: any) => run.id === delivery.id);
    results.push({ expected: model, actual: run.modelSelection.model, selection: settled.thread.modelSelection.model });
    await stableCapture(`delivery-${index + 1}-finished`);
  }
  expect(results).toEqual([
    { expected: "multi-turn", actual: "multi-turn", selection: "multi-turn" },
    { expected: "todo-list", actual: "todo-list", selection: "todo-list" },
  ]);
});
