/**
 * `PluginLayoutCellView` — renders a `customPluginLayout` top-level
 * {@link LayoutNode} (ticket #43, F4, carnet v2 §3): the plugin's registered
 * React component when its provider is loaded and declares the type,
 * otherwise {@link PluginLayoutFallback}.
 *
 * State is opaque to the domain (`unknown`) and round-trips through
 * `LayoutGateway` exactly like any other node field — `setState` here is the
 * only way a plugin component is meant to persist it (never direct gateway
 * calls from inside a plugin component).
 */

import { Component, type ErrorInfo, type ReactNode } from "react";

import type { CustomPluginLayoutCell, PluginAdmin } from "@/domain";
import { usePluginRuntime } from "./PluginRuntimeProvider";
import { PluginLayoutFallback } from "./PluginLayoutFallback";
import { resolvePluginLayoutAvailability } from "./layoutAvailability";

interface PluginLayoutCellViewProps {
  projectId: string;
  cell: CustomPluginLayoutCell;
  installedPlugins?: PluginAdmin[];
  onStateChange: (nextState: unknown) => void;
  onOpenPlugins: () => void;
  onChooseAnotherLayout: () => void;
}

interface PluginLayoutErrorBoundaryProps {
  resetKey: string;
  fallback: ReactNode;
  children: ReactNode;
}

class PluginLayoutErrorBoundary extends Component<
  PluginLayoutErrorBoundaryProps,
  { hasError: boolean }
> {
  state = { hasError: false };

  static getDerivedStateFromError(): { hasError: boolean } {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    console.error("[plugin-layout] render failed", error, errorInfo);
  }

  componentDidUpdate(prevProps: PluginLayoutErrorBoundaryProps): void {
    if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
      this.setState({ hasError: false });
    }
  }

  render(): ReactNode {
    if (this.state.hasError) return this.props.fallback;
    return this.props.children;
  }
}

export function PluginLayoutCellView({
  projectId,
  cell,
  installedPlugins,
  onStateChange,
  onOpenPlugins,
  onChooseAnotherLayout,
}: PluginLayoutCellViewProps) {
  const { registry } = usePluginRuntime();
  const availability = resolvePluginLayoutAvailability(registry, cell, installedPlugins);
  const providerDisplayName =
    registry.get(cell.pluginId)?.displayName ??
    installedPlugins?.find((p) => p.id === cell.pluginId)?.displayName;

  if (availability !== "available") {
    return (
      <PluginLayoutFallback
        cell={cell}
        availability={availability}
        providerDisplayName={providerDisplayName}
        onOpenPlugins={onOpenPlugins}
        onChooseAnotherLayout={onChooseAnotherLayout}
      />
    );
  }

  const Component = registry.layoutComponent(cell.pluginId, cell.layoutType)!;
  const fallback = (
    <PluginLayoutFallback
      cell={cell}
      availability="incompatible"
      providerDisplayName={providerDisplayName}
      onOpenPlugins={onOpenPlugins}
      onChooseAnotherLayout={onChooseAnotherLayout}
    />
  );
  return (
    <PluginLayoutErrorBoundary
      resetKey={`${cell.pluginId}:${cell.layoutType}:${cell.id}`}
      fallback={fallback}
    >
      <Component
        projectId={projectId}
        nodeId={cell.id}
        layoutType={cell.layoutType}
        state={cell.state}
        setState={onStateChange}
        availability="available"
      />
    </PluginLayoutErrorBoundary>
  );
}
