import { useEffect, useState, useRef, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import {
  Card,
  Select,
  Space,
  Empty,
  Spin,
  Statistic,
  Row,
  Col,
  Tag,
  Tooltip,
  message,
  theme,
  Typography,
  Divider,
  Badge,
  Flex,
} from 'antd';
import {
  ApartmentOutlined,
  TeamOutlined,
  NodeIndexOutlined,
  AimOutlined,
  InfoCircleOutlined,
  ReloadOutlined,
} from '@ant-design/icons';
import { listPluginData } from '../api/pluginData';
import {
  getPluginSchema,
  type PluginFieldSchema,
  type PluginSchemaResponse,
} from '../api/plugins';

const { Text } = Typography;

// ── Types ──

interface GraphNode {
  id: string;
  label: string;
  data: Record<string, unknown>;
}

interface GraphEdge {
  source: string;
  target: string;
  label: string;
}

interface GraphConfig {
  entity: string;
  relationshipEntity: string;
  sourceField: string;
  targetField: string;
  edgeLabelField: string;
  nodeLabelField: string;
}

interface NodePosition {
  x: number;
  y: number;
  vx: number;
  vy: number;
}

interface HoverState {
  nodeId: string | null;
  x: number;
  y: number;
}

// ── Constants ──

const RELATIONSHIP_COLORS: Record<string, { base: string; light: string; glow: string }> = {
  parent_child: { base: '#4F46E5', light: '#818CF8', glow: 'rgba(79,70,229,0.3)' },
  sibling:     { base: '#059669', light: '#34D399', glow: 'rgba(5,150,105,0.3)' },
  partner:     { base: '#D97706', light: '#FBBF24', glow: 'rgba(217,119,6,0.3)' },
  supplier:    { base: '#0891B2', light: '#22D3EE', glow: 'rgba(8,145,178,0.3)' },
  competitor:  { base: '#DC2626', light: '#F87171', glow: 'rgba(220,38,38,0.3)' },
};

const DEFAULT_REL_COLOR = { base: '#7C3AED', light: '#A78BFA', glow: 'rgba(124,58,237,0.3)' };

const REL_LABEL_MAP: Record<string, string> = {
  parent_child: '母子',
  sibling: '兄弟',
  partner: '伙伴',
  supplier: '供应商',
  competitor: '竞争',
};

const NODE_BASE_RADIUS = 18;
const NODE_CENTER_RADIUS = 26;
const NODE_HOVER_SCALE = 1.3;
const LABEL_MAX_LENGTH = 8;

// ── Helpers ──

function getRelColor(label: string) {
  return RELATIONSHIP_COLORS[label] || DEFAULT_REL_COLOR;
}

function getEdgeTypeLabel(label: string): string {
  return REL_LABEL_MAP[label] || label;
}

function truncateLabel(label: string): string {
  return label.length > LABEL_MAX_LENGTH
    ? label.slice(0, LABEL_MAX_LENGTH) + '...'
    : label;
}

/** Compute color for a node based on its connection count (degree). */
function getNodeDegree(nodeId: string, edges: GraphEdge[]): number {
  return edges.filter((e) => e.source === nodeId || e.target === nodeId).length;
}

/** Map a degree value to a node radius — more connections = larger node. */
function degreeToRadius(degree: number, isCenter: boolean): number {
  const base = isCenter ? NODE_CENTER_RADIUS : NODE_BASE_RADIUS;
  const bonus = Math.min(degree * 1.2, 10);
  return base + bonus;
}

/** Calculate circular layout positions. */
function computeCircularLayout(
  nodes: GraphNode[],
  centerX: number,
  centerY: number,
  radius: number,
): Map<string, NodePosition> {
  const positions = new Map<string, NodePosition>();
  const count = nodes.length;
  if (count === 0) return positions;

  if (count === 1) {
    positions.set(nodes[0].id, { x: centerX, y: centerY, vx: 0, vy: 0 });
    return positions;
  }

  nodes.forEach((node, i) => {
    const angle = (2 * Math.PI * i) / count - Math.PI / 2;
    positions.set(node.id, {
      x: centerX + radius * Math.cos(angle),
      y: centerY + radius * Math.sin(angle),
      vx: 0,
      vy: 0,
    });
  });

  return positions;
}

/** Draw a quadratic bezier curved edge with an arrowhead. */
function drawCurvedEdge(
  ctx: CanvasRenderingContext2D,
  fromX: number,
  fromY: number,
  toX: number,
  toY: number,
  color: string,
  lineWidth: number,
  highlighted: boolean,
  alpha: number,
) {
  const dx = toX - fromX;
  const dy = toY - fromY;
  const dist = Math.sqrt(dx * dx + dy * dy);
  if (dist < 1) return;

  // Control point offset perpendicular to the edge midpoint
  const curvature = Math.min(dist * 0.15, 30);
  const midX = (fromX + toX) / 2;
  const midY = (fromY + toY) / 2;
  const nx = -dy / dist;
  const ny = dx / dist;
  const cpX = midX + nx * curvature;
  const cpY = midY + ny * curvature;

  ctx.save();
  ctx.globalAlpha = alpha;
  ctx.strokeStyle = color;
  ctx.lineWidth = highlighted ? lineWidth + 1 : lineWidth;

  if (highlighted) {
    ctx.shadowColor = color;
    ctx.shadowBlur = 8;
  }

  ctx.beginPath();
  ctx.moveTo(fromX, fromY);
  ctx.quadraticCurveTo(cpX, cpY, toX, toY);
  ctx.stroke();

  // Arrowhead at target end
  const t = 0.95;
  const arrowT = t;
  const tangentX = 2 * (1 - arrowT) * (cpX - fromX) + 2 * arrowT * (toX - cpX);
  const tangentY = 2 * (1 - arrowT) * (cpY - fromY) + 2 * arrowT * (toY - cpY);
  const tangentLen = Math.sqrt(tangentX * tangentX + tangentY * tangentY);
  if (tangentLen < 1) {
    ctx.restore();
    return;
  }

  const arrowSize = highlighted ? 10 : 7;
  const ax = tangentX / tangentLen;
  const ay = tangentY / tangentLen;

  // Point slightly before target to avoid overlapping the node circle
  const endX = toX - ax * 4;
  const endY = toY - ay * 4;

  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.moveTo(endX, endY);
  ctx.lineTo(endX - arrowSize * ax + arrowSize * 0.4 * ay, endY - arrowSize * ay - arrowSize * 0.4 * ax);
  ctx.lineTo(endX - arrowSize * ax - arrowSize * 0.4 * ay, endY - arrowSize * ay + arrowSize * 0.4 * ax);
  ctx.closePath();
  ctx.fill();

  ctx.restore();

  // Return the midpoint for label placement
  return { labelX: cpX, labelY: cpY };
}

/** Draw a gradient-filled node circle with shadow. */
function drawNode(
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  radius: number,
  color: string,
  lightColor: string,
  glowColor: string,
  isCenter: boolean,
  isHovered: boolean,
  alpha: number,
) {
  ctx.save();
  ctx.globalAlpha = alpha;

  const r = isHovered ? radius * NODE_HOVER_SCALE : radius;

  // Outer glow / shadow
  if (isCenter || isHovered) {
    ctx.shadowColor = glowColor;
    ctx.shadowBlur = isCenter ? 20 : 14;
  } else {
    ctx.shadowColor = 'rgba(0,0,0,0.12)';
    ctx.shadowBlur = 6;
  }

  // Gradient fill
  const gradient = ctx.createRadialGradient(x - r * 0.3, y - r * 0.3, 0, x, y, r);
  if (isCenter) {
    gradient.addColorStop(0, lightColor);
    gradient.addColorStop(1, color);
  } else {
    gradient.addColorStop(0, 'rgba(255,255,255,0.9)');
    gradient.addColorStop(1, 'rgba(255,255,255,0.4)');
  }

  ctx.beginPath();
  ctx.arc(x, y, r, 0, 2 * Math.PI);
  ctx.fillStyle = gradient;
  ctx.fill();

  // Border
  ctx.shadowColor = 'transparent';
  ctx.shadowBlur = 0;
  ctx.strokeStyle = isCenter ? color : lightColor;
  ctx.lineWidth = isCenter ? 3 : 1.5;
  if (isHovered) {
    ctx.lineWidth += 1;
  }
  ctx.stroke();

  // Inner highlight ring for center node
  if (isCenter) {
    ctx.beginPath();
    ctx.arc(x, y, r + 4, 0, 2 * Math.PI);
    ctx.strokeStyle = glowColor;
    ctx.lineWidth = 2;
    ctx.setLineDash([4, 4]);
    ctx.stroke();
    ctx.setLineDash([]);
  }

  ctx.restore();
}

/** Draw the edge label with background pill. */
function drawEdgeLabel(
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  label: string,
  color: string,
  alpha: number,
) {
  ctx.save();
  ctx.globalAlpha = alpha * 0.85;

  const display = getEdgeTypeLabel(label);
  ctx.font = '11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
  const metrics = ctx.measureText(display);
  const textWidth = metrics.width;
  const padding = 6;
  const pillHeight = 18;
  const pillRadius = 9;

  // Background pill
  const pillX = x - textWidth / 2 - padding;
  const pillY = y - pillHeight / 2;

  ctx.fillStyle = color + '18';
  ctx.beginPath();
  ctx.roundRect(pillX, pillY, textWidth + padding * 2, pillHeight, pillRadius);
  ctx.fill();

  ctx.strokeStyle = color + '40';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.roundRect(pillX, pillY, textWidth + padding * 2, pillHeight, pillRadius);
  ctx.stroke();

  // Text
  ctx.fillStyle = color;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(display, x, y);

  ctx.restore();
}

/** Draw a node label below the node. */
function drawNodeLabel(
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  radius: number,
  label: string,
  textColor: string,
  isCenter: boolean,
  isHovered: boolean,
) {
  ctx.save();
  const display = truncateLabel(label);
  const r = isHovered ? radius * NODE_HOVER_SCALE : radius;
  const fontSize = isCenter ? 13 : isHovered ? 12 : 11;
  const fontWeight = isCenter ? '600' : isHovered ? '500' : '400';

  ctx.font = `${fontWeight} ${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  ctx.fillStyle = textColor;

  if (isCenter || isHovered) {
    ctx.globalAlpha = 1;
  } else {
    ctx.globalAlpha = 0.7;
  }

  ctx.fillText(display, x, y + r + 8);
  ctx.restore();
}

/**
 * 插件关系图谱页面 — 通过路由参数自加载 schema
 * 路由: /plugins/:pluginId/graph/:entityName
 */
export function PluginGraphPage() {
  const { pluginId, entityName } = useParams<{ pluginId: string; entityName: string }>();
  const { token } = theme.useToken();
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const animFrameRef = useRef<number>(0);
  const nodePositionsRef = useRef<Map<string, NodePosition>>(new Map());
  const visibleNodesRef = useRef<GraphNode[]>([]);
  const visibleEdgesRef = useRef<GraphEdge[]>([]);

  const [customers, setCustomers] = useState<GraphNode[]>([]);
  const [relationships, setRelationships] = useState<GraphEdge[]>([]);
  const [loading, setLoading] = useState(false);
  const [selectedCenter, setSelectedCenter] = useState<string | null>(null);
  const [relTypes, setRelTypes] = useState<string[]>([]);
  const [relFilter, setRelFilter] = useState<string | undefined>();
  const [graphConfig, setGraphConfig] = useState<GraphConfig | null>(null);
  const [fields, setFields] = useState<PluginFieldSchema[]>([]);
  const [hoverState, setHoverState] = useState<HoverState>({ nodeId: null, x: 0, y: 0 });
  const [canvasSize, setCanvasSize] = useState({ width: 800, height: 600 });

  // ── Computed stats ──

  const filteredRels = relFilter
    ? relationships.filter((r) => r.label === relFilter)
    : relationships;

  const visibleEdges = selectedCenter
    ? filteredRels.filter((r) => r.source === selectedCenter || r.target === selectedCenter)
    : filteredRels;

  const visibleNodeIds = new Set<string>();
  if (selectedCenter) {
    visibleNodeIds.add(selectedCenter);
    for (const e of visibleEdges) {
      visibleNodeIds.add(e.source);
      visibleNodeIds.add(e.target);
    }
  }
  const visibleNodes = selectedCenter
    ? customers.filter((n) => visibleNodeIds.has(n.id))
    : customers;

  const centerNode = customers.find((c) => c.id === selectedCenter);
  const centerDegree = selectedCenter ? getNodeDegree(selectedCenter, visibleEdges) : 0;

  // ── Schema loading ──

  useEffect(() => {
    if (!pluginId || !entityName) return;
    const abortController = new AbortController();

    async function loadSchema() {
      try {
        const schema: PluginSchemaResponse = await getPluginSchema(pluginId!);
        if (abortController.signal.aborted) return;

        const pages = schema.ui?.pages || [];
        const graphPage = pages.find(
          (p): p is typeof p & GraphConfig & { type: 'graph' } =>
            p.type === 'graph' && p.entity === entityName,
        );
        if (graphPage) {
          setGraphConfig({
            entity: graphPage.entity,
            relationshipEntity: graphPage.relationship_entity,
            sourceField: graphPage.source_field,
            targetField: graphPage.target_field,
            edgeLabelField: graphPage.edge_label_field,
            nodeLabelField: graphPage.node_label_field,
          });
        }

        const entity = schema.entities?.find((e) => e.name === entityName);
        if (entity) setFields(entity.fields);
      } catch {
        message.warning('Schema 加载失败，部分功能不可用');
      }
    }

    loadSchema();
    return () => abortController.abort();
  }, [pluginId, entityName]);

  // ── Data loading ──

  useEffect(() => {
    if (!pluginId || !graphConfig) return;
    const abortController = new AbortController();
    const gc = graphConfig;
    const labelField = fields.find((f) => f.name === gc.nodeLabelField)?.name || fields[1]?.name || 'name';

    async function loadData() {
      setLoading(true);
      try {
        let allCustomers: GraphNode[] = [];
        let page = 1;
        let hasMore = true;
        while (hasMore) {
          if (abortController.signal.aborted) return;
          const result = await listPluginData(pluginId!, gc.entity, page, 100);
          allCustomers = [
            ...allCustomers,
            ...result.data.map((r) => ({
              id: r.id,
              label: String(r.data[labelField] || '未命名'),
              data: r.data,
            })),
          ];
          hasMore = result.data.length === 100 && allCustomers.length < result.total;
          page++;
        }
        if (abortController.signal.aborted) return;
        setCustomers(allCustomers);

        let allRels: GraphEdge[] = [];
        page = 1;
        hasMore = true;
        const types = new Set<string>();
        while (hasMore) {
          if (abortController.signal.aborted) return;
          const result = await listPluginData(pluginId!, gc.relationshipEntity, page, 100);
          for (const r of result.data) {
            const relType = String(r.data[gc.edgeLabelField] || '');
            types.add(relType);
            allRels.push({
              source: String(r.data[gc.sourceField] || ''),
              target: String(r.data[gc.targetField] || ''),
              label: relType,
            });
          }
          hasMore = result.data.length === 100 && allRels.length < result.total;
          page++;
        }
        if (abortController.signal.aborted) return;
        setRelationships(allRels);
        setRelTypes(Array.from(types));
      } catch {
        message.warning('数据加载失败');
      }
      if (!abortController.signal.aborted) setLoading(false);
    }

    loadData();
    return () => abortController.abort();
  }, [pluginId, graphConfig, fields]);

  // ── Canvas resize observer ──

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width } = entry.contentRect;
        if (width > 0) {
          setCanvasSize({ width, height: Math.max(500, Math.min(700, width * 0.65)) });
        }
      }
    });

    observer.observe(container);
    return () => observer.disconnect();
  }, []);

  // ── Update refs for animation loop ──

  useEffect(() => {
    visibleNodesRef.current = visibleNodes;
    visibleEdgesRef.current = visibleEdges;
  }, [visibleNodes, visibleEdges]);

  // ── Main canvas drawing with requestAnimationFrame ──

  const drawGraph = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const nodes = visibleNodesRef.current;
    const edges = visibleEdgesRef.current;

    const width = canvasSize.width;
    const height = canvasSize.height;

    // High DPI support
    const dpr = window.devicePixelRatio || 1;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    ctx.scale(dpr, dpr);

    // Theme-aware colors
    const textColor = token.colorText;
    const bgColor = token.colorBgContainer;

    // Clear canvas
    ctx.clearRect(0, 0, width, height);

    // Background
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, width, height);

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

    // Compute layout
    const centerX = width / 2;
    const centerY = height / 2;
    const radius = Math.min(width, height) * 0.36;

    const positions = computeCircularLayout(nodes, centerX, centerY, radius);
    nodePositionsRef.current = positions;

    // Precompute degrees for node sizing
    const degreeMap = new Map<string, number>();
    for (const node of nodes) {
      degreeMap.set(node.id, getNodeDegree(node.id, edges));
    }

    // ── Draw edges first (behind nodes) ──

    for (const edge of edges) {
      const from = positions.get(edge.source);
      const to = positions.get(edge.target);
      if (!from || !to) continue;

      const colors = getRelColor(edge.label);
      const isHighlighted =
        hoverState.nodeId === edge.source || hoverState.nodeId === edge.target;
      const alpha = hoverState.nodeId ? (isHighlighted ? 1 : 0.15) : 0.7;
      const lw = isHighlighted ? 2.5 : 1.5;

      const labelPos = drawCurvedEdge(
        ctx, from.x, from.y, to.x, to.y,
        colors.base, lw, isHighlighted, alpha,
      );

      // Edge label
      if (edge.label && labelPos) {
        const labelAlpha = hoverState.nodeId ? (isHighlighted ? 1 : 0.1) : 0.9;
        drawEdgeLabel(ctx, labelPos.labelX, labelPos.labelY - 10, edge.label, colors.base, labelAlpha);
      }
    }

    // ── Draw nodes ──

    for (const node of nodes) {
      const pos = positions.get(node.id);
      if (!pos) continue;

      const isCenter = node.id === selectedCenter;
      const isHovered = node.id === hoverState.nodeId;
      const degree = degreeMap.get(node.id) || 0;
      const r = degreeToRadius(degree, isCenter);

      // Determine node color from its most common edge type, or default palette
      let nodeColorBase = '#4F46E5';
      let nodeColorLight = '#818CF8';
      let nodeColorGlow = 'rgba(79,70,229,0.3)';

      if (isCenter) {
        // Use the first connected edge color for center node
        const firstEdge = edges.find((e) => e.source === node.id || e.target === node.id);
        if (firstEdge) {
          const rc = getRelColor(firstEdge.label);
          nodeColorBase = rc.base;
          nodeColorLight = rc.light;
          nodeColorGlow = rc.glow;
        }
      } else {
        // Assign color based on index for non-center nodes
        const idx = nodes.indexOf(node);
        const palette = Object.values(RELATIONSHIP_COLORS);
        const pick = palette[idx % palette.length];
        nodeColorBase = pick.base;
        nodeColorLight = pick.light;
        nodeColorGlow = pick.glow;
      }

      const nodeAlpha = hoverState.nodeId
        ? (isHovered || (hoverState.nodeId && edges.some(
            (e) => (e.source === hoverState.nodeId && e.target === node.id) ||
                   (e.target === hoverState.nodeId && e.source === node.id),
          )) ? 1 : 0.2)
        : 1;

      drawNode(ctx, pos.x, pos.y, r, nodeColorBase, nodeColorLight, nodeColorGlow, isCenter, isHovered, nodeAlpha);
      drawNodeLabel(ctx, pos.x, pos.y, r, node.label, textColor, isCenter, isHovered);
    }

    // ── Hover tooltip ──

    if (hoverState.nodeId) {
      const hoveredNode = nodes.find((n) => n.id === hoverState.nodeId);
      if (hoveredNode) {
        const degree = degreeMap.get(hoverState.nodeId) || 0;
        const tooltipText = `${hoveredNode.label} (${degree} 条关系)`;
        ctx.save();
        ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
        const metrics = ctx.measureText(tooltipText);
        const tw = metrics.width + 16;
        const th = 28;
        const tx = hoverState.x - tw / 2;
        const ty = hoverState.y - 40;

        ctx.fillStyle = token.colorBgElevated;
        ctx.shadowColor = 'rgba(0,0,0,0.15)';
        ctx.shadowBlur = 8;
        ctx.beginPath();
        ctx.roundRect(tx, ty, tw, th, 6);
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.fillStyle = token.colorText;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(tooltipText, hoverState.x, ty + th / 2);
        ctx.restore();
      }
    }
  }, [canvasSize, selectedCenter, hoverState, token]);

  // ── Animation loop ──

  useEffect(() => {
    const animate = () => {
      drawGraph();
      animFrameRef.current = requestAnimationFrame(animate);
    };
    animFrameRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(animFrameRef.current);
  }, [drawGraph]);

  // ── Mouse interaction handlers ──

  const handleCanvasMouseMove = useCallback(
    (e: React.MouseEvent<HTMLCanvasElement>) => {
      const canvas = canvasRef.current;
      if (!canvas) return;

      const rect = canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const positions = nodePositionsRef.current;
      const nodes = visibleNodesRef.current;
      const edges = visibleEdgesRef.current;

      let foundId: string | null = null;
      for (const node of nodes) {
        const pos = positions.get(node.id);
        if (!pos) continue;
        const degree = getNodeDegree(node.id, edges);
        const r = degreeToRadius(degree, node.id === selectedCenter) * NODE_HOVER_SCALE;
        const dx = x - pos.x;
        const dy = y - pos.y;
        if (dx * dx + dy * dy < r * r) {
          foundId = node.id;
          break;
        }
      }

      canvas.style.cursor = foundId ? 'pointer' : 'default';
      setHoverState((prev) => {
        if (prev.nodeId === foundId) return prev;
        return { nodeId: foundId, x, y };
      });

      if (foundId) {
        setHoverState({ nodeId: foundId, x, y });
      }
    },
    [selectedCenter],
  );

  const handleCanvasMouseLeave = useCallback(() => {
    setHoverState({ nodeId: null, x: 0, y: 0 });
  }, []);

  const handleCanvasClick = useCallback(
    (e: React.MouseEvent<HTMLCanvasElement>) => {
      const canvas = canvasRef.current;
      if (!canvas) return;

      const rect = canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const positions = nodePositionsRef.current;
      const nodes = visibleNodesRef.current;
      const edges = visibleEdgesRef.current;

      for (const node of nodes) {
        const pos = positions.get(node.id);
        if (!pos) continue;
        const degree = getNodeDegree(node.id, edges);
        const r = degreeToRadius(degree, node.id === selectedCenter);
        const dx = x - pos.x;
        const dy = y - pos.y;
        if (dx * dx + dy * dy < r * r) {
          setSelectedCenter((prev) => (prev === node.id ? null : node.id));
          return;
        }
      }
    },
    [selectedCenter],
  );

  // ── Legend data ──

  const legendItems = relTypes.map((type) => ({
    label: getEdgeTypeLabel(type),
    rawLabel: type,
    color: getRelColor(type).base,
    count: relationships.filter((r) => r.label === type).length,
  }));

  // ── Render ──

  if (loading) {
    return (
      <div style={{ padding: 24, textAlign: 'center' }}>
        <Spin size="large" tip="加载图谱数据中..." />
      </div>
    );
  }

  return (
    <div style={{ padding: 24 }}>
      {/* Stats Row */}
      <Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
        <Col xs={24} sm={8}>
          <Card
            size="small"
            style={{ borderLeft: `3px solid ${token.colorPrimary}` }}
          >
            <Statistic
              title={
                <Text type="secondary" style={{ fontSize: 12 }}>
                  <TeamOutlined style={{ marginRight: 4 }} />
                  客户总数
                </Text>
              }
              value={customers.length}
              valueStyle={{ color: token.colorPrimary, fontWeight: 600 }}
            />
          </Card>
        </Col>
        <Col xs={24} sm={8}>
          <Card
            size="small"
            style={{ borderLeft: `3px solid ${token.colorSuccess}` }}
          >
            <Statistic
              title={
                <Text type="secondary" style={{ fontSize: 12 }}>
                  <NodeIndexOutlined style={{ marginRight: 4 }} />
                  关系总数
                </Text>
              }
              value={relationships.length}
              valueStyle={{ color: token.colorSuccess, fontWeight: 600 }}
            />
          </Card>
        </Col>
        <Col xs={24} sm={8}>
          <Card
            size="small"
            style={{ borderLeft: `3px solid ${token.colorWarning}` }}
          >
            <Statistic
              title={
                <Text type="secondary" style={{ fontSize: 12 }}>
                  <AimOutlined style={{ marginRight: 4 }} />
                  当前中心
                </Text>
              }
              value={centerNode?.label || '未选择'}
              valueStyle={{
                fontSize: 20,
                color: centerNode ? token.colorWarning : token.colorTextDisabled,
                fontWeight: 600,
              }}
            />
            {selectedCenter && (
              <Text type="secondary" style={{ fontSize: 11 }}>
                {centerDegree} 条直接关系
              </Text>
            )}
          </Card>
        </Col>
      </Row>

      {/* Main Graph Card */}
      <Card
        title={
          <Space>
            <ApartmentOutlined />
            <span>客户关系图谱</span>
            {relFilter && (
              <Tag
                color="blue"
                closable
                onClose={() => setRelFilter(undefined)}
              >
                {getEdgeTypeLabel(relFilter)}
              </Tag>
            )}
          </Space>
        }
        size="small"
        extra={
          <Space wrap>
            <Select
              placeholder="筛选关系类型"
              allowClear
              style={{ width: 150 }}
              value={relFilter}
              options={relTypes.map((t) => ({
                label: (
                  <Space>
                    <span
                      style={{
                        display: 'inline-block',
                        width: 10,
                        height: 10,
                        borderRadius: '50%',
                        backgroundColor: getRelColor(t).base,
                      }}
                    />
                    {getEdgeTypeLabel(t)}
                    <Text type="secondary" style={{ fontSize: 11 }}>
                      ({relationships.filter((r) => r.label === t).length})
                    </Text>
                  </Space>
                ),
                value: t,
              }))}
              onChange={(v) => setRelFilter(v)}
            />
            <Select
              placeholder="选择中心客户"
              allowClear
              showSearch
              style={{ width: 200 }}
              optionFilterProp="label"
              value={selectedCenter || undefined}
              options={customers.map((c) => ({
                label: c.label,
                value: c.id,
              }))}
              onChange={(v) => setSelectedCenter(v || null)}
            />
          </Space>
        }
      >
        {customers.length === 0 ? (
          <Empty
            description="暂无客户数据"
            image={Empty.PRESENTED_IMAGE_SIMPLE}
          />
        ) : (
          <div ref={containerRef} style={{ position: 'relative' }}>
            <canvas
              ref={canvasRef}
              onMouseMove={handleCanvasMouseMove}
              onMouseLeave={handleCanvasMouseLeave}
              onClick={handleCanvasClick}
              style={{
                width: '100%',
                height: canvasSize.height,
                borderRadius: 8,
                border: `1px solid ${token.colorBorderSecondary}`,
                display: 'block',
              }}
            />

            {/* Legend overlay */}
            {legendItems.length > 0 && (
              <div
                style={{
                  position: 'absolute',
                  bottom: 12,
                  left: 12,
                  background: token.colorBgElevated,
                  border: `1px solid ${token.colorBorderSecondary}`,
                  borderRadius: 8,
                  padding: '8px 12px',
                  boxShadow: token.boxShadowSecondary,
                  maxWidth: 220,
                }}
              >
                <Text
                  strong
                  style={{ fontSize: 11, color: token.colorTextSecondary, display: 'block', marginBottom: 4 }}
                >
                  关系类型图例
                </Text>
                <Flex wrap="wrap" gap={6}>
                  {legendItems.map((item) => (
                    <Tag
                      key={item.rawLabel}
                      color={item.color}
                      style={{
                        margin: 0,
                        fontSize: 11,
                        cursor: relFilter === item.rawLabel ? 'default' : 'pointer',
                        opacity: relFilter && relFilter !== item.rawLabel ? 0.4 : 1,
                      }}
                      onClick={() => {
                        setRelFilter((prev) =>
                          prev === item.rawLabel ? undefined : item.rawLabel,
                        );
                      }}
                    >
                      {item.label} ({item.count})
                    </Tag>
                  ))}
                </Flex>
              </div>
            )}

            {/* Info overlay */}
            {hoverState.nodeId && (
              <div
                style={{
                  position: 'absolute',
                  top: 12,
                  right: 12,
                  background: token.colorBgElevated,
                  border: `1px solid ${token.colorBorderSecondary}`,
                  borderRadius: 8,
                  padding: '8px 12px',
                  boxShadow: token.boxShadowSecondary,
                  maxWidth: 280,
                  transition: 'opacity 0.15s ease',
                }}
              >
                <Space direction="vertical" size={4}>
                  <Text strong>
                    {visibleNodes.find((n) => n.id === hoverState.nodeId)?.label}
                  </Text>
                  <Text type="secondary" style={{ fontSize: 11 }}>
                    <InfoCircleOutlined style={{ marginRight: 4 }} />
                    点击节点设为中心 / 再次点击取消
                  </Text>
                </Space>
              </div>
            )}
          </div>
        )}
      </Card>

      {/* Selected node detail panel */}
      {selectedCenter && centerNode && (
        <Card
          size="small"
          style={{ marginTop: 16 }}
          title={
            <Space>
              <Badge color={token.colorPrimary} />
              <Text strong>{centerNode.label}</Text>
              <Text type="secondary">— 详细信息</Text>
            </Space>
          }
          extra={
            <Tooltip title="取消选中">
              <Text
                type="secondary"
                style={{ cursor: 'pointer', fontSize: 12 }}
                onClick={() => setSelectedCenter(null)}
              >
                <ReloadOutlined style={{ marginRight: 4 }} />
                重置视图
              </Text>
            </Tooltip>
          }
        >
          <Row gutter={[16, 12]}>
            {Object.entries(centerNode.data).map(([key, value]) => {
              if (value == null || value === '') return null;
              const fieldSchema = fields.find((f) => f.name === key);
              const displayName = fieldSchema?.display_name || key;
              return (
                <Col xs={12} sm={8} md={6} key={key}>
                  <Text type="secondary" style={{ fontSize: 11, display: 'block' }}>
                    {displayName}
                  </Text>
                  <Text style={{ fontSize: 13 }}>{String(value)}</Text>
                </Col>
              );
            })}
          </Row>
          <Divider style={{ margin: '12px 0 8px' }} />
          <Text type="secondary" style={{ fontSize: 12 }}>
            直接关系: {centerDegree} 条 —
            显示 {visibleNodes.length} 个节点、{visibleEdges.length} 条边
          </Text>
        </Card>
      )}
    </div>
  );
}
