<template>
  <div class="flex flex-col h-full">
    <!-- 头部 -->
    <div class="flex items-center justify-between px-4 py-2 bg-base-300 text-xs text-base-content/60">
      <span>帖子树</span>
      <div class="flex items-center gap-2">
        <span v-if="store.highlightedNodeIds.size > 0" class="text-primary">
          已高亮 {{ store.highlightedNodeIds.size }} 个节点
        </span>
        <template v-if="showExpand">
          <button
            v-if="store.expandedPanel !== 'post-tree'"
            @click="store.expandPanel('post-tree')"
            class="btn btn-ghost btn-xs"
            title="放大"
          >⤢</button>
          <button
            v-if="store.expandedPanel !== 'default'"
            @click="store.resetLayout()"
            class="btn btn-ghost btn-xs"
            title="恢复"
          >⊡</button>
        </template>
      </div>
    </div>

    <!-- 帖子选择下拉框 -->
    <div class="px-4 py-2 bg-base-200 border-b border-base-300">
      <select
        v-model="selectedPostIdx"
        @change="onPostChange"
        class="select select-xs select-bordered w-full"
      >
        <option v-if="store.postList.length === 0" :value="-1">暂无帖子数据</option>
        <option
          v-for="post in store.postList"
          :key="post.index"
          :value="post.index"
        >
          {{ formatPostOption(post) }}
        </option>
      </select>
    </div>

    <!-- SVG 容器 -->
    <div ref="containerRef" class="flex-1 overflow-hidden bg-base-100">
      <svg ref="svgRef" class="w-full h-full" @click="handleSvgClick"></svg>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import * as d3 from 'd3'
import { useGraphStore } from '../stores/graph'
import { getNodeStyle } from '../config/nodeStyle'
import { getEdgeStyle } from '../config/edgeStyle'
import { applyHighlight } from '../utils/highlight'

const props = defineProps({
  showExpand: {
    type: Boolean,
    default: false
  }
})

const store = useGraphStore()

const containerRef = ref(null)
const svgRef = ref(null)

// 当前选中的帖子索引
const selectedPostIdx = ref(store.selectedPostIndex)

// zoom 实例和主 g 元素
let zoom = null
let mainG = null
let treeWidth = 0
let treeHeight = 0

// 帖子选择变化
function onPostChange() {
  store.selectPost(selectedPostIdx.value)
}

// 格式化帖子选项显示
function formatPostOption(post) {
  const date = post.createTime ? new Date(post.createTime * 1000).toLocaleDateString() : ''
  const title = post.postTitle || post.postId
  const shortTitle = title.length > 20 ? title.slice(0, 20) + '...' : title
  return date ? `${date} ${shortTitle}` : shortTitle
}

// 节点元素映射（统一存储所有节点位置）
let nodeElements = {}
let baseNodeElements = {}  // 基础节点（帖子树+匹配层），不含游走节点
let currentRoot = null

// 处理节点点击
function handleNodeClick(event, d) {
  event.stopPropagation()
  store.selectNode(d)
}

// 渲染树
function renderTree() {
  const svg = d3.select(svgRef.value)
  svg.selectAll('*').remove()
  nodeElements = {}

  const treeData = store.postTreeData
  if (!treeData || !treeData.id) return

  const container = containerRef.value
  if (!container) return

  const width = container.clientWidth
  const height = container.clientHeight

  svg.attr('viewBox', `0 0 ${width} ${height}`)

  // 创建层级数据
  const root = d3.hierarchy(treeData)
  currentRoot = root

  // 智能计算树的尺寸（垂直布局）
  const allNodes = root.descendants()
  const maxDepth = d3.max(allNodes, d => d.depth)
  const leafCount = allNodes.filter(d => !d.children).length

  // 计算最长文字长度（用于动态调整间距）
  const maxTextLen = d3.max(allNodes, d => {
    const name = d.data.name || ''
    return Math.min(name.length, 10) // 最多显示10个字
  }) || 6

  // 动态计算节点间距（根据文字长度）
  const nodeSpacing = Math.max(60, maxTextLen * 12)

  // 宽度：基于叶子节点数量和文字间距
  treeWidth = Math.max(400, leafCount * nodeSpacing + 100)
  // 高度：根据深度，增大层间距避免垂直重叠
  treeHeight = Math.max(400, (maxDepth + 1) * 120 + 50)

  // 创建缩放行为
  zoom = d3.zoom()
    .scaleExtent([0.1, 3])
    .on('zoom', (e) => {
      mainG.attr('transform', e.transform)
    })

  svg.call(zoom)

  // 创建主组
  mainG = svg.append('g')

  // 创建树布局（垂直方向：从上到下）
  const treeLayout = d3.tree()
    .size([treeWidth - 100, treeHeight - 50])
    .separation((a, b) => {
      // 同级节点间距根据是否有子节点调整
      if (a.parent === b.parent) {
        // 叶子节点需要更大间距放文字
        const aIsLeaf = !a.children
        const bIsLeaf = !b.children
        if (aIsLeaf || bIsLeaf) return 1.5
        return 1
      }
      return 2
    })

  treeLayout(root)

  // 内容组（带偏移）
  const contentG = mainG.append('g')
    .attr('transform', 'translate(50, 25)')

  // 绘制边（垂直方向）
  contentG.append('g')
    .attr('class', 'tree-edges')
    .selectAll('.tree-link')
    .data(root.links())
    .join('path')
    .attr('class', 'tree-link')
    .attr('fill', 'none')
    .attr('stroke', '#3498db')
    .attr('stroke-opacity', 0.3)
    .attr('stroke-width', 1)
    .attr('d', d => {
      const midY = (d.source.y + d.target.y) / 2
      return `M${d.source.x},${d.source.y} C${d.source.x},${midY} ${d.target.x},${midY} ${d.target.x},${d.target.y}`
    })

  // 绘制节点（垂直布局：x 是水平位置，y 是垂直位置）
  const nodes = contentG.append('g')
    .attr('class', 'tree-nodes')
    .selectAll('.tree-node')
    .data(root.descendants())
    .join('g')
    .attr('class', 'tree-node')
    .attr('transform', d => `translate(${d.x},${d.y})`)
    .style('cursor', 'pointer')
    .on('click', handleNodeClick)

  // 节点形状（使用统一配置）
  nodes.each(function(d) {
    const el = d3.select(this)
    const style = getNodeStyle(d)

    if (style.shape === 'rect') {
      el.append('rect')
        .attr('class', 'tree-shape')
        .attr('x', -4)
        .attr('y', -4)
        .attr('width', 8)
        .attr('height', 8)
        .attr('rx', 1)
        .attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)')
        .attr('stroke-width', 1)
    } else {
      el.append('circle')
        .attr('class', 'tree-shape')
        .attr('r', style.size / 2)
        .attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)')
        .attr('stroke-width', 1)
    }

    nodeElements[d.data.id] = { element: this, x: d.x + 50, y: d.y + 25 }
  })

  // 节点标签（使用统一配置）
  nodes.append('text')
    .attr('dy', d => d.children ? -10 : 4)
    .attr('dx', d => d.children ? 0 : 10)
    .attr('text-anchor', d => d.children ? 'middle' : 'start')
    .attr('fill', d => getNodeStyle(d).text.fill)
    .attr('font-size', d => getNodeStyle(d).text.fontSize)
    .attr('font-weight', d => getNodeStyle(d).text.fontWeight)
    .text(d => {
      const name = d.data.name
      const maxLen = 10
      return name.length > maxLen ? name.slice(0, maxLen) + '…' : name
    })

  // ========== 绘制匹配层 ==========
  renderMatchLayer(contentG, root, treeHeight)

  // 初始适应视图
  fitToView()
}

// 绘制匹配层（人设节点 + 连线）
function renderMatchLayer(contentG, root, baseTreeHeight) {
  const postGraph = store.currentPostGraph
  if (!postGraph || !postGraph.edges) return

  // 提取匹配边（只取帖子->人设方向的）
  const matchEdges = []
  for (const edge of Object.values(postGraph.edges)) {
    if (edge.type === '匹配' && edge.source.startsWith('帖子:') && edge.target.startsWith('人设:')) {
      matchEdges.push(edge)
    }
  }

  if (matchEdges.length === 0) return

  // 收集匹配的人设节点（去重）
  const matchedPersonaMap = new Map()
  for (const edge of matchEdges) {
    if (!matchedPersonaMap.has(edge.target)) {
      // 从人设节点ID提取信息: "人设:目的点:标签:进行产品种草"
      const parts = edge.target.split(':')
      const name = parts[parts.length - 1]
      const dimension = parts[1] // 灵感点/目的点/关键点
      const type = parts[2] // 标签/分类/点
      matchedPersonaMap.set(edge.target, {
        id: edge.target,
        name: name,
        dimension: dimension,
        type: type,
        sourceEdges: [] // 连接的帖子节点
      })
    }
    matchedPersonaMap.get(edge.target).sourceEdges.push({
      sourceId: edge.source,
      score: edge.score
    })
  }

  const matchedPersonas = Array.from(matchedPersonaMap.values())
  if (matchedPersonas.length === 0) return

  // 计算匹配层的 Y 位置（树的最大深度 + 间距）
  const maxY = d3.max(root.descendants(), d => d.y) || 0
  const matchLayerY = maxY + 100

  // 计算匹配节点的 X 位置（均匀分布）
  const minX = d3.min(root.descendants(), d => d.x) || 0
  const maxX = d3.max(root.descendants(), d => d.x) || 0
  const matchSpacing = (maxX - minX) / Math.max(matchedPersonas.length - 1, 1)

  matchedPersonas.forEach((persona, i) => {
    persona.x = matchedPersonas.length === 1
      ? (minX + maxX) / 2
      : minX + i * matchSpacing
    persona.y = matchLayerY
  })

  // 收集匹配边数据（统一数据结构：source, target）
  const matchLinksData = []
  for (const persona of matchedPersonas) {
    for (const srcEdge of persona.sourceEdges) {
      const sourceNode = nodeElements[srcEdge.sourceId]
      if (!sourceNode) continue
      matchLinksData.push({
        source: srcEdge.sourceId,
        target: persona.id,
        score: srcEdge.score,
        srcX: sourceNode.x - 50,
        srcY: sourceNode.y - 25,
        tgtX: persona.x,
        tgtY: persona.y
      })
    }
  }

  // 绘制匹配连线（使用 data binding）
  const matchLinksG = contentG.append('g').attr('class', 'match-links')

  matchLinksG.selectAll('.match-link')
    .data(matchLinksData)
    .join('path')
    .attr('class', 'match-link')
    .attr('fill', 'none')
    .attr('stroke', d => getEdgeStyle({ type: '匹配', score: d.score }).color)
    .attr('stroke-opacity', d => getEdgeStyle({ type: '匹配', score: d.score }).opacity)
    .attr('stroke-width', d => getEdgeStyle({ type: '匹配', score: d.score }).strokeWidth)
    .attr('stroke-dasharray', d => getEdgeStyle({ type: '匹配', score: d.score }).strokeDasharray)
    .attr('d', d => {
      const midY = (d.srcY + d.tgtY) / 2
      return `M${d.srcX},${d.srcY} C${d.srcX},${midY} ${d.tgtX},${midY} ${d.tgtX},${d.tgtY}`
    })

  // 绘制分数标签（使用 data binding）
  const scoreData = matchLinksData.filter(d => getEdgeStyle({ type: '匹配', score: d.score }).scoreText)

  const scoreGroups = matchLinksG.selectAll('.match-score')
    .data(scoreData)
    .join('g')
    .attr('class', 'match-score')
    .attr('transform', d => {
      const midX = (d.srcX + d.tgtX) / 2
      const midY = (d.srcY + d.tgtY) / 2
      return `translate(${midX}, ${midY})`
    })

  scoreGroups.append('rect')
    .attr('x', -14)
    .attr('y', -6)
    .attr('width', 28)
    .attr('height', 12)
    .attr('rx', 2)
    .attr('fill', '#1d232a')
    .attr('opacity', 0.9)

  scoreGroups.append('text')
    .attr('text-anchor', 'middle')
    .attr('dy', '0.35em')
    .attr('fill', d => getEdgeStyle({ type: '匹配', score: d.score }).color)
    .attr('font-size', '8px')
    .text(d => getEdgeStyle({ type: '匹配', score: d.score }).scoreText)

  // 绘制匹配节点
  const matchNodesG = contentG.append('g').attr('class', 'match-nodes')

  const matchNodes = matchNodesG.selectAll('.match-node')
    .data(matchedPersonas)
    .join('g')
    .attr('class', 'match-node')
    .attr('transform', d => `translate(${d.x},${d.y})`)
    .style('cursor', 'pointer')
    .on('click', handleMatchNodeClick)

  // 匹配节点形状（使用统一配置）
  matchNodes.each(function(d) {
    const el = d3.select(this)
    const style = getNodeStyle(d, { isMatch: true })

    if (style.shape === 'rect') {
      el.append('rect')
        .attr('class', 'tree-shape')
        .attr('x', -4)
        .attr('y', -4)
        .attr('width', 8)
        .attr('height', 8)
        .attr('rx', 1)
        .attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)')
        .attr('stroke-width', 1)
    } else {
      el.append('circle')
        .attr('class', 'tree-shape')
        .attr('r', 3)
        .attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)')
        .attr('stroke-width', 1)
    }

    // 保存匹配节点位置（统一存入 nodeElements）
    nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
  })

  // 匹配节点标签（使用统一配置）
  matchNodes.append('text')
    .attr('dy', 4)
    .attr('dx', 10)
    .attr('text-anchor', 'start')
    .attr('fill', d => getNodeStyle(d, { isMatch: true }).text.fill)
    .attr('font-size', d => getNodeStyle(d, { isMatch: true }).text.fontSize)
    .attr('font-weight', d => getNodeStyle(d, { isMatch: true }).text.fontWeight)
    .text(d => {
      const name = d.name
      const maxLen = 10
      return name.length > maxLen ? name.slice(0, maxLen) + '…' : name
    })

  // 更新总高度（用于 fitToView）
  treeHeight = matchLayerY + 50

  // 保存匹配层 Y 位置，供游走层使用
  lastMatchLayerY = matchLayerY

  // 保存基础节点快照（帖子树+匹配层），供游走层判断已有节点
  baseNodeElements = { ...nodeElements }
}

// 保存匹配层 Y 位置
let lastMatchLayerY = 0

// 绘制游走层（按层级渲染路径）
function renderWalkedLayer() {
  if (!mainG) return

  // 移除旧的游走层
  mainG.selectAll('.walked-layer').remove()

  // 重置 nodeElements 为基础节点（清除之前的游走节点）
  nodeElements = { ...baseNodeElements }

  const paths = store.postWalkedPaths
  if (!paths.length) return

  const contentG = mainG.select('g')
  if (contentG.empty()) return

  // 创建游走层组
  const walkedG = contentG.append('g').attr('class', 'walked-layer')

  // ========== 按路径位置分层遍历，确定每个新节点的层数 ==========
  // nodeLayer: nodeId -> layer (已有节点从 nodeElements 推断，新节点计算得出)
  const nodeLayer = new Map()
  const newNodes = new Set()  // 新增的节点

  console.log('=== renderWalkedLayer 层数计算 ===')
  console.log('路径数:', paths.length)
  console.log('lastMatchLayerY:', lastMatchLayerY)

  // 初始化：已有节点的层数（帖子树和匹配层）
  // 使用 baseNodeElements（不含之前的游走节点）
  // 匹配层的 Y 坐标是 lastMatchLayerY，作为基准层 0
  for (const [nodeId, info] of Object.entries(baseNodeElements)) {
    // 根据 Y 坐标推断层数（匹配层为0，帖子树在上面为负数）
    const layer = Math.round((info.y - 25 - lastMatchLayerY) / 80)
    nodeLayer.set(nodeId, Math.min(layer, 0))  // 已有节点层数 <= 0
  }
  console.log('基础节点数:', nodeLayer.size)

  // 找出所有路径的最大长度
  const maxPathLen = Math.max(...paths.map(p => p.nodes.length))
  console.log('最大路径长度:', maxPathLen)

  // 按位置分层遍历（从第2个节点开始，index=1）
  for (let pos = 1; pos < maxPathLen; pos++) {
    console.log(`--- 处理位置 ${pos} ---`)
    for (const path of paths) {
      if (pos >= path.nodes.length) continue

      const nodeId = path.nodes[pos]
      const prevNodeId = path.nodes[pos - 1]

      // 如果已经在树上，跳过
      if (nodeLayer.has(nodeId)) {
        console.log(`  节点 ${nodeId}: 已存在，层数=${nodeLayer.get(nodeId)}，跳过`)
        continue
      }

      // 找前驱节点的层数
      const prevLayer = nodeLayer.get(prevNodeId) ?? 0
      const newLayer = prevLayer + 1

      console.log(`  节点 ${nodeId}: 前驱=${prevNodeId}(层${prevLayer}) → 新层数=${newLayer}`)

      // 新节点层数 = 前驱层数 + 1
      nodeLayer.set(nodeId, newLayer)
      newNodes.add(nodeId)
    }
  }

  console.log('新增节点数:', newNodes.size)
  console.log('新增节点:', Array.from(newNodes))

  // ========== 按层分组新节点 ==========
  const layerGroups = new Map()  // layer -> Set of nodeIds
  for (const nodeId of newNodes) {
    const layer = nodeLayer.get(nodeId)
    if (!layerGroups.has(layer)) {
      layerGroups.set(layer, new Set())
    }
    layerGroups.get(layer).add(nodeId)
  }

  // ========== 计算新节点位置 ==========
  const treeNodes = Object.values(nodeElements)
  const minX = d3.min(treeNodes, d => d.x) || 50
  const maxX = d3.max(treeNodes, d => d.x) || 300
  const layerSpacing = 80  // 层间距

  // nodePositions 只存储新节点的位置
  const nodePositions = {}

  for (const [layer, nodeIds] of layerGroups) {
    const nodesAtLayer = Array.from(nodeIds)
    const layerY = lastMatchLayerY + layer * layerSpacing

    // 新节点均匀分布
    const spacing = (maxX - minX - 100) / Math.max(nodesAtLayer.length - 1, 1)
    nodesAtLayer.forEach((nodeId, i) => {
      nodePositions[nodeId] = {
        x: nodesAtLayer.length === 1 ? (minX + maxX) / 2 - 50 : (minX - 50) + i * spacing,
        y: layerY
      }
    })
  }

  // ========== 收集所有边 ==========
  const allEdges = new Map()
  for (const path of paths) {
    for (const edge of path.edges) {
      const edgeKey = `${edge.source}->${edge.target}`
      if (!allEdges.has(edgeKey)) {
        allEdges.set(edgeKey, edge)
      }
    }
  }

  // 获取节点位置的辅助函数（优先新节点位置，然后已有节点位置）
  function getNodePos(nodeId) {
    if (nodePositions[nodeId]) {
      return nodePositions[nodeId]
    }
    if (nodeElements[nodeId]) {
      return {
        x: nodeElements[nodeId].x - 50,
        y: nodeElements[nodeId].y - 25
      }
    }
    return null
  }

  // ========== 绘制所有路径上的边 ==========
  const edgesData = []
  console.log('=== 绘制边 ===')
  console.log('总边数:', allEdges.size)

  for (const edge of allEdges.values()) {
    const srcPos = getNodePos(edge.source)
    const tgtPos = getNodePos(edge.target)

    console.log(`边 ${edge.source} -> ${edge.target}:`,
      'srcPos=', srcPos ? `(${srcPos.x.toFixed(0)},${srcPos.y.toFixed(0)})` : 'null',
      'tgtPos=', tgtPos ? `(${tgtPos.x.toFixed(0)},${tgtPos.y.toFixed(0)})` : 'null'
    )

    if (srcPos && tgtPos) {
      edgesData.push({
        ...edge,
        srcX: srcPos.x,
        srcY: srcPos.y,
        tgtX: tgtPos.x,
        tgtY: tgtPos.y
      })
    } else {
      console.log('  ⚠️ 跳过：位置缺失')
    }
  }
  console.log('实际绘制边数:', edgesData.length)

  walkedG.selectAll('.walked-link')
    .data(edgesData)
    .join('path')
    .attr('class', 'walked-link')
    .attr('fill', 'none')
    .attr('stroke', d => getEdgeStyle({ type: d.type, score: d.score }).color)
    .attr('stroke-opacity', d => getEdgeStyle({ type: d.type, score: d.score }).opacity)
    .attr('stroke-width', d => getEdgeStyle({ type: d.type, score: d.score }).strokeWidth)
    .attr('stroke-dasharray', d => getEdgeStyle({ type: d.type, score: d.score }).strokeDasharray)
    .attr('d', d => {
      // 同一层的边（Y坐标相近）用向下弯曲的曲线
      if (Math.abs(d.srcY - d.tgtY) < 10) {
        const controlY = d.srcY + 50  // 向下弯曲
        const midX = (d.srcX + d.tgtX) / 2
        return `M${d.srcX},${d.srcY} Q${midX},${controlY} ${d.tgtX},${d.tgtY}`
      }
      // 不同层的边用 S 形曲线
      const midY = (d.srcY + d.tgtY) / 2
      return `M${d.srcX},${d.srcY} C${d.srcX},${midY} ${d.tgtX},${midY} ${d.tgtX},${d.tgtY}`
    })

  // 绘制分数标签
  const scoreData = edgesData.filter(d => getEdgeStyle({ type: d.type, score: d.score }).scoreText)
  const scoreGroups = walkedG.selectAll('.walked-score')
    .data(scoreData)
    .join('g')
    .attr('class', 'walked-score')
    .attr('transform', d => {
      const midX = (d.srcX + d.tgtX) / 2
      // 同一层的边用向下弯曲曲线，分数标签放在曲线中点（t=0.5 时 y = srcY + 25）
      const midY = Math.abs(d.srcY - d.tgtY) < 10 ? d.srcY + 25 : (d.srcY + d.tgtY) / 2
      return `translate(${midX}, ${midY})`
    })

  scoreGroups.append('rect')
    .attr('x', -14).attr('y', -6).attr('width', 28).attr('height', 12)
    .attr('rx', 2).attr('fill', '#1d232a').attr('opacity', 0.9)

  scoreGroups.append('text')
    .attr('text-anchor', 'middle').attr('dy', '0.35em')
    .attr('fill', d => getEdgeStyle({ type: d.type, score: d.score }).color)
    .attr('font-size', '8px')
    .text(d => getEdgeStyle({ type: d.type, score: d.score }).scoreText)

  // ========== 绘制新节点（nodePositions 中的都是新节点） ==========
  const newNodesData = []
  for (const [nodeId, pos] of Object.entries(nodePositions)) {
    // 获取节点数据
    const nodeData = store.postWalkedNodes.find(n => n.id === nodeId)
    if (nodeData) {
      newNodesData.push({ ...nodeData, x: pos.x, y: pos.y })
    }
  }

  const walkedNodeGroups = walkedG.selectAll('.walked-node')
    .data(newNodesData)
    .join('g')
    .attr('class', 'walked-node')
    .attr('transform', d => `translate(${d.x},${d.y})`)
    .style('cursor', 'pointer')
    .on('click', (event, d) => {
      event.stopPropagation()
      store.selectNode(d)
    })

  // 节点形状
  walkedNodeGroups.each(function(d) {
    const el = d3.select(this)
    const style = getNodeStyle(d)

    if (style.shape === 'rect') {
      el.append('rect')
        .attr('class', 'walked-shape')
        .attr('x', -4).attr('y', -4).attr('width', 8).attr('height', 8)
        .attr('rx', 1).attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)').attr('stroke-width', 1)
    } else {
      el.append('circle')
        .attr('class', 'walked-shape')
        .attr('r', 3).attr('fill', style.color)
        .attr('stroke', 'rgba(255,255,255,0.5)').attr('stroke-width', 1)
    }

    // 保存节点位置
    nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
  })

  // 节点标签
  walkedNodeGroups.append('text')
    .attr('dy', 4).attr('dx', 10).attr('text-anchor', 'start')
    .attr('fill', d => getNodeStyle(d).text.fill)
    .attr('font-size', d => getNodeStyle(d).text.fontSize)
    .attr('font-weight', d => getNodeStyle(d).text.fontWeight)
    .text(d => {
      const name = d.name
      const maxLen = 10
      return name.length > maxLen ? name.slice(0, maxLen) + '…' : name
    })

  // 更新高度（如果有新节点）
  if (Object.keys(nodePositions).length > 0) {
    const maxY = Math.max(...Object.values(nodePositions).map(p => p.y))
    treeHeight = Math.max(treeHeight, maxY + 50)
  }
}

// 匹配节点点击处理
function handleMatchNodeClick(event, d) {
  event.stopPropagation()
  store.selectNode(d)
}

// 适应视图（自动缩放以显示全部内容）
function fitToView() {
  if (!zoom || !mainG || !containerRef.value) return

  const container = containerRef.value
  const width = container.clientWidth
  const height = container.clientHeight

  // 计算缩放比例以适应容器
  const scaleX = width / treeWidth
  const scaleY = height / treeHeight
  const scale = Math.min(scaleX, scaleY, 1) * 0.9 // 留一点边距

  // 计算居中偏移
  const translateX = (width - treeWidth * scale) / 2
  const translateY = (height - treeHeight * scale) / 2

  const svg = d3.select(svgRef.value)
  svg.call(zoom.transform, d3.zoomIdentity.translate(translateX, translateY).scale(scale))
}

// 定位到指定节点
function zoomToNode(nodeId) {
  const nodeInfo = nodeElements[nodeId]
  if (!nodeInfo || !zoom || !containerRef.value) return

  const container = containerRef.value
  const width = container.clientWidth
  const height = container.clientHeight

  // 计算平移使节点居中
  const scale = 1
  const translateX = width / 2 - nodeInfo.x * scale
  const translateY = height / 2 - nodeInfo.y * scale

  const svg = d3.select(svgRef.value)
  svg.transition().duration(300).call(
    zoom.transform,
    d3.zoomIdentity.translate(translateX, translateY).scale(scale)
  )
}

// 更新高亮/置灰状态
function updateHighlight() {
  // 使用帖子游走的边集合（如果有），否则用人设游走的边集合
  const edgeSet = store.postWalkedEdgeSet.size > 0 ? store.postWalkedEdgeSet : store.walkedEdgeSet
  applyHighlight(svgRef.value, store.highlightedNodeIds, edgeSet, store.selectedNodeId)
}

// 点击空白取消激活
function handleSvgClick(event) {
  const target = event.target
  if (!target.closest('.tree-node') && !target.closest('.match-node') && !target.closest('.walked-node')) {
    store.clearSelection()
  }
}

// 监听选中/高亮变化，统一更新
watch(() => store.selectedNodeId, (nodeId, oldNodeId) => {
  updateHighlight()
  if (nodeId && nodeId !== oldNodeId) {
    zoomToNode(nodeId)
  }
})

watch(() => store.highlightedNodeIds.size, updateHighlight)

// 监听帖子游走结果变化，渲染游走层
watch(() => store.postWalkedNodes.length, () => {
  nextTick(renderWalkedLayer)
})

// 监听当前帖子变化，重新渲染树
watch(() => store.currentPostGraph, () => {
  nextTick(() => {
    renderTree()
  })
}, { immediate: false })

// 监听 selectedPostIndex 变化，同步下拉框
watch(() => store.selectedPostIndex, (newIdx) => {
  selectedPostIdx.value = newIdx
})

// 监听布局变化，过渡结束后重新适应视图
function handleTransitionEnd(e) {
  if (['width', 'height', 'flex', 'flex-grow', 'flex-shrink'].includes(e.propertyName)) {
    nextTick(() => {
      renderTree()
    })
  }
}

let transitionParent = null

onMounted(() => {
  nextTick(() => {
    renderTree()
  })

  // 监听父容器的过渡结束事件
  if (containerRef.value) {
    let parent = containerRef.value.parentElement
    while (parent && !parent.classList.contains('transition-all')) {
      parent = parent.parentElement
    }
    if (parent) {
      transitionParent = parent
      parent.addEventListener('transitionend', handleTransitionEnd)
    }
  }
})

onUnmounted(() => {
  if (transitionParent) {
    transitionParent.removeEventListener('transitionend', handleTransitionEnd)
  }
})
</script>
