﻿"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import ReactFlow, { Background, Controls, MiniMap, type ReactFlowInstance } from "reactflow";
import "reactflow/dist/style.css";
import MindmapNodeCard from "@/components/MindmapNodeCard";
import { treeToGraph } from "@/lib/treeToGraph";
import type { MindmapNode } from "@/types/mindmap";

type MindmapCanvasProps = {
  tree: MindmapNode;
  className?: string;
  onNodeChat?: (nodeLabel: string) => void;
};

const NODE_WIDTH = 220;
const NODE_HEIGHT = 84;
const nodeTypes = {
  mindmapNode: MindmapNodeCard,
};

function createInitialExpandedSet(tree: MindmapNode): Set<string> {
  return new Set([tree.id]);
}

export default function MindmapCanvas({ tree, className, onNodeChat }: MindmapCanvasProps) {
  const [expandedNodeIds, setExpandedNodeIds] = useState<Set<string>>(() =>
    createInitialExpandedSet(tree),
  );
  const [reactFlowInstance, setReactFlowInstance] =
    useState<ReactFlowInstance | null>(null);
  const hasFittedInitiallyRef = useRef(false);

  useEffect(() => {
    setExpandedNodeIds(createInitialExpandedSet(tree));
    hasFittedInitiallyRef.current = false;
  }, [tree]);

  const panToNode = useCallback(
    (nodeId: string, nextExpandedNodeIds: Set<string>) => {
      if (!reactFlowInstance) {
        return;
      }

      const nextGraph = treeToGraph(tree, {
        expandedNodeIds: nextExpandedNodeIds,
      });
      const focusNode = nextGraph.nodes.find((node) => node.id === nodeId);

      if (!focusNode) {
        return;
      }

      const viewport = reactFlowInstance.getViewport();
      const revealOffset = nextExpandedNodeIds.has(nodeId)
        ? NODE_WIDTH * 0.9
        : NODE_WIDTH * 0.45;

      window.requestAnimationFrame(() => {
        reactFlowInstance.setCenter(
          focusNode.position.x + revealOffset,
          focusNode.position.y + NODE_HEIGHT / 2,
          {
            zoom: viewport.zoom,
            duration: 320,
          },
        );
      });
    },
    [reactFlowInstance, tree],
  );

  const handleToggle = useCallback(
    (nodeId: string) => {
      setExpandedNodeIds((currentExpandedNodeIds) => {
        const nextExpandedNodeIds = new Set(currentExpandedNodeIds);

        if (nextExpandedNodeIds.has(nodeId)) {
          nextExpandedNodeIds.delete(nodeId);
        } else {
          nextExpandedNodeIds.add(nodeId);
        }

        panToNode(nodeId, nextExpandedNodeIds);
        return nextExpandedNodeIds;
      });
    },
    [panToNode],
  );

  const graph = useMemo(
    () =>
      treeToGraph(tree, {
        expandedNodeIds,
        onToggleNode: handleToggle,
        onOpenNodeChat: onNodeChat,
      }),
    [expandedNodeIds, handleToggle, onNodeChat, tree],
  );

  const keepNodeInteractive = useCallback(() => {
    // React Flow only enables pointer events for nodes when at least one node handler exists.
  }, []);

  useEffect(() => {
    if (!reactFlowInstance || hasFittedInitiallyRef.current || graph.nodes.length === 0) {
      return;
    }

    const frame = window.requestAnimationFrame(() => {
      reactFlowInstance.fitView({
        padding: 0.16,
        duration: 420,
        maxZoom: 1,
      });
      hasFittedInitiallyRef.current = true;
    });

    return () => window.cancelAnimationFrame(frame);
  }, [graph.nodes, reactFlowInstance]);

  return (
    <div className={`h-full w-full overflow-hidden bg-white ${className ?? ""}`}>
      <ReactFlow
        nodes={graph.nodes}
        edges={graph.edges}
        nodeTypes={nodeTypes}
        onInit={setReactFlowInstance}
        onNodeClick={keepNodeInteractive}
        panOnDrag
        zoomOnScroll
        zoomOnPinch
        zoomOnDoubleClick={false}
        nodesDraggable={false}
        nodesConnectable={false}
        elementsSelectable={false}
        nodesFocusable={false}
        edgesFocusable={false}
        selectNodesOnDrag={false}
        minZoom={0.2}
        maxZoom={2}
        proOptions={{ hideAttribution: true }}
      >
        <MiniMap
          pannable
          zoomable
          nodeBorderRadius={12}
          maskColor="rgba(148, 163, 184, 0.12)"
        />
        <Controls showInteractive={false} />
        <Background gap={20} color="#dbeafe" />
      </ReactFlow>
    </div>
  );
}
