/**
 * LS7 — `ProjectsView` integration: clicking a conversation in the Work panel swaps
 * the terminal grid for the read-only `ConversationViewer`; the back button returns
 * to the grid; switching project resets the viewer; and with no conversation open
 * the terminal grid is shown unchanged (non-regression).
 */
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";

import {
  MockAgentGateway,
  MockGitGateway,
  MockLayoutGateway,
  MockProfileGateway,
  MockProjectGateway,
  MockSystemGateway,
  MockTemplateGateway,
  MockTerminalGateway,
} from "@/adapters/mock";
import type {
  ConversationGateway,
  Gateways,
  WorkStateGateway,
} from "@/ports";
import type { ProjectWorkState } from "@/domain";
import { DIProvider } from "@/app/di";
import { ProjectsView } from "./ProjectsView";

const CONV_ID = "conversation-open-me";

/** A work-state gateway that returns a fixed, clickable conversation for any project. */
function fixedWorkState(): WorkStateGateway {
  const state: ProjectWorkState = {
    agents: [
      {
        agentId: "agent-1",
        name: "Worker",
        profileId: "codex",
        busy: { state: "idle" },
        tickets: [
          {
            ticketId: "ticket-1",
            conversationId: CONV_ID,
            position: 0,
            status: "inProgress",
            source: { kind: "human" },
            requesterLabel: "Anthony",
            taskPreview: "do the thing",
            taskLen: 12,
          },
        ],
      },
    ],
    conversations: [
      {
        conversationId: CONV_ID,
        status: "ready",
        objectivePreview: "Objectif du fil",
        summaryPreview: "Résumé du fil",
        summaryLen: 13,
        upTo: "turn-1",
        recentTurns: [],
      },
    ],
  };
  return {
    getProjectWorkState: async () => structuredClone(state),
    cancelBackgroundTask: async () => {},
    retryBackgroundTask: async () => {},
  };
}

/** A conversation gateway that returns one identifiable turn for any thread. */
function fixedConversation(): ConversationGateway {
  return {
    readPage: async () => ({
      turns: [
        {
          id: "t1",
          atMs: 1_700_000_000_000,
          role: "prompt",
          source: { kind: "human" },
          text: "contenu du fil ouvert",
          textLen: 21,
        },
      ],
      hasMore: false,
    }),
  };
}

function renderView(project: MockProjectGateway) {
  const agent = new MockAgentGateway();
  const gateways = {
    system: new MockSystemGateway(),
    project,
    agent,
    profile: new MockProfileGateway(),
    template: new MockTemplateGateway(agent),
    git: new MockGitGateway(),
    layout: new MockLayoutGateway(),
    terminal: new MockTerminalGateway(),
    workState: fixedWorkState(),
    conversation: fixedConversation(),
  } as unknown as Gateways;
  return render(
    <DIProvider gateways={gateways}>
      <ProjectsView />
    </DIProvider>,
  );
}

async function openProjectAndWorkTab(label: string) {
  await screen.findByText(label);
  // Open the project whose root is `label`.
  const li = screen
    .getAllByRole("listitem")
    .find((node) => within(node).queryByText(label));
  fireEvent.click(within(li!).getByRole("button", { name: "Open" }));
  await screen.findByRole("tab");
  // Switch the sidebar to the Work panel.
  fireEvent.click(screen.getByRole("button", { name: "Work" }));
}

describe("ProjectsView — LS7 conversation viewer integration", () => {
  it("opens the viewer on a conversation click and returns to the grid on back", async () => {
    const project = new MockProjectGateway();
    await project.createProject("alpha", "/p/a");
    renderView(project);

    await openProjectAndWorkTab("/p/a");

    // Before: the terminal grid owns the main area (non-regression baseline).
    expect(await screen.findByTestId("layout-grid")).toBeTruthy();
    expect(
      screen.queryByRole("button", { name: "← Retour aux terminaux" }),
    ).toBeNull();

    // Click the conversation row in the Work panel.
    const open = await screen.findByRole("button", {
      name: `open conversation ${CONV_ID}`,
    });
    fireEvent.click(open);

    // After: the viewer replaced the grid in the main area.
    expect(
      await screen.findByRole("button", { name: "← Retour aux terminaux" }),
    ).toBeTruthy();
    expect(await screen.findByText("contenu du fil ouvert")).toBeTruthy();
    expect(screen.queryByTestId("layout-grid")).toBeNull();

    // Back ⇒ the terminal grid is restored, the viewer is gone.
    fireEvent.click(
      screen.getByRole("button", { name: "← Retour aux terminaux" }),
    );
    expect(await screen.findByTestId("layout-grid")).toBeTruthy();
    expect(
      screen.queryByRole("button", { name: "← Retour aux terminaux" }),
    ).toBeNull();
  });

  it("resets the viewer when the active project changes", async () => {
    const project = new MockProjectGateway();
    await project.createProject("alpha", "/p/a");
    await project.createProject("beta", "/p/b");
    renderView(project);

    // Open BOTH projects as tabs (while the Projects sidebar list is visible).
    await screen.findByText("/p/a");
    for (const root of ["/p/a", "/p/b"]) {
      const li = screen
        .getAllByRole("listitem")
        .find((node) => within(node).queryByText(root));
      fireEvent.click(within(li!).getByRole("button", { name: "Open" }));
    }
    await waitFor(() => expect(screen.getAllByRole("tab")).toHaveLength(2));

    // Make alpha the active tab, then open its conversation viewer.
    fireEvent.click(screen.getByRole("tab", { name: "alpha" }));
    fireEvent.click(screen.getByRole("button", { name: "Work" }));
    fireEvent.click(
      await screen.findByRole("button", { name: `open conversation ${CONV_ID}` }),
    );
    await screen.findByRole("button", { name: "← Retour aux terminaux" });

    // Switch the active project (top tab) ⇒ viewer resets to the grid.
    fireEvent.click(screen.getByRole("tab", { name: "beta" }));

    await waitFor(() =>
      expect(
        screen.queryByRole("button", { name: "← Retour aux terminaux" }),
      ).toBeNull(),
    );
    expect(await screen.findByTestId("layout-grid")).toBeTruthy();
  });

  it("shows the terminal grid unchanged when no conversation is open (non-regression)", async () => {
    const project = new MockProjectGateway();
    await project.createProject("alpha", "/p/a");
    renderView(project);

    await screen.findByText("/p/a");
    fireEvent.click(screen.getByRole("button", { name: "Open" }));
    await screen.findByRole("tab");

    // No conversation opened ⇒ the main area is the terminal grid, no viewer.
    expect(await screen.findByTestId("layout-grid")).toBeTruthy();
    expect(
      screen.queryByRole("button", { name: "← Retour aux terminaux" }),
    ).toBeNull();
  });
});
