// Per-mode copy + result content
const CHAT_PRESETS = {
  quantity: {
    userText: '帮我分析一下这个项目的图纸，算一下工程量和材料用量。',
    files: [{ icon: 'file-image', label: 'floor_plan_longhu_p3.pdf' }],
    introText: '将为您执行',
    skillName: '图纸智能算量',
    parseCards: [
      { icon: 'file-image', color: 'var(--quantity)', title: '装修图纸', rows: [
        ['类型', 'CAD 平面图'], ['份数', '1 份 · 3 页'], ['将执行', '智能算量'],
      ]},
      { icon: 'check-circle-2', color: 'var(--risk-low)', title: '识别置信度', rows: [
        ['房间边界', '95%'], ['尺寸标注', '92%'], ['综合', '★★★★★ 高'],
      ]},
    ],
    steps: [
      { key: 'scan',      label: '识别文件类型', sub: '检测 PDF / 图片图纸' },
      { key: 'measure',   label: '图纸算量',     sub: '识别房间、尺寸、分部工程量' },
      { key: 'materials', label: '材料深化换算', sub: '换算采购用量、损耗与规格' },
    ],
    doneText: '算量已完成',
  },
  quote: {
    userText: '帮我看看和顺装饰这份报价单有没有坑，重点关注主材单价和合同条款。',
    files: [
      { icon: 'file-spreadsheet', label: '和顺装饰报价单.xlsx' },
      { icon: 'file-text', label: '装修施工合同 v1.pdf' },
    ],
    introText: '将为您执行',
    skillName: '报价智能审核',
    parseCards: [
      { icon: 'file-spreadsheet', color: 'var(--quote)', title: '报价文件', rows: [
        ['类型', 'Excel 报价单'], ['分项', '8 项'], ['总价', '¥128,000'],
      ]},
      { icon: 'file-text', color: 'var(--quote)', title: '合同文件', rows: [
        ['页数', '12 页'], ['条款', '5 条核心'], ['将执行', '风险审核'],
      ]},
    ],
    steps: [
      { key: 'parse',    label: '解析报价 + 合同', sub: '提取分项、单价、数量、条款' },
      { key: 'compare',  label: '市场价比对',      sub: '对照一线城市 75㎡ 区间数据库' },
      { key: 'risk',     label: '风险与漏项',      sub: '识别条款陷阱与遗漏项' },
    ],
    doneText: '审核已完成',
  },
  combined: {
    userText: '同时上传图纸和报价单，帮我用图纸核实报价工程量并审核条款。',
    files: [
      { icon: 'file-image', label: '一层平面图.dwg' },
      { icon: 'file-image', label: '水电点位图.dwg' },
      { icon: 'file-spreadsheet', label: '龙湖装修报价单.xlsx' },
    ],
    introText: '将为您执行',
    skillName: '图纸 + 报价联合审核',
    parseCards: [
      { icon: 'file-image', color: 'var(--combined)', title: '图纸文件', rows: [
        ['张数', '3 张'], ['类型', '平面 + 水电 + 立面'], ['用途', '基准算量'],
      ]},
      { icon: 'file-spreadsheet', color: 'var(--combined)', title: '报价文件', rows: [
        ['总价', '¥168,800'], ['分项', '8 项'], ['用途', '与算量比对'],
      ]},
    ],
    steps: [
      { key: 'measure',  label: 'Step 1 · 图纸算量',   sub: '得出基准工程量' },
      { key: 'crosscheck', label: 'Step 2 · 报价 vs 算量', sub: '逐项对比、计算虚报金额' },
      { key: 'audit',    label: 'Step 3 · 价格 + 条款审核', sub: '联合输出风险与影响' },
    ],
    doneText: '联审已完成',
  },
  compare: {
    userText: '帮我对比这三家装修公司的报价，统一口径后告诉我推荐哪家。',
    files: [
      { icon: 'file-spreadsheet', label: '和顺装饰.xlsx' },
      { icon: 'file-text', label: '龙湖装修.pdf' },
      { icon: 'file-spreadsheet', label: '万家装饰.xlsx' },
    ],
    introText: '将为您执行',
    skillName: '多家报价横向对比',
    parseCards: [
      { icon: 'building', color: 'var(--compare)', title: '报价范围', rows: [
        ['公司', '3 家'], ['面积', '75 ㎡'], ['总价跨度', '12.8w – 16.9w'],
      ]},
      { icon: 'scale', color: 'var(--compare)', title: '对比口径', rows: [
        ['面积口径', '建筑面积 75㎡'], ['主材档次', '已统一'], ['范围差异', '已标注'],
      ]},
    ],
    steps: [
      { key: 'unify',    label: '统一口径',     sub: '面积 / 主材档次 / 范围对齐' },
      { key: 'matrix',   label: '分项横向对比', sub: '8 项 × 3 家 = 24 单元格' },
      { key: 'recommend', label: '推荐 + 谈判策略', sub: '输出最佳家与议价方案' },
    ],
    doneText: '对比已完成',
  },
};

// Per-mode result card content
const RESULT_PRESETS = {
  quantity: {
    icon: 'ruler', color: 'var(--quantity)', bg: '#E7F1FB',
    title: '图纸算量结果已生成', sub: '龙湖 Phase III · 7 间空间 · 75 ㎡',
    metrics: [
      { label: '总面积', value: 75, unit: '㎡' },
      { label: '房间',   value: 7,  unit: '间' },
      { label: '分部',   value: 5,  unit: '大类' },
      { label: '置信度', stars: 4 },
    ],
    chips: [
      { icon: 'paintbrush', text: '乳胶漆 12 桶' },
      { icon: 'box',         text: '腻子 28 包' },
      { icon: 'package',     text: '水泥 15 包' },
      { icon: 'package-2',   text: '黄沙 22 包' },
      { icon: 'cable',       text: '水管 168 m' },
    ],
    warning: '厨房净尺寸为推算值，建议现场核量；阳台防水做法按默认厨卫做法计入。',
    detailLabel: '查看算量详情',
    secondary: { icon: 'layers', text: '对比报价' },
  },
  quote: {
    icon: 'search-check', color: 'var(--quote)', bg: '#FDEBD0',
    title: '报价审核报告已生成', sub: '和顺装饰 · ¥128,000 · 75 ㎡',
    metrics: [
      { label: '总分',    value: 72, unit: '/100' },
      { label: '高风险',  value: 3,  unit: '项' },
      { label: '中风险',  value: 5,  unit: '项' },
      { label: '漏项',    value: 4,  unit: '项' },
    ],
    chips: [
      { icon: 'alert-triangle', text: '墙砖偏高 28%' },
      { icon: 'alert-triangle', text: '水电范围不清' },
      { icon: 'plus-circle',    text: '阳台防水漏报' },
      { icon: 'file-text',      text: '条款 2 处模糊' },
    ],
    warning: '建议执行整改后预计可降本 ¥9,800 – ¥14,500。',
    detailLabel: '查看审核详情',
    secondary: { icon: 'message-square-text', text: '生成谈判话术' },
  },
  combined: {
    icon: 'layers', color: 'var(--combined)', bg: '#EEDDF5',
    title: '联审报告已生成', sub: '龙湖装修 · ¥168,800 · 基准算量 vs 报价',
    metrics: [
      { label: '总分',     value: 65,    unit: '/100' },
      { label: '虚报金额', value: 11420, unit: '元', isMoney: true },
      { label: '漏项金额', value: 2280,  unit: '元', isMoney: true },
      { label: '高风险',   value: 5,     unit: '项' },
    ],
    chips: [
      { icon: 'alert-triangle', text: '吊顶虚报 30%' },
      { icon: 'alert-triangle', text: '墙砖虚报 22%' },
      { icon: 'plus-circle',    text: '阳台防水漏报' },
      { icon: 'file-text',      text: '条款冲突 2 处' },
    ],
    warning: '4 项工程量偏差超 10%，建议按图纸算量值重新结算，可省约 ¥9,140。',
    detailLabel: '查看联审详情',
    secondary: { icon: 'scale', text: '加一家对比' },
  },
  compare: {
    icon: 'scale', color: 'var(--compare)', bg: '#D5F5E3',
    title: '三家对比结果已生成', sub: '龙湖 Phase III · 75 ㎡ · 总价差异 31.8%',
    metrics: [
      { label: '公司数',  value: 3,    unit: '家' },
      { label: '推荐',    text: '万家', highlight: true },
      { label: '总价跨度', value: '12.8 – 16.9', unit: 'w' },
      { label: '差异率',  value: '31.8', unit: '%' },
    ],
    chips: [
      { icon: 'trophy',         text: '推荐：万家装饰 78 分' },
      { icon: 'alert-triangle', text: '谨慎：龙湖装修 +32%' },
      { icon: 'split',          text: '主因：工程量差异' },
      { icon: 'message-square-text', text: '4 条议价策略' },
    ],
    warning: '建议以万家为主谈对象，用其单价复议 A/B；吊顶按算量 24.6㎡ 结算。',
    detailLabel: '查看对比详情',
    secondary: { icon: 'layers', text: '针对推荐做联审' },
  },
};

const Chat = ({ session, turns, onOpenDetail, onExport, onRetry }) => {
  const scrollRef = React.useRef(null);

  // Scroll to bottom whenever turns change OR session switches
  React.useEffect(() => {
    if (!scrollRef.current) return;
    // Find the scrolling ancestor (the .content div with overflow: auto)
    let el = scrollRef.current.parentElement;
    while (el && getComputedStyle(el).overflowY !== 'auto' && getComputedStyle(el).overflowY !== 'scroll') {
      el = el.parentElement;
    }
    const target = el || scrollRef.current;
    // Defer to next frame so layout settles
    requestAnimationFrame(() => {
      target.scrollTop = target.scrollHeight;
    });
  }, [turns, session.id]);

  return (
    <div className="chat-wrap" ref={scrollRef}>
      <div className="chat-day">今天 · {session.time}</div>
      {turns.map((t, i) => (
        <ChatTurn
          key={t.id}
          turn={t}
          isLast={i === turns.length - 1}
          onOpenDetail={() => onOpenDetail(t.kind, t.id)}
          onExport={onExport}
          onRetry={() => onRetry && onRetry(t)}
        />
      ))}
    </div>
  );
};

const ChatTurn = ({ turn, isLast, onOpenDetail, onExport, onRetry }) => {
  const { SKILLS } = window.BB_DATA;
  const { kind, phase, resultVariant = 'default', time } = turn;
  const source = turn.source || 'mock';
  const status = turn.status || (phase >= 4 ? 'done' : 'running');
  const isRealQuantity = source === 'real' && kind === 'quantity';
  const isRealQuote = source === 'real' && kind === 'quote';
  const isRealCombined = source === 'real' && kind === 'combined';
  const isRealCompare = source === 'real' && kind === 'compare';
  const isRealTurn = isRealQuantity || isRealQuote || isRealCombined || isRealCompare;
  const isDone = isRealTurn ? status === 'done' && !!turn.viewModel : phase >= 4;
  const isError = isRealTurn && status === 'error';
  const preset = CHAT_PRESETS[kind] || CHAT_PRESETS.quantity;
  const skill = SKILLS[kind];
  const skillColor = skill ? skill.color : 'var(--quantity)';
  const uploadedFiles = isRealTurn && turn.files && turn.files.length
    ? turn.files.map(f => ({ icon: f.icon || 'file-image', label: f.label || f.name }))
    : preset.files;
  const userText = isRealTurn
    ? (isRealCompare
      ? (turn.context ? `按多家报价对比模式分析这些报价：${turn.context}` : '按多家报价对比模式分析上传报价单，输出横向矩阵、推荐和谈判策略。')
      : isRealCombined
      ? (turn.context ? `按图纸+报价联审模式分析这些文件：${turn.context}` : '按图纸+报价联审模式分析上传图纸和报价单，先算量再核实报价工程量与条款。')
      : isRealQuote
      ? (turn.context ? `按报价审核模式分析这份报价：${turn.context}` : '按报价审核模式分析上传报价单，输出风险、明细和建议。')
      : (turn.context
        ? `按算量模式分析这些图纸：${turn.context}`
        : '按算量模式分析上传图纸，输出房间、面积和水电数据。'))
    : preset.userText;
  const parseCards = isRealQuantity ? [
    { icon: 'file-image', color: 'var(--quantity)', title: '上传图纸', rows: [
      ['类型', 'PDF / 图片'], ['份数', `${uploadedFiles.length} 份`], ['将执行', '真实 API 算量'],
    ]},
    { icon: status === 'error' ? 'circle-alert' : 'activity', color: status === 'error' ? 'var(--risk-high)' : 'var(--risk-low)', title: '任务状态', rows: [
      ['请求状态', status === 'done' ? '已完成' : status === 'error' ? '失败' : '运行中'],
      ['API', '/api/takeoff'],
      ['模式', '前端选择：算量'],
    ]},
  ] : isRealQuote ? [
    { icon: 'file-spreadsheet', color: 'var(--quote)', title: '上传报价', rows: [
      ['类型', '报价文件'], ['份数', '1 份'], ['将执行', '真实 API 审核'],
    ]},
    { icon: status === 'error' ? 'circle-alert' : 'activity', color: status === 'error' ? 'var(--risk-high)' : 'var(--risk-low)', title: '任务状态', rows: [
      ['请求状态', status === 'done' ? '已完成' : status === 'error' ? '失败' : '运行中'],
      ['API', '/api/review/single'],
      ['模式', '前端选择：报价审核'],
    ]},
  ] : isRealCombined ? [
    { icon: 'file-image', color: 'var(--quantity)', title: '上传图纸', rows: [
      ['类型', 'PDF / 图片图纸'], ['份数', `${(turn.rawFloorPlans || []).length || uploadedFiles.filter(f => f.icon === 'file-image').length} 份`], ['将执行', '真实 API 算量'],
    ]},
    { icon: 'file-spreadsheet', color: 'var(--quote)', title: '上传报价', rows: [
      ['类型', '报价附件'], ['份数', `${(turn.rawQuoteFiles || []).length || 0} 份`], ['将执行', '真实 API 联审'],
    ]},
    { icon: status === 'error' ? 'circle-alert' : 'activity', color: status === 'error' ? 'var(--risk-high)' : 'var(--risk-low)', title: '任务状态', rows: [
      ['请求状态', status === 'done' ? '已完成' : status === 'error' ? '失败' : '运行中'],
      ['API', '/api/review/combined'],
      ['模式', '前端选择：图纸+报价联审'],
    ]},
  ] : isRealCompare ? [
    { icon: 'building-2', color: 'var(--compare)', title: '上传报价', rows: [
      ['类型', '多家报价文件'], ['份数', `${uploadedFiles.length} 份`], ['将执行', '真实 API 对比'],
    ]},
    { icon: status === 'error' ? 'circle-alert' : 'activity', color: status === 'error' ? 'var(--risk-high)' : 'var(--risk-low)', title: '任务状态', rows: [
      ['请求状态', status === 'done' ? '已完成' : status === 'error' ? '失败' : '运行中'],
      ['API', '/api/review/compare'],
      ['模式', '前端选择：多家报价对比'],
    ]},
  ] : preset.parseCards;

  const getStepState = (i) => {
    if (isRealTurn) {
      if (status === 'done') return 'done';
      if (status === 'error') return i < 2 ? 'done' : 'error';
      if (i < 2) {
        if (phase > i + 1) return 'done';
        if (phase === i + 1) return 'active';
        return 'idle';
      }
      return phase >= 3 ? 'active' : 'idle';
    }
    if (phase > i + 1) return 'done';
    if (phase === i + 1) return 'active';
    return 'idle';
  };
  const getStepProgress = (i) => {
    const st = getStepState(i);
    if (st === 'done') return 100;
    if (st === 'active') return isRealTurn && i === 2 ? 85 : 65;
    if (st === 'error') return 100;
    return 0;
  };

  return (
    <>
      {time && <div className="chat-turn-divider"><span>{time}</span></div>}

      {/* User message */}
      <div className="msg msg-user">
        <div className="msg-user-bubble">
          {userText}
          <div className="file-chips">
            {uploadedFiles.map((f, i) => (
              <span className="file-chip" key={i}>
                <Icon name={f.icon} size={12} />
                {f.label}
              </span>
            ))}
          </div>
        </div>
      </div>

      {/* AI message */}
      <div className="msg msg-ai">
        <div className="msg-avatar"><Icon name="triangle" size={14} strokeWidth={2.5} /></div>
        <div className="msg-ai-body">
          <div className="msg-ai-name">BitBuild AI</div>
          <div className="msg-ai-content">
            <p>已识别到您上传的文件。{preset.introText} <strong style={{ color: skillColor }}>{preset.skillName}</strong>：</p>
            <div className="file-parse-grid">
              {parseCards.map((c, i) => (
                <div className="file-parse-card" key={i}>
                  <div className="file-parse-head">
                    <Icon name={c.icon} size={14} style={{ color: c.color }} />
                    <div className="file-parse-title">{c.title}</div>
                  </div>
                  <div className="file-parse-row">
                    {c.rows.map((r, j) => (
                      <React.Fragment key={j}>
                        {r[0]}：<strong>{r[1]}</strong>{j < c.rows.length - 1 && <br/>}
                      </React.Fragment>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          </div>

          {phase >= 1 && (
            <div className="progress-card">
              <div className="progress-head">
                {isError ? (
                  <><Icon name="circle-alert" size={16} className="icon" style={{ color: 'var(--risk-high)' }} /> {isRealCompare ? '对比失败' : isRealCombined ? '联审失败' : isRealQuote ? '审核失败' : '算量失败'}</>
                ) : !isDone ? (
                  <><Icon name="loader-2" size={16} className="icon spinning" /> 正在处理中…</>
                ) : (
                  <><Icon name="check-circle-2" size={16} className="icon" style={{ color: 'var(--risk-low)' }} /> {preset.doneText}</>
                )}
              </div>
              {preset.steps.map((s, i) => {
                const st = getStepState(i);
                const prog = getStepProgress(i);
                return (
                  <div className="progress-step" key={s.key}>
                    <div className={`progress-step-icon ${st}`}>
                      {st === 'done' && <Icon name="check" size={14} strokeWidth={2.5} />}
                      {st === 'active' && <Icon name="loader-2" size={14} className="icon spinning" />}
                      {st === 'idle' && <Icon name="circle" size={14} />}
                      {st === 'error' && <Icon name="circle-alert" size={14} />}
                    </div>
                    <div className="progress-step-label">
                      {s.label}
                      <div className="sub">{s.sub}</div>
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', fontVariantNumeric: 'tabular-nums' }}>
                      {st === 'done' ? '完成' : st === 'active' ? `${prog}%` : st === 'error' ? '失败' : '等待中'}
                    </div>
                    <div className="progress-step-bar">
                      <div className={`progress-step-fill ${st === 'done' ? 'done' : ''}`} style={{ width: `${prog}%`, background: st === 'error' ? 'var(--risk-high)' : undefined }} />
                    </div>
                  </div>
                );
              })}
            </div>
          )}

          {isError && (
            <div className="result-card">
              <div className="result-body">
                <div className="result-warning" style={{ marginTop: 0, background: 'var(--risk-high-bg)', color: 'var(--risk-high)' }}>
                  <Icon name="circle-alert" size={14} strokeWidth={2} style={{ marginTop: 1 }} />
                  <div>{turn.error || (isRealCompare ? '多家报价对比失败，请确认后端服务、API Key 和上传文件后重试。' : isRealCombined ? '联审失败，请确认后端服务、API Key 和上传文件后重试。' : isRealQuote ? '报价审核失败，请确认后端服务、API Key 和上传文件后重试。' : '算量失败，请确认后端服务、API Key 和上传文件后重试。')}</div>
                </div>
              </div>
              <div className="result-actions">
                <button className="btn btn-primary" onClick={onRetry}>
                  <Icon name="refresh-cw" size={14} /> {isRealCompare ? '重新对比' : isRealCombined ? '重新联审' : isRealQuote ? '重新审核' : '重新算量'}
                </button>
              </div>
            </div>
          )}

          {isDone && (
            <ResultCard
              variant={resultVariant}
              kind={kind}
              turn={turn}
              onOpenDetail={onOpenDetail}
              onExport={onExport}
            />
          )}
        </div>
      </div>
    </>
  );
};

const buildRealQuantityResult = (turn) => {
  const vm = turn.viewModel;
  const summary = vm.summary || {};
  if (vm.reportMissing) {
    return {
      icon: 'file-warning',
      color: 'var(--risk-mid)',
      bg: '#FFF8E7',
      title: '报告模型缺失',
      sub: `${vm.projectName} · 请重新算量或下载旧报告`,
      metrics: [
        { label: '总面积', value: 0, unit: '㎡' },
        { label: '房间', value: 0, unit: '间' },
        { label: '湿区', value: 0, unit: '间' },
        { label: '状态', text: 'NO REPORT' },
      ],
      chips: [{ icon: 'file-warning', text: '缺少 report_model，不展示前端推算结果' }],
      warning: (vm.reviewItems && vm.reviewItems[0]) || '',
      detailLabel: '查看说明',
    };
  }
  const warning = (vm.warnings && vm.warnings[0]) || (vm.reviewItems && vm.reviewItems[0]) || (vm.assumptions && vm.assumptions[0]) || '';
  return {
    icon: 'ruler',
    color: 'var(--quantity)',
    bg: '#E7F1FB',
    title: '真实算量结果已生成',
    sub: `${vm.projectName} · ${summary.roomCount || 0} 间空间 · ${summary.totalArea || 0} ㎡`,
    metrics: [
      { label: '总面积', value: summary.totalArea || 0, unit: '㎡' },
      { label: '房间', value: summary.roomCount || 0, unit: '间' },
      { label: '湿区', value: summary.wetRoomCount || 0, unit: '间' },
      { label: '置信度', stars: Math.max(1, Math.min(5, vm.confidenceScore || 4)) },
    ],
    chips: [
      { icon: 'wallpaper', text: `墙面 ${summary.totalWallArea || 0} ㎡` },
      { icon: 'plug', text: `插座 ${summary.socketCount || 0} 个` },
      { icon: 'lightbulb', text: `灯位 ${summary.lightCount || 0} 个` },
      { icon: 'droplets', text: `给排水点 ${summary.waterPointCount || 0} 个` },
      { icon: 'circle-dot', text: `地漏 ${summary.floorDrainCount || 0} 个` },
    ],
    warning,
    detailLabel: '查看真实算量详情',
  };
};

const buildRealQuoteResult = (turn) => {
  const vm = turn.viewModel;
  const summary = vm.summary || {};
  if (vm.reportMissing) {
    return {
      icon: 'file-warning',
      color: 'var(--risk-mid)',
      bg: '#FFF8E7',
      title: '报告模型缺失',
      sub: `${vm.projectName} · 请重新审核或下载旧报告`,
      metrics: [
        { label: '总分', value: 0, unit: '/100' },
        { label: '高风险', value: 0, unit: '项' },
        { label: '中风险', value: 0, unit: '项' },
        { label: '状态', text: 'NO REPORT' },
      ],
      chips: [{ icon: 'file-warning', text: '缺少 quote_report_model，不展示前端推算结果' }],
      warning: (vm.reviewItems && vm.reviewItems[0]) || '',
      detailLabel: '查看说明',
    };
  }
  const warning = (vm.warnings && vm.warnings[0]) || (vm.topRisks && vm.topRisks[0] && vm.topRisks[0].title) || '';
  return {
    icon: 'search-check',
    color: 'var(--quote)',
    bg: '#FDEBD0',
    title: '真实报价审核报告已生成',
    sub: `${vm.company} · ¥${(summary.totalPrice || 0).toLocaleString()} · ${summary.area || 0} ㎡`,
    metrics: [
      { label: '总分', value: summary.totalScore || 0, unit: '/100' },
      { label: '高风险', value: summary.highRiskCount || 0, unit: '项' },
      { label: '中风险', value: summary.midRiskCount || 0, unit: '项' },
      { label: '漏项', value: summary.missingCount || 0, unit: '项' },
    ],
    chips: [
      { icon: 'list-checks', text: `明细 ${summary.itemCount || 0} 项` },
      { icon: 'alert-triangle', text: `TOP 风险 ${vm.topRisks.length || 0} 条` },
      { icon: 'message-square-dot', text: `谈判项 ${vm.negotiation.length || 0} 条` },
      { icon: 'file-text', text: `条款 ${vm.contractClauses.length || 0} 条` },
    ],
    warning,
    detailLabel: '查看真实审核详情',
    secondary: { icon: 'layers', text: '复用做联审' },
  };
};

const buildRealCombinedResult = (turn) => {
  const vm = turn.viewModel;
  const summary = vm.summary || {};
  if (vm.baselineLowConfidence) {
    return {
      icon: 'file-warning',
      color: 'var(--risk-mid)',
      bg: '#FFF8E7',
      title: '图纸置信度过低，未进入联审',
      sub: `${vm.projectName} · 请补充更清晰图纸后重试`,
      metrics: [
        { label: '总分', value: 0, unit: '/100' },
        { label: '工程量比对', value: 0, unit: '项' },
        { label: '报价附件', value: (turn.rawQuoteFiles || []).length, unit: '份' },
        { label: '状态', text: '需补图' },
      ],
      chips: [{ icon: 'file-warning', text: '图纸置信度不足，后端已按策略早退' }],
      warning: (vm.reviewItems && vm.reviewItems[0]) || '',
      detailLabel: '查看联审状态',
    };
  }
  if (vm.reportMissing) {
    return {
      icon: 'file-warning',
      color: 'var(--risk-mid)',
      bg: '#FFF8E7',
      title: '联审报告模型缺失',
      sub: `${vm.projectName} · 请重新联审或下载旧报告`,
      metrics: [
        { label: '总分', value: 0, unit: '/100' },
        { label: '高风险', value: 0, unit: '项' },
        { label: '工程量比对', value: 0, unit: '项' },
        { label: '状态', text: 'NO REPORT' },
      ],
      chips: [{ icon: 'file-warning', text: '缺少 quote_report_model，不展示前端推算结果' }],
      warning: (vm.reviewItems && vm.reviewItems[0]) || '',
      detailLabel: '查看说明',
    };
  }
  const warning = (vm.warnings && vm.warnings[0])
    || ((vm.detailData && vm.detailData.top_risks && vm.detailData.top_risks[0]) ? vm.detailData.top_risks[0].title : '')
    || '';
  return {
    icon: 'layers',
    color: 'var(--combined)',
    bg: '#EEDDF5',
    title: '真实联审报告已生成',
    sub: `${vm.company} · ¥${(summary.totalPrice || 0).toLocaleString()} · 基准算量 vs 报价`,
    metrics: [
      { label: '总分', value: summary.totalScore || 0, unit: '/100' },
      { label: '虚报金额', value: summary.overstateTotal || 0, unit: '元', isMoney: true },
      { label: '漏项金额', value: summary.understateTotal || 0, unit: '元', isMoney: true },
      { label: '高风险', value: summary.highRiskCount || 0, unit: '项' },
    ],
    chips: [
      { icon: 'git-compare-arrows', text: `工程量比对 ${summary.quantityCheckCount || 0} 项` },
      { icon: 'list-checks', text: `明细 ${summary.itemCount || 0} 项` },
      { icon: 'alert-triangle', text: `TOP 风险 ${(vm.detailData && vm.detailData.top_risks || []).length} 条` },
      { icon: 'file-text', text: `条款 ${(vm.detailData && vm.detailData.contract_clauses || []).length} 条` },
    ],
    warning,
    detailLabel: '查看真实联审详情',
    secondary: { icon: 'scale', text: '加一家做对比' },
  };
};

const buildRealCompareResult = (turn) => {
  const vm = turn.viewModel;
  const summary = vm.summary || {};
  const companies = vm.companies || [];
  if (vm.reportMissing) {
    return {
      icon: 'file-warning',
      color: 'var(--risk-mid)',
      bg: '#FFF8E7',
      title: '报告模型缺失',
      sub: `${vm.projectName} · 请重新对比或下载旧报告`,
      metrics: [
        { label: '公司数', value: summary.companyCount || companies.length || 0, unit: '家' },
        { label: '推荐', text: 'NO REPORT' },
        { label: '矩阵项', value: summary.matrixItemCount || 0, unit: '项' },
        { label: '状态', text: 'NO REPORT' },
      ],
      chips: [{ icon: 'file-warning', text: '缺少 quote_compare_report_v1，不展示前端推算结果' }],
      warning: (vm.reviewItems && vm.reviewItems[0]) || '',
      detailLabel: '查看说明',
    };
  }

  const totals = companies
    .map(c => Number(c.normalizedTotalPrice || c.totalPrice || c.originalTotalPrice || 0))
    .filter(v => Number.isFinite(v) && v > 0);
  const minTotal = totals.length ? Math.min(...totals) : 0;
  const maxTotal = totals.length ? Math.max(...totals) : 0;
  const rangeText = minTotal && maxTotal
    ? `${(minTotal / 10000).toFixed(1)} - ${(maxTotal / 10000).toFixed(1)}`
    : '—';
  const causes = summary.mainCauses || [];
  const bestCompany = summary.bestValueCompany || ((vm.recommendation && vm.recommendation.bestValue) || {}).company || '未给出';
  const warning = (vm.warnings && vm.warnings[0])
    || (vm.compareNotes && vm.compareNotes[0])
    || ((vm.recommendation && vm.recommendation.bestValue) || {}).reason
    || '';

  return {
    icon: 'scale',
    color: 'var(--compare)',
    bg: '#D5F5E3',
    title: '真实多家报价对比已生成',
    sub: `${vm.projectName} · ${summary.companyCount || companies.length || 0} 家报价 · 总价差异 ${summary.totalDiffPct || 0}%`,
    metrics: [
      { label: '公司数', value: summary.companyCount || companies.length || 0, unit: '家' },
      { label: '推荐', text: bestCompany, highlight: true },
      { label: '总价跨度', value: rangeText, unit: 'w' },
      { label: '差异率', value: summary.totalDiffPct || 0, unit: '%' },
    ],
    chips: [
      { icon: 'trophy', text: `推荐：${bestCompany}` },
      { icon: 'table-2', text: `矩阵 ${summary.matrixItemCount || (vm.matrix || []).length || 0} 项` },
      { icon: 'split', text: `主因：${causes[0] ? causes[0].label : '待核验'}` },
      { icon: 'message-square-text', text: (vm.recommendation && vm.recommendation.negotiationTactics) ? '已生成谈判策略' : '谈判策略待补充' },
    ],
    warning,
    detailLabel: '查看真实对比详情',
    secondary: { icon: 'layers', text: '针对推荐做联审' },
  };
};

const ResultCard = ({ variant, kind = 'quantity', turn, onOpenDetail, onExport }) => {
  const isRealQuantity = turn && turn.source === 'real' && turn.kind === 'quantity' && turn.viewModel;
  const isRealQuote = turn && turn.source === 'real' && turn.kind === 'quote' && turn.viewModel;
  const isRealCombined = turn && turn.source === 'real' && turn.kind === 'combined' && turn.viewModel;
  const isRealCompare = turn && turn.source === 'real' && turn.kind === 'compare' && turn.viewModel;
  const isRealDownloadable = isRealQuantity || isRealQuote || isRealCombined || isRealCompare;
  const r = isRealQuantity
    ? buildRealQuantityResult(turn)
    : isRealQuote
      ? buildRealQuoteResult(turn)
      : isRealCombined
        ? buildRealCombinedResult(turn)
        : isRealCompare
          ? buildRealCompareResult(turn)
          : (RESULT_PRESETS[kind] || RESULT_PRESETS.quantity);
  const canDownload = isRealDownloadable && turn.jobId && !(turn.viewModel && turn.viewModel.baselineLowConfidence);
  const doExport = (format) => {
    if (canDownload && onExport) onExport(turn.jobId, format);
  };

  return (
    <div className="result-card" data-variant={variant}>
      <div className="result-head">
        <div className="result-head-icon" style={{ background: r.bg, color: r.color }}>
          <Icon name={r.icon} size={16} />
        </div>
        <div>
          <div className="result-head-title">{r.title}</div>
          <div className="result-head-sub">{r.sub}</div>
        </div>
        <div style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--text-muted)' }}>刚刚</div>
      </div>
      <div className="result-body">
        <div className="result-metrics">
          {r.metrics.map((m, i) => (
            <div key={i}>
              <div className="result-metric-label">{m.label}</div>
              <div className="result-metric-value" style={m.highlight ? { color: r.color } : {}}>
                {m.stars ? (
                  <span className="viz-confidence">
                    {[0,1,2,3].map(i => <Icon key={i} name="star" size={14} style={{ fill: i < m.stars ? 'currentColor' : 'transparent', color: i < m.stars ? r.color : 'var(--border-strong)' }} strokeWidth={i < m.stars ? 0 : 1.5} />)}
                    <Icon name="star" size={14} style={{ color: 'var(--border-strong)' }} strokeWidth={1.5} />
                  </span>
                ) : m.text ? (
                  <span style={{ fontSize: 18 }}>{m.text}</span>
                ) : (
                  <>
                    {m.isMoney ? '¥' : ''}{typeof m.value === 'number' ? m.value.toLocaleString() : m.value}
                    <small>{m.unit}</small>
                  </>
                )}
              </div>
            </div>
          ))}
        </div>

        {variant !== 'minimal' && (
          <div className="result-materials">
            {r.chips.map((c, i) => (
              <span className="material-chip" key={i}><Icon name={c.icon} size={12} /> {c.text}</span>
            ))}
          </div>
        )}

        {variant !== 'minimal' && r.warning && (
          <div className="result-warning">
            <Icon name="alert-triangle" size={14} strokeWidth={2} style={{ marginTop: 1 }} />
            <div>{r.warning}</div>
          </div>
        )}
      </div>
      <div className="result-actions">
        <button className="btn btn-primary" onClick={onOpenDetail}>
          <Icon name="layout-list" size={14} /> {r.detailLabel}
        </button>
        <button className="btn btn-ghost" disabled={isRealDownloadable && !canDownload} onClick={() => doExport('excel')}>
          <Icon name="file-spreadsheet" size={14} /> 下载 Excel
        </button>
        <button className="btn btn-ghost" disabled={isRealDownloadable && !canDownload} onClick={() => doExport('pdf')}>
          <Icon name="file-text" size={14} /> 下载 PDF
        </button>
        {r.secondary && (
          <button className="btn btn-ghost" style={{ marginLeft: 'auto' }}>
            <Icon name={r.secondary.icon} size={14} /> {r.secondary.text}
          </button>
        )}
      </div>
    </div>
  );
};

window.Chat = Chat;
window.ChatTurn = ChatTurn;
