/**
 * F-1 — `LayoutGrid` cell routing (Option 1, Terminal + MCP): **every** agent
 * cell renders the raw {@link TerminalView}; no structured chat view is ever
 * mounted. This replaces the former §17.6 `cellKind:"chat"` routing — the human
 * view is now the native interactive PTY, and cross-model delegation flows
 * through MCP tools, not a chat view. Wired through the real {@link DIProvider}
 * with the in-memory mocks, exactly like `LayoutGrid.test.tsx`.
 *
 * The decisive case: an agent cell always renders the terminal and never swaps
 * to a chat view (the structured chat surface was removed in the F-2 cleanup).
 *
 * Under jsdom xterm's `open` may bail, so the opener that triggers the launch
 * might not run; we therefore stub xterm (as in the original test) so the launch
 * does fire and we genuinely exercise the post-launch routing — which must stay
 * on the terminal regardless of the reported kind.
 */
import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor as rtlWaitFor } from "@testing-library/react";

// Make xterm "wire up" under jsdom: the real `Terminal.open` throws without a
// layout engine, which makes `TerminalView`'s effect bail before it ever calls
// the opener — so the launch would never fire. A minimal stub lets `term.open`
// succeed and the opener run, so the launch (and any routing it could trigger)
// is genuinely exercised. We do NOT stub the routing — only xterm.
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", () => ({}));

// jsdom has no ResizeObserver; TerminalView installs one after `term.open`.
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";

/** Seeds an agent in the gateway and pins it onto the (single) leaf cell. */
async function seedPinnedAgent(): Promise<{
  gateways: Gateways;
  layout: MockLayoutGateway;
  agentGateway: MockAgentGateway;
}> {
  const layout = new MockLayoutGateway();
  const agentGateway = new MockAgentGateway();
  const terminal = new MockTerminalGateway();
  const system = new MockSystemGateway();

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

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

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

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

describe("LayoutGrid cell routing (F-1, Terminal + MCP)", () => {
  it("a plain (agent-less) cell renders the terminal view, never a chat view", async () => {
    const layout = new MockLayoutGateway();
    const gateways = {
      layout,
      agent: new MockAgentGateway(),
      terminal: new MockTerminalGateway(),
      system: new MockSystemGateway(),
    } as unknown as Gateways;

    renderGrid(gateways);

    await rtlWaitFor(() => expect(screen.getByTestId("layout-leaf")).toBeTruthy());
    expect(screen.getByTestId("terminal-view")).toBeTruthy();
    expect(screen.queryByTestId("agent-chat-view")).toBeNull();
  });

  it("a pty agent cell renders the terminal view, never a chat view", async () => {
    const { gateways } = await seedPinnedAgent();
    renderGrid(gateways);

    await rtlWaitFor(() => expect(screen.getByTestId("layout-leaf")).toBeTruthy());
    expect(screen.getByTestId("terminal-view")).toBeTruthy();
    expect(screen.queryByTestId("agent-chat-view")).toBeNull();
  });

  it("re-mounting a known agent cell (persisted session) repaints as a terminal, never chat", async () => {
    const layout = new MockLayoutGateway();
    const agentGateway = new MockAgentGateway();
    const terminal = new MockTerminalGateway();
    const system = new MockSystemGateway();

    const agent = await agentGateway.createAgent("p1", { name: "Worker", profileId: "claude" });
    // Seed a persisted session on the leaf — the pre-F-1 path would have re-mounted
    // such a known agent cell as a chat view; now it must always be a terminal.
    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;
    await layout.mutateLayout("p1", { type: "setCellAgent", target: leafId, agent: agent.id });
    await layout.mutateLayout("p1", {
      type: "setSession",
      target: leafId,
      session: "running-session",
    });

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

    // First mount.
    const { unmount } = renderGrid(gateways);
    await rtlWaitFor(() => expect(screen.getByTestId("terminal-view")).toBeTruthy());
    expect(screen.queryByTestId("agent-chat-view")).toBeNull();
    unmount();

    // Re-mount (as after a tab/layout navigation): the known agent cell with its
    // persisted session must repaint as a terminal — never a chat view.
    renderGrid(gateways);
    await rtlWaitFor(() => expect(screen.getByTestId("terminal-view")).toBeTruthy());
    expect(screen.queryByTestId("agent-chat-view")).toBeNull();
  });
});
