/**
 * Live re-attach on background wake (inter-agent delegation UX bug).
 *
 * Symptom reported by the user: when an agent talks to another agent (a turn
 * delegated through `idea_ask_agent`), the target agent's cell does NOT show it
 * working live — the user has to toggle the cell view (Plain ↔ agent) and back
 * to see it. Root cause: the orchestrator wakes a cold target in the BACKGROUND
 * (`node_id = None`, a fresh PTY session) and publishes `AgentLaunched`, but the
 * cell pinned to that agent only refreshed its dropdown — it never re-attached
 * its terminal to the newly-live session. TerminalView's output subscription is
 * established once at mount and only re-bound on a `key` change, so the live
 * output of the delegated turn never reached the cell until the manual toggle
 * (which remounts the terminal and runs the background-attach path).
 *
 * The fix re-attaches declaratively: when the pinned agent becomes live in
 * another (non-visible) cell after mount, the cell binds to that session and
 * re-keys the terminal. This test drives exactly that sequence and asserts the
 * cell's session follows the background one — no view toggle.
 *
 * Like the sibling layout tests, we stub xterm (the headless obstacle under
 * jsdom) so the leaf's launch/attach logic runs untouched.
 */
import { describe, it, expect } from "vitest";
import { render, waitFor } from "@testing-library/react";

import { vi } from "vitest";

// Minimal xterm stub so `term.open` succeeds and the opener (→ launchAgent) runs.
vi.mock("@xterm/xterm", () => ({
  Terminal: class {
    loadAddon() {}
    open() {}
    onData() {
      return { dispose() {} };
    }
    onResize() {
      return { dispose() {} };
    }
    write() {}
    dispose() {}
    get cols() {
      return 80;
    }
    get rows() {
      return 24;
    }
  },
}));
vi.mock("@xterm/addon-fit", () => ({
  FitAddon: class {
    fit() {}
  },
}));
vi.mock("@xterm/xterm/css/xterm.css", () => ({}));

if (typeof globalThis.ResizeObserver === "undefined") {
  globalThis.ResizeObserver = class {
    observe() {}
    unobserve() {}
    disconnect() {}
  } as unknown as typeof ResizeObserver;
}

import type { Gateways } from "@/ports";
import {
  MockAgentGateway,
  MockLayoutGateway,
  MockSystemGateway,
  MockTerminalGateway,
} from "@/adapters/mock";
import { DIProvider } from "@/app/di";
import { leaves } from "./layout";
import { LayoutGrid } from "./LayoutGrid";

function renderGrid(gateways: Gateways) {
  return render(
    <DIProvider gateways={gateways}>
      <LayoutGrid projectId="p1" cwd="/home/me/proj" />
    </DIProvider>,
  );
}

describe("live re-attach on background wake (inter-agent delegation)", () => {
  it("re-attaches the cell to the agent's background session without a view toggle", async () => {
    const layout = new MockLayoutGateway();
    const agentGateway = new MockAgentGateway();
    const terminal = new MockTerminalGateway();
    const system = new MockSystemGateway();

    const agent = await agentGateway.createAgent("p1", {
      name: "Git",
      profileId: "claude",
    });

    // Pin the agent on the single root leaf.
    const initial = await layout.loadLayout("p1");
    const leafId = leaves(initial)[0].id;
    await layout.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: agent.id,
    });

    const gateways = {
      layout,
      agent: agentGateway,
      terminal,
      system,
    } as unknown as Gateways;
    renderGrid(gateways);

    // The cell launches the agent at mount and persists its (own-cell) session.
    let firstSession = "";
    await waitFor(async () => {
      const leaf = leaves(await layout.loadLayout("p1"))[0];
      expect(leaf.session).toBeTruthy();
      firstSession = leaf.session!;
    });

    // Now simulate an inter-agent delegation waking the agent in the BACKGROUND:
    // its own-cell session ends, then the orchestrator relaunches it on a fresh
    // session pinned to a non-visible node (`node_id = None` on the backend),
    // and publishes `AgentLaunched`.
    await agentGateway.stopLiveAgent("p1", agent.id);
    const bgHandle = await agentGateway.launchAgent(
      "p1",
      agent.id,
      { cwd: "/home/me/proj", rows: 24, cols: 80, nodeId: "background-node" },
      () => {},
    );
    expect(bgHandle.sessionId).not.toBe(firstSession);
    system.emit({
      type: "agentLaunched",
      agentId: agent.id,
      sessionId: bgHandle.sessionId,
    });

    // Without the fix the cell keeps its (now dead) session; with it, the cell
    // re-binds to the live background session — proving it re-subscribed to the
    // delegated turn's output with no manual Plain↔agent toggle.
    await waitFor(async () => {
      const leaf = leaves(await layout.loadLayout("p1"))[0];
      expect(leaf.session).toBe(bgHandle.sessionId);
    });
  });
});
