/**
 * BinderProvider — composition root for the binder subsystem.
 *
 * Wires the four binder concerns (rect registry, binding service, link
 * repo, active state machine) into one provider so a single mount inside
 * the EngineProvider gives every binder consumer (FormRenderer, evidence
 * picker, SVG overlay) what it needs.
 *
 * The provider is split out from the engine because in a future
 * subsystem-extraction these will live in separate packages — the engine
 * will publish only the event bus and the engine services, while
 * `evidence-binder` will export this provider.
 */

import {
  createContext,
  useContext,
  useEffect,
  useMemo,
  type ReactNode,
} from "react";

import type { EvidenceLink } from "@shared/evidence-link";

import type { EventBus } from "@engine/events";

import {
  ActiveStateProvider,
  useActiveState,
} from "./state/active";
import {
  createInMemoryLinkRepo,
  type EvidenceLinkRepository,
} from "./repos/in-memory-links";
import {
  createBindingService,
  type BindingService,
} from "./services/bindings";
import {
  RectRegistryProvider,
  createRectRegistryContextValue,
  type RectRegistryContextValue,
} from "./visual-guide/react-hooks";

export interface BinderServices {
  readonly links: EvidenceLinkRepository;
  readonly bindings: BindingService;
  readonly rect: RectRegistryContextValue;
}

const BinderServicesContext = createContext<BinderServices | null>(null);

export function useBinder(): BinderServices {
  const ctx = useContext(BinderServicesContext);
  if (!ctx) throw new Error("useBinder: missing <BinderProvider />");
  return ctx;
}

export interface BinderProviderProps {
  readonly children: ReactNode;
  /**
   * The engine's event bus, threaded in by the composition root so the
   * binder can emit §4 events without importing work/EngineContext
   * (work cannot be a dependency of binder — see DependencyMap §2).
   */
  readonly bus: EventBus;
  /**
   * Tests can inject a pre-built service set; production constructs a
   * fresh one. The rect registry is *always* fresh per provider mount
   * because its observers attach to the current `window`.
   */
  readonly services?: Omit<BinderServices, "rect">;
  /**
   * Restored evidence links for this session. Seeded directly into the
   * repo (no bus events) so reload does not spuriously re-emit
   * `EvidenceLinkCreated`.
   */
  readonly initialLinks?: readonly EvidenceLink[];
}

export function BinderProvider({
  children,
  bus,
  services,
  initialLinks,
}: BinderProviderProps) {
  const built = useMemo<BinderServices>(() => {
    const links = services?.links ?? createInMemoryLinkRepo();
    const bindings = services?.bindings ?? createBindingService(links, bus);
    const rect = createRectRegistryContextValue();
    return { links, bindings, rect };
  }, [bus, services]);

  useEffect(() => {
    if (!initialLinks?.length || services?.links) return;
    for (const link of initialLinks) {
      if (!built.links.get(link.id)) {
        built.links.create(link);
      }
    }
  }, [built.links, initialLinks, services?.links]);

  // Disconnect rect observers + listeners on unmount.
  useEffect(() => {
    return () => {
      built.rect.observer.disconnect();
    };
  }, [built.rect]);

  return (
    <BinderServicesContext.Provider value={built}>
      <RectRegistryProvider value={built.rect}>
        <ActiveStateProvider bus={bus}>{children}</ActiveStateProvider>
      </RectRegistryProvider>
    </BinderServicesContext.Provider>
  );
}

export { useActiveState };
