import { useEffect, useMemo, useRef, useState } from "react";
import ForceGraph2D from "react-force-graph-2d";
import { Network } from "lucide-react";
import type { Triple } from "../lib/api";
import { EmptyState } from "../ui";

// 节点按度着色：枢纽紫 / 关联青 / 叶子灰。
function nodeColor(deg: number): string {
  if (deg >= 4) return "#8b5cf6";
  if (deg >= 2) return "#22d3ee";
  return "#64748b";
}

function build(triples: Triple[]) {
  const deg = new Map<string, number>();
  for (const t of triples) {
    if (!t.s || !t.o) continue;
    deg.set(t.s, (deg.get(t.s) ?? 0) + 1);
    deg.set(t.o, (deg.get(t.o) ?? 0) + 1);
  }
  const nodes = [...deg.keys()].map((id) => ({ id, deg: deg.get(id)! }));
  const links = triples.filter((t) => t.s && t.o).map((t) => ({ source: t.s, target: t.o, label: t.p }));
  return { nodes, links };
}

// GraphView 用 react-force-graph-2d 渲染知识三元组：活物理力导向、可拖拽、滚轮缩放、悬停高亮。
// 实体=节点（按度着色/缩放），关系=带标签边；onNode 非空时点节点回调（笔记关系图用）。
export function GraphView({ triples, height = 360, onNode }: { triples: Triple[]; height?: number; onNode?: (id: string) => void }) {
  const wrapRef = useRef<HTMLDivElement>(null);
  const fgRef = useRef<any>(null);
  const [width, setWidth] = useState(600);
  const [hover, setHover] = useState<string | null>(null);

  useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => setWidth(el.clientWidth));
    ro.observe(el);
    setWidth(el.clientWidth);
    return () => ro.disconnect();
  }, []);

  const data = useMemo(() => build(triples), [triples]);

  // 加强斥力 + 拉开边距，避免节点挤成一团（d3 默认 charge=-30 对稍大的图偏弱）。
  useEffect(() => {
    const fg = fgRef.current;
    if (!fg) return;
    fg.d3Force("charge")?.strength(-180);
    fg.d3Force("link")?.distance(52);
    fg.d3ReheatSimulation?.();
  }, [data]);

  // 悬停某节点时，与之相邻的节点/边高亮，其余淡出。
  const neighbors = useMemo(() => {
    const map = new Map<string, Set<string>>();
    for (const l of data.links) {
      if (!map.has(l.source)) map.set(l.source, new Set());
      if (!map.has(l.target)) map.set(l.target, new Set());
      map.get(l.source)!.add(l.target);
      map.get(l.target)!.add(l.source);
    }
    return map;
  }, [data]);

  if (triples.length === 0) {
    return <EmptyState icon={Network} title="暂无图谱" desc="入库文本后，LLM 会抽取实体与关系；笔记互相 [[链接]] 也会连成图。" />;
  }

  const dim = (id: string) => hover !== null && hover !== id && !(neighbors.get(hover)?.has(id) ?? false);

  return (
    <div ref={wrapRef} className="overflow-hidden rounded-md border border-line bg-ink-950/60" style={{ height }}>
      <ForceGraph2D
        ref={fgRef}
        graphData={data}
        width={width}
        height={height}
        backgroundColor="rgba(0,0,0,0)"
        cooldownTicks={120}
        onEngineStop={() => fgRef.current?.zoomToFit(400, 50)}
        nodeRelSize={5}
        nodeVal={(n: any) => 1 + (n.deg || 0)}
        nodeLabel={(n: any) => `${n.id}（度 ${n.deg}）`}
        onNodeHover={(n: any) => setHover(n ? n.id : null)}
        onNodeClick={(n: any) => onNode?.(n.id)}
        linkColor={(l: any) => (hover && l.source.id !== hover && l.target.id !== hover ? "rgba(57,67,90,0.25)" : "#39435a")}
        linkDirectionalArrowLength={3}
        linkDirectionalArrowRelPos={1}
        linkLabel={(l: any) => l.label}
        nodeCanvasObjectMode={() => "replace"}
        nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, scale: number) => {
          const r = Math.max(3, 4 + (node.deg || 0) * 0.9);
          const faded = dim(node.id);
          ctx.globalAlpha = faded ? 0.2 : 1;
          ctx.beginPath();
          ctx.arc(node.x, node.y, r, 0, 2 * Math.PI);
          ctx.fillStyle = nodeColor(node.deg);
          ctx.fill();
          if (hover === node.id) {
            ctx.lineWidth = 1.5 / scale;
            ctx.strokeStyle = "#fff";
            ctx.stroke();
          }
          const fs = 10 / scale;
          ctx.font = `${fs}px sans-serif`;
          ctx.textAlign = "center";
          ctx.textBaseline = "top";
          ctx.fillStyle = faded ? "rgba(203,213,225,0.25)" : "#cbd5e1";
          const label = node.id.length > 12 ? node.id.slice(0, 12) + "…" : node.id;
          ctx.fillText(label, node.x, node.y + r + 1);
          ctx.globalAlpha = 1;
        }}
        linkCanvasObjectMode={() => "after"}
        linkCanvasObject={(link: any, ctx: CanvasRenderingContext2D, scale: number) => {
          if (scale < 1.2 || !link.label) return; // 放大到一定程度才显示关系文字，避免拥挤
          const s = link.source,
            t = link.target;
          if (!s || !t) return;
          const mx = (s.x + t.x) / 2,
            my = (s.y + t.y) / 2;
          ctx.font = `${8 / scale}px sans-serif`;
          ctx.fillStyle = "#7c8aa5";
          ctx.textAlign = "center";
          ctx.fillText(link.label, mx, my);
        }}
      />
    </div>
  );
}
