#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const { build } = require('esbuild');
// const { convertV8ToGraph } = require('./convert_v8_to_graph'); // 已废弃，使用v3版本
const { convertV8ToGraphV2, convertV8ToGraphSimplified } = require('./convert_v8_to_graph_v3');

// 读取命令行参数
const args = process.argv.slice(2);
if (args.length === 0) {
  console.error('Usage: node index.js <path-to-run_context.json> [output.html] [--simplified]');
  process.exit(1);
}

const inputFile = args[0];
const outputFile = args[1] || 'query_graph_output.html';
const useSimplified = args.includes('--simplified');

// 读取输入数据
const inputData = JSON.parse(fs.readFileSync(inputFile, 'utf-8'));

// 提取清洗后的数据
const cleanedData = inputData.multimodal_cleaned_posts || null;
const cleanedPosts = cleanedData ? cleanedData.posts : [];
console.log(`📊 发现清洗数据: ${cleanedPosts.length} 个帖子`);

// 检测数据格式并转换
let data;
if (inputData.rounds && inputData.o) {
  // v6.1.2.8 格式，需要转换
  console.log('✨ 检测到 v6.1.2.8 格式，正在转换为图结构...');

  // 尝试读取 search_results.json（兼容旧版本）
  let searchResults = null;
  const searchResultsPath = path.join(path.dirname(inputFile), 'search_results.json');
  if (fs.existsSync(searchResultsPath)) {
    console.log('📄 读取外部搜索结果数据（兼容模式）...');
    searchResults = JSON.parse(fs.readFileSync(searchResultsPath, 'utf-8'));
  } else {
    console.log('✅ 使用 run_context.json 中的内嵌搜索结果');
  }

  // 尝试读取 search_extract.json（多模态提取数据）
  let extractionData = null;
  const extractionPath = path.join(path.dirname(inputFile), 'search_extract.json');
  if (fs.existsSync(extractionPath)) {
    console.log('📸 读取多模态提取数据...');
    extractionData = JSON.parse(fs.readFileSync(extractionPath, 'utf-8'));
  } else {
    console.log('ℹ️  未找到 search_extract.json，跳过多模态展示');
  }

  // 选择转换函数
  let graphData;
  let fullData = null; // 用于目录的完整数据

  if (useSimplified) {
    console.log('🎨 使用简化视图（合并query节点）');
    // 生成简化版用于画布
    graphData = convertV8ToGraphSimplified(inputData, searchResults, extractionData);
    // 生成完整版用于目录
    const fullGraphData = convertV8ToGraphV2(inputData, searchResults, extractionData);
    fullData = {
      nodes: fullGraphData.nodes,
      edges: fullGraphData.edges,
      iterations: fullGraphData.iterations
    };
    console.log(`✅ 简化版: ${Object.keys(graphData.nodes).length} 个节点, ${graphData.edges.length} 条边`);
    console.log(`📋 完整版(用于目录): ${Object.keys(fullData.nodes).length} 个节点`);
  } else {
    console.log('📊 使用详细视图（完整流程）');
    graphData = convertV8ToGraphV2(inputData, searchResults, extractionData);
    console.log(`✅ 转换完成: ${Object.keys(graphData.nodes).length} 个节点, ${graphData.edges.length} 条边`);
  }

  data = {
    nodes: graphData.nodes,
    edges: graphData.edges,
    iterations: graphData.iterations,
    fullData: fullData // 传递完整数据
  };
} else if (inputData.nodes && inputData.edges) {
  // v6.1.2.5 格式，直接使用
  console.log('✨ 检测到 v6.1.2.5 格式，直接使用');
  data = inputData;
} else {
  console.error('❌ 无法识别的数据格式');
  process.exit(1);
}

// 创建临时 React 组件文件
const reactComponentPath = path.join(__dirname, 'temp_flow_component_v2.jsx');
const reactComponent = `
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import {
  ReactFlow,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
  Handle,
  Position,
  useReactFlow,
  ReactFlowProvider,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';

const data = ${JSON.stringify(data, null, 2)};
const cleanedPosts = ${JSON.stringify(cleanedPosts, null, 2)};

// 根据节点类型获取边框颜色
function getNodeTypeColor(type) {
  const typeColors = {
    'root': '#6b21a8',        // 紫色 - 根节点
    'round': '#7c3aed',       // 深紫 - Round节点
    'step': '#f59e0b',        // 橙色 - 步骤节点
    'seg': '#10b981',         // 绿色 - 分词
    'q': '#3b82f6',           // 蓝色 - Query
    'sug': '#06b6d4',         // 青色 - Sug建议词
    'seed': '#84cc16',        // 黄绿 - Seed
    'add_word': '#22c55e',    // 绿色 - 加词生成
    'search_word': '#8b5cf6', // 紫色 - 搜索词
    'post': '#ec4899',        // 粉色 - 帖子
    'filtered_sug': '#14b8a6',// 青绿 - 筛选的sug
    'next_q': '#2563eb',      // 深蓝 - 下轮查询
    'next_seed': '#65a30d',   // 深黄绿 - 下轮种子
    'search': '#8b5cf6',      // 深紫 - 搜索（兼容旧版）
    'operation': '#f59e0b',   // 橙色 - 操作节点（兼容旧版）
    'query': '#3b82f6',       // 蓝色 - 查询（兼容旧版）
    'note': '#ec4899',        // 粉色 - 帖子（兼容旧版）
  };
  return typeColors[type] || '#9ca3af';
}

// 查询节点组件 - 卡片样式
function QueryNode({ id, data, sourcePosition, targetPosition }) {
  // 所有节点默认展开
  const expanded = true;

  // 获取节点类型颜色
  const typeColor = getNodeTypeColor(data.nodeType || 'query');

  return (
    <div>
      <Handle
        type="target"
        position={targetPosition || Position.Left}
        style={{ background: typeColor, width: 8, height: 8 }}
      />
      <div
        style={{
          padding: '12px',
          borderRadius: '8px',
          border: data.isHighlighted ? \`3px solid \${typeColor}\` :
                  data.isCollapsed ? \`2px solid \${typeColor}\` :
                  data.isSelected === false ? '2px dashed #d1d5db' :
                  \`2px solid \${typeColor}\`,
          background: data.isHighlighted ? '#eef2ff' :
                      data.isSelected === false ? '#f9fafb' : 'white',
          minWidth: '200px',
          maxWidth: '280px',
          boxShadow: data.isHighlighted ? '0 0 0 4px rgba(102, 126, 234, 0.25), 0 4px 16px rgba(102, 126, 234, 0.4)' :
                     data.isCollapsed ? '0 4px 12px rgba(102, 126, 234, 0.15)' :
                     data.level === 0 ? '0 4px 12px rgba(139, 92, 246, 0.15)' : '0 2px 6px rgba(0, 0, 0, 0.06)',
          transition: 'all 0.3s ease',
          cursor: 'pointer',
          position: 'relative',
          opacity: data.isSelected === false ? 0.6 : 1,
        }}
      >
        {/* 折叠当前节点按钮 - 左边 */}
        <div
          style={{
            position: 'absolute',
            top: '6px',
            left: '6px',
            width: '20px',
            height: '20px',
            borderRadius: '50%',
            background: '#f59e0b',
            color: 'white',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: '11px',
            fontWeight: 'bold',
            cursor: 'pointer',
            transition: 'all 0.2s ease',
            zIndex: 10,
          }}
          onClick={(e) => {
            e.stopPropagation();
            if (data.onHideSelf) {
              data.onHideSelf();
            }
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.background = '#d97706';
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.background = '#f59e0b';
          }}
          title="隐藏当前节点"
        >
          ×
        </div>

        {/* 聚焦按钮 - 右上角 */}
        <div
          style={{
            position: 'absolute',
            top: '6px',
            right: '6px',
            width: '20px',
            height: '20px',
            borderRadius: '50%',
            background: data.isFocused ? '#10b981' : '#e5e7eb',
            color: data.isFocused ? 'white' : '#6b7280',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            fontSize: '11px',
            fontWeight: 'bold',
            cursor: 'pointer',
            transition: 'all 0.2s ease',
            zIndex: 10,
          }}
          onClick={(e) => {
            e.stopPropagation();
            if (data.onFocus) {
              data.onFocus();
            }
          }}
          onMouseEnter={(e) => {
            if (!data.isFocused) {
              e.currentTarget.style.background = '#d1d5db';
            }
          }}
          onMouseLeave={(e) => {
            if (!data.isFocused) {
              e.currentTarget.style.background = '#e5e7eb';
            }
          }}
          title={data.isFocused ? '取消聚焦' : '聚焦到此节点'}
        >
          🎯
        </div>

        {/* 折叠/展开子节点按钮 - 右边第二个位置 */}
        {data.hasChildren && (
          <div
            style={{
              position: 'absolute',
              top: '6px',
              right: '30px',
              width: '20px',
              height: '20px',
              borderRadius: '50%',
              background: data.isCollapsed ? '#667eea' : '#e5e7eb',
              color: data.isCollapsed ? 'white' : '#6b7280',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: '11px',
              fontWeight: 'bold',
              cursor: 'pointer',
              transition: 'all 0.2s ease',
              zIndex: 10,
            }}
            onClick={(e) => {
              e.stopPropagation();
              data.onToggleCollapse();
            }}
            title={data.isCollapsed ? '展开子节点' : '折叠子节点'}
          >
            {data.isCollapsed ? '+' : '−'}
          </div>
        )}

        {/* 卡片内容 */}
        <div>
          {/* 标题行 */}
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '8px', paddingLeft: '24px', paddingRight: data.hasChildren ? '54px' : '28px' }}>
            <div style={{ flex: 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '3px' }}>
                <div style={{
                  fontSize: '13px',
                  fontWeight: data.level === 0 ? '700' : '600',
                  color: data.level === 0 ? '#6b21a8' : '#1f2937',
                  lineHeight: '1.3',
                  flex: 1,
                }}>
                  {data.title}
                </div>
                {data.isSelected === false && (
                  <div style={{
                    fontSize: '9px',
                    padding: '1px 4px',
                    borderRadius: '3px',
                    background: '#fee2e2',
                    color: '#991b1b',
                    fontWeight: '500',
                    flexShrink: 0,
                  }}>
                    未选中
                  </div>
                )}
              </div>
            </div>
          </div>

        {/* 展开的详细信息 - 始终显示 */}
        <div style={{ fontSize: '11px', lineHeight: 1.4 }}>
            <div style={{ display: 'flex', gap: '4px', marginBottom: '6px', flexWrap: 'wrap' }}>
              <span style={{
                display: 'inline-block',
                padding: '1px 6px',
                borderRadius: '10px',
                background: '#eff6ff',
                color: '#3b82f6',
                fontSize: '10px',
                fontWeight: '500',
              }}>
                Lv.{data.level}
              </span>
              <span style={{
                display: 'inline-block',
                padding: '1px 6px',
                borderRadius: '10px',
                background: '#f0fdf4',
                color: '#16a34a',
                fontSize: '10px',
                fontWeight: '500',
              }}>
                {data.score}
              </span>
              {data.strategy && data.strategy !== 'root' && (
                <span style={{
                  display: 'inline-block',
                  padding: '1px 6px',
                  borderRadius: '10px',
                  background: '#fef3c7',
                  color: '#92400e',
                  fontSize: '10px',
                  fontWeight: '500',
                }}>
                  {data.strategy}
                </span>
              )}
              {(data.typeLabel || data.type_label) && (
                <span style={{
                  display: 'inline-block',
                  padding: '1px 6px',
                  borderRadius: '10px',
                  background: '#fce7f3',
                  color: '#9f1239',
                  fontSize: '10px',
                  fontWeight: '500',
                }}>
                  {data.typeLabel || data.type_label}
                </span>
              )}
            {data.is_suggestion && data.suggestion_label && (
              <span style={{
                display: 'inline-block',
                padding: '1px 6px',
                borderRadius: '10px',
                background: '#ede9fe',
                color: '#6d28d9',
                fontSize: '10px',
                fontWeight: '600',
              }}>
                {data.suggestion_label}
              </span>
            )}
            </div>

            {data.parent && (
              <div style={{ color: '#6b7280', fontSize: '10px', marginTop: '4px', paddingTop: '4px', borderTop: '1px solid #f3f4f6' }}>
                <strong>Parent:</strong> {data.parent}
              </div>
            )}
            {data.nodeType === 'domain_combination' && Array.isArray(data.source_word_details) && data.source_word_details.length > 0 && (
              <div style={{
                marginTop: '6px',
                paddingTop: '6px',
                borderTop: '1px solid #f3f4f6',
                fontSize: '10px',
                color: '#6b7280',
                lineHeight: '1.5',
              }}>
                <strong style={{ color: '#4b5563' }}>来源词得分:</strong>
                <div style={{ marginTop: '4px', display: 'flex', flexDirection: 'column', gap: '4px' }}>
                  {data.source_word_details.map((detail, idx) => {
                    const words = (detail.words || []).map((w) => {
                      const numericScore = typeof w.score === 'number' ? w.score : parseFloat(w.score || '0');
                      const formattedScore = Number.isFinite(numericScore) ? numericScore.toFixed(2) : '0.00';
                      return w.text + ' (' + formattedScore + ')';
                    }).join(' + ');
                    return (
                      <div key={idx} style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', alignItems: 'center' }}>
                        <span style={{ color: '#2563eb' }}>{words}</span>
                      </div>
                    );
                  })}
                </div>
                <div style={{ marginTop: '4px', fontWeight: '500', color: data.is_above_sources ? '#16a34a' : '#dc2626' }}>
                  {data.is_above_sources ? '✅ 组合得分高于所有来源词' : '⚠️ 组合得分未超过全部来源词'}
                </div>
              </div>
            )}
            {data.selectedWord && (
              <div style={{
                marginTop: '6px',
                paddingTop: '6px',
                borderTop: '1px solid #f3f4f6',
                fontSize: '10px',
                color: '#6b7280',
                lineHeight: '1.5',
              }}>
                <strong style={{ color: '#4b5563' }}>选择词:</strong>
                <span style={{ marginLeft: '4px', color: '#3b82f6', fontWeight: '500' }}>{data.selectedWord}</span>
                {data.seed_score !== undefined && (
                  <div style={{ marginTop: '4px' }}>
                    <strong style={{ color: '#4b5563' }}>种子得分:</strong>
                    <span style={{ marginLeft: '4px', color: '#16a34a', fontWeight: '500' }}>
                      {typeof data.seed_score === 'number' ? data.seed_score.toFixed(2) : data.seed_score}
                    </span>
                  </div>
                )}
              </div>
            )}
            {data.evaluationReason && (
              <div style={{
                marginTop: '6px',
                paddingTop: '6px',
                borderTop: '1px solid #f3f4f6',
                fontSize: '10px',
                color: '#6b7280',
                lineHeight: '1.5',
              }}>
                <strong style={{ color: '#4b5563' }}>评估:</strong>
                <div style={{ marginTop: '2px' }}>{data.evaluationReason}</div>
              </div>
            )}
            {data.occurrences && data.occurrences.length > 1 && (
              <div style={{
                marginTop: '6px',
                paddingTop: '6px',
                borderTop: '1px solid #f3f4f6',
                fontSize: '10px',
                color: '#6b7280',
              }}>
                <strong style={{ color: '#4b5563' }}>演化历史 ({data.occurrences.length}次):</strong>
                <div style={{ marginTop: '4px' }}>
                  {data.occurrences.map((occ, idx) => (
                    <div key={idx} style={{ marginTop: '2px', paddingLeft: '8px' }}>
                      <span style={{ color: '#3b82f6', fontWeight: '500' }}>R{occ.round}</span>
                      {' · '}
                      <span>{occ.strategy}</span>
                      {occ.score !== undefined && (
                        <span style={{ color: '#16a34a', marginLeft: '4px' }}>
                          ({typeof occ.score === 'number' ? occ.score.toFixed(2) : occ.score})
                        </span>
                      )}
                    </div>
                  ))}
                </div>
              </div>
            )}
            {data.hasSearchResults && (
              <div style={{
                marginTop: '6px',
                paddingTop: '6px',
                borderTop: '1px solid #f3f4f6',
                fontSize: '10px',
                background: '#fef3c7',
                padding: '4px 6px',
                borderRadius: '4px',
                color: '#92400e',
                fontWeight: '500',
              }}>
                🔍 找到 {data.postCount} 个帖子
              </div>
            )}
          </div>
        </div>
      </div>
      <Handle
        type="source"
        position={sourcePosition || Position.Right}
        style={{ background: '#667eea', width: 8, height: 8 }}
      />
    </div>
  );
}

// 笔记节点组件 - 卡片样式，带轮播图
function NoteNode({ id, data, sourcePosition, targetPosition }) {
  const [currentImageIndex, setCurrentImageIndex] = useState(0);
  const [showEvalDetails, setShowEvalDetails] = useState(false);
  const expanded = true;
  const hasImages = data.imageList && data.imageList.length > 0;

  const nextImage = (e) => {
    e.stopPropagation();
    if (hasImages) {
      setCurrentImageIndex((prev) => (prev + 1) % data.imageList.length);
    }
  };

  const prevImage = (e) => {
    e.stopPropagation();
    if (hasImages) {
      setCurrentImageIndex((prev) => (prev - 1 + data.imageList.length) % data.imageList.length);
    }
  };

  const handleCardClick = (e) => {
    // 如果点击的是链接或按钮（或其子元素），不处理（避免双重触发）
    if (e.target.closest('a') || e.target.closest('button')) {
      return;
    }
    // 打开原帖链接
    if (data.note_url) {
      window.open(data.note_url, '_blank', 'noopener,noreferrer');
    }
  };

  return (
    <div>
      <Handle
        type="target"
        position={targetPosition || Position.Left}
        style={{ background: '#ec4899', width: 8, height: 8 }}
      />
      <div
        onClick={handleCardClick}
        style={{
          padding: '28px',
          borderRadius: '40px',
          border: data.isHighlighted ? '6px solid #ec4899' : '4px solid #fce7f3',
          background: data.isHighlighted ? '#eef2ff' : 'white',
          minWidth: '880px',
          maxWidth: '1200px',
          boxShadow: data.isHighlighted ? '0 0 0 8px rgba(236, 72, 153, 0.25), 0 8px 32px rgba(236, 72, 153, 0.4)' : '0 8px 24px rgba(236, 72, 153, 0.15)',
          transition: 'all 0.3s ease',
          cursor: 'pointer',
        }}
      >
        {/* 🆕 原始问题展示 - 最顶部 */}
        {data.originalQuestion && (
          <div style={{
            marginBottom: '20px',
            paddingBottom: '20px',
            borderBottom: '2px solid #fce7f3',
          }}>
            <div style={{
              fontSize: '36px',
              color: '#6b21a8',
              lineHeight: '1.4',
              fontWeight: '600',
            }}>
              <span style={{ fontWeight: '700' }}>[原始需求问题]</span> {data.originalQuestion}
            </div>
          </div>
        )}

        {/* 帖子标题 - 明确标注 */}
        <div style={{ marginBottom: '20px', paddingBottom: '16px', borderBottom: '2px solid #fce7f3' }}>
          <div style={{ fontSize: '38px', fontWeight: '600', color: '#831843', lineHeight: '1.4' }}>
            <span style={{ fontSize: '32px', color: '#831843', fontWeight: '500' }}>帖子标题: </span>
            {data.title.replace(/^\[R\]\s*/, '')}
          </div>
        </div>

        {/* V3评估信息 - 可展开 */}
        {data.evaluator_version === 'v3.0' && (
          <div style={{ marginBottom: '20px', paddingBottom: '16px', borderBottom: '2px solid #fce7f3' }}>
            {/* 第1行：知识判定 + 内容知识 + 星级 */}
            <div style={{ display: 'flex', alignItems: 'center', gap: '20px', marginBottom: '10px', flexWrap: 'wrap' }}>
              <span style={{ fontSize: '28px', fontWeight: '600', color: data.is_knowledge ? '#166534' : '#991b1b' }}>
                {data.is_knowledge ? '✓ 是知识' : '✗ 非知识'}
              </span>
              {data.is_content_knowledge !== null && data.is_content_knowledge !== undefined && (
                <>
                  <span style={{ fontSize: '28px', fontWeight: '600', color: data.is_content_knowledge ? '#166534' : '#991b1b' }}>
                    {data.is_content_knowledge ? '✓ 是内容知识' : '✗ 非内容知识'}
                  </span>
                  {data.is_content_knowledge && data.content_knowledge_evaluation?.knowledge_score != null && (
                    <span style={{ fontSize: '24px', lineHeight: '1' }}>
                      {'⭐'.repeat(Math.min(5, Math.ceil(data.content_knowledge_evaluation.knowledge_score / 20)))}
                    </span>
                  )}
                  {data.is_content_knowledge && data.content_knowledge_evaluation?.knowledge_score != null && (
                    <span style={{ fontSize: '26px', fontWeight: '600', color: '#166534' }}>
                      {data.content_knowledge_evaluation.knowledge_score}分
                    </span>
                  )}
                </>
              )}
            </div>

            {/* 第2行：匹配度得分（仅内容知识显示） */}
            {data.is_content_knowledge && data.final_score !== null && data.final_score !== undefined && (
              <div style={{ display: 'flex', alignItems: 'center', gap: '16px', flexWrap: 'wrap', marginBottom: '12px' }}>
                <span style={{ fontSize: '32px', fontWeight: '700', color: data.final_score >= 60 ? '#166534' : '#ea580c' }}>
                  匹配度得分 {data.final_score.toFixed(1)}分
                </span>
                <span style={{
                  padding: '4px 16px',
                  borderRadius: '20px',
                  fontSize: '26px',
                  fontWeight: '600',
                  background: data.final_score >= 85 ? '#dcfce7' : data.final_score >= 60 ? '#fef3c7' : '#fee2e2',
                  color: data.final_score >= 85 ? '#166534' : data.final_score >= 60 ? '#854d0e' : '#991b1b'
                }}>
                  {data.match_level}
                </span>
                {data.purpose_score != null && (
                  <span style={{ fontSize: '26px', color: '#9f1239' }}>
                    目的{data.purpose_score}分
                  </span>
                )}
                {data.category_score != null && (
                  <span style={{ fontSize: '26px', color: '#9f1239' }}>
                    品类{data.category_score}分
                  </span>
                )}
              </div>
            )}

            {/* 展开按钮（所有V3评估都显示） */}
            {data.evaluator_version === 'v3.0' && (
              <div style={{ marginBottom: '12px' }}>
                <button
                  onClick={(e) => { e.stopPropagation(); setShowEvalDetails(!showEvalDetails); }}
                  style={{
                    fontSize: '24px',
                    padding: '6px 16px',
                    borderRadius: '12px',
                    border: '2px solid #ec4899',
                    background: 'white',
                    color: '#ec4899',
                    cursor: 'pointer',
                    fontWeight: '600',
                    transition: 'all 0.2s'
                  }}
                >
                  {showEvalDetails ? '收起详情 ▲' : '展开详情 ▼'}
                </button>
              </div>
            )}

            {/* 详细内容（展开后显示） */}
            {showEvalDetails && (
              <div style={{ marginTop: '16px', paddingTop: '16px', borderTop: '1px solid #f3f4f6' }}>
                {/* 1. 知识评估 */}
                {data.is_knowledge !== null && (
                  <div style={{ marginBottom: '16px', padding: '12px', background: '#fafafa', borderRadius: '8px' }}>
                    <div style={{ fontSize: '36px', fontWeight: '600', color: '#831843', marginBottom: '8px' }}>
                      1️⃣ 知识评估
                    </div>
                    <div style={{ fontSize: '34px', color: '#9f1239', lineHeight: '1.4' }}>
                      {data.knowledge_evaluation?.conclusion || '无评估信息'}
                    </div>
                  </div>
                )}

                {/* 2. 内容知识评估 */}
                {data.is_content_knowledge && data.content_knowledge_evaluation && (
                  <div style={{ marginBottom: '16px', padding: '12px', background: '#fafafa', borderRadius: '8px' }}>
                    <div style={{ fontSize: '36px', fontWeight: '600', color: '#831843', marginBottom: '8px' }}>
                      2️⃣ 内容知识评估 ({data.knowledge_score || 0}分)
                    </div>
                    <div style={{ fontSize: '34px', color: '#9f1239', lineHeight: '1.4' }}>
                      {data.content_knowledge_evaluation.summary || '无评估信息'}
                    </div>
                  </div>
                )}

                {/* 3. 与原始需求匹配 */}
                {(data.purpose_evaluation || data.category_evaluation) && (
                  <div style={{ marginBottom: '16px', padding: '12px', background: '#fafafa', borderRadius: '8px' }}>
                    <div style={{ fontSize: '36px', fontWeight: '600', color: '#831843', marginBottom: '8px' }}>
                      3️⃣ 与原始需求匹配
                    </div>
                    {data.purpose_evaluation && (
                      <div style={{ fontSize: '34px', color: '#9f1239', lineHeight: '1.4', marginBottom: '12px' }}>
                        <div style={{ fontWeight: '600', marginBottom: '6px' }}>
                          目的性匹配（{data.purpose_score}分）
                        </div>
                        <div>{data.purpose_evaluation.core_basis || '无评估信息'}</div>
                      </div>
                    )}
                    {data.category_evaluation && (
                      <div style={{ fontSize: '34px', color: '#9f1239', lineHeight: '1.4' }}>
                        <div style={{ fontWeight: '600', marginBottom: '6px' }}>
                          品类匹配（{data.category_score}分）
                        </div>
                        <div>{data.category_evaluation.core_basis || '无评估信息'}</div>
                      </div>
                    )}
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* V2评估信息 - 兼容旧数据 */}
        {data.evaluator_version !== 'v3.0' && (data.knowledge_score !== undefined || data.post_relevance_score !== undefined || data.is_knowledge !== undefined) && (
          <div style={{
            marginBottom: '20px',
            paddingBottom: '16px',
            borderBottom: '2px solid #fce7f3',
          }}>
            {/* 知识评估 (V2) */}
            {(data.knowledge_score !== undefined || data.is_knowledge !== undefined) && (
              <div style={{ marginBottom: '16px' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '8px' }}>
                  {data.knowledge_level && (
                    <span style={{ fontSize: '24px', lineHeight: '1' }}>
                      {'⭐'.repeat(data.knowledge_level)}
                    </span>
                  )}
                  {data.knowledge_score != null && (
                    <span style={{
                      fontSize: '34px',
                      fontWeight: '700',
                      color: data.knowledge_score >= 70 ? '#166534' : data.knowledge_score >= 40 ? '#854d0e' : '#991b1b',
                    }}>
                      知识: {data.knowledge_score.toFixed(0)}分
                    </span>
                  )}
                  {!data.knowledge_score && data.is_knowledge !== undefined && (
                    <span style={{
                      display: 'inline-block',
                      padding: '6px 20px',
                      borderRadius: '24px',
                      fontSize: '34px',
                      fontWeight: '600',
                      background: data.is_knowledge ? '#dcfce7' : '#fee2e2',
                      color: data.is_knowledge ? '#166534' : '#991b1b',
                    }}>
                      {data.is_knowledge ? '✓ 知识' : '✗ 非知识'}
                    </span>
                  )}
                </div>
                {data.knowledge_evaluation?.summary && (
                  <div style={{ fontSize: '30px', color: '#9f1239', lineHeight: '1.4', marginTop: '8px' }}>
                    {data.knowledge_evaluation.summary}
                  </div>
                )}
                {!data.knowledge_evaluation?.summary && data.knowledge_reason && (
                  <div style={{ fontSize: '30px', color: '#9f1239', lineHeight: '1.4', marginTop: '8px' }}>
                    {data.knowledge_reason}
                  </div>
                )}
              </div>
            )}

            {/* 相关性评估 (V2) */}
            {data.post_relevance_score != null && (
              <div>
                <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px' }}>
                  <span style={{ fontSize: '34px', fontWeight: '600', color: '#9f1239' }}>
                    相关性: {data.post_relevance_score.toFixed(0)}分
                  </span>
                  {data.relevance_conclusion && (
                    <span style={{
                      padding: '4px 16px',
                      borderRadius: '20px',
                      fontSize: '30px',
                      fontWeight: '600',
                      background: data.relevance_conclusion.includes('高度') ? '#dcfce7' : data.relevance_conclusion.includes('中度') ? '#fef3c7' : '#fee2e2',
                      color: data.relevance_conclusion.includes('高度') ? '#166534' : data.relevance_conclusion.includes('中度') ? '#854d0e' : '#991b1b',
                    }}>
                      {data.relevance_conclusion}
                    </span>
                  )}
                </div>
                {data.relevance_evaluation?.summary && (
                  <div style={{ fontSize: '30px', color: '#9f1239', lineHeight: '1.4' }}>
                    {data.relevance_evaluation.summary}
                  </div>
                )}
                {data.relevance_evaluation?.purpose_score != null && data.relevance_evaluation?.category_score != null && (
                  <div style={{ fontSize: '28px', color: '#9f1239', marginTop: '6px', opacity: 0.8 }}>
                    目的性:{data.relevance_evaluation.purpose_score.toFixed(0)}分(70%) |
                    品类:{data.relevance_evaluation.category_score.toFixed(0)}分(30%)
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* 轮播图 */}
        {hasImages && (
          <div style={{
            position: 'relative',
            marginBottom: '16px',
            borderRadius: '24px',
            overflow: 'hidden',
          }}>
            <img
              src={data.imageList[currentImageIndex].image_url}
              alt={\`Image \${currentImageIndex + 1}\`}
              style={{
                width: '100%',
                height: 'auto',
                objectFit: 'contain',
                display: 'block',
              }}
              onError={(e) => {
                e.target.style.display = 'none';
              }}
            />
            {data.imageList.length > 1 && (
              <>
                {/* 左右切换按钮 */}
                <button
                  onClick={prevImage}
                  style={{
                    position: 'absolute',
                    left: '8px',
                    top: '800px',
                    background: 'rgba(0, 0, 0, 0.5)',
                    color: 'white',
                    border: 'none',
                    borderRadius: '50%',
                    width: '72px',
                    height: '72px',
                    cursor: 'pointer',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    fontSize: '40px',
                  }}
                >
                  ‹
                </button>
                <button
                  onClick={nextImage}
                  style={{
                    position: 'absolute',
                    right: '8px',
                    top: '800px',
                    background: 'rgba(0, 0, 0, 0.5)',
                    color: 'white',
                    border: 'none',
                    borderRadius: '50%',
                    width: '72px',
                    height: '72px',
                    cursor: 'pointer',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    fontSize: '40px',
                  }}
                >
                  ›
                </button>
                {/* 图片计数 */}
                <div style={{
                  position: 'absolute',
                  bottom: '8px',
                  right: '8px',
                  background: 'rgba(0, 0, 0, 0.6)',
                  color: 'white',
                  padding: '4px 12px',
                  borderRadius: '20px',
                  fontSize: '20px',
                }}>
                  {currentImageIndex + 1}/{data.imageList.length}
                </div>
              </>
            )}
          </div>
        )}

        {/* 互动数据 */}
        {data.interact_info && (
          <div style={{
            display: 'flex',
            gap: '16px',
            marginBottom: '16px',
            flexWrap: 'wrap',
            fontSize: '22px',
            color: '#9f1239',
          }}>
            {data.interact_info.liked_count > 0 && (
              <span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                ❤️ {data.interact_info.liked_count}
              </span>
            )}
            {data.interact_info.collected_count > 0 && (
              <span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                ⭐ {data.interact_info.collected_count}
              </span>
            )}
            {data.interact_info.comment_count > 0 && (
              <span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                💬 {data.interact_info.comment_count}
              </span>
            )}
            {data.interact_info.shared_count > 0 && (
              <span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                🔗 {data.interact_info.shared_count}
              </span>
            )}
          </div>
        )}

        {/* 被哪些query找到 */}
        {data.foundByQueries && data.foundByQueries.length > 0 && (
          <div style={{
            marginBottom: '16px',
            padding: '12px 16px',
            background: '#f0fdf4',
            borderRadius: '12px',
            fontSize: '20px',
          }}>
            <strong style={{ color: '#16a34a' }}>🔍 被找到:</strong>
            <div style={{ marginTop: '8px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
              {data.foundByQueries.map((query, idx) => (
                <span key={idx} style={{
                  display: 'inline-block',
                  padding: '4px 12px',
                  background: '#dcfce7',
                  color: '#166534',
                  borderRadius: '8px',
                  fontSize: '18px',
                }}>
                  {query}
                </span>
              ))}
            </div>
            {data.foundInRounds && data.foundInRounds.length > 0 && (
              <div style={{ marginTop: '8px', color: '#6b7280' }}>
                出现在: Round {data.foundInRounds.join(', ')}
              </div>
            )}
          </div>
        )}

        {/* 标签 */}
        {(data.matchLevel || data.score) && (
          <div style={{ display: 'flex', gap: '12px', marginBottom: '16px', flexWrap: 'wrap' }}>
            {data.matchLevel && (
              <span style={{
                display: 'inline-block',
                padding: '4px 16px',
                borderRadius: '24px',
                background: '#fff1f2',
                color: '#be123c',
                fontSize: '20px',
                fontWeight: '500',
              }}>
                {data.matchLevel}
              </span>
            )}
            {/* Score标签已隐藏 - V2不再需要 */}
          </div>
        )}

        {/* 描述 */}
        {expanded && data.description && (
          <div style={{
            fontSize: '22px',
            color: '#9f1239',
            lineHeight: '1.5',
            paddingTop: '16px',
            borderTop: '2px solid #fbcfe8',
          }}>
            {data.description}
          </div>
        )}

        {/* 评估理由 */}
        {expanded && data.evaluationReason && (
          <div style={{
            fontSize: '20px',
            color: '#831843',
            lineHeight: '1.5',
            paddingTop: '16px',
            marginTop: '16px',
            borderTop: '2px solid #fbcfe8',
          }}>
            <strong style={{ color: '#9f1239' }}>评估:</strong>
            <div style={{ marginTop: '4px' }}>{data.evaluationReason}</div>
          </div>
        )}
      </div>
      <Handle
        type="source"
        position={sourcePosition || Position.Right}
        style={{ background: '#ec4899', width: 8, height: 8 }}
      />
    </div>
  );
}

// AnalysisNode 组件：展示AI分析（左侧OCR文字，右侧缩略图+描述）
function AnalysisNode({ data }) {
  const nodeStyle = {
    background: '#fffbeb',
    border: '2px solid #fbbf24',
    borderRadius: '8px',
    padding: '12px',
    minWidth: '700px',
    maxWidth: '900px',
    fontSize: '12px',
    boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
    cursor: 'pointer',
  };

  const handleCardClick = (e) => {
    // 如果点击的是链接或按钮（或其子元素），不处理（避免双重触发）
    if (e.target.closest('a') || e.target.closest('button')) {
      return;
    }
    // 打开原帖链接
    if (data.note_url) {
      window.open(data.note_url, '_blank', 'noopener,noreferrer');
    }
  };

  return (
    <div style={nodeStyle} onClick={handleCardClick}>
      <Handle
        type="target"
        position={Position.Left}
        style={{ background: '#fbbf24', width: 8, height: 8 }}
      />

      {/* 标题 */}
      <div style={{
        fontSize: '14px',
        fontWeight: 'bold',
        marginBottom: '8px',
        color: '#92400e',
      }}>
        🖼️ {data.query}
      </div>

      {/* 评分和互动数据 */}
      <div style={{
        display: 'flex',
        justifyContent: 'space-between',
        marginBottom: '8px',
        padding: '6px',
        background: '#fef3c7',
        borderRadius: '4px',
      }}>
        <div style={{ fontSize: '11px', fontWeight: 'bold' }}>
          Score: {data.interact_info?.relevance_score || 0}
        </div>
        <div style={{ display: 'flex', gap: '12px', fontSize: '11px' }}>
          {data.interact_info?.liked_count > 0 && (
            <span>❤️ {data.interact_info.liked_count}</span>
          )}
          {data.interact_info?.collected_count > 0 && (
            <span>⭐ {data.interact_info.collected_count}</span>
          )}
          {data.interact_info?.comment_count > 0 && (
            <span>💬 {data.interact_info.comment_count}</span>
          )}
        </div>
      </div>

      {/* 完整正文内容 */}
      {data.body_text && (
        <div style={{
          padding: '8px',
          background: 'white',
          borderRadius: '4px',
          marginBottom: '12px',
          fontSize: '11px',
          lineHeight: '1.5',
          border: '1px solid #fbbf24',
          whiteSpace: 'pre-wrap',
          wordBreak: 'break-word',
        }}>
          {data.body_text}
        </div>
      )}

      {/* AI分析 - 左右分栏 */}
      {data.extraction && data.extraction.images && (
        <div style={{
          display: 'flex',
          flexDirection: 'column',
          gap: '12px',
        }}>
          {data.extraction.images.map((img, idx) => (
            <div
              key={idx}
              style={{
                display: 'flex',
                flexDirection: 'row',
                gap: '16px',
                padding: '10px',
                background: 'white',
                borderRadius: '4px',
                border: '1px solid #d97706',
                alignItems: 'flex-start',
              }}
            >
              {/* 左侧：OCR提取文字 */}
              <div style={{
                flex: '1',  // 1/3宽度
                minWidth: '0',
              }}>
                <div style={{
                  fontSize: '11px',
                  fontWeight: 'bold',
                  color: '#92400e',
                  marginBottom: '6px',
                }}>
                  📝 图片 {idx + 1}/{data.extraction.images.length}
                </div>

                {img.extract_text && (
                  <div style={{
                    fontSize: '11px',
                    color: '#1f2937',
                    lineHeight: '1.6',
                    padding: '8px',
                    background: '#fef9e7',
                    borderRadius: '3px',
                    borderLeft: '3px solid #f39c12',
                    wordBreak: 'break-word',
                  }}>
                    <div style={{
                      fontSize: '10px',
                      fontWeight: 'bold',
                      color: '#d97706',
                      marginBottom: '4px',
                    }}>
                      【提取文字】
                    </div>
                    {img.extract_text}
                  </div>
                )}
              </div>

              {/* 右侧：缩略图 + 描述 */}
              <div style={{
                flex: '2',  // 2/3宽度
                display: 'flex',
                flexDirection: 'column',
                gap: '8px',
                minWidth: '200px',
              }}>
                {/* 缩略图 */}
                {data.image_list && data.image_list[idx] && (
                  <img
                    src={(data.image_list[idx].image_url || data.image_list[idx])}
                    alt={'图片' + (idx + 1)}
                    style={{
                      width: '100%',
                      height: 'auto',
                      maxHeight: '180px',
                      objectFit: 'contain',
                      borderRadius: '4px',
                      border: '1px solid #d97706',
                      cursor: 'pointer',
                    }}
                    onError={(e) => {
                      e.target.style.display = 'none';
                    }}
                  />
                )}

                {/* 描述文字（完整展示） */}
                {img.description && (
                  <div
                    style={{
                      fontSize: '10px',
                      color: '#78350f',
                      lineHeight: '1.5',
                      wordBreak: 'break-word',
                      padding: '8px',
                      background: '#fef9e7',
                      borderRadius: '3px',
                      border: '1px solid #f39c12',
                    }}
                  >
                    <div style={{
                      fontSize: '9px',
                      fontWeight: 'bold',
                      color: '#d97706',
                      marginBottom: '4px',
                    }}>
                      【图片描述】
                    </div>
                    {img.description}
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>
      )}

      {/* 查看原帖链接 */}
      {data.note_url && (
        <div style={{ marginTop: '8px', fontSize: '10px' }}>
          <a
            href={data.note_url}
            target="_blank"
            rel="noopener noreferrer"
            style={{ color: '#92400e', textDecoration: 'underline' }}
          >
            🔗 查看原帖
          </a>
        </div>
      )}

      <Handle
        type="source"
        position={Position.Right}
        style={{ background: '#fbbf24', width: 8, height: 8 }}
      />
    </div>
  );
}

const nodeTypes = {
  query: QueryNode,
  note: NoteNode,
  post: NoteNode,  // 帖子节点使用 NoteNode 组件渲染
  analysis: AnalysisNode,
};

// 根据 score 获取颜色
function getScoreColor(score) {
  if (score >= 0.7) return '#10b981'; // 绿色 - 高分
  if (score >= 0.4) return '#f59e0b'; // 橙色 - 中分
  return '#ef4444'; // 红色 - 低分
}

// 截断文本，保留头尾，中间显示省略号
function truncateMiddle(text, maxLength = 20) {
  if (!text || text.length <= maxLength) return text;
  const headLength = Math.ceil(maxLength * 0.4);
  const tailLength = Math.floor(maxLength * 0.4);
  const head = text.substring(0, headLength);
  const tail = text.substring(text.length - tailLength);
  return \`\${head}...\${tail}\`;
}

// 根据策略获取颜色
// 智能提取主要策略的辅助函数
function getPrimaryStrategy(nodeData) {
  // 优先级1: 使用 primaryStrategy 字段
  if (nodeData.primaryStrategy) {
    return nodeData.primaryStrategy;
  }

  // 优先级2: 从 occurrences 数组中获取最新的策略
  if (nodeData.occurrences && Array.isArray(nodeData.occurrences) && nodeData.occurrences.length > 0) {
    const latestOccurrence = nodeData.occurrences[nodeData.occurrences.length - 1];
    if (latestOccurrence && latestOccurrence.strategy) {
      return latestOccurrence.strategy;
    }
  }

  // 优先级3: 拆分组合策略字符串，取第一个
  if (nodeData.strategy && typeof nodeData.strategy === 'string') {
    const strategies = nodeData.strategy.split(' + ');
    if (strategies.length > 0 && strategies[0]) {
      return strategies[0].trim();
    }
  }

  // 默认返回原始strategy或未知
  return nodeData.strategy || '未知';
}

function getStrategyColor(strategy) {
  const strategyColors = {
    '初始分词': '#10b981',
    '调用sug': '#06b6d4',
    '同义改写': '#f59e0b',
    '加词': '#3b82f6',
    '抽象改写': '#8b5cf6',
    '基于部分匹配改进': '#ec4899',
    '结果分支-抽象改写': '#a855f7',
    '结果分支-同义改写': '#fb923c',
    // v6.1.2.8 新增策略
    '原始问题': '#6b21a8',
    '来自分词': '#10b981',
    '加词生成': '#ef4444',
    '建议词': '#06b6d4',
    '执行搜索': '#8b5cf6',
    // 添加简化版本的策略映射
    '分词': '#10b981',
    '推荐词': '#06b6d4',
  };
  return strategyColors[strategy] || '#9ca3af';
}

// 树节点组件
function TreeNode({ node, level, children, isCollapsed, onToggle, isSelected, onSelect }) {
  const hasChildren = children && children.length > 0;
  const score = node.data.score ? parseFloat(node.data.score) : 0;
  const strategy = getPrimaryStrategy(node.data);  // 使用智能提取函数
  const strategyColor = getStrategyColor(strategy);
  const nodeActualType = node.data.nodeType || node.type; // 获取实际节点类型
  const isDomainCombination = nodeActualType === 'domain_combination';

  let sourceSummary = '';
  if (isDomainCombination && Array.isArray(node.data.source_word_details) && node.data.source_word_details.length > 0) {
    const summaryParts = [];
    node.data.source_word_details.forEach((detail) => {
      const words = Array.isArray(detail.words) ? detail.words : [];
      const wordTexts = [];
      words.forEach((w) => {
        const numericScore = typeof w.score === 'number' ? w.score : parseFloat(w.score || '0');
        const formattedScore = Number.isFinite(numericScore) ? numericScore.toFixed(2) : '0.00';
        wordTexts.push(w.text + ' (' + formattedScore + ')');
      });
      if (wordTexts.length > 0) {
        const segmentLabel = detail.segment_type ? '[' + detail.segment_type + '] ' : '';
        summaryParts.push(segmentLabel + wordTexts.join(' + '));
      }
    });
    sourceSummary = summaryParts.join(' | ');
  }

  // 计算字体颜色：根据分数提升幅度判断
  let fontColor = '#374151'; // 默认颜色
  if (node.type === 'note') {
    const evaluatorVersion = node.data.evaluator_version || '';

    if (evaluatorVersion === 'v3.0') {
      // V3评估：基于is_knowledge, is_content_knowledge和final_score判断颜色
      const isKnowledge = node.data.is_knowledge;
      const isContentKnowledge = node.data.is_content_knowledge;
      const finalScore = node.data.final_score;

      if (!isKnowledge || !isContentKnowledge) {
        fontColor = '#ef4444'; // 红色 - 非知识或非内容知识
      } else if (finalScore !== null && finalScore !== undefined) {
        if (finalScore >= 60) {
          fontColor = '#22c55e'; // 绿色 - 内容知识且高分
        } else {
          fontColor = '#eab308'; // 黄色 - 内容知识但分数偏低
        }
      }
    } else {
      // V2评估：基于知识得分和相关性得分判断颜色
      const knowledgeScore = node.data.knowledge_score;
      const relevanceScore = node.data.post_relevance_score;

      if (knowledgeScore != null && relevanceScore != null) {
        if (knowledgeScore <= 40) {
          fontColor = '#ef4444'; // 红色 - 知识得分低
        } else if (knowledgeScore > 40 && relevanceScore > 40) {
          fontColor = '#22c55e'; // 绿色 - 知识和相关性都高
        } else {
          fontColor = '#eab308'; // 黄色 - 知识得分高但相关性低
        }
      } else {
        // V1兼容：如果没有V2评估数据，使用matchLevel判断
        fontColor = node.data.matchLevel === 'unsatisfied' ? '#ef4444' : '#374151';
      }
    }
  } else if (node.data.seed_score !== undefined) {
    const parentScore = parseFloat(node.data.seed_score);
    const gain = score - parentScore;
    fontColor = gain >= 0.05 ? '#16a34a' : '#ef4444';
  } else if (node.data.isSelected === false) {
    fontColor = '#ef4444';
  }

  return (
    <div style={{ marginLeft: level * 12 + 'px', marginBottom: '8px' }}>
      <div
        style={{
          padding: '6px 8px',
          borderRadius: '4px',
          cursor: 'pointer',
          background: 'transparent',
          border: isSelected ? '1px solid #3b82f6' : '1px solid transparent',
          display: 'flex',
          alignItems: 'center',
          gap: '6px',
          transition: 'all 0.2s ease',
          position: 'relative',
          overflow: 'visible',
        }}
        onMouseEnter={(e) => {
          if (!isSelected) e.currentTarget.style.background = '#f9fafb';
        }}
        onMouseLeave={(e) => {
          if (!isSelected) e.currentTarget.style.background = 'transparent';
        }}
      >
        {/* 策略类型竖线 */}
        <div style={{
          width: '3px',
          height: '20px',
          background: strategyColor,
          borderRadius: '2px',
          flexShrink: 0,
          position: 'relative',
          zIndex: 1,
        }} />

        {hasChildren && (
          <span
            style={{
              fontSize: '10px',
              color: '#6b7280',
              cursor: 'pointer',
              width: '16px',
              textAlign: 'center',
              position: 'relative',
              zIndex: 1,
            }}
            onClick={(e) => {
              e.stopPropagation();
              onToggle();
            }}
          >
            {isCollapsed ? '▶' : '▼'}
          </span>
        )}
        {!hasChildren && <span style={{ width: '16px', position: 'relative', zIndex: 1 }}></span>}

        <div
          style={{
            flex: 1,
            fontSize: '12px',
            color: '#374151',
            position: 'relative',
            zIndex: 1,
            minWidth: 0,
            display: 'flex',
            flexDirection: 'column',
            gap: '4px',
          }}
          onClick={onSelect}
        >
          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
          }}>
            {/* 文本标题 - 左侧 */}
            <div style={{
              fontWeight: level === 0 ? '600' : '400',
              flex: 1,
              minWidth: 0,
              color: node.data.scoreColor || fontColor,
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
            }}
            title={node.data.title || node.id}
            >
              {node.data.title || node.id}
            </div>

            {/* 域标识 - 右侧，挨着分数，优先显示域类型，否则显示域索引或域字符串，但domain_combination节点不显示 */}
            {(node.data.domain_type || node.data.domains_str || (node.data.domain_index !== null && node.data.domain_index !== undefined)) && nodeActualType !== 'domain_combination' && (
              <span style={{
                fontSize: '12px',
                color: '#fff',
                background: '#6366f1',
                padding: '2px 5px',
                borderRadius: '3px',
                flexShrink: 0,
                fontWeight: '600',
                marginLeft: '4px',
              }}
              title={
                node.data.domain_type ? '域: ' + node.data.domain_type + ' (D' + node.data.domain_index + ')' :
                node.data.domains_str ? '域: ' + node.data.domains_str :
                '域 D' + node.data.domain_index
              }
              >
                {node.data.domain_type || node.data.domains_str || ('D' + node.data.domain_index)}
              </span>
            )}

            {node.data.is_suggestion && node.data.suggestion_label && (
              <span style={{
                fontSize: '12px',
                color: '#fff',
                background: '#8b5cf6',
                padding: '2px 5px',
                borderRadius: '3px',
                flexShrink: 0,
                fontWeight: '600',
              }}
              >
                {node.data.suggestion_label}
              </span>
            )}

            {/* 类型标签 - 显示在右侧靠近分数，蓝色背景 */}
            {node.data.type_label && (
              <span style={{
                fontSize: '12px',
                color: '#fff',
                background: '#6366f1',
                padding: '2px 5px',
                borderRadius: '3px',
                flexShrink: 0,
                fontWeight: '600',
              }}
              title={'类型: ' + node.data.type_label}
              >
                {node.data.type_label}
              </span>
            )}

            {/* 分数显示 - 步骤和轮次节点不显示分数 */}
            {nodeActualType !== 'step' && nodeActualType !== 'round' && (
              <span style={{
                fontSize: '11px',
                color: '#6b7280',
                fontWeight: '500',
                flexShrink: 0,
                minWidth: '35px',
                textAlign: 'right',
              }}>
                {node.type === 'note' && node.data.evaluator_version === 'v3.0' && node.data.final_score !== null && node.data.final_score !== undefined
                  ? node.data.final_score.toFixed(1)
                  : score.toFixed(2)}
              </span>
            )}
          </div>

          {/* V3评估信息行 - 仅对note类型且有V3评估数据显示 */}
          {node.type === 'note' && node.data.evaluator_version === 'v3.0' && (
            <div style={{ fontSize: '10px', color: '#2563eb', marginTop: '2px', display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
              <span style={{ fontWeight: '600', color: '#2563eb' }}>评估结论:</span>
              <span style={{ color: node.data.is_knowledge ? '#16a34a' : '#dc2626', fontWeight: '500' }}>
                {node.data.is_knowledge ? '✓ 是知识' : '✗ 非知识'}
              </span>
              {node.data.is_content_knowledge !== null && node.data.is_content_knowledge !== undefined && (
                <span style={{ color: node.data.is_content_knowledge ? '#16a34a' : '#dc2626', fontWeight: '500' }}>
                  | {node.data.is_content_knowledge ? '✓ 是内容知识' : '✗ 非内容知识'}
                </span>
              )}
              {node.data.is_content_knowledge && node.data.final_score !== null && node.data.final_score !== undefined && (
                <>
                  <span style={{ fontWeight: '500', color: '#2563eb' }}>| {node.data.match_level}</span>
                  <span style={{ fontWeight: '600', color: node.data.final_score >= 60 ? '#16a34a' : '#ea580c' }}>
                    | {node.data.final_score.toFixed(1)}分
                  </span>
                </>
              )}
            </div>
          )}

          {/* 域组合的来源词得分（树状视图，右对齐） */}
          {isDomainCombination && sourceSummary && (
            <div style={{
              fontSize: '10px',
              color: '#2563eb',
              lineHeight: '1.4',
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'flex-end',
              gap: '2px',
              textAlign: 'right',
            }}>
              {node.data.source_word_details.map((detail, idx) => {
                const words = Array.isArray(detail.words) ? detail.words : [];
                const summary = words.map((w) => {
                  const numericScore = typeof w.score === 'number' ? w.score : parseFloat(w.score || '0');
                  const formattedScore = Number.isFinite(numericScore) ? numericScore.toFixed(2) : '0.00';
                  return w.text + ' (' + formattedScore + ')';
                }).join(' + ');
                return (
                  <span key={idx} title={summary}>
                    {summary}
                  </span>
                );
              })}
            </div>
          )}

          {/* 分数下划线 - 步骤和轮次节点不显示 */}
          {nodeActualType !== 'step' && nodeActualType !== 'round' && (
            <div style={{
              width: (score * 100) + '%',
              height: '2px',
              background: getScoreColor(score),
              borderRadius: '1px',
            }} />
          )}
        </div>
      </div>

      {hasChildren && !isCollapsed && (
        <div>
          {children}
        </div>
      )}
    </div>
  );
}

// 使用 dagre 自动布局
function getLayoutedElements(nodes, edges, direction = 'LR') {
  console.log('🎯 Starting layout with dagre...');
  console.log('Input:', nodes.length, 'nodes,', edges.length, 'edges');

  // 检查 dagre 是否加载
  if (typeof window === 'undefined' || typeof window.dagre === 'undefined') {
    console.warn('⚠️ Dagre not loaded, using fallback layout');
    // 降级到简单布局
    const levelGroups = {};
    nodes.forEach(node => {
      const level = node.data.level || 0;
      if (!levelGroups[level]) levelGroups[level] = [];
      levelGroups[level].push(node);
    });

    Object.entries(levelGroups).forEach(([level, nodeList]) => {
      const x = parseInt(level) * 480;
      nodeList.forEach((node, index) => {
        node.position = { x, y: index * 260 };
        node.targetPosition = 'left';
        node.sourcePosition = 'right';
      });
    });

    return { nodes, edges };
  }

  try {
    const dagreGraph = new window.dagre.graphlib.Graph();
    dagreGraph.setDefaultEdgeLabel(() => ({}));

    const isHorizontal = direction === 'LR';
    dagreGraph.setGraph({
      rankdir: direction,
      nodesep: 800,   // 垂直间距 - 增加以适应更高的note节点（卡片高度2600px + 800px间距）
      ranksep: 400,  // 水平间距 - 增加以容纳更宽的节点
    });

    // 添加节点 - 根据节点类型设置不同的尺寸
    nodes.forEach((node) => {
      let nodeWidth = 320;
      let nodeHeight = 220;

      // note 节点有轮播图，需要更大的空间
      if (node.type === 'note') {
        nodeWidth = 360;
        nodeHeight = 2600;  // 更新以适应完整内容：1:1图片(880px) + 标题/原始问题/评估(500px) + 正文/AI提取(最多1200px)
      }
      // analysis 节点内容很多，需要更大的空间
      else if (node.type === 'analysis') {
        nodeWidth = 900;   // 宽度足够容纳左右分栏
        nodeHeight = 600;  // 高度足够容纳多张图片
      }

      dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
    });

    // 添加边
    edges.forEach((edge) => {
      dagreGraph.setEdge(edge.source, edge.target);
    });

    // 计算布局
    window.dagre.layout(dagreGraph);
    console.log('✅ Dagre layout completed');

    // 更新节点位置和 handle 位置
    nodes.forEach((node) => {
      const nodeWithPosition = dagreGraph.node(node.id);

      if (!nodeWithPosition) {
        console.warn('Node position not found for:', node.id);
        return;
      }

      node.targetPosition = isHorizontal ? 'left' : 'top';
      node.sourcePosition = isHorizontal ? 'right' : 'bottom';

      // 根据节点类型获取尺寸
      let nodeWidth = 320;
      let nodeHeight = 220;
      if (node.type === 'note') {
        nodeWidth = 360;
        nodeHeight = 2600;  // 与dagre布局参数保持一致
      }

      // 将 dagre 的中心点位置转换为 React Flow 的左上角位置
      node.position = {
        x: nodeWithPosition.x - nodeWidth / 2,
        y: nodeWithPosition.y - nodeHeight / 2,
      };
    });

    // 为同层级的 note 节点添加交错偏移，避免视觉重叠
    console.log('=== 开始交错偏移逻辑 ===');
    console.log('总节点数:', nodes.length);

    const noteNodes = nodes.filter(n => n.type === 'note');
    console.log('过滤后的 note 节点数:', noteNodes.length);

    if (noteNodes.length > 1) {
      // 输出排序前的位置
      console.log('排序前的 note 节点位置:');
      noteNodes.forEach((n, i) => {
        console.log('  [' + i + '] ' + n.id.substring(0, 40) + '... | type=' + n.type + ' | pos=(' + n.position.x.toFixed(0) + ', ' + n.position.y.toFixed(0) + ')');
      });

      // 按 Y 坐标排序
      noteNodes.sort((a, b) => a.position.y - b.position.y);

      console.log('排序后的 note 节点位置:');
      noteNodes.forEach((n, i) => {
        console.log('  [' + i + '] ' + n.id.substring(0, 40) + '... | pos=(' + n.position.x.toFixed(0) + ', ' + n.position.y.toFixed(0) + ')');
      });

      // 为相邻的 note 节点添加 X 方向的交错（3个位置：左、中、右）
      const baseX = noteNodes.length > 0 ? noteNodes[0].position.x : 0;
      const leftX = baseX - 1500;
      const centerX = baseX;
      const rightX = baseX + 1500;

      let appliedCount = 0;
      noteNodes.forEach((node, index) => {
        const oldX = node.position.x;
        const position = index % 3;

        if (position === 0) {
          node.position.x = leftX;
          console.log('  [' + index + '] 设置为左侧: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
        } else if (position === 1) {
          node.position.x = centerX;
          console.log('  [' + index + '] 设置为中间: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
        } else {
          node.position.x = rightX;
          console.log('  [' + index + '] 设置为右侧: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
        }
        appliedCount++;
      });

      console.log('总共应用了 ' + appliedCount + ' 次偏移');
    } else {
      console.log('note 节点数量 <= 1，不需要交错');
    }
    console.log('=== 交错偏移逻辑结束 ===');

    console.log('✅ Layout completed, sample node:', nodes[0]);
    return { nodes, edges };
  } catch (error) {
    console.error('❌ Error in dagre layout:', error);
    console.error('Error details:', error.message, error.stack);

    // 降级处理
    console.log('Using fallback layout...');
    const levelGroups = {};
    nodes.forEach(node => {
      const level = node.data.level || 0;
      if (!levelGroups[level]) levelGroups[level] = [];
      levelGroups[level].push(node);
    });

    Object.entries(levelGroups).forEach(([level, nodeList]) => {
      const x = parseInt(level) * 480;
      nodeList.forEach((node, index) => {
        node.position = { x, y: index * 260 };
        node.targetPosition = 'left';
        node.sourcePosition = 'right';
      });
    });

    return { nodes, edges };
  }
}

function transformData(data) {
  const nodes = [];
  const edges = [];

  const originalIdToCanvasId = {}; // 原始ID -> 画布ID的映射
  const canvasIdToNodeData = {}; // 避免重复创建相同的节点
  let analysisNodeCount = 0; // 用于给analysis节点添加X偏移

  // 🆕 获取原始问题（从root节点）
  const originalQuestion = data.nodes['root_o']?.query || '';

  // 创建节点
  Object.entries(data.nodes).forEach(([originalId, node]) => {
    // 统一处理所有类型的节点
    const nodeType = node.type || 'query';

    // 直接使用originalId作为canvasId，避免冲突
    const canvasId = originalId;

    originalIdToCanvasId[originalId] = canvasId;

    // 如果这个 canvasId 还没有创建过节点，则创建
    if (!canvasIdToNodeData[canvasId]) {
      canvasIdToNodeData[canvasId] = true;

      // 根据节点类型创建不同的数据结构
      if (nodeType === 'note' || nodeType === 'post') {
        nodes.push({
          id: canvasId,
          originalId: originalId,
          type: 'note',
          data: {
            title: node.query || node.title || '帖子',
            matchLevel: node.match_level,
            score: node.relevance_score ? node.relevance_score.toFixed(2) : '0.00',
            description: node.body_text || node.desc || '',
            isSelected: node.is_selected !== undefined ? node.is_selected : true,
            imageList: node.image_list || [],
            note_url: node.note_url || '',
            evaluationReason: node.evaluationReason || node.evaluation_reason || '',
            interact_info: node.interact_info || {},
            nodeType: nodeType,
            // 🆕 评估字段 (V2)
            // 知识评估
            is_knowledge: node.is_knowledge !== undefined ? node.is_knowledge : null,
            knowledge_reason: node.knowledge_reason || '',
            knowledge_score: node.knowledge_score !== undefined ? node.knowledge_score : null,
            knowledge_level: node.knowledge_level !== undefined ? node.knowledge_level : null,
            knowledge_evaluation: node.knowledge_evaluation || null,
            // 相关性评估
            post_relevance_score: node.post_relevance_score !== undefined ? node.post_relevance_score : null,
            relevance_level: node.relevance_level || '',
            relevance_reason: node.relevance_reason || '',
            relevance_conclusion: node.relevance_conclusion || '',
            relevance_evaluation: node.relevance_evaluation || null,
            // 🆕 评估字段 (V3)
            is_content_knowledge: node.is_content_knowledge !== undefined ? node.is_content_knowledge : null,
            purpose_score: node.purpose_score !== undefined ? node.purpose_score : null,
            category_score: node.category_score !== undefined ? node.category_score : null,
            final_score: node.final_score !== undefined ? node.final_score : null,
            match_level: node.match_level || '',
            evaluator_version: node.evaluator_version || '',
            content_knowledge_evaluation: node.content_knowledge_evaluation || null,
            purpose_evaluation: node.purpose_evaluation || null,
            category_evaluation: node.category_evaluation || null,
            // 🆕 原始问题
            originalQuestion: originalQuestion
          },
          position: { x: 0, y: 0 },
        });
      } else if (nodeType === 'analysis') {
        // AI分析节点 - 添加X偏移避免叠加
        const xOffset = analysisNodeCount * 150; // 每个节点偏移150px
        analysisNodeCount++;

        nodes.push({
          id: canvasId,
          originalId: originalId,
          type: 'analysis',
          data: {
            query: node.query || '[AI分析]',
            note_id: node.note_id,
            note_url: node.note_url,
            title: node.title || '',
            body_text: node.body_text || '',
            interact_info: node.interact_info || {},
            extraction: node.extraction || null,
            image_list: node.image_list || [],
          },
          position: { x: xOffset, y: 0 },
        });
      } else {
        // query, seg, q, search, root 等节点
        let displayTitle = node.query || originalId;

        nodes.push({
          id: canvasId,
          originalId: originalId,
          type: 'query', // 使用 query 组件渲染所有非note节点
          data: {
            title: displayTitle,
            level: node.level || 0,
            score: node.relevance_score ? node.relevance_score.toFixed(2) : '0.00',
            strategy: node.strategy || '',
            parent: node.parent_query || '',
            isSelected: node.is_selected !== undefined ? node.is_selected : true,
            evaluationReason: node.evaluationReason || node.evaluation_reason || '',
            nodeType: nodeType, // 传递实际节点类型用于样式
            searchCount: node.search_count, // search 节点特有
            totalPosts: node.total_posts, // search 节点特有
            selectedWord: node.selected_word || '', // 加词节点特有 - 显示选择的词
            scoreColor: node.scoreColor || null,        // SUG节点的颜色标识
            parentQScore: node.parentQScore || 0,       // 父Q得分（用于调试）
            domain_index: node.domain_index !== undefined ? node.domain_index : null, // 域索引
            domain_type: node.domain_type || '', // 域类型（如"中心名词"、"核心动作"），只有Q节点有，segment节点不显示
            segment_type: node.segment_type || '', // segment类型（只有segment节点才有）
            type_label: node.type_label || '', // 类型标签
            domains: node.domains || [], // 域索引数组（domain_combination节点特有）
            domains_str: node.domains_str || '', // 域标识字符串（如"D0,D1"）
            from_segments: node.from_segments || [], // 来源segments（domain_combination节点特有）
            source_word_details: node.source_word_details || [], // 组合来源词及其得分
            source_scores: node.source_scores || [], // 扁平来源得分
            is_above_sources: node.is_above_sources || false, // 组合是否高于来源得分
            max_source_score: node.max_source_score !== undefined ? node.max_source_score : null, // 来源最高分
            item_type: node.item_type || '', // 构建下一轮节点来源类型
            is_suggestion: node.is_suggestion || false,
            suggestion_label: node.suggestion_label || '',
          },
          position: { x: 0, y: 0 },
        });
      }
    }
  });

  // 创建边 - 使用虚线样式，映射到画布ID
  data.edges.forEach((edge, index) => {
    const edgeColors = {
      '初始分词': '#10b981',
      '调用sug': '#06b6d4',
      '同义改写': '#f59e0b',
      '加词': '#3b82f6',
      '抽象改写': '#8b5cf6',
      '基于部分匹配改进': '#ec4899',
      '结果分支-抽象改写': '#a855f7',
      '结果分支-同义改写': '#fb923c',
      'query_to_note': '#ec4899',
    };

    const color = edgeColors[edge.strategy] || edgeColors[edge.edge_type] || '#d1d5db';
    const isNoteEdge = edge.edge_type === 'query_to_note';

    edges.push({
      id: \`edge-\${index}\`,
      source: originalIdToCanvasId[edge.from], // 使用画布ID
      target: originalIdToCanvasId[edge.to],   // 使用画布ID
      type: 'simplebezier', // 使用简单贝塞尔曲线
      animated: isNoteEdge,
      style: {
        stroke: color,
        strokeWidth: isNoteEdge ? 2.5 : 2,
        strokeDasharray: isNoteEdge ? '5,5' : '8,4',
      },
      markerEnd: {
        type: 'arrowclosed',
        color: color,
        width: 20,
        height: 20,
      },
    });
  });

  // 使用 dagre 自动计算布局 - 从左到右
  return getLayoutedElements(nodes, edges, 'LR');
}

function FlowContent() {
  // 画布使用简化数据
  const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
    console.log('🔍 Transforming data for canvas...');
    const result = transformData(data);
    console.log('✅ Canvas data:', result.nodes.length, 'nodes,', result.edges.length, 'edges');
    return result;
  }, []);

  // 目录使用完整数据（如果存在）
  const { nodes: fullNodes, edges: fullEdges } = useMemo(() => {
    if (data.fullData) {
      console.log('🔍 Transforming full data for tree directory...');
      const result = transformData(data.fullData);
      console.log('✅ Directory data:', result.nodes.length, 'nodes,', result.edges.length, 'edges');
      return result;
    }
    // 如果没有 fullData，使用简化数据
    return { nodes: initialNodes, edges: initialEdges };
  }, [initialNodes, initialEdges]);

  // 初始化：找出所有有子节点的节点，默认折叠（画布节点）
  const initialCollapsedNodes = useMemo(() => {
    const nodesWithChildren = new Set();
    initialEdges.forEach(edge => {
      nodesWithChildren.add(edge.source);
    });
    // 排除根节点（level 0），让根节点默认展开
    const rootNode = initialNodes.find(n => n.data.level === 0);
    if (rootNode) {
      nodesWithChildren.delete(rootNode.id);
    }
    return nodesWithChildren;
  }, [initialNodes, initialEdges]);

  // 树节点的折叠状态需要在树构建后初始化
  const [collapsedNodes, setCollapsedNodes] = useState(() => initialCollapsedNodes);
  const [collapsedTreeNodes, setCollapsedTreeNodes] = useState(new Set());
  const [selectedNodeId, setSelectedNodeId] = useState(null);
  const [hiddenNodes, setHiddenNodes] = useState(new Set()); // 用户手动隐藏的节点
  const [focusMode, setFocusMode] = useState(false); // 全局聚焦模式，默认关闭
  const [focusedNodeId, setFocusedNodeId] = useState(null); // 单独聚焦的节点ID
  const [sidebarWidth, setSidebarWidth] = useState(400); // 左侧目录宽度
  const [isResizing, setIsResizing] = useState(false); // 是否正在拖拽调整宽度
  const [activeTab, setActiveTab] = useState('directory'); // Tab切换: 'directory' | 'cleaned'
  const [selectedPost, setSelectedPost] = useState(null); // 选中的清洗帖子

  // 拖拽调整侧边栏宽度的处理逻辑
  const handleMouseDown = useCallback(() => {
    setIsResizing(true);
  }, []);

  useEffect(() => {
    if (!isResizing) return;

    const handleMouseMove = (e) => {
      const newWidth = e.clientX;
      // 限制宽度范围：300px - 700px
      if (newWidth >= 300 && newWidth <= 700) {
        setSidebarWidth(newWidth);
      }
    };

    const handleMouseUp = () => {
      setIsResizing(false);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [isResizing]);

  // 获取 React Flow 实例以控制画布
  const { setCenter, fitView } = useReactFlow();

  // 获取某个节点的所有后代节点ID
  const getDescendants = useCallback((nodeId) => {
    const descendants = new Set();
    const queue = [nodeId];

    while (queue.length > 0) {
      const current = queue.shift();
      initialEdges.forEach(edge => {
        if (edge.source === current && !descendants.has(edge.target)) {
          descendants.add(edge.target);
          queue.push(edge.target);
        }
      });
    }

    return descendants;
  }, [initialEdges]);

  // 获取直接父节点
  const getDirectParents = useCallback((nodeId) => {
    const parents = [];
    initialEdges.forEach(edge => {
      if (edge.target === nodeId) {
        parents.push(edge.source);
      }
    });
    return parents;
  }, [initialEdges]);

  // 获取直接子节点
  const getDirectChildren = useCallback((nodeId) => {
    const children = [];
    initialEdges.forEach(edge => {
      if (edge.source === nodeId) {
        children.push(edge.target);
      }
    });
    return children;
  }, [initialEdges]);

  // 切换节点折叠状态
  const toggleNodeCollapse = useCallback((nodeId) => {
    setCollapsedNodes(prev => {
      const newSet = new Set(prev);
      const descendants = getDescendants(nodeId);

      if (newSet.has(nodeId)) {
        // 展开：移除此节点，但保持其他折叠的节点
        newSet.delete(nodeId);
      } else {
        // 折叠：添加此节点
        newSet.add(nodeId);
      }

      return newSet;
    });
  }, [getDescendants]);

  // 过滤可见的节点和边，并重新计算布局
  const { nodes, edges } = useMemo(() => {
    const nodesToHide = new Set();

    // 判断使用哪个节点ID进行聚焦：优先使用单独聚焦的节点，否则使用全局聚焦模式的选中节点
    const effectiveFocusNodeId = focusedNodeId || (focusMode ? selectedNodeId : null);

    // 聚焦模式：只显示聚焦节点、其父节点和直接子节点
    if (effectiveFocusNodeId) {
      const visibleInFocus = new Set([effectiveFocusNodeId]);

      // 添加所有父节点
      initialEdges.forEach(edge => {
        if (edge.target === effectiveFocusNodeId) {
          visibleInFocus.add(edge.source);
        }
      });

      // 添加所有直接子节点
      initialEdges.forEach(edge => {
        if (edge.source === effectiveFocusNodeId) {
          visibleInFocus.add(edge.target);
        }
      });

      // 隐藏不在聚焦范围内的节点
      initialNodes.forEach(node => {
        if (!visibleInFocus.has(node.id)) {
          nodesToHide.add(node.id);
        }
      });
    } else {
      // 非聚焦模式：使用原有的折叠逻辑
      // 收集所有被折叠节点的后代
      collapsedNodes.forEach(collapsedId => {
        const descendants = getDescendants(collapsedId);
        descendants.forEach(id => nodesToHide.add(id));
      });
    }

    // 添加用户手动隐藏的节点
    hiddenNodes.forEach(id => nodesToHide.add(id));

    const visibleNodes = initialNodes
      .filter(node => !nodesToHide.has(node.id))
      .map(node => ({
        ...node,
        data: {
          ...node.data,
          isCollapsed: collapsedNodes.has(node.id),
          hasChildren: initialEdges.some(e => e.source === node.id),
          onToggleCollapse: () => toggleNodeCollapse(node.id),
          onHideSelf: () => {
            setHiddenNodes(prev => {
              const newSet = new Set(prev);
              newSet.add(node.id);
              return newSet;
            });
          },
          onFocus: () => {
            // 切换聚焦状态
            if (focusedNodeId === node.id) {
              setFocusedNodeId(null); // 如果已经聚焦，则取消聚焦
            } else {
              // 先取消之前的聚焦，然后聚焦到当前节点
              setFocusedNodeId(node.id);

              // 延迟聚焦视图到该节点
              setTimeout(() => {
                fitView({
                  nodes: [{ id: node.id }],
                  duration: 800,
                  padding: 0.3,
                });
              }, 100);
            }
          },
          isFocused: focusedNodeId === node.id,
          isHighlighted: selectedNodeId === node.id,
        }
      }));

    const visibleEdges = initialEdges.filter(
      edge => !nodesToHide.has(edge.source) && !nodesToHide.has(edge.target)
    );

    // 重新计算布局 - 只对可见节点
    if (typeof window !== 'undefined' && typeof window.dagre !== 'undefined') {
      try {
        const dagreGraph = new window.dagre.graphlib.Graph();
        dagreGraph.setDefaultEdgeLabel(() => ({}));

        dagreGraph.setGraph({
          rankdir: 'LR',
          nodesep: 800,   // 与static layout保持一致，确保不重叠
          ranksep: 400,  // 增加水平间距
        });

        visibleNodes.forEach((node) => {
          let nodeWidth = 320;
          let nodeHeight = 220;

          // note 节点有轮播图，需要更大的空间
          if (node.type === 'note') {
            nodeWidth = 360;
            nodeHeight = 2600;  // 与static layout保持一致
          }

          dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
        });

        visibleEdges.forEach((edge) => {
          dagreGraph.setEdge(edge.source, edge.target);
        });

        window.dagre.layout(dagreGraph);

        visibleNodes.forEach((node) => {
          const nodeWithPosition = dagreGraph.node(node.id);
          if (nodeWithPosition) {
            // 根据节点类型获取对应的尺寸
            let nodeWidth = 320;
            let nodeHeight = 220;
            if (node.type === 'note') {
              nodeWidth = 360;
              nodeHeight = 2600;  // 与static layout保持一致
            }

            node.position = {
              x: nodeWithPosition.x - nodeWidth / 2,
              y: nodeWithPosition.y - nodeHeight / 2,
            };
            node.targetPosition = 'left';
            node.sourcePosition = 'right';
          }
        });

        // 为同层级的 note 节点添加交错偏移，避免视觉重叠
        console.log('[DYNAMIC LAYOUT] 开始应用交错偏移');
        const noteNodesToStagger = visibleNodes.filter(n => n.type === 'note');
        console.log('[DYNAMIC LAYOUT] note 节点数:', noteNodesToStagger.length);

        if (noteNodesToStagger.length > 1) {
          // 按 Y 坐标排序
          noteNodesToStagger.sort((a, b) => a.position.y - b.position.y);

          console.log('[DYNAMIC LAYOUT] 排序后准备应用偏移:');
          noteNodesToStagger.forEach((n, i) => {
            console.log('  [' + i + '] ' + n.id.substring(0, 40) + '... | pos=(' + n.position.x.toFixed(0) + ', ' + n.position.y.toFixed(0) + ')');
          });

          // 为相邻的 note 节点添加 X 方向的交错（3个位置：左、中、右）
          const baseX = noteNodesToStagger.length > 0 ? noteNodesToStagger[0].position.x : 0;
          const leftX = baseX - 1500;
          const centerX = baseX;
          const rightX = baseX + 1500;

          let appliedCount = 0;
          noteNodesToStagger.forEach((node, index) => {
            const oldX = node.position.x;
            const position = index % 3;

            if (position === 0) {
              node.position.x = leftX;
              console.log('[DYNAMIC LAYOUT] [' + index + '] 设置为左侧: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
            } else if (position === 1) {
              node.position.x = centerX;
              console.log('[DYNAMIC LAYOUT] [' + index + '] 设置为中间: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
            } else {
              node.position.x = rightX;
              console.log('[DYNAMIC LAYOUT] [' + index + '] 设置为右侧: X ' + oldX.toFixed(0) + ' → ' + node.position.x.toFixed(0));
            }
            appliedCount++;
          });

          console.log('[DYNAMIC LAYOUT] 总共应用了 ' + appliedCount + ' 次偏移');
        }

        console.log('✅ Dynamic layout recalculated for', visibleNodes.length, 'visible nodes');
      } catch (error) {
        console.error('❌ Error in dynamic layout:', error);
      }
    }

    return { nodes: visibleNodes, edges: visibleEdges };
  }, [initialNodes, initialEdges, collapsedNodes, hiddenNodes, focusMode, focusedNodeId, getDescendants, toggleNodeCollapse, selectedNodeId]);

  // 构建树形结构 - 允许一个节点有多个父节点
  // 为目录构建树（使用完整数据）
  const buildTree = useCallback(() => {
    // 使用完整数据构建目录树
    const nodeMap = new Map();
    fullNodes.forEach(node => {
      nodeMap.set(node.id, node);
    });

    // 为每个节点创建树节点的副本（允许多次出现）
    const createTreeNode = (nodeId, pathKey) => {
      const node = nodeMap.get(nodeId);
      if (!node) return null;

      return {
        ...node,
        treeKey: pathKey, // 唯一的树路径key，用于React key
        children: []
      };
    };

    // 构建父子关系映射：记录每个节点的所有父节点，去重边
    const parentToChildren = new Map();
    const childToParents = new Map();

    fullEdges.forEach(edge => {
      // 记录父->子关系（去重：同一个父节点到同一个子节点只记录一次）
      if (!parentToChildren.has(edge.source)) {
        parentToChildren.set(edge.source, []);
      }
      const children = parentToChildren.get(edge.source);
      if (!children.includes(edge.target)) {
        children.push(edge.target);
      }

      // 记录子->父关系（用于判断是否有多个父节点，也去重）
      if (!childToParents.has(edge.target)) {
        childToParents.set(edge.target, []);
      }
      const parents = childToParents.get(edge.target);
      if (!parents.includes(edge.source)) {
        parents.push(edge.source);
      }
    });

    // 递归构建树
    const buildSubtree = (nodeId, pathKey, visitedInPath) => {
      // 避免循环引用：如果当前路径中已经访问过这个节点，跳过
      if (visitedInPath.has(nodeId)) {
        return null;
      }

      const treeNode = createTreeNode(nodeId, pathKey);
      if (!treeNode) return null;

      const newVisitedInPath = new Set(visitedInPath);
      newVisitedInPath.add(nodeId);

      const children = parentToChildren.get(nodeId) || [];
      treeNode.children = children
        .map((childId, index) => buildSubtree(childId, pathKey + '-' + childId + '-' + index, newVisitedInPath))
        .filter(child => child !== null);

      return treeNode;
    };

    // 找出所有根节点（没有入边的节点）
    const hasParent = new Set();
    fullEdges.forEach(edge => {
      hasParent.add(edge.target);
    });

    const roots = [];
    fullNodes.forEach((node, index) => {
      if (!hasParent.has(node.id)) {
        const treeNode = buildSubtree(node.id, 'root-' + node.id + '-' + index, new Set());
        if (treeNode) roots.push(treeNode);
      }
    });

    return roots;
  }, [fullNodes, fullEdges]);

  const treeRoots = useMemo(() => buildTree(), [buildTree]);

  // 生成树形文本结构（使用完整数据）
  const generateTreeText = useCallback(() => {
    const lines = [];

    // 递归生成树形文本
    const traverse = (nodes, prefix = '', isLast = true, depth = 0) => {
      nodes.forEach((node, index) => {
        const isLastNode = index === nodes.length - 1;
        const nodeData = fullNodes.find(n => n.id === node.id)?.data || {};
        const nodeType = nodeData.nodeType || node.data?.nodeType || 'unknown';
        const title = nodeData.title || node.data?.title || node.id;

        // 优先从node.data获取score，然后从nodeData获取
        let score = null;
        if (node.data?.score !== undefined && node.data?.score !== null) {
          score = node.data.score;
        } else if (node.data?.relevance_score !== undefined && node.data?.relevance_score !== null) {
          score = node.data.relevance_score;
        } else if (nodeData.score !== undefined && nodeData.score !== null) {
          score = nodeData.score;
        } else if (nodeData.relevance_score !== undefined && nodeData.relevance_score !== null) {
          score = nodeData.relevance_score;
        }

        const strategy = nodeData.strategy || node.data?.strategy || '';

        // 构建当前行 - score可能是数字或字符串，step/round节点不显示分数
        const connector = isLastNode ? '└─' : '├─';
        let scoreText = '';
        if (nodeType !== 'step' && nodeType !== 'round' && score !== null && score !== undefined) {
          // score可能已经是字符串格式（如 "0.05"），也可能是数字
          const scoreStr = typeof score === 'number' ? score.toFixed(2) : score;
          scoreText = \` (分数: \${scoreStr})\`;
        }
        const strategyText = strategy ? \` [\${strategy}]\` : '';

        lines.push(\`\${prefix}\${connector} \${title}\${scoreText}\${strategyText}\`);

        // 递归处理子节点
        if (node.children && node.children.length > 0) {
          const childPrefix = prefix + (isLastNode ? '   ' : '│  ');
          traverse(node.children, childPrefix, isLastNode, depth + 1);
        }
      });
    };

    // 添加标题
    const rootNode = fullNodes.find(n => n.data?.level === 0);
    if (rootNode) {
      lines.push(\`📊 查询扩展树形结构\`);
      lines.push(\`原始问题: \${rootNode.data.title || rootNode.data.query}\`);
      lines.push('');
    }

    traverse(treeRoots);

    return lines.join('\\n');
  }, [treeRoots, fullNodes]);

  // 复制树形结构到剪贴板
  const copyTreeToClipboard = useCallback(async () => {
    try {
      const treeText = generateTreeText();
      await navigator.clipboard.writeText(treeText);
      alert('✅ 树形结构已复制到剪贴板！');
    } catch (err) {
      console.error('复制失败:', err);
      alert('❌ 复制失败，请手动复制');
    }
  }, [generateTreeText]);

  // 初始化树节点折叠状态
  useEffect(() => {
    const getAllTreeKeys = (nodes) => {
      const keys = new Set();
      const traverse = (node) => {
        if (node.children && node.children.length > 0) {
          // 排除根节点
          if (node.data.level !== 0) {
            keys.add(node.treeKey);
          }
          node.children.forEach(traverse);
        }
      };
      nodes.forEach(traverse);
      return keys;
    };

    setCollapsedTreeNodes(getAllTreeKeys(treeRoots));
  }, [treeRoots]);

  // 映射完整节点ID到画布简化节点ID
  const mapTreeNodeToCanvasNode = useCallback((treeNodeId) => {
    // 如果是简化模式，需要映射
    if (data.fullData) {
      // 从完整数据中找到节点
      const fullNode = fullNodes.find(n => n.id === treeNodeId);
      if (!fullNode) return treeNodeId;

      // 根据节点类型和文本找到画布上的简化节点
      const nodeText = fullNode.data.title || fullNode.data.query;
      const nodeType = fullNode.data.nodeType || fullNode.type;

      // Query类节点：找 query_xxx
      if (['q', 'seg', 'sug', 'add_word', 'query'].includes(nodeType)) {
        const canvasNode = initialNodes.find(n =>
          (n.data.title === nodeText || n.data.query === nodeText) &&
          ['query'].includes(n.type)
        );
        return canvasNode ? canvasNode.id : treeNodeId;
      }

      // Post节点：按note_id查找
      if (nodeType === 'post' || nodeType === 'note') {
        const noteId = fullNode.data.note_id;
        if (noteId) {
          const canvasNode = initialNodes.find(n => n.data.note_id === noteId);
          return canvasNode ? canvasNode.id : treeNodeId;
        }
      }

      // 其他节点类型（Round/Step等）：直接返回
      return treeNodeId;
    }

    // 非简化模式，直接返回
    return treeNodeId;
  }, [data.fullData, fullNodes, initialNodes]);

  const renderTree = useCallback((treeNodes, level = 0) => {
    return treeNodes.map(node => {
      // 使用 treeKey 来区分树中的不同实例
      const isCollapsed = collapsedTreeNodes.has(node.treeKey);
      const isSelected = selectedNodeId === node.id;

      return (
        <TreeNode
          key={node.treeKey}
          node={node}
          level={level}
          isCollapsed={isCollapsed}
          isSelected={isSelected}
          onToggle={() => {
            setCollapsedTreeNodes(prev => {
              const newSet = new Set(prev);
              if (newSet.has(node.treeKey)) {
                newSet.delete(node.treeKey);
              } else {
                newSet.add(node.treeKey);
              }
              return newSet;
            });
          }}
          onSelect={() => {
            // 将目录节点ID映射到画布节点ID
            const treeNodeId = node.id;
            const canvasNodeId = mapTreeNodeToCanvasNode(treeNodeId);

            // 检查画布上是否存在这个节点
            const canvasNodeExists = initialNodes.some(n => n.id === canvasNodeId);
            if (!canvasNodeExists) {
              console.warn(\`节点 \${canvasNodeId} 在画布上不存在（可能被简化了）\`);
              return;
            }

            const nodeId = canvasNodeId;

            // 展开所有祖先节点
            const ancestorIds = [nodeId];
            const findAncestors = (id) => {
              initialEdges.forEach(edge => {
                if (edge.target === id && !ancestorIds.includes(edge.source)) {
                  ancestorIds.push(edge.source);
                  findAncestors(edge.source);
                }
              });
            };
            findAncestors(nodeId);

            // 如果节点或其祖先被隐藏，先恢复它们
            setHiddenNodes(prev => {
              const newSet = new Set(prev);
              ancestorIds.forEach(id => newSet.delete(id));
              return newSet;
            });

            setSelectedNodeId(nodeId);

            // 获取选中节点的直接子节点
            const childrenIds = [];
            initialEdges.forEach(edge => {
              if (edge.source === nodeId) {
                childrenIds.push(edge.target);
              }
            });

            setCollapsedNodes(prev => {
              const newSet = new Set(prev);
              // 展开所有祖先节点
              ancestorIds.forEach(id => newSet.delete(id));
              // 展开选中节点本身
              newSet.delete(nodeId);
              // 展开选中节点的直接子节点
              childrenIds.forEach(id => newSet.delete(id));
              return newSet;
            });

            // 延迟聚焦，等待节点展开和布局重新计算
            setTimeout(() => {
              fitView({
                nodes: [{ id: nodeId }],
                duration: 800,
                padding: 0.3,
              });
            }, 300);
          }}
        >
          {node.children && node.children.length > 0 && renderTree(node.children, level + 1)}
        </TreeNode>
      );
    });
  }, [collapsedTreeNodes, selectedNodeId, nodes, setCenter, initialEdges, setCollapsedNodes, fitView, mapTreeNodeToCanvasNode, initialNodes, setHiddenNodes]);

  console.log('📊 Rendering with', nodes.length, 'visible nodes and', edges.length, 'visible edges');

  if (nodes.length === 0) {
    return (
      <div style={{ padding: 50, color: 'red', fontSize: 20 }}>
        ERROR: No nodes to display!
      </div>
    );
  }

  return (
    <div style={{ width: '100vw', height: '100vh', background: '#f9fafb', display: 'flex', flexDirection: 'column' }}>
      {/* 顶部面包屑导航栏 */}
      <div style={{
        minHeight: '48px',
        maxHeight: '120px',
        background: 'white',
        borderBottom: '1px solid #e5e7eb',
        display: 'flex',
        alignItems: 'flex-start',
        padding: '12px 24px',
        zIndex: 1000,
        boxShadow: '0 1px 3px rgba(0, 0, 0, 0.05)',
        flexShrink: 0,
        overflowY: 'auto',
      }}>
        <div style={{ width: '100%' }}>
          {selectedNodeId ? (
            <div style={{ fontSize: '12px', color: '#6b7280' }}>
              {/* 面包屑导航 - 显示所有路径 */}
              {(() => {
                const selectedNode = nodes.find(n => n.id === selectedNodeId);
                if (!selectedNode) return null;

                // 找到所有从根节点到当前节点的路径
                const findAllPaths = (targetId) => {
                  const paths = [];

                  const buildPath = (nodeId, currentPath) => {
                    const node = initialNodes.find(n => n.id === nodeId);
                    if (!node) return;

                    const newPath = [node, ...currentPath];

                    // 找到所有父节点
                    const parents = initialEdges.filter(e => e.target === nodeId).map(e => e.source);

                    if (parents.length === 0) {
                      // 到达根节点
                      paths.push(newPath);
                    } else {
                      // 递归处理所有父节点
                      parents.forEach(parentId => {
                        buildPath(parentId, newPath);
                      });
                    }
                  };

                  buildPath(targetId, []);
                  return paths;
                };

                const allPaths = findAllPaths(selectedNodeId);

                // 去重：将路径转换为字符串进行比较
                const uniquePaths = [];
                const pathStrings = new Set();
                allPaths.forEach(path => {
                  const pathString = path.map(n => n.id).join('->');
                  if (!pathStrings.has(pathString)) {
                    pathStrings.add(pathString);
                    uniquePaths.push(path);
                  }
                });

                return (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
                    {uniquePaths.map((path, pathIndex) => (
                      <div key={pathIndex} style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}>
                        {pathIndex > 0 && <span style={{ color: '#d1d5db', marginRight: '4px' }}>或</span>}
                        {path.map((node, index) => {
                          // 获取节点的 score、strategy 和 isSelected
                          const nodeScore = node.data.score ? parseFloat(node.data.score) : 0;
                          const nodeStrategy = getPrimaryStrategy(node.data);  // 使用智能提取函数
                          const strategyColor = getStrategyColor(nodeStrategy);
                          const nodeIsSelected = node.type === 'note' ? node.data.matchLevel !== 'unsatisfied' : node.data.isSelected !== false;
                          const nodeActualType = node.data.nodeType || node.type; // 获取实际节点类型

                          // 计算路径节点字体颜色：根据分数提升幅度判断
                          let pathFontColor = '#374151'; // 默认颜色
                          if (node.type === 'note') {
                            pathFontColor = node.data.matchLevel === 'unsatisfied' ? '#ef4444' : '#374151';
                          } else if (node.data.seed_score !== undefined) {
                            const parentScore = parseFloat(node.data.seed_score);
                            const gain = nodeScore - parentScore;
                            pathFontColor = gain >= 0.05 ? '#16a34a' : '#ef4444';
                          } else if (index > 0) {
                            const prevNode = path[index - 1];
                            const prevScore = prevNode.data.score ? parseFloat(prevNode.data.score) : 0;
                            const gain = nodeScore - prevScore;
                            pathFontColor = gain >= 0.05 ? '#16a34a' : '#ef4444';
                          } else if (node.data.isSelected === false) {
                            pathFontColor = '#ef4444';
                          }

                          return (
                          <React.Fragment key={node.id + '-' + index}>
                            <span
                              onClick={() => {
                                const nodeId = node.id;

                                // 找到所有祖先节点
                                const ancestorIds = [nodeId];
                                const findAncestors = (id) => {
                                  initialEdges.forEach(edge => {
                                    if (edge.target === id && !ancestorIds.includes(edge.source)) {
                                      ancestorIds.push(edge.source);
                                      findAncestors(edge.source);
                                    }
                                  });
                                };
                                findAncestors(nodeId);

                                // 如果节点或其祖先被隐藏，先恢复它们
                                setHiddenNodes(prev => {
                                  const newSet = new Set(prev);
                                  ancestorIds.forEach(id => newSet.delete(id));
                                  return newSet;
                                });

                                // 展开目录树中到达该节点的路径
                                // 需要找到所有包含该节点的树路径的 treeKey，并展开它们的父节点
                                setCollapsedTreeNodes(prev => {
                                  const newSet = new Set(prev);
                                  // 清空所有折叠状态，让目录树完全展开到选中节点
                                  // 这样可以确保选中节点在目录中可见
                                  return new Set();
                                });

                                setSelectedNodeId(nodeId);
                                setTimeout(() => {
                                  fitView({
                                    nodes: [{ id: nodeId }],
                                    duration: 800,
                                    padding: 0.3,
                                  });
                                }, 100);
                              }}
                              style={{
                                padding: '6px 8px',
                                borderRadius: '4px',
                                background: 'white',
                                border: index === path.length - 1 ? '2px solid #3b82f6' : '1px solid #d1d5db',
                                color: '#374151',
                                fontWeight: index === path.length - 1 ? '600' : '400',
                                width: '180px',
                                cursor: 'pointer',
                                transition: 'all 0.2s ease',
                                position: 'relative',
                                display: 'inline-flex',
                                flexDirection: 'column',
                                gap: '4px',
                              }}
                              onMouseEnter={(e) => {
                                e.currentTarget.style.opacity = '0.8';
                              }}
                              onMouseLeave={(e) => {
                                e.currentTarget.style.opacity = '1';
                              }}
                              title={\`\${node.data.title || node.id} (Score: \${nodeScore.toFixed(2)}, Strategy: \${nodeStrategy}, Selected: \${nodeIsSelected})\`}
                            >
                              {/* 上半部分：竖线 + 图标 + 文字 + 分数 */}
                              <div style={{
                                display: 'flex',
                                alignItems: 'center',
                                gap: '6px',
                              }}>
                                {/* 策略类型竖线 */}
                                <div style={{
                                  width: '3px',
                                  height: '16px',
                                  background: strategyColor,
                                  borderRadius: '2px',
                                  flexShrink: 0,
                                }} />

                                {/* 节点文字 - 左侧 */}
                                <span style={{
                                  flex: 1,
                                  fontSize: '12px',
                                  color: pathFontColor,
                                  overflow: 'hidden',
                                  textOverflow: 'ellipsis',
                                  whiteSpace: 'nowrap',
                                }}>
                                  {node.data.title || node.id}
                                </span>

                                {/* 域标识 - 右侧,挨着分数 */}
                                {(node.data.domain_type || node.data.domains_str || (node.data.domain_index !== null && node.data.domain_index !== undefined)) && (
                                  <span style={{
                                    fontSize: '12px',
                                    color: '#fff',
                                    background: '#6366f1',
                                    padding: '2px 5px',
                                    borderRadius: '3px',
                                    flexShrink: 0,
                                    fontWeight: '600',
                                    marginLeft: '4px',
                                  }}
                                  title={
                                    node.data.domain_type ? '域: ' + node.data.domain_type + ' (D' + node.data.domain_index + ')' :
                                    node.data.domains_str ? '域: ' + node.data.domains_str :
                                    '域 D' + node.data.domain_index
                                  }
                                  >
                                    {node.data.domain_type || node.data.domains_str || ('D' + node.data.domain_index)}
                                  </span>
                                )}

                                {/* 分数显示 - 步骤和轮次节点不显示分数 */}
                                {nodeActualType !== 'step' && nodeActualType !== 'round' && (
                                  <span style={{
                                    fontSize: '10px',
                                    color: '#6b7280',
                                    fontWeight: '500',
                                    flexShrink: 0,
                                    minWidth: '35px',
                                    textAlign: 'right',
                                    marginLeft: '4px',
                                  }}>
                                    {nodeScore.toFixed(2)}
                                  </span>
                                )}
                              </div>

                              {/* 分数下划线 - 步骤和轮次节点不显示 */}
                              {nodeActualType !== 'step' && nodeActualType !== 'round' && (
                                <div style={{
                                  width: (nodeScore * 100) + '%',
                                  height: '2px',
                                  background: getScoreColor(nodeScore),
                                  borderRadius: '1px',
                                  marginLeft: '9px',
                                }} />
                              )}
                            </span>
                            {index < path.length - 1 && <span style={{ color: '#9ca3af' }}>›</span>}
                          </React.Fragment>
                        )})}
                      </div>
                    ))}
                  </div>
                );
              })()}
            </div>
          ) : (
            <div style={{ fontSize: '13px', color: '#9ca3af', textAlign: 'center' }}>
              选择一个节点查看路径
            </div>
          )}
        </div>
      </div>

      {/* 主内容区：目录 + 画布 */}
      <div style={{
        display: 'flex',
        flex: 1,
        overflow: 'hidden',
        cursor: isResizing ? 'col-resize' : 'default',
        userSelect: isResizing ? 'none' : 'auto',
      }}>
        {/* 左侧目录树/清洗结果 */}
        <div style={{
          width: \`\${sidebarWidth}px\`,
          background: 'white',
          borderRight: '1px solid #e5e7eb',
          display: 'flex',
          flexDirection: 'column',
          flexShrink: 0,
        }}>
          {/* Tab切换 */}
          <div style={{
            display: 'flex',
            borderBottom: '1px solid #e5e7eb',
            background: '#f9fafb',
          }}>
            <button
              onClick={() => setActiveTab('directory')}
              style={{
                flex: 1,
                padding: '12px 16px',
                border: 'none',
                background: activeTab === 'directory' ? 'white' : 'transparent',
                borderBottom: activeTab === 'directory' ? '2px solid #3b82f6' : '2px solid transparent',
                color: activeTab === 'directory' ? '#111827' : '#6b7280',
                fontWeight: activeTab === 'directory' ? '600' : '400',
                fontSize: '14px',
                cursor: 'pointer',
                transition: 'all 0.2s',
              }}
            >
              节点目录
            </button>
            <button
              onClick={() => setActiveTab('cleaned')}
              style={{
                flex: 1,
                padding: '12px 16px',
                border: 'none',
                background: activeTab === 'cleaned' ? 'white' : 'transparent',
                borderBottom: activeTab === 'cleaned' ? '2px solid #3b82f6' : '2px solid transparent',
                color: activeTab === 'cleaned' ? '#111827' : '#6b7280',
                fontWeight: activeTab === 'cleaned' ? '600' : '400',
                fontSize: '14px',
                cursor: 'pointer',
                transition: 'all 0.2s',
              }}
            >
              排序&清洗结果 ({cleanedPosts.length})
            </button>
          </div>

          {/* Tab内容区 */}
          {activeTab === 'directory' && (
          <>
          <div style={{
            padding: '12px 16px',
            borderBottom: '1px solid #e5e7eb',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
          }}>
            <span style={{
              fontWeight: '600',
              fontSize: '14px',
              color: '#111827',
            }}>
              节点目录
            </span>
            <div style={{ display: 'flex', gap: '6px' }}>
              <button
                onClick={() => {
                  setCollapsedTreeNodes(new Set());
                }}
                style={{
                  fontSize: '11px',
                  padding: '4px 8px',
                  borderRadius: '4px',
                  border: '1px solid #d1d5db',
                  background: 'white',
                  color: '#6b7280',
                  cursor: 'pointer',
                  fontWeight: '500',
                }}
                title="展开全部节点"
              >
                全部展开
              </button>
              <button
                onClick={() => {
                  const getAllTreeKeys = (nodes) => {
                    const keys = new Set();
                    const traverse = (node) => {
                      if (node.children && node.children.length > 0) {
                        keys.add(node.treeKey);
                        node.children.forEach(traverse);
                      }
                    };
                    nodes.forEach(traverse);
                    return keys;
                  };
                  setCollapsedTreeNodes(getAllTreeKeys(treeRoots));
                }}
                style={{
                  fontSize: '11px',
                  padding: '4px 8px',
                  borderRadius: '4px',
                  border: '1px solid #d1d5db',
                  background: 'white',
                  color: '#6b7280',
                  cursor: 'pointer',
                  fontWeight: '500',
                }}
                title="折叠全部节点"
              >
                全部折叠
              </button>
              <button
                onClick={copyTreeToClipboard}
                style={{
                  fontSize: '11px',
                  padding: '4px 8px',
                  borderRadius: '4px',
                  border: '1px solid #3b82f6',
                  background: '#3b82f6',
                  color: 'white',
                  cursor: 'pointer',
                  fontWeight: '500',
                  transition: 'all 0.2s',
                }}
                onMouseEnter={(e) => e.currentTarget.style.background = '#2563eb'}
                onMouseLeave={(e) => e.currentTarget.style.background = '#3b82f6'}
                title="复制树形结构为文本格式"
              >
                📋 复制树形结构
              </button>
            </div>
          </div>
          <div style={{
            flex: 1,
            overflowX: 'auto',
            overflowY: 'auto',
            padding: '8px',
          }}>
            <div style={{ minWidth: 'fit-content' }}>
              {renderTree(treeRoots)}
            </div>
          </div>
          </>
          )}

          {/* 清洗结果列表 */}
          {activeTab === 'cleaned' && (
            <div style={{
              flex: 1,
              overflowY: 'auto',
              padding: '8px',
            }}>
              {cleanedPosts.length === 0 ? (
                <div style={{
                  padding: '20px',
                  textAlign: 'center',
                  color: '#9ca3af',
                  fontSize: '14px',
                }}>
                  暂无清洗数据<br/>
                  <span style={{ fontSize: '12px', marginTop: '8px', display: 'block' }}>
                    请先运行 extract_topn_multimodal.py 脚本
                  </span>
                </div>
              ) : (
                <div>
                  <div style={{
                    padding: '8px 12px',
                    marginBottom: '8px',
                    background: '#f3f4f6',
                    borderRadius: '6px',
                    fontSize: '12px',
                    color: '#6b7280',
                  }}>
                    共 {cleanedPosts.length} 个帖子
                  </div>
                  {cleanedPosts.map((post, index) => (
                    <div
                      key={post.note_id}
                      onClick={() => setSelectedPost(post)}
                      style={{
                        padding: '12px',
                        marginBottom: '8px',
                        background: selectedPost?.note_id === post.note_id ? '#eff6ff' : 'white',
                        border: \`1px solid \${selectedPost?.note_id === post.note_id ? '#3b82f6' : '#e5e7eb'}\`,
                        borderRadius: '6px',
                        cursor: 'pointer',
                        transition: 'all 0.2s',
                      }}
                      onMouseEnter={(e) => {
                        if (selectedPost?.note_id !== post.note_id) {
                          e.currentTarget.style.background = '#f9fafb';
                        }
                      }}
                      onMouseLeave={(e) => {
                        if (selectedPost?.note_id !== post.note_id) {
                          e.currentTarget.style.background = 'white';
                        }
                      }}
                    >
                      <div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
                        <div style={{
                          minWidth: '24px',
                          height: '24px',
                          borderRadius: '50%',
                          background: '#3b82f6',
                          color: 'white',
                          display: 'flex',
                          alignItems: 'center',
                          justifyContent: 'center',
                          fontSize: '12px',
                          fontWeight: '600',
                        }}>
                          {index + 1}
                        </div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{
                            fontSize: '13px',
                            fontWeight: '500',
                            color: '#111827',
                            marginBottom: '4px',
                            overflow: 'hidden',
                            textOverflow: 'ellipsis',
                            display: '-webkit-box',
                            WebkitLineClamp: 2,
                            WebkitBoxOrient: 'vertical',
                          }}>
                            {post.title}
                          </div>
                          <div style={{ display: 'flex', gap: '12px', fontSize: '11px', color: '#6b7280' }}>
                            <span>得分: {post.final_score?.toFixed(1) || 'N/A'}</span>
                            <span>图片: {post.content_structured?.total_images || 0}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>

        {/* 可拖拽的分隔条 */}
        <div
          onMouseDown={handleMouseDown}
          style={{
            width: '4px',
            cursor: 'col-resize',
            background: isResizing ? '#3b82f6' : 'transparent',
            transition: isResizing ? 'none' : 'background 0.2s',
            flexShrink: 0,
            position: 'relative',
          }}
          onMouseEnter={(e) => e.currentTarget.style.background = '#e5e7eb'}
          onMouseLeave={(e) => {
            if (!isResizing) e.currentTarget.style.background = 'transparent';
          }}
        >
          {/* 拖拽提示线 */}
          <div style={{
            position: 'absolute',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            width: '1px',
            height: '40px',
            background: '#9ca3af',
            opacity: isResizing ? 1 : 0.3,
          }} />
        </div>

        {/* 画布区域 */}
        <div style={{ flex: 1, position: 'relative' }}>

          {/* 右侧图例 */}
          <div style={{
            position: 'absolute',
            top: '20px',
            right: '20px',
            background: 'white',
            padding: '16px',
            borderRadius: '12px',
            boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
            zIndex: 1000,
            maxWidth: '260px',
            border: '1px solid #e5e7eb',
          }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
          <h3 style={{ fontSize: '14px', fontWeight: '600', color: '#111827', margin: 0 }}>图例</h3>
          <button
            onClick={() => setFocusMode(!focusMode)}
            style={{
              fontSize: '11px',
              padding: '4px 8px',
              borderRadius: '4px',
              border: '1px solid',
              borderColor: focusMode ? '#3b82f6' : '#d1d5db',
              background: focusMode ? '#3b82f6' : 'white',
              color: focusMode ? 'white' : '#6b7280',
              cursor: 'pointer',
              fontWeight: '500',
            }}
            title={focusMode ? '关闭聚焦模式' : '开启聚焦模式'}
          >
            {focusMode ? '🎯 聚焦' : '📊 全图'}
          </button>
        </div>

        <div style={{ fontSize: '12px' }}>
          {/* 画布节点展开/折叠控制 */}
          <div style={{ marginBottom: '12px', paddingBottom: '12px', borderBottom: '1px solid #f3f4f6' }}>
            <div style={{ fontSize: '12px', fontWeight: '500', marginBottom: '8px', color: '#374151' }}>节点控制</div>
            <div style={{ display: 'flex', gap: '6px' }}>
              <button
                onClick={() => {
                  setCollapsedNodes(new Set());
                }}
                style={{
                  fontSize: '11px',
                  padding: '4px 8px',
                  borderRadius: '4px',
                  border: '1px solid #d1d5db',
                  background: 'white',
                  color: '#6b7280',
                  cursor: 'pointer',
                  fontWeight: '500',
                  flex: 1,
                }}
                title="展开画布中所有节点的子节点"
              >
                全部展开
              </button>
              <button
                onClick={() => {
                  const allNodeIds = new Set(initialNodes.map(n => n.id));
                  setCollapsedNodes(allNodeIds);
                }}
                style={{
                  fontSize: '11px',
                  padding: '4px 8px',
                  borderRadius: '4px',
                  border: '1px solid #d1d5db',
                  background: 'white',
                  color: '#6b7280',
                  cursor: 'pointer',
                  fontWeight: '500',
                  flex: 1,
                }}
                title="折叠画布中所有节点的子节点"
              >
                全部折叠
              </button>
            </div>
          </div>

          <div style={{ paddingTop: '12px', borderTop: '1px solid #f3f4f6' }}>
            <div style={{ fontSize: '12px', fontWeight: '500', marginBottom: '8px', color: '#374151' }}>策略类型</div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#10b981', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>初始分词</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#06b6d4', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>调用sug</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#f59e0b', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>同义改写</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#3b82f6', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>加词</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#8b5cf6', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>抽象改写</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#ec4899', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>基于部分匹配改进</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#a855f7', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>结果分支-抽象改写</span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', margin: '6px 0' }}>
              <div style={{ width: '20px', height: '2px', marginRight: '8px', background: '#fb923c', opacity: 0.7 }}></div>
              <span style={{ color: '#6b7280', fontSize: '11px' }}>结果分支-同义改写</span>
            </div>
          </div>

          <div style={{
            marginTop: '12px',
            paddingTop: '12px',
            borderTop: '1px solid #f3f4f6',
            fontSize: '11px',
            color: '#9ca3af',
            lineHeight: '1.5',
          }}>
            💡 点击节点左上角 × 隐藏节点
          </div>

          {/* 隐藏节点列表 - 在图例内部 */}
          {hiddenNodes.size > 0 && (
            <div style={{
              marginTop: '12px',
              paddingTop: '12px',
              borderTop: '1px solid #f3f4f6',
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
                <h4 style={{ fontSize: '12px', fontWeight: '600', color: '#111827' }}>已隐藏节点</h4>
                <button
                  onClick={() => setHiddenNodes(new Set())}
                  style={{
                    fontSize: '10px',
                    color: '#3b82f6',
                    background: 'none',
                    border: 'none',
                    cursor: 'pointer',
                    textDecoration: 'underline',
                  }}
                >
                  全部恢复
                </button>
              </div>
              <div style={{ fontSize: '12px', maxHeight: '200px', overflow: 'auto' }}>
                {Array.from(hiddenNodes).map(nodeId => {
                  const node = initialNodes.find(n => n.id === nodeId);
                  if (!node) return null;
                  return (
                    <div
                      key={nodeId}
                      style={{
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center',
                        padding: '6px 8px',
                        margin: '4px 0',
                        background: '#f9fafb',
                        borderRadius: '6px',
                        fontSize: '11px',
                      }}
                    >
                      <span
                        style={{
                          flex: 1,
                          overflow: 'hidden',
                          textOverflow: 'ellipsis',
                          whiteSpace: 'nowrap',
                          color: '#374151',
                        }}
                        title={node.data.title || nodeId}
                      >
                        {node.data.title || nodeId}
                      </span>
                      <button
                        onClick={() => {
                          setHiddenNodes(prev => {
                            const newSet = new Set(prev);
                            newSet.delete(nodeId);
                            return newSet;
                          });
                        }}
                        style={{
                          marginLeft: '8px',
                          fontSize: '10px',
                          color: '#10b981',
                          background: 'none',
                          border: 'none',
                          cursor: 'pointer',
                          flexShrink: 0,
                        }}
                      >
                        恢复
                      </button>
                    </div>
                  );
                })}
              </div>
            </div>
          )}
          </div>
          </div>

          {/* React Flow 画布 */}
          <ReactFlow
            nodes={nodes}
            edges={edges}
            nodeTypes={nodeTypes}
            fitView
            fitViewOptions={{ padding: 0.2, duration: 500 }}
            minZoom={0.4}
            maxZoom={1.5}
            nodesDraggable={true}
            nodesConnectable={false}
            elementsSelectable={true}
            defaultEdgeOptions={{
              type: 'smoothstep',
            }}
            proOptions={{ hideAttribution: true }}
            onNodeClick={(event, clickedNode) => {
              setSelectedNodeId(clickedNode.id);
            }}
          >
            <Controls style={{ bottom: '20px', left: 'auto', right: '20px' }} />
            <Background variant="dots" gap={20} size={1} color="#e5e7eb" />
          </ReactFlow>

          {/* 清洗帖子详情卡片 */}
          {selectedPost && (
            <div
              style={{
                position: 'absolute',
                top: 0,
                right: 0,
                bottom: 0,
                left: 0,
                background: 'rgba(0, 0, 0, 0.3)',
                zIndex: 1000,
                display: 'flex',
                justifyContent: 'flex-end',
              }}
              onClick={() => setSelectedPost(null)}
            >
              <div
                style={{
                  width: '600px',
                  maxWidth: '90%',
                  background: '#fff5f5',
                  boxShadow: '-4px 0 20px rgba(0, 0, 0, 0.15)',
                  overflowY: 'auto',
                  display: 'flex',
                  flexDirection: 'column',
                }}
                onClick={(e) => e.stopPropagation()}
              >
                {/* 卡片头部 */}
                <div style={{
                  padding: '20px 24px',
                  borderBottom: '2px solid #fecaca',
                  background: 'white',
                  position: 'sticky',
                  top: 0,
                  zIndex: 1,
                }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
                    <h3 style={{
                      margin: 0,
                      fontSize: '18px',
                      fontWeight: '600',
                      color: '#111827',
                      flex: 1,
                      paddingRight: '16px',
                      lineHeight: '1.4',
                    }}>
                      {selectedPost.title}
                    </h3>
                    <button
                      onClick={() => setSelectedPost(null)}
                      style={{
                        fontSize: '24px',
                        lineHeight: '24px',
                        border: 'none',
                        background: 'none',
                        color: '#9ca3af',
                        cursor: 'pointer',
                        padding: '0',
                        width: '24px',
                        height: '24px',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        borderRadius: '4px',
                        transition: 'all 0.2s',
                      }}
                      onMouseEnter={(e) => {
                        e.currentTarget.style.background = '#f3f4f6';
                        e.currentTarget.style.color = '#111827';
                      }}
                      onMouseLeave={(e) => {
                        e.currentTarget.style.background = 'none';
                        e.currentTarget.style.color = '#9ca3af';
                      }}
                    >
                      ×
                    </button>
                  </div>
                </div>

                {/* 卡片内容 */}
                <div style={{ flex: 1, overflowY: 'auto', padding: '24px' }}>
                  {/* 基本信息 */}
                  <div style={{
                    display: 'flex',
                    gap: '16px',
                    marginBottom: '24px',
                    padding: '16px',
                    background: 'white',
                    borderRadius: '8px',
                    border: '1px solid #fecaca',
                  }}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '4px' }}>得分</div>
                      <div style={{ fontSize: '20px', fontWeight: '600', color: '#ef4444' }}>
                        {selectedPost.final_score?.toFixed(1) || 'N/A'}
                      </div>
                    </div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '4px' }}>图片数量</div>
                      <div style={{ fontSize: '20px', fontWeight: '600', color: '#3b82f6' }}>
                        {selectedPost.content_structured?.total_images || 0}
                      </div>
                    </div>
                    <div style={{ flex: 1 }}>
                      <a
                        href={selectedPost.note_url}
                        target="_blank"
                        rel="noopener noreferrer"
                        style={{
                          display: 'inline-block',
                          marginTop: '20px',
                          padding: '8px 16px',
                          background: '#3b82f6',
                          color: 'white',
                          borderRadius: '6px',
                          textDecoration: 'none',
                          fontSize: '12px',
                          fontWeight: '500',
                          transition: 'all 0.2s',
                        }}
                        onMouseEnter={(e) => e.currentTarget.style.background = '#2563eb'}
                        onMouseLeave={(e) => e.currentTarget.style.background = '#3b82f6'}
                      >
                        查看原帖 →
                      </a>
                    </div>
                  </div>

                  {/* 清洗后的内容 */}
                  {selectedPost.content_structured && selectedPost.content_structured.formatted_text && (
                    <div style={{ marginBottom: '24px' }}>
                      <h4 style={{
                        fontSize: '14px',
                        fontWeight: '600',
                        color: '#111827',
                        marginBottom: '12px',
                        display: 'flex',
                        alignItems: 'center',
                        gap: '8px',
                      }}>
                        <span style={{ fontSize: '16px' }}>📝</span>
                        清洗后的结构化内容
                      </h4>
                      <div style={{
                        padding: '16px',
                        background: 'white',
                        borderRadius: '8px',
                        border: '1px solid #fecaca',
                        whiteSpace: 'pre-wrap',
                        fontSize: '13px',
                        lineHeight: '1.6',
                        color: '#374151',
                      }}>
                        {selectedPost.content_structured.formatted_text}
                      </div>
                    </div>
                  )}

                  {/* 图片列表 */}
                  {selectedPost.images && selectedPost.images.length > 0 && (
                    <div>
                      <h4 style={{
                        fontSize: '14px',
                        fontWeight: '600',
                        color: '#111827',
                        marginBottom: '12px',
                        display: 'flex',
                        alignItems: 'center',
                        gap: '8px',
                      }}>
                        <span style={{ fontSize: '16px' }}>🖼️</span>
                        图片详情 ({selectedPost.images.length})
                      </h4>
                      {selectedPost.images.map((img, idx) => (
                        <div
                          key={idx}
                          style={{
                            marginBottom: '16px',
                            padding: '16px',
                            background: 'white',
                            borderRadius: '8px',
                            border: '1px solid #fecaca',
                          }}
                        >
                          <div style={{
                            fontSize: '12px',
                            fontWeight: '600',
                            color: '#6b7280',
                            marginBottom: '8px',
                          }}>
                            图片 {idx + 1}
                          </div>
                          <img
                            src={img.original_url}
                            alt={'图片' + (idx + 1)}
                            style={{
                              width: '100%',
                              borderRadius: '6px',
                              marginBottom: '12px',
                            }}
                          />
                          {img.extract_text_cleaned && (
                            <div style={{
                              padding: '12px',
                              background: '#f9fafb',
                              borderRadius: '6px',
                              fontSize: '12px',
                              lineHeight: '1.6',
                              color: '#374151',
                            }}>
                              {img.extract_text_cleaned}
                            </div>
                          )}
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function App() {
  return (
    <ReactFlowProvider>
      <FlowContent />
    </ReactFlowProvider>
  );
}

const root = createRoot(document.getElementById('root'));
root.render(<App />);
`;

fs.writeFileSync(reactComponentPath, reactComponent);

// 调试：保存临时组件副本用于检查
fs.writeFileSync(path.join(__dirname, 'debug_component.jsx'), reactComponent);
console.log('📝 已保存临时组件副本: debug_component.jsx');

// 使用 esbuild 打包
console.log('🎨 Building modern visualization...');

build({
  entryPoints: [reactComponentPath],
  bundle: true,
  outfile: path.join(__dirname, 'bundle_v2.js'),
  format: 'iife',
  loader: {
    '.css': 'css',
  },
  minify: false,
  treeShaking: false, // 禁用tree shaking
  ignoreAnnotations: true, // 忽略所有注解，防止纯函数优化
  keepNames: true, // 保留函数和变量名
  sourcemap: 'inline',
  // 强制所有 React 引用指向同一个位置，避免多副本
  alias: {
    'react': path.join(__dirname, 'node_modules/react'),
    'react-dom': path.join(__dirname, 'node_modules/react-dom'),
    'react/jsx-runtime': path.join(__dirname, 'node_modules/react/jsx-runtime'),
    'react/jsx-dev-runtime': path.join(__dirname, 'node_modules/react/jsx-dev-runtime'),
  },
  define: {
    'process.env.NODE_ENV': '"development"' // 使用开发模式，减少优化
  },
}).then(() => {
  // 读取打包后的 JS
  const bundleJs = fs.readFileSync(path.join(__dirname, 'bundle_v2.js'), 'utf-8');

  // 调试：检查bundle中是否包含评估UI代码
  const hasEvalCode = bundleJs.includes('知识内容') || bundleJs.includes('is_knowledge');
  console.log('📝 Bundle调试: 包含评估代码 =', hasEvalCode);
  if (hasEvalCode) {
    console.log('  ✓ 评估UI代码在bundle中');
  } else {
    console.log('  ⚠️  评估UI代码不在bundle中，检查临时组件文件...');
    const tempContent = fs.readFileSync(reactComponentPath, 'utf-8');
    const hasTempEvalCode = tempContent.includes('知识内容');
    console.log('  临时组件文件包含评估代码 =', hasTempEvalCode);
  }

  // 读取 CSS
  const cssPath = path.join(__dirname, 'node_modules/@xyflow/react/dist/style.css');
  const css = fs.readFileSync(cssPath, 'utf-8');

  // 生成最终 HTML
  const html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>查询图可视化</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
    <script src="https://unpkg.com/dagre@0.8.5/dist/dagre.min.js"></script>
    <script>
      // 过滤特定的 React 警告
      const originalError = console.error;
      console.error = (...args) => {
        if (typeof args[0] === 'string' && args[0].includes('Each child in a list should have a unique "key" prop')) {
          return;
        }
        originalError.apply(console, args);
      };
    </script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            overflow: hidden;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }
        #root {
            width: 100vw;
            height: 100vh;
        }
        ${css}

        /* 自定义样式覆盖 */
        .react-flow__edge-path {
            stroke-linecap: round;
        }
        .react-flow__controls {
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            border: 1px solid #e5e7eb;
            border-radius: 8px;
        }
        .react-flow__controls-button {
            border: none;
            border-bottom: 1px solid #e5e7eb;
        }
        .react-flow__controls-button:hover {
            background: #f9fafb;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <script>${bundleJs}</script>
</body>
</html>`;

  // 调试：详细检查bundle和HTML内容
  const bundleHas知识 = bundleJs.includes('知识内容');
  const bundleHasIsKnowledge = bundleJs.includes('is_knowledge');
  const bundleHasDataIsKnowledge = bundleJs.includes('data.is_knowledge');

  console.log('📝 Bundle内容检查:');
  console.log('  包含 "知识内容":', bundleHas知识);
  console.log('  包含 "is_knowledge":', bundleHasIsKnowledge);
  console.log('  包含 "data.is_knowledge":', bundleHasDataIsKnowledge);
  console.log('  Bundle长度:', bundleJs.length);

  const htmlHas知识 = html.includes('知识内容');
  const htmlHasIsKnowledge = html.includes('is_knowledge');
  const htmlHasDataIsKnowledge = html.includes('data.is_knowledge');

  console.log('📝 HTML内容检查:');
  console.log('  包含 "知识内容":', htmlHas知识);
  console.log('  包含 "is_knowledge":', htmlHasIsKnowledge);
  console.log('  包含 "data.is_knowledge":', htmlHasDataIsKnowledge);
  console.log('  HTML长度:', html.length);

  // 如果bundle有但HTML没有，保存用于调试
  if ((bundleHas知识 || bundleHasDataIsKnowledge) && !htmlHas知识 && !htmlHasDataIsKnowledge) {
    console.log('  ⚠️  Bundle中有评估代码但HTML中没有！');
    fs.writeFileSync(path.join(__dirname, 'debug_bundle.js'), bundleJs);
    console.log('  已保存 debug_bundle.js 用于调试');
  }

  // 写入输出文件
  fs.writeFileSync(outputFile, html);

  // 调试：暂时保留bundle文件用于分析
  console.log('📝 保留 bundle_v2.js 和 temp_flow_component_v2.jsx 用于调试');

  // 清理临时文件（调试期间注释掉）
  // fs.unlinkSync(reactComponentPath);
  // fs.unlinkSync(path.join(__dirname, 'bundle_v2.js'));

  console.log('✅ Visualization generated: ' + outputFile);
  console.log('📊 Nodes: ' + Object.keys(data.nodes).length);
  console.log('🔗 Edges: ' + data.edges.length);
}).catch(error => {
  console.error('❌ Build error:', error);
  process.exit(1);
});
