/**
 * T4b — closing the conversation-id persistence loop.
 *
 * Three layers, mirroring the SetCellAgent coverage:
 *  1. The mock agent gateway assigns a fresh conversation id on a first launch
 *     (cell had none) and resumes (no new id) when one is passed in.
 *  2. `MockLayoutGateway` applies the `setCellConversation` op, persisting/clearing
 *     the id on the leaf.
 *  3. The grid wiring: when an agent cell launches and the backend assigns an id,
 *     `setCellConversation` is emitted so the next open resumes; and a leaf that
 *     already carries an id passes it back into `launchAgent`.
 *
 * As elsewhere (TerminalView.test), xterm may bail under jsdom, so the render-level
 * assertions only fire when the opener actually ran.
 */
import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";

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

function renderGrid(
  layout: MockLayoutGateway,
  agent: { launchAgent: unknown; listAgents: unknown; reattach: unknown },
  projectId = "p1",
  terminal: MockTerminalGateway = new MockTerminalGateway(),
) {
  const gateways = { layout, terminal, agent } as unknown as Gateways;
  return render(
    <DIProvider gateways={gateways}>
      <LayoutGrid projectId={projectId} cwd="/home/me/proj" />
    </DIProvider>,
  );
}

describe("setCellConversation — mock layout gateway", () => {
  it("persists then clears the conversation id on the leaf", async () => {
    const layout = new MockLayoutGateway();
    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;

    const after = await layout.mutateLayout("p1", {
      type: "setCellConversation",
      target: leafId,
      conversationId: "conv-42",
    });
    expect(leaves(after)[0].conversationId).toBe("conv-42");

    const cleared = await layout.mutateLayout("p1", {
      type: "setCellConversation",
      target: leafId,
      conversationId: null,
    });
    expect(leaves(cleared)[0].conversationId).toBeUndefined();
  });
});

describe("MockAgentGateway.launchAgent — session assignment", () => {
  it("assigns a fresh conversation id when the cell has none", async () => {
    const agent = new MockAgentGateway();
    await agent.createAgent("p1", { name: "A", profileId: "p" });
    const list = await agent.listAgents("p1");

    const handle = await agent.launchAgent(
      "p1",
      list[0].id,
      { cwd: "/x", rows: 24, cols: 80 } as OpenTerminalOptions,
      () => {},
    );
    expect(handle.assignedConversationId).toBeTruthy();
  });

  it("does NOT assign a new id when resuming an existing conversation", async () => {
    const agent = new MockAgentGateway();
    await agent.createAgent("p1", { name: "A", profileId: "p" });
    const list = await agent.listAgents("p1");

    const handle = await agent.launchAgent(
      "p1",
      list[0].id,
      { cwd: "/x", rows: 24, cols: 80, conversationId: "existing-conv" },
      () => {},
    );
    expect(handle.assignedConversationId).toBeUndefined();
  });
});

describe("LayoutGrid — conversation-id loop wiring", () => {
  it("persists the assigned id on the leaf after an agent launch", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const a = await agent.createAgent("p1", { name: "MyAgent", profileId: "p" });

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

    // A launch handle that always reports an assigned id (the "first launch").
    const launchAgent = vi.fn(
      async (
        _p: string,
        _ag: string,
        _opts: OpenTerminalOptions,
        _onData: (b: Uint8Array) => void,
      ): Promise<TerminalHandle> => ({
        sessionId: "sess-1",
        assignedConversationId: "assigned-conv-1",
        write: vi.fn().mockResolvedValue(undefined),
        resize: vi.fn().mockResolvedValue(undefined),
        detach: vi.fn(),
        close: vi.fn().mockResolvedValue(undefined),
      }),
    );
    const stubAgent = {
      listAgents: () => Promise.resolve([a]),
      launchAgent,
      reattach: vi.fn(),
    };

    renderGrid(layout, stubAgent, "p1");

    await waitFor(() => expect(screen.getByRole("combobox")).toBeTruthy());

    // Only assert the loop when the opener actually ran (xterm may bail in jsdom).
    await waitFor(async () => {
      if (launchAgent.mock.calls.length > 0) {
        const fresh = await layout.loadLayout("p1");
        expect(leaves(fresh)[0].conversationId).toBe("assigned-conv-1");
      } else {
        expect(true).toBe(true);
      }
    });
  });

  it("passes the leaf's existing conversation id into launchAgent (resume)", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const a = await agent.createAgent("p1", { name: "MyAgent", profileId: "p" });

    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;
    await layout.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: a.id,
    });
    // The leaf already carries a conversation id (a prior assignment).
    await layout.mutateLayout("p1", {
      type: "setCellConversation",
      target: leafId,
      conversationId: "prior-conv",
    });

    const launchAgent = vi.fn(
      async (
        _p: string,
        _ag: string,
        _opts: OpenTerminalOptions,
        _onData: (b: Uint8Array) => void,
      ): Promise<TerminalHandle> => ({
        sessionId: "sess-2",
        write: vi.fn().mockResolvedValue(undefined),
        resize: vi.fn().mockResolvedValue(undefined),
        detach: vi.fn(),
        close: vi.fn().mockResolvedValue(undefined),
      }),
    );
    const stubAgent = {
      listAgents: () => Promise.resolve([a]),
      launchAgent,
      reattach: vi.fn(),
    };

    renderGrid(layout, stubAgent, "p1");

    await waitFor(() => expect(screen.getByRole("combobox")).toBeTruthy());

    await waitFor(() => {
      if (launchAgent.mock.calls.length > 0) {
        const opts = launchAgent.mock.calls[0][2] as OpenTerminalOptions;
        expect(opts.conversationId).toBe("prior-conv");
      } else {
        expect(true).toBe(true);
      }
    });
  });
});
