/**
 * F4 — `PluginLayoutCellView` (ticket #43, carnet §10 F4 acceptance criteria:
 * "rendu composant mock, state roundtrip, fallback sans mutation du layout").
 */
import { describe, expect, it, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";

import type { CustomPluginLayoutCell } from "@/domain";
import type { Gateways } from "@/ports";
import { DIProvider } from "@/app/di";
import { createMockGateways } from "@/adapters/mock";
import {
  PluginCommandRegistry,
  PluginLayoutRegistry,
  PluginMenuRegistry,
  PluginRuntimeRegistry,
  type LoadedPlugin,
  type PluginLayoutProps,
} from "@/plugins/runtime";
import { PluginRuntimeProvider } from "./PluginRuntimeProvider";
import { PluginLayoutCellView } from "./PluginLayoutCellView";

function cell(overrides: Partial<CustomPluginLayoutCell> = {}): CustomPluginLayoutCell {
  return {
    id: "leaf-1",
    pluginId: "dev.acme.gitgraph",
    layoutType: "dev.acme.gitgraph.layout",
    state: { commits: 3 },
    ...overrides,
  };
}

let latestLayoutProps: PluginLayoutProps | null = null;

function MockLayoutComponent(props: PluginLayoutProps) {
  latestLayoutProps = props;
  return (
    <div>
      <p data-testid="state">{JSON.stringify(props.state)}</p>
      <button onClick={() => props.setState({ commits: 4 })}>bump</button>
    </div>
  );
}

function ThrowingLayoutComponent(_props: PluginLayoutProps): JSX.Element {
  throw new Error("plugin layout render failed");
}

function stubPlugin(component = MockLayoutComponent): LoadedPlugin {
  const contributes = {
    menus: [],
    menuItems: [],
    layouts: [{ type: "dev.acme.gitgraph.layout", label: "Git Graph", component: "GitGraphLayout" }],
    mcpServers: [],
  };
  const layouts = new PluginLayoutRegistry("dev.acme.gitgraph", new Set(["dev.acme.gitgraph.layout"]));
  layouts.register({ type: "dev.acme.gitgraph.layout", component });
  return {
    pluginId: "dev.acme.gitgraph",
    displayName: "Git Graph",
    contributes,
    commands: new PluginCommandRegistry("dev.acme.gitgraph", new Set()),
    layouts,
    menu: new PluginMenuRegistry("dev.acme.gitgraph"),
    dispose: async () => {},
  };
}

function renderCell(
  registry: PluginRuntimeRegistry,
  props: Partial<{ cell: CustomPluginLayoutCell; onStateChange: (s: unknown) => void }> = {},
) {
  const gateways: Gateways = createMockGateways();
  return render(
    <DIProvider gateways={gateways}>
      <PluginRuntimeProvider value={{ registry, failures: [], pending: [], loading: false }}>
        <PluginLayoutCellView
          projectId="proj-1"
          cell={props.cell ?? cell()}
          onStateChange={props.onStateChange ?? (() => {})}
          onOpenPlugins={() => {}}
          onChooseAnotherLayout={() => {}}
        />
      </PluginRuntimeProvider>
    </DIProvider>,
  );
}

describe("PluginLayoutCellView", () => {
  it("renders the mock registered component when the provider is loaded", () => {
    const registry = new PluginRuntimeRegistry();
    registry.add(stubPlugin());
    latestLayoutProps = null;
    renderCell(registry);
    expect(screen.getByTestId("state").textContent).toBe(JSON.stringify({ commits: 3 }));
    expect(latestLayoutProps).not.toBeNull();
    expect(Object.prototype.hasOwnProperty.call(latestLayoutProps, "gateways")).toBe(false);
  });

  it("round-trips state through setState → onStateChange", () => {
    const registry = new PluginRuntimeRegistry();
    registry.add(stubPlugin());
    let lastState: unknown = null;
    renderCell(registry, {
      onStateChange: (s) => {
        lastState = s;
      },
    });

    fireEvent.click(screen.getByRole("button", { name: "bump" }));
    expect(lastState).toEqual({ commits: 4 });
  });

  it("renders the non-destructive fallback when the provider is not loaded, without mutating the cell", () => {
    const registry = new PluginRuntimeRegistry(); // empty — provider not loaded
    const testCell = cell();
    renderCell(registry, { cell: testCell });

    expect(screen.getByText("Layout indisponible")).toBeTruthy();
    // The cell object itself is untouched — the domain identity/state survive.
    expect(testCell).toEqual(cell());
  });

  it("isolates a plugin layout render failure to the plugin cell fallback", () => {
    const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
    try {
      const registry = new PluginRuntimeRegistry();
      registry.add(stubPlugin(ThrowingLayoutComponent));
      renderCell(registry);

      expect(screen.getByText("Layout indisponible")).toBeTruthy();
      expect(screen.queryByTestId("state")).toBeNull();
    } finally {
      consoleError.mockRestore();
    }
  });
});
