"use client";

import { useCallback, useEffect, useRef } from "react";
import { useReactFlow } from "@xyflow/react";
import { toast } from "sonner";

import { useGraphStore } from "../stores/useGraphStore";
import {
  computeLayout,
  terminateWorker,
  SOFT_CAP_NODE_COUNT,
} from "../lib/elk-layout";

import type { ElkLayoutOptions, LayoutResult } from "../lib/elk-layout";

const DEBOUNCE_MS = 300;
const LAYOUT_ANIMATION_MS = 200;
const LOADING_INDICATOR_DELAY_MS = 200;

export function useAutoLayout() {
  const { fitView } = useReactFlow();
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const isFirstLayout = useRef(true);

  const nodeCount = useGraphStore((s) => s.nodeCount);
  const setNodes = useGraphStore((s) => s.setNodes);
  const setEdges = useGraphStore((s) => s.setEdges);
  const layoutDirection = useGraphStore((s) => s.layoutDirection);
  const edgeRouting = useGraphStore((s) => s.edgeRouting);
  const isLayouting = useGraphStore((s) => s.isLayouting);
  const setIsLayouting = useGraphStore((s) => s.setIsLayouting);

  const runLayout = useCallback(
    async (options?: Partial<ElkLayoutOptions>) => {
      const currentNodes = useGraphStore.getState().nodes;
      const currentEdges = useGraphStore.getState().edges;

      if (currentNodes.length === 0) return;

      if (currentNodes.length > SOFT_CAP_NODE_COUNT) {
        toast.warning(
          `This diagram has ${currentNodes.length} nodes (recommended max: ${SOFT_CAP_NODE_COUNT}). Consider splitting into smaller diagrams for better performance.`,
        );
      }

      // Only show loading indicator if layout takes > 200ms (AC#2)
      const loadingTimer = setTimeout(() => {
        setIsLayouting(true);
      }, LOADING_INDICATOR_DELAY_MS);

      // Add layouting class for CSS transition animation
      const flowNodes =
        document.querySelectorAll<HTMLElement>(".react-flow__node");
      flowNodes.forEach((el) => el.classList.add("layouting"));

      try {
        const result: LayoutResult = await computeLayout(
          currentNodes,
          currentEdges,
          {
            direction:
              options?.direction ??
              useGraphStore.getState().layoutDirection,
            edgeRouting:
              options?.edgeRouting ??
              useGraphStore.getState().edgeRouting,
            ...options,
          },
        );

        setNodes(result.nodes);
        if (result.edges) {
          setEdges(result.edges);
        }

        // Fit view after layout, with a small delay to let transition run
        setTimeout(() => {
          fitView({ duration: LAYOUT_ANIMATION_MS, padding: 0.1 });
        }, LAYOUT_ANIMATION_MS);
      } catch (error) {
        // Ignore cancellations from single-flight pattern
        if (
          error instanceof Error &&
          (error.message === "Layout superseded" ||
            error.message === "Layout cancelled")
        ) {
          return;
        }
        toast.error(
          `Layout failed: ${error instanceof Error ? error.message : "Unknown error"}`,
        );
      } finally {
        clearTimeout(loadingTimer);
        // Remove layouting class after animation
        setTimeout(() => {
          const flowNodesFinal =
            document.querySelectorAll<HTMLElement>(".react-flow__node");
          flowNodesFinal.forEach((el) => el.classList.remove("layouting"));
          setIsLayouting(false);
        }, LAYOUT_ANIMATION_MS);
      }
    },
    [setNodes, setEdges, setIsLayouting, fitView],
  );

  const triggerLayout = useCallback(
    (options?: Partial<ElkLayoutOptions>) => {
      if (debounceRef.current) {
        clearTimeout(debounceRef.current);
      }
      debounceRef.current = setTimeout(() => {
        runLayout(options);
      }, DEBOUNCE_MS);
    },
    [runLayout],
  );

  // Run layout on initial load when there are nodes
  useEffect(() => {
    if (isFirstLayout.current && nodeCount > 0) {
      isFirstLayout.current = false;
      // Check if all nodes are at default position (0,0) — meaning they need layout
      const currentNodes = useGraphStore.getState().nodes;
      const needsLayout = currentNodes.every(
        (n) => n.position.x === 0 && n.position.y === 0,
      );
      if (needsLayout) {
        runLayout();
      }
    }
  }, [nodeCount, runLayout]);

  // Re-layout when direction or routing changes
  useEffect(() => {
    if (!isFirstLayout.current && nodeCount > 0) {
      triggerLayout();
    }
    // Only trigger on direction/routing changes, not on node count
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [layoutDirection, edgeRouting]);

  // Cleanup worker on unmount
  useEffect(() => {
    return () => {
      if (debounceRef.current) {
        clearTimeout(debounceRef.current);
      }
      terminateWorker();
    };
  }, []);

  return { triggerLayout, isLayouting };
}
