import React, { useEffect, useMemo, useRef, useState } from "react";
import apiService from "../../lib/api";
import Graph from "graphology";

export interface Node {
  id: string;
  label: string;
  category: string;
  stageid: number;
}

export interface NetworkGraphProps {
  onNodeClick?: (node: { id: string; label?: string; [key: string]: unknown }) => void;
}

// Helper to robustly parse backend response
function parseApiResponse(raw: any): any[] {
  let data = raw;
  try {
    if (typeof data === "string") data = JSON.parse(data);
    if (typeof data === "string") data = JSON.parse(data);
  } catch {}
  return Array.isArray(data) ? data : [];
}

export function NetworkGraph({ onNodeClick}: NetworkGraphProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const sigmaRef = useRef<any>(null);
  const graphRef = useRef<any>(null);
  const [nodes, setNodes] = useState<Node[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [filterCategory, setFilterCategory] = useState<string>("all");
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);

  // ------------- Fetch and robust parse ----------------
  useEffect(() => {
    let aborted = false;
    const controller = new AbortController();

    (async () => {
      setIsLoading(true);
      try {
        const res = await apiService.callInnovationProcess<any[]>(
          { graph_production_function: {} }
        );
        if (aborted) return;
        // Use robust parser for backend response
        const data = parseApiResponse(JSON.parse(res.data)?.graph_production)
        setNodes(
          data.map((item: any) => ({
            id: String(item.stageid),
            label: item.title,
            category: item.category,
            stageid: item.stageid,
          }))
        );
        console.log('Fetched nodes:', data);
      } catch (err: any) {
        if (err.name === "AbortError") {
          // ignore
        } else {
          console.error("Failed to fetch graph data:", err);
          setNodes([]);
        }
      } finally {
        if (!aborted) setIsLoading(false);
      }
    })();

    return () => {
      aborted = true;
      controller.abort();
    };
  }, []);

  // compute unique categories
  const categories = useMemo(() => {
    const set = new Set<string>();
    nodes.forEach((n) => set.add(n.category));
    return ["all", ...Array.from(set)];
  }, [nodes]);

  // ------------- Build graph + Sigma (client-only) ----------------
  useEffect(() => {
    // don't run on server or before container available or while loading
    if (typeof window === "undefined" || !containerRef.current || isLoading) return;

    let renderer: any = null;
    let Sigma: any = null;
    let isCancelled = false;

    (async () => {
      try {
        // dynamic import for sigma only
        const sigmaModule = await import("sigma");
        Sigma = sigmaModule.default || sigmaModule.Sigma || sigmaModule;

        if (isCancelled || !containerRef.current) return;

        const graph = new Graph();
        graphRef.current = graph;

        // color map (you can extend)
        const categoryToColor: Record<string, string> = {
          دانشگاه: "#3B82F6",
          مشاور: "#10B981",
          "دانش بنیان": "#F59E0B",
          استارتاپ: "#EF4444",
          شرکت: "#8B5CF6",
          صندوق: "#06B6D4",
          شتابدهنده: "#9333EA",
          "مرکز نوآوری": "#F472B6",
          center: "#000000",
        };

        // add central node
        const CENTER_ID = "center";
        graph.addNode(CENTER_ID, {
          label: "مرکز نوآوری اصلی",
          x: 0,
          y: 0,
          size: 20,
          category: "center",
          color: categoryToColor.center,
        });

        // add all nodes
        nodes.forEach((node, i) => {
          // Place nodes in a circle, but all nodes are always present
          const len = Math.max(1, nodes.length);
          const radius = Math.max(5, Math.min(20, Math.ceil(len / 2)));
          const angleStep = (2 * Math.PI) / len;
          const angle = i * angleStep;
          const jitter = (Math.random() - 0.5) * 0.4;
          const x = Math.cos(angle) * (radius + jitter);
          const y = Math.sin(angle) * (radius + jitter);
          graph.addNode(node.id, {
            label: node.label,
            x,
            y,
            size: 8,
            category: node.category,
            color: categoryToColor[node.category] || "#94A3B8",
            payload: node,
          });
          graph.addEdge(CENTER_ID, node.id, { size: 1, color: "#CBD5E1" });
        });

        // Highlight nodes by filter
        const highlightByCategory = (category: string) => {
          graph.forEachNode((n: string, attrs: any) => {
            if (category === "all" || attrs.category === category) {
              graph.setNodeAttribute(n, "color", categoryToColor[attrs.category] || "#94A3B8");
              graph.setNodeAttribute(n, "size", attrs.category === "center" ? 20 : 12);
              graph.setNodeAttribute(n, "zIndex", 1);
              graph.setNodeAttribute(n, "opacity", 1);
            } else {
              graph.setNodeAttribute(n, "color", "#888888");
              graph.setNodeAttribute(n, "size", 7);
              graph.setNodeAttribute(n, "zIndex", 0);
              graph.setNodeAttribute(n, "opacity", 0.3);
            }
          });
        };
        highlightByCategory(filterCategory);

        // Listen for filterCategory changes to re-apply highlight
        // (This is needed if filterCategory changes after initial render)
        const filterListener = () => highlightByCategory(filterCategory);
        // Optionally, you could use a custom event or observer if needed
        // For now, we rely on the effect re-running due to filterCategory in deps

        // create renderer
        renderer = new Sigma(graph, containerRef.current, {
          renderLabels: true,
          defaultNodeColor: "#94A3B8",
          defaultEdgeColor: "#CBD5E1",
          labelColor: { color: "#fff" }, // Set label color to white
        });

        sigmaRef.current = renderer;

        // Helper: set highlight states by mutating node attributes
        const setHighlight = (nodeId: string | null) => {
          graph.forEachNode((n: string, attrs: any) => {
            if (nodeId && (n === nodeId || graph.hasEdge(n, nodeId) || graph.hasEdge(nodeId, n))) {
              graph.setNodeAttribute(n, "size", attrs.size ? Math.min(24, attrs.size * 1.6) : 12);
              graph.setNodeAttribute(n, "color", attrs.color ? attrs.color : "#fff");
              graph.setNodeAttribute(n, "highlighted", true);
            } else {
              graph.setNodeAttribute(n, "size", attrs.size && attrs.category === "center" ? 20 : 8);
              // restore original color if we stored it; otherwise keep
              graph.setNodeAttribute(n, "color", attrs.category === "center" ? categoryToColor.center : attrs.color);
              graph.setNodeAttribute(n, "highlighted", false);
            }
          });
          // ask renderer to refresh (sigma v2 triggers update automatically when graph changes)
        };

        // events: hover highlight and click select
        const onEnter = (e: any) => {
          const nodeId = e.node;
          setHighlight(nodeId);
        };
        const onLeave = () => {
          setHighlight(selectedNodeId); // keep selected highlighted, or none
        };
        const onClick = (e: any) => {
          const nodeId = e.node as string;
          setSelectedNodeId((prev) => (prev === nodeId ? null : nodeId));
          // call external callback with payload if exists
          const attrs = graph.getNodeAttributes(nodeId);
          onNodeClick?.({ id: nodeId, label: attrs?.label, ...(attrs?.payload ?? {}) });
        };

        renderer.on("enterNode", onEnter);
        renderer.on("leaveNode", onLeave);
        renderer.on("clickNode", onClick);

        // if there is a pre-selected node (state), reflect it
        if (selectedNodeId) setHighlight(selectedNodeId);

        // cleanup on re-run
        return () => {
          try {
            renderer.removeListener("enterNode", onEnter);
            renderer.removeListener("leaveNode", onLeave);
            renderer.removeListener("clickNode", onClick);
          } catch {}
        };
      } catch (err) {
        console.error("Failed to initialize graph / sigma:", err);
      }
    })();

    return () => {
      isCancelled = true;
      // kill previous renderer & graph
      if (sigmaRef.current) {
        try {
          sigmaRef.current.kill?.();
        } catch {}
      }
      sigmaRef.current = null;
      graphRef.current = null;
      if (renderer) {
        try {
          renderer.kill?.();
        } catch {}
      }
      renderer = null;
    };
    // rebuild whenever nodes, filterCategory or selectedNodeId changes
  }, [nodes, filterCategory, isLoading, onNodeClick, selectedNodeId]);

  return (
    <div className="relative w-full h-full flex flex-col">
      <div className="p-2 flex items-center gap-2">
        <label className="text-sm">فیلتر:</label>
        <select
          value={filterCategory}
          onChange={(e) => setFilterCategory(e.target.value)}
          className="px-2 py-1 border rounded bg-white text-gray-900 dark:bg-gray-800 dark:text-white dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-primary"
        >
          {categories.map((c) => (
            <option key={c} value={c}>
              {c === "all" ? "همه" : c}
            </option>
          ))}
        </select>
        <div className="ml-4 text-sm text-gray-600">
          {isLoading ? "در حال بارگذاری..." : `نمایش ${filterCategory === "all" ? nodes.length : nodes.filter(n => n.category === filterCategory).length} گره`}
        </div>
      </div>

      <div ref={containerRef} className="flex-1 relative" style={{ minHeight: 360 }} />

      {/* overlay selected info */}
      <div className="p-2">
        {selectedNodeId ? (
          <>
            <div className="text-sm">انتخاب شده: {selectedNodeId}</div>
            <button
              onClick={() => setSelectedNodeId(null)}
              className="mt-1 px-2 py-1 text-sm border rounded"
            >
              پاک کردن انتخاب
            </button>
          </>
        ) : null}
      </div>
    </div>
  );
}

export default NetworkGraph;
