<template>
  <div class="kg-wrapper">
    <div
      class="knowledge-graph"
      ref="containerRef"
      :style="{ width: svgWidth + 'px', height: svgHeight + 'px' }"
    >
      <svg class="graph-svg" :width="svgWidth" :height="svgHeight">
      <!-- 连线 -->
      <path
        v-for="(edge, i) in layoutEdges"
        :key="'e' + i"
        :d="edge.path"
        class="graph-edge"
        :class="{ highlighted: highlightedNode && (edge.source === highlightedNode || edge.target === highlightedNode) }"
      />
      <!-- 连线标签 -->
      <text
        v-for="(edge, i) in layoutEdges"
        :key="'el' + i"
        :x="edge.labelX"
        :y="edge.labelY"
        class="edge-label"
        :class="{ highlighted: highlightedNode && (edge.source === highlightedNode || edge.target === highlightedNode) }"
      >{{ edge.label }}</text>
    </svg>

    <!-- 结点 -->
    <div
      v-for="node in layoutNodes"
      :key="node.id"
      class="graph-node"
      :class="{ highlighted: highlightedNode === node.id }"
      :style="{
        left: node.x + 'px',
        top: node.y + 'px',
        '--node-hue': node.hue
      }"
      @mouseenter="highlightedNode = node.id"
      @mouseleave="highlightedNode = null"
    >
      <div class="node-label">{{ node.label }}</div>
      <div class="node-desc">{{ node.desc }}</div>
    </div>
  </div>
</div>
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue'

const props = defineProps({
  nodes: { type: Array, default: () => [] },
  edges: { type: Array, default: () => [] }
})

const containerRef = ref(null)
const highlightedNode = ref(null)

// ---- 布局计算 ----
const NODE_W = 160
const NODE_H = 56
const LEVEL_GAP = 160
const SIBLING_GAP = 24
const PADDING = 20

const layoutNodes = computed(() => {
  if (!props.nodes.length) return []

  // 建立邻接表
  const children = {}
  const parent = {}
  const indeg = {}
  for (const n of props.nodes) { indeg[n.id] = 0; children[n.id] = [] }
  for (const e of props.edges) {
    if (children[e.source]) children[e.source].push(e.target)
    parent[e.target] = e.source
    indeg[e.target] = (indeg[e.target] || 0) + 1
  }

  // 找根结点（入度为0）
  const roots = props.nodes.filter(n => (indeg[n.id] || 0) === 0).map(n => n.id)
  const rootId = roots[0] || props.nodes[0].id

  // 计算层级（BFS）
  const level = {}
  const queue = [rootId]
  level[rootId] = 0
  let maxLevel = 0
  const seen = new Set([rootId])
  while (queue.length) {
    const cur = queue.shift()
    for (const ch of children[cur] || []) {
      if (!seen.has(ch)) {
        seen.add(ch)
        level[ch] = level[cur] + 1
        if (level[ch] > maxLevel) maxLevel = level[ch]
        queue.push(ch)
      }
    }
  }

  // 按层级分组
  const byLevel = {}
  for (const n of props.nodes) {
    const lv = level[n.id] !== undefined ? level[n.id] : maxLevel
    if (!byLevel[lv]) byLevel[lv] = []
    byLevel[lv].push(n.id)
  }

  // 计算每个结点 X 位置（同层均匀分布）
  const xPos = {}
  for (const lv of Object.keys(byLevel).map(Number).sort((a, b) => a - b)) {
    const ids = byLevel[lv]
    const totalW = ids.length * NODE_W + (ids.length - 1) * SIBLING_GAP
    ids.forEach((id, i) => {
      xPos[id] = PADDING + i * (NODE_W + SIBLING_GAP) + NODE_W / 2
    })
  }

  // 计算 Y 位置
  const yPos = {}
  for (const n of props.nodes) {
    const lv = level[n.id] !== undefined ? level[n.id] : maxLevel
    yPos[n.id] = PADDING + lv * (NODE_H + LEVEL_GAP) + NODE_H / 2
  }

  // 分配颜色
  const hues = [210, 260, 180, 30, 330, 160]
  const nodeColors = {}
  for (const n of props.nodes) {
    const lv = level[n.id] !== undefined ? level[n.id] : 0
    nodeColors[n.id] = hues[lv % hues.length]
  }

  return props.nodes.map(n => ({
    ...n,
    x: xPos[n.id] - NODE_W / 2,
    y: yPos[n.id] - NODE_H / 2,
    cx: xPos[n.id],
    cy: yPos[n.id],
    hue: nodeColors[n.id]
  }))
})

const layoutEdges = computed(() => {
  if (!props.edges.length || !layoutNodes.value.length) return []
  const nodeMap = {}
  for (const n of layoutNodes.value) nodeMap[n.id] = n

  return props.edges.map(e => {
    const src = nodeMap[e.source]
    const tgt = nodeMap[e.target]
    if (!src || !tgt) return null

    const x1 = src.cx, y1 = src.cy + NODE_H / 2
    const x2 = tgt.cx, y2 = tgt.cy - NODE_H / 2
    const cy = (y1 + y2) / 2

    // 三次贝塞尔曲线
    const path = `M ${x1} ${y1} C ${x1} ${cy}, ${x2} ${cy}, ${x2} ${y2}`

    // 标签位置（曲线中点偏左）
    const labelX = (x1 + x2) / 2
    const labelY = (y1 + y2) / 2 - 6

    return { source: e.source, target: e.target, label: e.label, path, labelX, labelY }
  }).filter(Boolean)
})

// SVG 尺寸
const svgWidth = computed(() => {
  if (!layoutNodes.value.length) return 400
  const maxX = Math.max(...layoutNodes.value.map(n => n.x + NODE_W))
  return maxX + PADDING
})

const svgHeight = computed(() => {
  if (!layoutNodes.value.length) return 300
  const maxY = Math.max(...layoutNodes.value.map(n => n.y + NODE_H))
  return maxY + PADDING
})
</script>

<style scoped>
.kg-wrapper {
  overflow-x: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border-color);
  border-radius: 16px;
}

.knowledge-graph {
  position: relative;
  background:
    radial-gradient(circle at 20% 30%, rgba(59, 130, 246, 0.03), transparent 60%),
    radial-gradient(circle at 80% 70%, rgba(139, 92, 246, 0.03), transparent 60%);
  overflow: visible;
}

.graph-svg {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  overflow: visible;
}

.graph-edge {
  fill: none;
  stroke: var(--border-color);
  stroke-width: 1.5;
  stroke-linecap: round;
  transition: stroke 0.3s, stroke-width 0.3s;
}

.graph-edge.highlighted {
  stroke: var(--primary-color);
  stroke-width: 2.5;
  filter: drop-shadow(0 0 4px rgba(59, 130, 246, 0.3));
}

.edge-label {
  fill: var(--text-tertiary);
  font-size: 11px;
  text-anchor: middle;
  font-family: var(--sans);
  transition: fill 0.3s;
}

.edge-label.highlighted {
  fill: var(--primary-color);
  font-weight: 600;
}

/* ---- 结点 ---- */
.graph-node {
  position: absolute;
  width: 160px;
  min-height: 48px;
  background: var(--card-bg);
  border: 1.5px solid var(--border-color);
  border-radius: 12px;
  padding: 8px 12px;
  cursor: default;
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.04);
  z-index: 1;
}

.graph-node::before {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 14px;
  background: linear-gradient(135deg,
    hsla(var(--node-hue, 210), 70%, 55%, 0.2),
    hsla(calc(var(--node-hue, 210) + 30), 70%, 55%, 0.05));
  opacity: 0;
  transition: opacity 0.3s;
  z-index: -1;
}

.graph-node:hover,
.graph-node.highlighted {
  transform: translateY(-3px) scale(1.03);
  border-color: hsl(var(--node-hue, 210), 70%, 55%);
  box-shadow:
    0 8px 24px rgba(0,0,0,0.1),
    0 0 0 1px hsla(var(--node-hue, 210), 70%, 55%, 0.15);
}

.graph-node:hover::before,
.graph-node.highlighted::before {
  opacity: 1;
}

.node-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.3;
  white-space: nowrap;
}

.node-desc {
  font-size: 11px;
  color: var(--text-tertiary);
  margin-top: 2px;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
  .graph-node {
    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  }
  .graph-node:hover,
  .graph-node.highlighted {
    box-shadow:
      0 8px 24px rgba(0,0,0,0.3),
      0 0 0 1px hsla(var(--node-hue, 210), 70%, 55%, 0.15);
  }
}

/* 响应式 */
@media (max-width: 768px) {
  .graph-node {
    width: 120px;
    min-height: 40px;
    padding: 6px 8px;
  }
  .node-label {
    font-size: 11px;
  }
  .node-desc {
    display: none;
  }
}
</style>
