/**
 * 知识图谱布局纯函数（dagre 集成）。
 *
 * 从 knowledge-graph.tsx 抽离，便于单元测试。
 */

import dagre from "@dagrejs/dagre"
import type { EdgeLabel, GraphLabel, NodeLabel } from "@dagrejs/dagre"
import type { Edge, Node } from "@xyflow/react"
import type { GraphNodeData, KpWithRelations } from "./types"

export interface GraphLayoutNodeData {
  kp: KpWithRelations
  label: string
  /** 图谱节点附加数据（掌握度、视图模式等），由 knowledge-graph.tsx 注入 */
  graphData?: GraphNodeData
  // 索引签名：满足 @xyflow/react Node<GraphLayoutNodeData> 的 Record<string, unknown> 约束
  [key: string]: unknown
}

export interface GraphLayout {
  nodes: Node<GraphLayoutNodeData>[]
  edges: Edge[]
  width: number
  height: number
}

/** 节点尺寸常量 */
export const NODE_WIDTH = 180
export const NODE_HEIGHT = 80
export const RANK_SEP = 90
export const NODE_SEP = 40

/**
 * 使用 dagre 计算分层有向图布局。
 *
 * @param knowledgePoints 知识点列表（含 parentId 和 prerequisiteIds）
 * @returns React Flow 格式的 nodes/edges + 画布尺寸
 */
export function computeGraphLayout(
  knowledgePoints: KpWithRelations[],
): GraphLayout {
  if (knowledgePoints.length === 0) {
    return { nodes: [], edges: [], width: 0, height: 0 }
  }

  const g = new dagre.graphlib.Graph<GraphLabel, NodeLabel, EdgeLabel>()
  g.setGraph({ rankdir: "TB", nodesep: NODE_SEP, ranksep: RANK_SEP })
  g.setDefaultEdgeLabel(() => ({}))

  // P2 性能优化：用 Set 替代 O(n²) 的 some() 查找，降为 O(n)
  const kpIdSet = new Set(knowledgePoints.map((kp) => kp.id))

  // 添加节点
  for (const kp of knowledgePoints) {
    g.setNode(kp.id, { width: NODE_WIDTH, height: NODE_HEIGHT })
  }

  // 添加 parentId 边（树归属，实线）
  for (const kp of knowledgePoints) {
    if (kp.parentId && kpIdSet.has(kp.parentId)) {
      g.setEdge(kp.parentId, kp.id)
    }
  }

  // 添加 prerequisite 边（依赖，虚线箭头）
  for (const kp of knowledgePoints) {
    for (const prereqId of kp.prerequisiteIds) {
      if (kpIdSet.has(prereqId)) {
        g.setEdge(prereqId, kp.id)
      }
    }
  }

  dagre.layout(g)

  const nodes: Node<GraphLayoutNodeData>[] = knowledgePoints.map((kp) => {
    const dagreNode = g.node(kp.id)
    return {
      id: kp.id,
      type: "kpNode",
      position: {
        x: (dagreNode.x ?? 0) - NODE_WIDTH / 2,
        y: (dagreNode.y ?? 0) - NODE_HEIGHT / 2,
      },
      data: { kp, label: kp.name },
    }
  })

  const edges: Edge[] = []

  // parentId 边
  for (const kp of knowledgePoints) {
    if (kp.parentId && kpIdSet.has(kp.parentId)) {
      edges.push({
        id: `parent-${kp.parentId}-${kp.id}`,
        source: kp.parentId,
        target: kp.id,
        type: "default",
        className: "edge-parent",
      })
    }
  }

  // prerequisite 边
  for (const kp of knowledgePoints) {
    for (const prereqId of kp.prerequisiteIds) {
      if (kpIdSet.has(prereqId)) {
        edges.push({
          id: `prereq-${prereqId}-${kp.id}`,
          source: prereqId,
          target: kp.id,
          type: "prerequisiteEdge",
          className: "edge-prerequisite",
          animated: false,
        })
      }
    }
  }

  const graph = g.graph()
  const width = graph.width ?? 0
  const height = graph.height ?? 0

  return { nodes, edges, width, height }
}
