/**
 * Ticket #83 — the app-exit "work in progress" confirmation popup.
 *
 * Pins the carnet #83 contract: the popup only appears on the backend's
 * `app-exit-work-guard` event (never derived locally), with the exact title
 * and pluralized body copy, a compact capped detail list, `Annuler` as a pure
 * local no-op, and `Quitter quand même` calling `confirmAppExit()`.
 */
import { describe, it, expect } from "vitest";
import { render, screen, act, fireEvent } from "@testing-library/react";

import type { AppExitWorkGuardState } from "@/domain";
import type { Gateways } from "@/ports";
import { MockSystemGateway } from "@/adapters/mock";
import { DIProvider } from "@/app/di";
import { AppExitConfirmDialog } from "./AppExitConfirmDialog";

function setup() {
  const system = new MockSystemGateway();
  const gateways = { system } as unknown as Gateways;
  render(
    <DIProvider gateways={gateways}>
      <AppExitConfirmDialog />
    </DIProvider>,
  );
  return { system };
}

/** The gateway subscribes asynchronously; flush the microtask before emitting. */
async function flush() {
  await act(async () => {
    await Promise.resolve();
    await Promise.resolve();
  });
}

function state(over: Partial<AppExitWorkGuardState> = {}): AppExitWorkGuardState {
  return {
    hasWorkInProgress: true,
    busyAgentCount: 0,
    activeBackgroundTaskCount: 0,
    totalWorkCount: 0,
    details: [],
    ...over,
  };
}

const dialog = () => screen.queryByRole("alertdialog");

describe("AppExitConfirmDialog", () => {
  it("renders nothing until the app-exit-work-guard event fires", async () => {
    await setup();
    await flush();
    expect(dialog()).toBeNull();
  });

  it("shows the exact title and singular body for exactly one active work item", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(
        state({ busyAgentCount: 1, totalWorkCount: 1 }),
      );
    });

    expect(dialog()).not.toBeNull();
    expect(screen.getByText("Du travail est encore en cours")).toBeTruthy();
    expect(
      screen.getByText(
        "1 travail actif sera interrompu si vous quittez IdeA maintenant.",
      ),
    ).toBeTruthy();
    expect(
      screen.getByText(
        "Annulez la fermeture pour laisser les agents et les tâches se terminer.",
      ),
    ).toBeTruthy();
  });

  it("pluralizes and distinguishes agent/background-task counts when both are present", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(
        state({ busyAgentCount: 2, activeBackgroundTaskCount: 1, totalWorkCount: 3 }),
      );
    });

    expect(
      screen.getByText(
        "2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.",
      ),
    ).toBeTruthy();
  });

  it("shows up to 5 detail lines then a '+N autres' summary", async () => {
    const { system } = await setup();
    await flush();

    const details: AppExitWorkGuardState["details"] = Array.from({ length: 7 }, (_, i) => ({
      kind: "busyAgent",
      projectId: "p1",
      projectName: "IdeA",
      agentId: `a${i}`,
      agentName: `Agent${i}`,
      ticketId: null,
    }));

    act(() => {
      system.emitAppExitWorkGuard(
        state({ busyAgentCount: 7, totalWorkCount: 7, details }),
      );
    });

    expect(screen.getByText("• Agent Agent0 — IdeA")).toBeTruthy();
    expect(screen.getByText("• Agent Agent4 — IdeA")).toBeTruthy();
    expect(screen.queryByText("• Agent Agent5 — IdeA")).toBeNull();
    expect(screen.getByText("+ 2 autres")).toBeTruthy();
  });

  it("formats a background-task detail line using the short task id fallback", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(
        state({
          activeBackgroundTaskCount: 1,
          totalWorkCount: 1,
          details: [
            {
              kind: "activeBackgroundTask",
              projectId: "p1",
              projectName: "IdeA",
              agentId: "a1",
              agentName: "DevBackend",
              taskId: "0123456789abcdef",
              state: "running",
              taskKind: "command",
            },
          ],
        }),
      );
    });

    expect(screen.getByText("• Tâche de fond 01234567 — IdeA")).toBeTruthy();
  });

  it("'Annuler' closes the popup locally without calling confirmAppExit", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(state({ busyAgentCount: 1, totalWorkCount: 1 }));
    });
    expect(dialog()).not.toBeNull();

    fireEvent.click(screen.getByRole("button", { name: "Annuler" }));

    expect(dialog()).toBeNull();
    expect(system.confirmAppExitCallCount).toBe(0);
  });

  it("Escape is equivalent to Annuler", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(state({ busyAgentCount: 1, totalWorkCount: 1 }));
    });
    expect(dialog()).not.toBeNull();

    fireEvent.keyDown(window, { key: "Escape" });

    expect(dialog()).toBeNull();
    expect(system.confirmAppExitCallCount).toBe(0);
  });

  it("'Quitter quand même' calls confirmAppExit", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(state({ busyAgentCount: 1, totalWorkCount: 1 }));
    });

    fireEvent.click(screen.getByRole("button", { name: /Quitter quand même/ }));

    await flush();
    expect(system.confirmAppExitCallCount).toBe(1);
  });

  it("focuses 'Annuler' by default when the popup opens", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(state({ busyAgentCount: 1, totalWorkCount: 1 }));
    });

    expect(document.activeElement).toBe(screen.getByRole("button", { name: "Annuler" }));
  });

  it("does not auto-close when a fresh guard event arrives while already open", async () => {
    const { system } = await setup();
    await flush();

    act(() => {
      system.emitAppExitWorkGuard(state({ busyAgentCount: 1, totalWorkCount: 1 }));
    });
    expect(dialog()).not.toBeNull();

    act(() => {
      system.emitAppExitWorkGuard(
        state({ busyAgentCount: 2, activeBackgroundTaskCount: 1, totalWorkCount: 3 }),
      );
    });

    // Still open, summary refreshed in place.
    expect(dialog()).not.toBeNull();
    expect(
      screen.getByText(
        "2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.",
      ),
    ).toBeTruthy();
  });
});
