import { useEffect, useMemo, useRef, useState } from "react";
import type { FC } from "react";
import * as d3 from "d3";
import type { Goal } from "../../types/goal";
import type { Edge as EdgeType, Message } from "../../types/message";
import { ArrowMarkers } from "./components/ArrowMarkers";
import { Edge } from "./components/Edge";
import { Node } from "./components/Node";
import styles from "./styles/FlowChart.module.css";
import { Tooltip } from "@douyinfe/semi-ui";

// 整体流图组件：构建层级、渲染主链与子链（消息组）
interface FlowChartProps {
  goals: Goal[];
  msgGroups?: Record<string, Message[]>;
  onNodeClick?: (node: Goal, edge?: EdgeType) => void;
}

interface LayoutNode extends d3.HierarchyPointNode<Goal> {
  x: number;
  y: number;
}

type TreeGoal = Goal & { children?: TreeGoal[] };
const VIRTUAL_ROOT_ID = "__VIRTUAL_ROOT__";

export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeClick }) => {
  // 确保 goals 中包含 END 节点
  const displayGoals = useMemo(() => {
    if (!goals) return [];
    const hasEnd = goals.some((g) => g.id === "END");
    if (hasEnd) return goals;
    const endGoal: Goal = {
      id: "END",
      description: "终止",
      status: "completed",
      created_at: new Date().toISOString(),
      reason: "",
    };
    return [...goals, endGoal];
  }, [goals]);

  console.log("%c [ goals ]-28", "font-size:13px; background:pink; color:#bf2c9f;", displayGoals);
  const svgRef = useRef<SVGSVGElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const [dimensions, setDimensions] = useState({ width: 1200, height: 800 });
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
  const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
  const [isPanning, setIsPanning] = useState(false);
  const [zoom, setZoom] = useState(1);
  const panStartRef = useRef({ x: 0, y: 0, originX: 0, originY: 0 });
  const zoomRange = { min: 0.6, max: 2.4 };
  const clampZoom = (value: number) => Math.min(zoomRange.max, Math.max(zoomRange.min, value));
  const resetView = () => {
    setZoom(1);
    setPanOffset({ x: 0, y: 0 });
  };

  useEffect(() => {
    const element = containerRef.current;
    if (!element) return;
    const update = () => {
      const rect = element.getBoundingClientRect();
      const width = Math.max(800, rect.width || 0);
      const height = Math.max(600, rect.height || 0);
      setDimensions({ width, height });
    };
    update();
    if (typeof ResizeObserver === "undefined") {
      window.addEventListener("resize", update);
      return () => window.removeEventListener("resize", update);
    }
    const observer = new ResizeObserver(() => update());
    observer.observe(element);
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    if (!isPanning) return;
    const handleMove = (event: MouseEvent) => {
      const { x, y, originX, originY } = panStartRef.current;
      const nextX = originX + (event.clientX - x);
      const nextY = originY + (event.clientY - y);
      setPanOffset({ x: nextX, y: nextY });
    };
    const handleUp = () => {
      setIsPanning(false);
    };
    window.addEventListener("mousemove", handleMove);
    window.addEventListener("mouseup", handleUp);
    return () => {
      window.removeEventListener("mousemove", handleMove);
      window.removeEventListener("mouseup", handleUp);
    };
  }, [isPanning]);

  // 计算树布局坐标（横向）
  const layoutData = useMemo(() => {
    if (!displayGoals || displayGoals.length === 0) return null;
    const root = buildHierarchy(displayGoals);
    const margin = { top: 60, right: 140, bottom: 60, left: 140 };
    const treeLayout = d3
      .tree<Goal>()
      .size([dimensions.width - margin.left - margin.right, dimensions.height - margin.top - margin.bottom])
      .separation((a, b) => (a.parent === b.parent ? 1.2 : 1.5));
    const treeData = treeLayout(root);
    return {
      nodes: treeData.descendants() as LayoutNode[],
      links: treeData.links(),
      margin,
    };
  }, [displayGoals, dimensions]);

  // 选中节点到根的路径，用于高亮与弱化
  const pathNodeIds = useMemo(() => {
    if (!layoutData || !selectedNodeId) return new Set<string>();
    const targetNode = layoutData.nodes.find((node) => node.data.id === selectedNodeId);
    const set = new Set<string>();
    let current: LayoutNode | null | undefined = targetNode || null;
    while (current) {
      set.add(current.data.id);
      current = current.parent as LayoutNode | null;
    }
    return set;
  }, [layoutData, selectedNodeId]);

  const mainLinks = useMemo(() => {
    if (!layoutData) return [];
    const nonVirtualLinks = layoutData.links.filter(
      (link) => link.source.data.id !== VIRTUAL_ROOT_ID && link.target.data.id !== VIRTUAL_ROOT_ID,
    );
    // Remove the early return so we can merge nonVirtualLinks with manual sibling links
    // if (nonVirtualLinks.length > 0) return nonVirtualLinks;

    const nodeMap = new Map(layoutData.nodes.map((node) => [node.data.id, node]));
    const orderedNodes = displayGoals
      .map((goal) => nodeMap.get(goal.id))
      .filter((node): node is LayoutNode => Boolean(node));
    const links: d3.HierarchyPointLink<Goal>[] = [];

    // 处理主链（Sibling 顺序连接）
    for (let i = 0; i < orderedNodes.length - 1; i += 1) {
      const current = orderedNodes[i];
      const next = orderedNodes[i + 1];

      // 如果是 END 节点前的节点，且该节点有子节点（非 END 的子节点），则不直接连接到 END
      // 而是将子节点连接到 END
      if (next.data.id === "END" && current.children && current.children.length > 0) {
        // 过滤掉已经在 mainLinks 中的连接（理论上 current -> children 已经在 nonVirtualLinks 中）
        // 这里添加 children -> END 的连接
        current.children.forEach((child) => {
          // 确保 child 不是 END（虽然 END 此时是 sibling）
          if (child.data.id !== "END") {
            links.push({
              source: child as LayoutNode,
              target: next,
            });
          }
        });
        // 不添加 current -> END
        continue;
      }

      // 避免重复：如果 nonVirtualLinks 中已经存在 current -> next（即父子关系），则跳过
      const exists = nonVirtualLinks.some(
        (l) => l.source.data.id === current.data.id && l.target.data.id === next.data.id,
      );
      if (!exists) {
        links.push({
          source: current,
          target: next,
        });
      }
    }

    return [...nonVirtualLinks, ...links];
  }, [layoutData, displayGoals]);

  // 节点点击：记录选中状态并回传对应边
  const handleNodeClick = (node: LayoutNode) => {
    setSelectedNodeId(node.data.id);
    const nearestLink = mainLinks.find((link) => link.target.data.id === node.data.id);
    const edge: EdgeType | undefined = nearestLink
      ? {
          id: `${nearestLink.source.data.id}-${nearestLink.target.data.id}`,
          source: nearestLink.source.data.id,
          target: nearestLink.target.data.id,
          label: "",
        }
      : undefined;
    onNodeClick?.(node.data, edge);
  };

  // 当前选中节点的消息链
  const selectedMessages = useMemo(() => {
    if (!selectedNodeId) return [];
    return msgGroups[selectedNodeId] || [];
  }, [msgGroups, selectedNodeId]);

  // 消息链布局与虚线连接路径
  const messageOverlay = useMemo(() => {
    if (!layoutData || !selectedNodeId || selectedMessages.length === 0) return null;
    const anchorNode = layoutData.nodes.find((n) => n.data.id === selectedNodeId);
    if (!anchorNode) return null;
    const anchorX = anchorNode.x;
    const anchorY = anchorNode.y;
    const count = selectedMessages.length;
    const sides = count + 2;
    const radius = Math.max(90, 44 + count * 16 + Math.max(0, count - 4) * 6);
    const centerX = anchorX;
    const centerY = anchorY + radius;
    const angleStep = (Math.PI * 2) / sides;

    const msgNodes = selectedMessages.map((message, index) => {
      const angle = -Math.PI / 2 - angleStep * (index + 1);
      return {
        x: centerX + Math.cos(angle) * radius,
        y: centerY + Math.sin(angle) * radius,
        message,
      };
    });

    const nextGoalLink = mainLinks.find((l) => l.source.data.id === selectedNodeId);
    const nextGoalTarget = nextGoalLink?.target;

    // 节点半高度，用于计算下边界连接点
    const nodeHalfH = 5;

    const paths: { d: string; dashed?: boolean; terminal?: boolean }[] = [];
    const buildSegment = (sx: number, sy: number, tx: number, ty: number) => {
      const controlX = (sx + tx) / 2 - 40;
      const controlY = Math.max(sy, ty) - 30;
      return `M${sx},${sy} Q${controlX},${controlY} ${tx},${ty}`;
    };

    for (let i = -1; i < msgNodes.length - 1; i += 1) {
      // 起始点：如果是第一个点(i < 0)，则从 anchorNode 的下边界开始 (anchorY + nodeHalfH)
      // 否则从上一个 msgNode 开始
      const from = i < 0 ? { x: anchorX, y: anchorY + nodeHalfH } : msgNodes[i];
      const to = msgNodes[i + 1];
      const d = buildSegment(from.x, from.y, to.x, to.y);
      paths.push({ d, dashed: true, terminal: true });
    }

    if (nextGoalTarget && msgNodes.length > 0) {
      const last = msgNodes[msgNodes.length - 1];
      const tx = nextGoalTarget.x;
      // 结束点：连接到 nextGoalTarget 的下边界 (ty + nodeHalfH)
      const ty = nextGoalTarget.y + nodeHalfH;
      const controlX = (last.x + tx) / 2;
      const controlY = Math.max(last.y, ty) - 2;
      const d = `M${last.x},${last.y} Q${controlX},${controlY} ${tx},${ty}`;
      paths.push({ d, dashed: true, terminal: true });
    }

    return { msgNodes, paths };
  }, [layoutData, selectedMessages, selectedNodeId, mainLinks]);

  if (!layoutData) return <div>Loading...</div>;

  return (
    <div
      className={styles.container}
      ref={containerRef}
    >
      <svg
        ref={svgRef}
        width="100%"
        height="100%"
        viewBox={`0 0 ${dimensions.width} ${dimensions.height}`}
        preserveAspectRatio="xMidYMid meet"
        className={`${styles.svg} ${isPanning ? styles.panning : ""}`}
        onWheel={(event) => {
          event.preventDefault();
          const rect = svgRef.current?.getBoundingClientRect();
          if (!rect) return;
          const cursorX = event.clientX - rect.left;
          const cursorY = event.clientY - rect.top;
          const nextZoom = clampZoom(zoom * (event.deltaY > 0 ? 0.92 : 1.08));
          if (nextZoom === zoom) return;
          const scale = nextZoom / zoom;
          setPanOffset((prev) => ({
            x: cursorX - (cursorX - prev.x) * scale,
            y: cursorY - (cursorY - prev.y) * scale,
          }));
          setZoom(nextZoom);
        }}
        onDoubleClick={() => resetView()}
        onMouseDown={(event) => {
          if (event.button !== 0) return;
          const target = event.target as Element;
          if (target.closest(`.${styles.nodes}`) || target.closest(`.${styles.links}`)) return;
          panStartRef.current = {
            x: event.clientX,
            y: event.clientY,
            originX: panOffset.x,
            originY: panOffset.y,
          };
          setIsPanning(true);
        }}
      >
        <defs>
          <ArrowMarkers />
        </defs>
        {/* 主链内容偏移：保证上方留白且横向居中 */}
        <g transform={`translate(${panOffset.x},${panOffset.y}) scale(${zoom})`}>
          <g transform={`translate(${layoutData.margin.left},${layoutData.margin.top})`}>
            {(() => {
              const visibleNodes = layoutData.nodes.filter((n) => n.data.id !== VIRTUAL_ROOT_ID);
              const xs = visibleNodes.map((n) => n.x);
              const ys = visibleNodes.map((n) => n.y);
              const nodeHalfW = 70;
              const nodeHalfH = 26;
              const minX = Math.min(...xs) - nodeHalfW;
              const maxX = Math.max(...xs) + nodeHalfW;
              const minY = Math.min(...ys) - nodeHalfH;
              const contentW = Math.max(1, maxX - minX);
              const innerW = dimensions.width - layoutData.margin.left - layoutData.margin.right;
              const offsetX = (innerW - contentW) / 2 - minX;
              const topPadding = 24;
              const offsetY = topPadding - minY;
              return (
                <g transform={`translate(${offsetX},${offsetY})`}>
                  <g className={styles.links}>
                    {mainLinks
                      .filter(
                        (link) => link.source.data.id !== VIRTUAL_ROOT_ID && link.target.data.id !== VIRTUAL_ROOT_ID,
                      )
                      .map((link, index) => {
                        const isInPath =
                          pathNodeIds.size > 0 &&
                          pathNodeIds.has(link.source.data.id) &&
                          pathNodeIds.has(link.target.data.id);
                        const dimmed = false;
                        return (
                          <Edge
                            key={`edge-line-${index}`}
                            link={link}
                            label={link.target.data.reason}
                            highlighted={isInPath}
                            dimmed={dimmed}
                            onClick={() => handleNodeClick(link.source)}
                            mode="line"
                          />
                        );
                      })}
                  </g>
                  <g className={styles.nodes}>
                    {layoutData.nodes
                      .filter((node) => node.data.id !== VIRTUAL_ROOT_ID)
                      .map((node) => {
                        const inPath = pathNodeIds.size > 0 && pathNodeIds.has(node.data.id);
                        const dimmed = false;
                        return (
                          <Node
                            key={node.data.id}
                            node={node}
                            selected={selectedNodeId === node.data.id}
                            highlighted={inPath}
                            dimmed={dimmed}
                            onClick={() => handleNodeClick(node)}
                          />
                        );
                      })}
                  </g>
                  <g className={styles.links}>
                    {mainLinks
                      .filter(
                        (link) => link.source.data.id !== VIRTUAL_ROOT_ID && link.target.data.id !== VIRTUAL_ROOT_ID,
                      )
                      .map((link, index) => {
                        const isInPath =
                          pathNodeIds.size > 0 &&
                          pathNodeIds.has(link.source.data.id) &&
                          pathNodeIds.has(link.target.data.id);
                        const dimmed = false;
                        return (
                          <Edge
                            key={`edge-label-${index}`}
                            link={link}
                            label={link.target.data.reason}
                            highlighted={isInPath}
                            dimmed={dimmed}
                            onClick={() => handleNodeClick(link.source)}
                            mode="label"
                          />
                        );
                      })}
                  </g>
                  {messageOverlay && (
                    <g>
                      {messageOverlay.paths.map((p, idx) => (
                        <path
                          key={`msg-path-${idx}`}
                          d={p.d}
                          fill="none"
                          stroke={p.dashed ? "#7aa0d6" : "#00c853"}
                          strokeWidth={1.5}
                          strokeDasharray={p.dashed ? "6,6" : undefined}
                          markerEnd={p.terminal ? "url(#arrow-default)" : undefined}
                          opacity={0.7}
                          strokeLinecap="round"
                        />
                      ))}
                      {messageOverlay.msgNodes.map((mn, idx) =>
                        (() => {
                          const content = mn.message.content;
                          let contentStr = "";
                          if (typeof content === "string") {
                            contentStr = content;
                          } else if (content) {
                            contentStr = content.text || JSON.stringify(content);
                          }
                          const fullText = mn.message.description || contentStr || "";
                          const truncateMiddle = (text: string, limit: number) => {
                            if (text.length <= limit) return text;
                            return `${text.slice(0, 2)}...${text.slice(-2)}`;
                          };
                          const shortText = truncateMiddle(fullText, 6);
                          return (
                            <g
                              key={`msg-node-${idx}`}
                              transform={`translate(${mn.x},${mn.y})`}
                            >
                              <Tooltip content={fullText}>
                                <text
                                  x={0}
                                  y={16}
                                  fontSize={12}
                                  fill="#333"
                                  textAnchor="middle"
                                >
                                  {shortText}
                                </text>
                              </Tooltip>
                            </g>
                          );
                        })(),
                      )}
                    </g>
                  )}
                </g>
              );
            })()}
          </g>
        </g>
      </svg>
      <div className={styles.controls}>
        <button
          type="button"
          className={styles.controlButton}
          onClick={() => setZoom((prev) => clampZoom(prev * 1.1))}
        >
          +
        </button>
        <button
          type="button"
          className={styles.controlButton}
          onClick={() => setZoom((prev) => clampZoom(prev * 0.9))}
        >
          −
        </button>
        <button
          type="button"
          className={styles.controlButton}
          onClick={resetView}
        >
          复位
        </button>
      </div>
    </div>
  );
};

// 生成层级结构，支持多根节点（通过虚拟根合并）
function buildHierarchy(goals: Goal[]): d3.HierarchyNode<TreeGoal> {
  const childrenMap = new Map<string, Goal[]>();
  goals.forEach((goal) => {
    if (!goal.parent_id) return;
    const list = childrenMap.get(goal.parent_id) || [];
    list.push(goal);
    childrenMap.set(goal.parent_id, list);
  });

  const roots = goals.filter((g) => !g.parent_id);
  const primaryRoot = roots[0] || goals[0];
  if (!primaryRoot) throw new Error("No root goal found");

  function buildTree(goal: Goal): TreeGoal {
    const children = (childrenMap.get(goal.id) || []).map((child) => buildTree(child));
    return {
      ...goal,
      children: children.length > 0 ? children : undefined,
    };
  }

  if (roots.length <= 1) {
    return d3.hierarchy(buildTree(primaryRoot));
  }

  const virtualRoot: TreeGoal = {
    id: VIRTUAL_ROOT_ID,
    description: "",
    status: "completed",
    created_at: "",
    children: roots.map((goal) => buildTree(goal)),
  };
  return d3.hierarchy(virtualRoot);
}
