import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";

import { IdleMissionRuntimeProvider } from "../runtime/MissionRuntimeContext";
import type { DeviceUiPlugin, RegisteredDeviceModel } from "./contracts";
import { createDevicePluginRegistry, type DevicePluginRegistry } from "./registry";

interface DevicePluginHostValue {
  registry: DevicePluginRegistry;
  selection: RegisteredDeviceModel | null;
  selectionTransitionPending: boolean;
  selectionTransitionError: string | null;
  selectModel: (modelId: string) => Promise<boolean>;
  clearSelection: () => Promise<boolean>;
}

const DevicePluginHostContext = createContext<DevicePluginHostValue | null>(null);

export function DevicePluginHostProvider({
  plugins,
  children,
}: {
  plugins: readonly DeviceUiPlugin[];
  children: ReactNode;
}) {
  const registry = useMemo(() => createDevicePluginRegistry(plugins), [plugins]);
  const [selectedModelId, setSelectedModelId] = useState<string | null>(null);
  const [selectionTransitionPending, setSelectionTransitionPending] = useState(false);
  const [selectionTransitionError, setSelectionTransitionError] = useState<string | null>(null);
  const transitionInFlight = useRef(false);
  const deactivationHandlers = useRef(new Map<string, () => Promise<boolean>>());
  const selection = selectedModelId ? registry.resolveModel(selectedModelId) : null;

  const registerDeactivation = useCallback(
    (pluginId: string, handler: () => Promise<boolean>) => {
      deactivationHandlers.current.set(pluginId, handler);
      return () => {
        if (deactivationHandlers.current.get(pluginId) === handler) {
          deactivationHandlers.current.delete(pluginId);
        }
      };
    },
    [],
  );

  const transitionTo = useCallback(
    async (nextModelId: string | null): Promise<boolean> => {
      if (transitionInFlight.current) return false;
      if (nextModelId !== null && !registry.resolveModel(nextModelId)) {
        throw new Error(`Модель устройства не зарегистрирована: ${nextModelId}.`);
      }
      if (nextModelId === selectedModelId) return true;

      transitionInFlight.current = true;
      setSelectionTransitionPending(true);
      setSelectionTransitionError(null);
      try {
        const current = selectedModelId ? registry.resolveModel(selectedModelId) : null;
        const deactivate = current
          ? deactivationHandlers.current.get(current.plugin.manifest.metadata.id)
          : undefined;
        if (current && !deactivate) {
          setSelectionTransitionError(
            "Активный плагин не зарегистрировал безопасное завершение сессии.",
          );
          return false;
        }
        if (deactivate) {
          try {
            if (!(await deactivate())) {
              setSelectionTransitionError(
                "Плагин не подтвердил завершение текущей сессии.",
              );
              return false;
            }
          } catch {
            setSelectionTransitionError(
              "Не удалось безопасно завершить текущую сессию плагина.",
            );
            return false;
          }
        }
        setSelectedModelId(nextModelId);
        return true;
      } finally {
        transitionInFlight.current = false;
        setSelectionTransitionPending(false);
      }
    },
    [registry, selectedModelId],
  );

  const deactivationRegistrars = useMemo(
    () =>
      new Map(
        registry.plugins.map((plugin) => [
          plugin.manifest.metadata.id,
          (handler: () => Promise<boolean>) =>
            registerDeactivation(plugin.manifest.metadata.id, handler),
        ]),
      ),
    [registerDeactivation, registry.plugins],
  );

  const value = useMemo<DevicePluginHostValue>(
    () => ({
      registry,
      selection,
      selectionTransitionPending,
      selectionTransitionError,
      selectModel: (modelId) => transitionTo(modelId),
      clearSelection: () => transitionTo(null),
    }),
    [
      registry,
      selection,
      selectionTransitionError,
      selectionTransitionPending,
      transitionTo,
    ],
  );

  const runtimeTree = [...registry.plugins].reverse().reduce<ReactNode>((child, plugin) => {
    const RuntimeProvider = plugin.RuntimeProvider;
    return (
      <RuntimeProvider
        key={plugin.manifest.metadata.id}
        activeModel={
          selection?.plugin.manifest.metadata.id === plugin.manifest.metadata.id
            ? selection.model
            : null
        }
        registerDeactivation={deactivationRegistrars.get(plugin.manifest.metadata.id)!}
      >
        {child}
      </RuntimeProvider>
    );
  }, children);

  return (
    <DevicePluginHostContext.Provider value={value}>
      <IdleMissionRuntimeProvider>{runtimeTree}</IdleMissionRuntimeProvider>
    </DevicePluginHostContext.Provider>
  );
}

export function useDevicePluginHost(): DevicePluginHostValue {
  const value = useContext(DevicePluginHostContext);
  if (!value) {
    throw new Error("DevicePluginHostProvider не подключён в composition root.");
  }
  return value;
}
