/**
 * SigmaGraph — Obsidian-style knowledge graph using Sigma.js + graphology + ForceAtlas2.
 *
 * WebGL renderer (smooth on 1k+ nodes), ForceAtlas2 layout (the algorithm Gephi & Obsidian use),
 * edges thinned by intensity, click → side panel.
 */
"use client";
import "@react-sigma/core/lib/style.css";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import { UndirectedGraph } from "graphology";
import forceAtlas2 from "graphology-layout-forceatlas2";
import {
  SigmaContainer,
  useLoadGraph,
  useRegisterEvents,
  useSigma,
  useSetSettings,
} from "@react-sigma/core";

interface RawNode {
  entity_pk: number;
  entity_class: string;
  entity_id: string;
  canonical_name: string;
  total_mentions: number;
  documents_count: number;
}
interface RawLink {
  source: number;
  target: number;
  weight: number;
}

const CLASS_COLOR: Record<string, string> = {
  person: "#ff6ec7",
  organization: "#ff8a4d",
  location: "#3fde6a",
  event: "#ffa500",
  uap_object: "#ff3344",
  vehicle: "#5b9bd5",
  operation: "#9b5de5",
  concept: "#06d6a0",
};
const CLASS_FOLDER: Record<string, string> = {
  person: "people",
  organization: "organizations",
  location: "locations",
  event: "events",
  uap_object: "uap-objects",
  vehicle: "vehicles",
  operation: "operations",
  concept: "concepts",
};
const CLASS_LABEL: Record<string, string> = {
  person: "Pessoas",
  organization: "Organizações",
  location: "Locais",
  event: "Eventos",
  uap_object: "UAP",
  vehicle: "Veículos",
  operation: "Operações",
  concept: "Conceitos",
};

const ALL_CLASSES = [
  "person", "organization", "location", "event",
  "uap_object", "vehicle", "operation", "concept",
];

function edgeColor(weight: number): string {
  if (weight >= 10) return "#00ff9c";
  if (weight >= 5) return "#7fdbff";
  if (weight >= 3) return "#a78bfa";
  return "#3a4a5e";
}

interface NodeAttrs {
  label: string;
  color: string;
  size: number;
  entity_class: string;
  entity_id: string;
  canonical_name: string;
  total_mentions: number;
  documents_count: number;
  x?: number;
  y?: number;
  hidden?: boolean;
}

interface SigmaPayload {
  nodes: RawNode[];
  links: RawLink[];
}

function GraphLoader({
  payload,
  visibleClasses,
  hoverNodeKey,
  selectedNodeKey,
  onSelect,
  onHover,
}: {
  payload: SigmaPayload;
  visibleClasses: Set<string>;
  hoverNodeKey: string | null;
  selectedNodeKey: string | null;
  onSelect: (key: string | null) => void;
  onHover: (key: string | null) => void;
}) {
  const loadGraph = useLoadGraph();
  const registerEvents = useRegisterEvents();
  const setSettings = useSetSettings();
  const sigma = useSigma();

  // (Re)load graph when payload changes
  useEffect(() => {
    const graph = new UndirectedGraph();
    const visible = payload.nodes.filter((n) => visibleClasses.has(n.entity_class));
    const allowedPks = new Set(visible.map((n) => n.entity_pk));

    // First pass: count degree from links so we can drop isolated nodes
    const degree = new Map<number, number>();
    for (const l of payload.links) {
      if (!allowedPks.has(l.source) || !allowedPks.has(l.target)) continue;
      if (l.source === l.target) continue;
      degree.set(l.source, (degree.get(l.source) ?? 0) + 1);
      degree.set(l.target, (degree.get(l.target) ?? 0) + 1);
    }

    // Drop isolates — FA2 lays them out as ugly arcs along gravity contours.
    for (const n of visible) {
      const d = degree.get(n.entity_pk) ?? 0;
      if (d === 0) continue;
      const angle = Math.random() * 2 * Math.PI;
      const r = Math.sqrt(Math.random()) * 4;
      const sizePx = Math.max(3, Math.min(22, 3 + Math.sqrt(d) * 2.2));
      graph.addNode(String(n.entity_pk), {
        label: n.canonical_name.length > 36 ? n.canonical_name.slice(0, 34) + "…" : n.canonical_name,
        color: CLASS_COLOR[n.entity_class] ?? "#7fdbff",
        size: sizePx,
        entity_class: n.entity_class,
        entity_id: n.entity_id,
        canonical_name: n.canonical_name,
        total_mentions: n.total_mentions,
        documents_count: n.documents_count,
        degree: d,
        x: Math.cos(angle) * r,
        y: Math.sin(angle) * r,
      } as NodeAttrs);
    }

    for (const l of payload.links) {
      if (!allowedPks.has(l.source) || !allowedPks.has(l.target)) continue;
      const s = String(l.source);
      const t = String(l.target);
      if (!graph.hasNode(s) || !graph.hasNode(t) || graph.hasEdge(s, t)) continue;
      graph.addEdge(s, t, {
        size: Math.max(0.6, Math.min(6, Math.log2(l.weight + 1) * 1.3)),
        color: edgeColor(l.weight),
        weight: l.weight,
      });
    }

    if (graph.order === 0) {
      loadGraph(graph);
      return;
    }

    // ForceAtlas2 — Obsidian-like clustering. Two passes: aggressive spread, then settle.
    // Pass 1: violent spread to escape the hub-collapse local minimum
    forceAtlas2.assign(graph, {
      iterations: 300,
      settings: {
        gravity: 0.05,
        scalingRatio: 80,
        slowDown: 1,
        adjustSizes: false,
        barnesHutOptimize: graph.order > 150,
        barnesHutTheta: 0.5,
        linLogMode: false,
        strongGravityMode: false,
        outboundAttractionDistribution: true,
        edgeWeightInfluence: 0.4,
      },
    });
    // Pass 2: settle, prevent overlap
    forceAtlas2.assign(graph, {
      iterations: 700,
      settings: {
        gravity: 0.08,
        scalingRatio: 55,
        slowDown: 12,
        adjustSizes: true,
        barnesHutOptimize: graph.order > 150,
        barnesHutTheta: 0.5,
        linLogMode: false,
        strongGravityMode: false,
        outboundAttractionDistribution: true,
        edgeWeightInfluence: 0.6,
      },
    });

    // Recenter graph on its centroid so animatedReset puts it at viewport center.
    let cx = 0;
    let cy = 0;
    let count = 0;
    graph.forEachNode((_, attrs) => {
      cx += (attrs as { x: number }).x;
      cy += (attrs as { x: number; y: number }).y;
      count += 1;
    });
    if (count > 0) {
      cx /= count;
      cy /= count;
      graph.forEachNode((node, attrs) => {
        graph.setNodeAttribute(node, "x", (attrs as { x: number }).x - cx);
        graph.setNodeAttribute(node, "y", (attrs as { y: number }).y - cy);
      });
    }

    loadGraph(graph);

    // Defer camera fit until Sigma indexes the new graph in normalized space.
    setTimeout(() => {
      const cam = sigma.getCamera();
      // Compute bbox of node coords in viewport (rendered) space and zoom to fit.
      let xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
      sigma.getGraph().forEachNode((nodeKey) => {
        const display = sigma.getNodeDisplayData(nodeKey);
        if (!display) return;
        xMin = Math.min(xMin, display.x);
        xMax = Math.max(xMax, display.x);
        yMin = Math.min(yMin, display.y);
        yMax = Math.max(yMax, display.y);
      });
      if (!isFinite(xMin)) {
        cam.animatedReset({ duration: 400 });
        return;
      }
      const extent = Math.max(xMax - xMin, yMax - yMin);
      // sigma camera ratio: 1 == default; the visible viewport in normalized space is ~1 unit.
      // Use extent * padding so cluster fills ~75% of the view.
      const ratio = Math.max(0.3, extent * 1.15);
      cam.animate(
        {
          x: (xMin + xMax) / 2,
          y: (yMin + yMax) / 2,
          ratio,
          angle: 0,
        },
        { duration: 700 },
      );
    }, 200);
  }, [payload, visibleClasses, loadGraph, sigma]);

  // Highlight on hover/select via dynamic settings
  useEffect(() => {
    setSettings({
      nodeReducer: (node, data) => {
        const isSelected = selectedNodeKey === node;
        const isHovered = hoverNodeKey === node;
        const graph = sigma.getGraph();
        const neighbors = selectedNodeKey
          ? new Set(graph.neighbors(selectedNodeKey))
          : null;
        const isNeighborOfSelected = neighbors?.has(node) ?? false;
        const dim = selectedNodeKey && !isSelected && !isNeighborOfSelected;
        return {
          ...data,
          highlighted: isHovered || isSelected,
          color: dim ? "#1a2434" : (data as { color?: string }).color,
          label: isSelected || isHovered || isNeighborOfSelected || (data as { degree?: number }).degree! >= 4 ? data.label : "",
          forceLabel: isSelected || isHovered,
          zIndex: isSelected ? 3 : isHovered ? 2 : 1,
        };
      },
      edgeReducer: (edge, data) => {
        if (!selectedNodeKey) return data;
        const graph = sigma.getGraph();
        const [s, t] = graph.extremities(edge);
        const touches = s === selectedNodeKey || t === selectedNodeKey;
        return {
          ...data,
          color: touches ? (data as { color?: string }).color : "#0d1421",
          size: touches ? Math.max(((data as { size?: number }).size ?? 1) * 1.4, 1.5) : (data as { size?: number }).size ?? 1,
        };
      },
      labelColor: { color: "#c8d4e6" },
      labelSize: 13,
      labelWeight: "500",
      labelFont: "system-ui, -apple-system, sans-serif",
      renderEdgeLabels: false,
      defaultEdgeColor: "#3a4a5e",
      defaultNodeColor: "#7fdbff",
      enableEdgeEvents: false,
      hideEdgesOnMove: false,
      hideLabelsOnMove: false,
      allowInvalidContainer: true,
    });
  }, [selectedNodeKey, hoverNodeKey, setSettings, sigma]);

  // Mouse events
  useEffect(() => {
    registerEvents({
      clickNode: ({ node }) => onSelect(node),
      enterNode: ({ node }) => onHover(node),
      leaveNode: () => onHover(null),
      clickStage: () => onSelect(null),
    });
  }, [registerEvents, onSelect, onHover]);

  return null;
}

interface EntityDetail {
  canonical_name: string;
  entity_class: string;
  entity_id: string;
  total_mentions: number;
  documents_count: number;
  neighbors: Array<{
    entity_pk: number;
    entity_class: string;
    entity_id: string;
    canonical_name: string;
    weight: number;
    total_mentions: number;
  }>;
}

const detailCache = new Map<string, EntityDetail>();

export function SigmaGraph() {
  const [payload, setPayload] = useState<SigmaPayload | null>(null);
  const [selectedClasses, setSelectedClasses] = useState<Set<string>>(new Set(ALL_CLASSES));
  const [loading, setLoading] = useState(true);
  const [limit, setLimit] = useState(80);
  const [minWeight, setMinWeight] = useState(1);
  const [search, setSearch] = useState("");

  const [hoverKey, setHoverKey] = useState<string | null>(null);
  const [selectedKey, setSelectedKey] = useState<string | null>(null);
  const [detail, setDetail] = useState<EntityDetail | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);

  // Load seed
  useEffect(() => {
    setLoading(true);
    const classesParam = Array.from(selectedClasses).join(",");
    fetch(`/api/graph/seed?limit=${limit}&min_weight=${minWeight}&classes=${classesParam}`)
      .then((r) => r.json())
      .then((data: { nodes?: RawNode[]; links?: RawLink[] }) => {
        setPayload({ nodes: data.nodes ?? [], links: data.links ?? [] });
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [limit, minWeight, selectedClasses]);

  // Filter by search (visible classes already done in payload load)
  const filteredPayload = useMemo<SigmaPayload | null>(() => {
    if (!payload) return null;
    if (!search.trim()) return payload;
    const s = search.toLowerCase();
    const nodes = payload.nodes.filter(
      (n) =>
        n.canonical_name.toLowerCase().includes(s) ||
        n.entity_id.toLowerCase().includes(s),
    );
    const pks = new Set(nodes.map((n) => n.entity_pk));
    const links = payload.links.filter((l) => pks.has(l.source) && pks.has(l.target));
    return { nodes, links };
  }, [payload, search]);

  // Selected node attrs (need to look up in payload)
  const selectedNode = useMemo(() => {
    if (!selectedKey || !payload) return null;
    return payload.nodes.find((n) => String(n.entity_pk) === selectedKey) ?? null;
  }, [selectedKey, payload]);

  // Load detail on select
  useEffect(() => {
    if (!selectedNode) {
      setDetail(null);
      return;
    }
    const key = `${selectedNode.entity_class}/${selectedNode.entity_id}`;
    const cached = detailCache.get(key);
    if (cached) {
      setDetail(cached);
      return;
    }
    setDetail(null);
    setDetailLoading(true);
    fetch(
      `/api/graph?op=neighbors&class=${selectedNode.entity_class}&id=${encodeURIComponent(selectedNode.entity_id)}&limit=12`,
    )
      .then((r) => r.json())
      .then((data: { entity?: RawNode; neighbors?: EntityDetail["neighbors"] }) => {
        const d: EntityDetail = {
          canonical_name: data.entity?.canonical_name ?? selectedNode.canonical_name,
          entity_class: selectedNode.entity_class,
          entity_id: selectedNode.entity_id,
          total_mentions: data.entity?.total_mentions ?? selectedNode.total_mentions,
          documents_count: data.entity?.documents_count ?? selectedNode.documents_count,
          neighbors: data.neighbors ?? [],
        };
        detailCache.set(key, d);
        setDetail(d);
      })
      .catch(() => setDetail(null))
      .finally(() => setDetailLoading(false));
  }, [selectedNode]);

  const expand = useCallback(
    async (entityClass: string, entityId: string) => {
      try {
        const r = await fetch(
          `/api/graph?op=neighbors&class=${entityClass}&id=${encodeURIComponent(entityId)}&limit=15`,
        );
        if (!r.ok) return;
        const data = (await r.json()) as { entity?: RawNode; neighbors?: Array<RawNode & { weight: number }> };
        if (!data.entity || !data.neighbors) return;
        setPayload((prev) => {
          if (!prev) return prev;
          const existing = new Set(prev.nodes.map((n) => n.entity_pk));
          const newNodes = data.neighbors!
            .filter((n) => !existing.has(n.entity_pk))
            .map((n) => ({
              entity_pk: n.entity_pk,
              entity_class: n.entity_class,
              entity_id: n.entity_id,
              canonical_name: n.canonical_name,
              total_mentions: n.total_mentions,
              documents_count: 0,
            } as RawNode));
          const edgeKey = (a: number, b: number) => `${Math.min(a, b)}-${Math.max(a, b)}`;
          const existingEdges = new Set(prev.links.map((l) => edgeKey(l.source, l.target)));
          const newLinks = data.neighbors!
            .filter((n) => !existingEdges.has(edgeKey(data.entity!.entity_pk, n.entity_pk)))
            .map((n) => ({ source: data.entity!.entity_pk, target: n.entity_pk, weight: n.weight }));
          return {
            nodes: [...prev.nodes, ...newNodes],
            links: [...prev.links, ...newLinks],
          };
        });
      } catch {
        /* ignore */
      }
    },
    [],
  );

  const toggleClass = useCallback((cls: string) => {
    setSelectedClasses((prev) => {
      const next = new Set(prev);
      if (next.has(cls)) next.delete(cls);
      else next.add(cls);
      return next.size > 0 ? next : prev;
    });
  }, []);

  return (
    <div className="relative w-full h-full bg-[#040810] overflow-hidden">
      {/* LEFT sidebar */}
      <div className="absolute top-20 left-4 z-20 w-[240px] max-h-[calc(100vh-180px)] overflow-y-auto bg-[#0a121e]/95 backdrop-blur border border-[rgba(0,255,156,0.20)] rounded p-3 space-y-4">
        <div>
          <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
            🔍 buscar nó
          </div>
          <input
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            placeholder="nome ou id..."
            className="w-full bg-transparent border border-[rgba(0,255,156,0.20)] focus:border-[#00ff9c] rounded px-2 py-1.5 font-mono text-xs text-[#c8d4e6] outline-none"
          />
        </div>

        <div>
          <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
            classes
          </div>
          <div className="space-y-1">
            {ALL_CLASSES.map((cls) => {
              const active = selectedClasses.has(cls);
              const color = CLASS_COLOR[cls] ?? "#7fdbff";
              return (
                <button
                  key={cls}
                  onClick={() => toggleClass(cls)}
                  className={`w-full flex items-center gap-2 px-2 py-1 font-mono text-[11px] rounded border transition ${
                    active ? "" : "opacity-30 hover:opacity-60"
                  }`}
                  style={{
                    color,
                    borderColor: color,
                    background: active ? `${color}12` : "transparent",
                  }}
                >
                  <span className="inline-block w-2 h-2 rounded-full" style={{ background: color }} />
                  <span className="flex-1 text-left">{CLASS_LABEL[cls]}</span>
                  {active && <span>✓</span>}
                </button>
              );
            })}
          </div>
        </div>

        <div>
          <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
            top entidades
          </div>
          <div className="flex flex-wrap gap-1">
            {[20, 40, 80, 150, 300].map((n) => (
              <button
                key={n}
                onClick={() => setLimit(n)}
                className={`px-2 py-1 font-mono text-[11px] rounded border ${
                  limit === n
                    ? "border-[#00ff9c] text-[#00ff9c] bg-[rgba(0,255,156,0.10)]"
                    : "border-[rgba(127,219,255,0.20)] text-[#8896aa] hover:text-[#7fdbff]"
                }`}
              >
                {n}
              </button>
            ))}
          </div>
        </div>

        <div>
          <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
            mostrar vínculos com ≥
          </div>
          <div className="flex flex-wrap gap-1">
            {[1, 2, 3, 5, 10].map((n) => (
              <button
                key={n}
                onClick={() => setMinWeight(n)}
                className={`px-2 py-1 font-mono text-[11px] rounded border ${
                  minWeight === n
                    ? "border-[#00ff9c] text-[#00ff9c] bg-[rgba(0,255,156,0.10)]"
                    : "border-[rgba(127,219,255,0.20)] text-[#8896aa] hover:text-[#7fdbff]"
                }`}
              >
                {n}×
              </button>
            ))}
          </div>
        </div>

        <div>
          <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
            força do vínculo
          </div>
          <div className="space-y-1 text-[10px] font-mono">
            <div className="flex items-center gap-2">
              <span className="inline-block w-6 h-0.5" style={{ background: "#00ff9c" }} />
              <span className="text-[#8896aa]">≥ 10 co-menções</span>
            </div>
            <div className="flex items-center gap-2">
              <span className="inline-block w-6 h-0.5" style={{ background: "#7fdbff" }} />
              <span className="text-[#8896aa]">5–9</span>
            </div>
            <div className="flex items-center gap-2">
              <span className="inline-block w-6 h-0.5" style={{ background: "#a78bfa" }} />
              <span className="text-[#8896aa]">3–4</span>
            </div>
          </div>
        </div>

        <div className="pt-2 border-t border-[rgba(0,255,156,0.10)] font-mono text-[10px] text-[#5a6678]">
          {loading ? "carregando…" : `${filteredPayload?.nodes.length ?? 0} nós · ${filteredPayload?.links.length ?? 0} arestas`}
          <div className="mt-1 text-[9px]">Sigma.js + ForceAtlas2</div>
        </div>
      </div>

      {/* RIGHT side panel — selected entity */}
      {selectedNode && (
        <div
          className="absolute top-20 right-4 z-20 w-[340px] max-h-[calc(100vh-180px)] overflow-y-auto bg-[#0a121e]/95 backdrop-blur border-2 rounded p-4 space-y-4"
          style={{ borderColor: CLASS_COLOR[selectedNode.entity_class] ?? "#7fdbff" }}
        >
          <div className="flex items-start justify-between gap-2">
            <div className="flex-1 min-w-0">
              <div
                className="font-mono text-[10px] uppercase tracking-widest mb-1"
                style={{ color: CLASS_COLOR[selectedNode.entity_class] ?? "#7fdbff" }}
              >
                {CLASS_LABEL[selectedNode.entity_class] ?? selectedNode.entity_class}
              </div>
              <h3 className="font-mono text-base text-[#c8d4e6] font-bold leading-tight break-words">
                {selectedNode.canonical_name}
              </h3>
              <div className="font-mono text-[10px] text-[#5a6678] mt-1 truncate">
                {selectedNode.entity_id}
              </div>
            </div>
            <button
              onClick={() => setSelectedKey(null)}
              className="text-[#5a6678] hover:text-[#ff6b6b] flex-shrink-0"
              aria-label="fechar"
            >
              ✕
            </button>
          </div>

          <div className="grid grid-cols-2 gap-2">
            <div className="px-3 py-2 bg-[#060a13] border border-[rgba(0,255,156,0.20)] rounded">
              <div className="font-mono text-[9px] uppercase text-[#5a6678]">menções</div>
              <div className="font-mono text-lg text-[#00ff9c] mt-0.5">{selectedNode.total_mentions}</div>
            </div>
            <div className="px-3 py-2 bg-[#060a13] border border-[rgba(127,219,255,0.20)] rounded">
              <div className="font-mono text-[9px] uppercase text-[#5a6678]">documentos</div>
              <div className="font-mono text-lg text-[#7fdbff] mt-0.5">{selectedNode.documents_count}</div>
            </div>
          </div>

          <div className="space-y-1.5">
            <Link
              href={`/e/${CLASS_FOLDER[selectedNode.entity_class]}/${selectedNode.entity_id}`}
              className="block w-full px-3 py-2 font-mono text-xs uppercase tracking-widest border-2 border-[#00ff9c] text-[#00ff9c] bg-[rgba(0,255,156,0.08)] hover:bg-[rgba(0,255,156,0.18)] rounded text-center"
            >
              abrir página completa →
            </Link>
            <button
              onClick={() => expand(selectedNode.entity_class, selectedNode.entity_id)}
              className="block w-full px-3 py-2 font-mono text-xs uppercase tracking-widest border border-[#7fdbff] text-[#7fdbff] hover:bg-[rgba(127,219,255,0.10)] rounded"
            >
              + expandir vizinhos
            </button>
          </div>

          <div>
            <div className="font-mono text-[10px] uppercase tracking-widest text-[#5a6678] mb-2">
              {detailLoading ? "carregando vizinhos…" : `top vínculos (${detail?.neighbors.length ?? 0})`}
            </div>
            {detail?.neighbors && detail.neighbors.length > 0 ? (
              <ul className="space-y-1">
                {detail.neighbors.map((n) => {
                  const color = CLASS_COLOR[n.entity_class] ?? "#7fdbff";
                  return (
                    <li key={n.entity_pk}>
                      <button
                        onClick={() => {
                          // Focus the neighbor — if in payload, select; else inject
                          const inPayload = payload?.nodes.some((p) => p.entity_pk === n.entity_pk);
                          if (inPayload) {
                            setSelectedKey(String(n.entity_pk));
                          } else {
                            setPayload((prev) =>
                              prev
                                ? {
                                    nodes: [
                                      ...prev.nodes,
                                      {
                                        entity_pk: n.entity_pk,
                                        entity_class: n.entity_class,
                                        entity_id: n.entity_id,
                                        canonical_name: n.canonical_name,
                                        total_mentions: n.total_mentions,
                                        documents_count: 0,
                                      },
                                    ],
                                    links: [
                                      ...prev.links,
                                      {
                                        source: selectedNode.entity_pk,
                                        target: n.entity_pk,
                                        weight: n.weight,
                                      },
                                    ],
                                  }
                                : prev,
                            );
                            setTimeout(() => setSelectedKey(String(n.entity_pk)), 200);
                          }
                        }}
                        className="group w-full flex items-center gap-2 text-left p-1.5 -mx-1.5 rounded hover:bg-[rgba(0,255,156,0.04)]"
                      >
                        <span
                          className="inline-block w-2 h-2 rounded-full flex-shrink-0"
                          style={{ background: color }}
                        />
                        <span className="text-[11px] text-[#c8d4e6] group-hover:text-[#00ff9c] flex-1 truncate">
                          {n.canonical_name}
                        </span>
                        <span className="font-mono text-[10px] text-[#5a6678] flex-shrink-0">
                          ×{n.weight}
                        </span>
                      </button>
                    </li>
                  );
                })}
              </ul>
            ) : !detailLoading ? (
              <p className="font-mono text-[10px] text-[#5a6678] italic">sem co-menções</p>
            ) : null}
          </div>
        </div>
      )}

      {/* Hover tooltip */}
      {hoverKey && payload && (() => {
        const n = payload.nodes.find((nn) => String(nn.entity_pk) === hoverKey);
        if (!n) return null;
        return (
          <div
            className="absolute top-20 right-4 z-10 pointer-events-none px-3 py-2 bg-[#0a121e] border-2 rounded text-xs font-mono"
            style={{ borderColor: CLASS_COLOR[n.entity_class] ?? "#7fdbff" }}
          >
            <div className="text-[#c8d4e6] font-bold">{n.canonical_name}</div>
            <div className="text-[10px] text-[#5a6678]">
              {CLASS_LABEL[n.entity_class]} · {n.total_mentions} menções · {n.documents_count} docs
            </div>
            <div className="text-[9px] text-[#7fdbff] mt-0.5">clique para detalhes</div>
          </div>
        );
      })()}

      {/* Sigma container */}
      <SigmaContainer
        style={{ width: "100%", height: "100%", background: "#040810" }}
        settings={{
          allowInvalidContainer: true,
          renderEdgeLabels: false,
          labelColor: { color: "#c8d4e6" },
          labelSize: 13,
          labelWeight: "500",
          defaultEdgeColor: "#3a4a5e",
          minCameraRatio: 0.05,
          maxCameraRatio: 10,
          labelDensity: 0.07,
          labelGridCellSize: 100,
          labelRenderedSizeThreshold: 6,
        }}
      >
        {filteredPayload && (
          <GraphLoader
            payload={filteredPayload}
            visibleClasses={selectedClasses}
            hoverNodeKey={hoverKey}
            selectedNodeKey={selectedKey}
            onSelect={setSelectedKey}
            onHover={setHoverKey}
          />
        )}
      </SigmaContainer>
    </div>
  );
}
