// ===== page-report.jsx =====
const { useState: useSR, useMemo: useMR, useRef: useRR, useEffect: useER } = React;

const LEVEL_META = {
  low:        { label: '低风险',    desc: '文本呈现明显的人类写作特征，AI 生成概率较低。可放心提交。' },
  mid:        { label: '中风险',    desc: '部分段落呈现可疑的 AI 生成痕迹，建议针对性核查并改写。' },
  'mid-high': { label: '中高风险',  desc: '检测发现明显 AI 生成特征，建议对标记段落进行人工改写后再次自查。' },
  high:       { label: '高风险',    desc: '文本呈现强烈的 AI 生成统计特征，建议对全文进行系统性人工改写。' },
};

function adaptApiReport(apiReport) {
  const mm = apiReport.model_match || {};
  const models = [
    { name: 'ChatGPT (GPT-4)', match: Math.round(mm.chatgpt  || 0) },
    { name: 'Claude',          match: Math.round(mm.claude   || 0) },
    { name: 'DeepSeek',        match: Math.round(mm.deepseek || 0) },
    { name: '文心一言',         match: Math.round(mm.wenxin   || 0) },
    { name: '通义千问',         match: Math.round(mm.qwen     || 0) },
    { name: '豆包',            match: Math.round(mm.doubao   || 0) },
  ].sort((a, b) => b.match - a.match);
  const meta = LEVEL_META[apiReport.risk_level] || LEVEL_META.mid;
  const dims = apiReport.dimensions || {};
  const metaStats = apiReport.meta_stats || {};
  return {
    id: apiReport.id,
    orderId: apiReport.order_id,
    title: (apiReport.file_name || '我的检测报告').replace(/\.[^.]+$/, ''),
    author: '私密用户',
    submitted: new Date(apiReport.created_at).toLocaleString('zh-CN'),
    words: apiReport.word_count || 0,
    durationSec: apiReport.duration_sec || 0,
    aigc: Math.round(apiReport.aigc_probability || 0),
    level: apiReport.risk_level,
    levelLabel: meta.label,
    levelDesc: meta.desc,
    summary: apiReport.summary || '',
    keyFindings: apiReport.key_findings || [],
    metaStats,
    clicheTop: metaStats.cliche_top || [],
    voiceTop: metaStats.voice_top || [],
    sampleInfo: metaStats.sample_info || {},
    dims: {
      perplexity: Math.round(dims.perplexity || 0),
      burstiness: Math.round(dims.burstiness || 0),
      cliche:     Math.round(dims.cliche || 0),
      regularity: Math.round(dims.regularity || 0),
      voice:      Math.round(dims.voice || 0),
    },
    models,
    paragraphs: apiReport.paragraphs || [],
    suggestions: apiReport.suggestions || [],
  };
}

function ReportPage({ navigate, params }) {
  const requestedId = params?.id || 'r-1085';
  const urlToken = params?.token || '';
  const isDemo = requestedId.startsWith('r-');

  // URL 自带 token 时立即落盘到 localStorage（覆盖旧值），保证后续刷新/再访问都能用
  if (!isDemo && requestedId && urlToken) {
    window.API.saveToken(requestedId, urlToken);
  }

  const [reportId, setReportId]     = useSR(requestedId);
  const [realReport, setRealReport] = useSR(null);
  const [loading, setLoading]       = useSR(!isDemo);
  const [error, setError]           = useSR(null);

  useER(() => {
    setReportId(requestedId);
    window.scrollTo({ top: 0, behavior: 'instant' });
    if (requestedId.startsWith('r-')) {
      setRealReport(null);
      setLoading(false);
      setError(null);
    } else {
      setLoading(true);
      setError(null);
      // URL token 优先，否则 fall back 到 localStorage
      window.API.getReport(requestedId, urlToken || undefined)
        .then(data => setRealReport(adaptApiReport(data)))
        .catch(err => setError(err))
        .finally(() => setLoading(false));
    }
  }, [requestedId, urlToken]);

  // Demo mode allows switching between samples
  const handleSwitchSample = (id) => {
    if (!id.startsWith('r-')) return;
    setReportId(id);
  };

  if (loading) {
    return (
      <div className="min-h-screen bg-ink-50 flex items-center justify-center">
        <div className="text-center">
          <I.Spinner size={32} className="text-brand-600 mx-auto" />
          <div className="mt-4 text-[14px] text-ink-500">加载报告中...</div>
        </div>
      </div>
    );
  }

  if (error) {
    const isNotReady = error.code === 'report_not_ready';
    const isAuthFail = error.code === 'invalid_token' || error.status === 403;
    return (
      <div className="min-h-screen bg-ink-50 flex items-center justify-center px-6">
        <Card className="p-8 text-center max-w-md">
          <div className="w-14 h-14 mx-auto rounded-full bg-bad-50 text-bad-600 flex items-center justify-center"><I.AlertTriangle size={26} /></div>
          <h2 className="mt-4 text-[20px] font-bold text-navy-900">
            {isNotReady ? '报告尚未生成' : isAuthFail ? '无权访问' : '报告加载失败'}
          </h2>
          <p className="mt-2 text-[13.5px] text-ink-500 leading-relaxed">
            {isNotReady ? '请等待管理员确认收款，报告生成后页面会自动更新。' :
             isAuthFail ? '访问令牌已失效。如果你是订单本人，请通过订单页返回查看。' :
             (error.message || '未知错误')}
          </p>
          <Button kind="dark" size="lg" onClick={() => navigate('home')} className="mt-6">返回首页</Button>
        </Card>
      </div>
    );
  }

  const report = isDemo
    ? (MOCK.REPORTS_BY_ID[reportId] || MOCK.REPORTS[1])
    : realReport;

  if (!report) return null;

  const mountKey = `${reportId}`;
  const risk = getRisk(report.aigc);
  const pdfUrl = isDemo
    ? null
    : window.API.getReportPdfUrl(requestedId);

  return (
    <div className="min-h-screen bg-ink-50">
      <ReportHeader report={report} navigate={navigate} pdfUrl={pdfUrl} />

      {/* 仅 demo 模式显示示例切换 */}
      {isDemo && (
        <div className="border-b border-ink-200 bg-white sticky top-16 z-30">
          <div className="max-w-[1200px] mx-auto px-6 lg:px-8 h-12 flex items-center gap-3 overflow-x-auto">
            <span className="text-[11px] tracking-wider uppercase text-ink-400 font-semibold flex-shrink-0">示例样本</span>
            {MOCK.REPORTS.map(r => (
              <button key={r.id} onClick={() => handleSwitchSample(r.id)}
                className={`flex-shrink-0 h-8 px-3 rounded-full text-[12.5px] inline-flex items-center gap-2 transition-all
                  ${reportId === r.id ? 'bg-navy-900 text-white' : 'bg-ink-50 text-ink-700 hover:bg-ink-100'}`}>
                <span className={`w-1.5 h-1.5 rounded-full ${getRisk(r.aigc).dotClass}`} />
                {r.title.length > 16 ? r.title.slice(0, 16) + '…' : r.title}
                <span className="num opacity-70">{r.aigc}%</span>
              </button>
            ))}
          </div>
        </div>
      )}

      <div className="max-w-[1200px] mx-auto px-6 lg:px-8 py-10" key={mountKey}>
        <div className="grid lg:grid-cols-12 gap-6">
          <div className="lg:col-span-12">
            <Card className="p-7">
              <div className="flex flex-wrap items-start justify-between gap-4">
                <div className="min-w-0 flex-1">
                  <div className="flex items-center gap-2 text-[11.5px] tracking-wider uppercase text-ink-500 font-semibold">
                    <I.FileText size={13} /> AIGC 智能检测报告
                    <span className="text-ink-300">·</span>
                    <span className="mono">{(report.id || '').toUpperCase()}</span>
                  </div>
                  <h1 className="mt-2 text-[26px] sm:text-[30px] font-bold text-navy-900 tracking-tight leading-tight" style={{ textWrap: 'balance' }}>
                    {report.title}
                  </h1>
                  <div className="mt-3 flex flex-wrap items-center gap-x-5 gap-y-2 text-[12.5px] text-ink-500">
                    <MetaItem icon={<I.University size={13} />} label="作者" value={report.author} />
                    <MetaItem icon={<I.Clock size={13} />}      label="提交时间" value={report.submitted} />
                    <MetaItem icon={<I.FileText size={13} />}   label="字数" value={`${(report.words || 0).toLocaleString()} 字`} />
                    {report.durationSec > 0 && <MetaItem icon={<I.Cpu size={13} />} label="检测耗时" value={`${report.durationSec} 秒`} />}
                    <MetaItem icon={<I.Layers size={13} />}     label="引擎版本" value="Mojian v1.4" />
                  </div>
                </div>
                <div className="flex items-center gap-2 flex-shrink-0">
                  {pdfUrl ? (
                    <Button kind="dark" size="md" icon={<I.Download size={14} />} onClick={() => window.open(pdfUrl, '_blank')}>下载 PDF 报告</Button>
                  ) : (
                    <Button kind="dark" size="md" icon={<I.Download size={14} />} disabled>下载 PDF（示例）</Button>
                  )}
                </div>
              </div>
            </Card>
          </div>
        </div>

        <SectionTitle n="01" title="总体评分" subtitle="基于 5 维度统计模型的加权 AIGC 概率，与对应的风险等级。" />
        <Card className="p-7 sm:p-9">
          <div className="grid lg:grid-cols-12 gap-8 items-center">
            <div className="lg:col-span-5 flex justify-center">
              <Gauge value={report.aigc} size={260} sublabel="加权 AIGC 概率" />
            </div>
            <div className="lg:col-span-7">
              <div className="flex items-center gap-3">
                <RiskIcon level={report.level} />
                <div>
                  <div className="text-[11px] tracking-[0.18em] uppercase font-semibold" style={{ color: risk.color }}>风险等级</div>
                  <div className="text-[28px] font-bold tracking-tight" style={{ color: risk.color }}>{report.levelLabel}</div>
                </div>
              </div>
              <p className="mt-4 text-[14.5px] text-ink-700 leading-relaxed max-w-xl">{report.levelDesc}</p>

              <div className="mt-7">
                <div className="relative h-2.5 rounded-full overflow-hidden"
                     style={{ background: 'linear-gradient(90deg, #10B981 0%, #10B981 25%, #F59E0B 40%, #F59E0B 60%, #EF4444 75%, #DC2626 100%)' }}>
                  <div className="absolute top-1/2 -translate-y-1/2 w-4 h-4 rounded-full bg-white ring-2 ring-navy-900 transition-all duration-1000" style={{ left: `calc(${report.aigc}% - 8px)` }} />
                </div>
                <div className="mt-2 grid grid-cols-4 text-[11px] text-ink-500 num">
                  <div>0 · 安全</div>
                  <div className="text-center">30 · 低</div>
                  <div className="text-center">60 · 中</div>
                  <div className="text-right">100 · 高</div>
                </div>
              </div>

              <div className="mt-7 grid grid-cols-3 gap-3">
                <MetricChip label="人类相似度" value={`${100 - report.aigc}%`} tone="ok" />
                <MetricChip label="样本句数" value={Math.max(1, Math.round(report.words / 38))} tone="ink" />
                <MetricChip label="标记段落" value={`${report.paragraphs.filter(p => p.risk !== 'safe').length} / ${report.paragraphs.length}`} tone="brand" />
              </div>
            </div>
          </div>
        </Card>

        {report.summary && (
          <>
            <SectionTitle n="02" title="整体诊断" subtitle="基于本地统计特征 + 大模型语义判读的综合判定。" />
            <Card className="p-7 sm:p-8">
              <div className="flex items-start gap-4">
                <div className="w-9 h-9 rounded-lg bg-brand-50 text-brand-600 flex items-center justify-center flex-shrink-0">
                  <I.FileText size={18} />
                </div>
                <p className="text-[15px] text-ink-700 leading-[1.85] flex-1">{report.summary}</p>
              </div>
            </Card>
          </>
        )}

        {report.keyFindings && report.keyFindings.length > 0 && (
          <>
            <SectionTitle n="03" title="关键发现" subtitle="3-5 条最有诊断价值的具体发现。" />
            <Card className="p-7 sm:p-8">
              <ul className="space-y-2.5">
                {report.keyFindings.map((f, i) => (
                  <li key={i} className="flex items-start gap-3 p-3 rounded-lg bg-warn-50 ring-1 ring-warn-100">
                    <div className="num font-bold text-warn-600 text-[13px] flex-shrink-0 w-6 leading-6 text-center">
                      {String(i + 1).padStart(2, '0')}
                    </div>
                    <div className="text-[13.5px] text-ink-700 leading-relaxed flex-1">{f}</div>
                  </li>
                ))}
              </ul>
            </Card>
          </>
        )}

        {(report.metaStats && report.metaStats.char_count) && (
          <>
            <SectionTitle n="04" title="客观统计特征" subtitle="本地纯 Python 统计模块对全文的真实计算结果（毫秒级、零 AI 估算）。" />
            <Card className="p-7 sm:p-8">
              <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
                <StatBox label="总字符数" value={(report.metaStats.char_count || 0).toLocaleString()} unit="字符" />
                <StatBox label="段落数"   value={report.metaStats.paragraph_count || 0} unit="段" />
                <StatBox label="句子数"   value={report.metaStats.sentence_count || 0}   unit="句" />
                <StatBox label="平均句长" value={report.metaStats.avg_sentence_len || 0} unit="字/句" />
                <StatBox label="句长标准差" value={report.metaStats.sentence_len_std || 0} unit="σ" />
                <StatBox label="最短句"   value={report.metaStats.min_sentence_len || 0} unit="字" />
                <StatBox label="最长句"   value={report.metaStats.max_sentence_len || 0} unit="字" />
                <StatBox label="综合分"   value={report.metaStats.composite || 0} unit="/ 100" highlight />
              </div>

              {(report.clicheTop.length > 0 || report.voiceTop.length > 0) && (
                <div className="mt-6 grid grid-cols-1 lg:grid-cols-2 gap-5">
                  <HitsList title="LLM 套语命中 Top" tone="bad" items={report.clicheTop}
                    emptyText="未检测到典型 LLM 套语" />
                  <HitsList title="人类痕迹标志命中 Top" tone="ok" items={report.voiceTop}
                    emptyText="未检测到第一人称、案例引出等人类痕迹" />
                </div>
              )}

              {report.sampleInfo && report.sampleInfo.strategy === 'smart_sample' && (
                <div className="mt-5 p-3 rounded-lg bg-ink-50 text-[12px] text-ink-500 leading-relaxed">
                  ※ 全文较长（共 {report.sampleInfo.total} 段），段落级分析采用智能抽样（头尾 + 最长/最短 + 中间均匀采样）。
                  完整全文的 5 维统计指标基于全文真算（非抽样），上方数据为全文统计结果。
                </div>
              )}
            </Card>
          </>
        )}

        <SectionTitle n="05" title="多维度统计分析" subtitle="5 个维度的归一化得分（0–100）。点击维度可查看判定逻辑。" />
        <div className="grid lg:grid-cols-12 gap-5">
          <Card className="p-6 lg:col-span-6 flex flex-col items-center">
            <Radar dims={report.dims} size={380} />
          </Card>
          <div className="lg:col-span-6">
            <DimensionList dims={report.dims} />
          </div>
        </div>

        <SectionTitle n="06" title="段落级风险标记" subtitle="本地算法对全文每段都打分；中/高风险段配有大模型语义点评。" extra={<RiskLegend />} />
        <Card className="p-7 sm:p-9">
          <ParagraphSection paragraphs={report.paragraphs} metaStats={report.metaStats} />
        </Card>

        <SectionTitle n="07" title="AI 模型特征匹配" subtitle="文本统计特征与各主流大模型生成样本的相似度。" />
        <div className="grid lg:grid-cols-12 gap-5">
          <Card className="p-7 lg:col-span-8">
            <ModelBars models={report.models} />
          </Card>
          <Card className="p-7 lg:col-span-4">
            <div className="text-[11.5px] tracking-[0.18em] uppercase text-ink-500 font-semibold">最高匹配模型</div>
            <div className="mt-3 text-[28px] font-bold text-navy-900 tracking-tight">{report.models[0]?.name || '-'}</div>
            <div className="mt-1 text-[13px] text-ink-500">匹配度 <span className="num font-semibold text-bad-600">{report.models[0]?.match ?? 0}%</span></div>
            <p className="mt-5 text-[12.5px] text-ink-500 leading-relaxed">
              匹配度高仅表明文本与该模型生成样本在统计特征上接近，<strong className="text-navy-900">不构成"该模型生成"的判定</strong>。
              用作改写参考即可。
            </p>
            <div className="mt-5 pt-5 border-t border-ink-200">
              <div className="text-[11.5px] uppercase tracking-[0.18em] text-ink-500 font-semibold">模型指纹库</div>
              <div className="mt-2 text-[12.5px] text-ink-700">8 大主流模型 · 更新于 2026-05-18</div>
            </div>
          </Card>
        </div>

        <SectionTitle n="08" title="改写建议" subtitle="基于本报告各维度异常点，给出针对性的改写方向。" />
        <Card className="p-7 sm:p-9">
          <ul className="space-y-3">
            {report.suggestions.map((s, i) => (
              <li key={i} className="flex items-start gap-4 p-4 rounded-xl bg-ink-50 ring-1 ring-ink-200">
                <div className="w-7 h-7 rounded-md bg-navy-900 text-white flex items-center justify-center text-[12px] font-semibold num flex-shrink-0">{i + 1}</div>
                <div className="text-[14px] text-ink-700 leading-relaxed">{s}</div>
              </li>
            ))}
            {report.suggestions.length === 0 && (
              <li className="text-[13px] text-ink-500 text-center py-6">本次检测未发现需要专项改写的高风险段落。</li>
            )}
          </ul>

          <div className="mt-6 p-5 rounded-xl bg-brand-50 ring-1 ring-brand-100 flex items-start gap-3">
            <I.Sparkles size={16} className="text-brand-600 mt-0.5" />
            <div className="text-[13px] text-brand-600 leading-relaxed">
              <strong className="font-semibold">改写小提示：</strong>
              不要用 AI 反向"降 AIGC"——这往往会引入新的套话。更可靠的方法是<span className="text-navy-900">替换为你自己的研究细节、反例和情绪化判断</span>，
              然后用墨鉴再次检测对照。
            </div>
          </div>
        </Card>

        <div className="mt-10 p-6 rounded-2xl bg-navy-950 text-white">
          <div className="flex items-start gap-4">
            <I.ShieldCheck size={28} className="text-brand-500 mt-1 flex-shrink-0" />
            <div className="text-[13px] leading-relaxed text-ink-300">
              <strong className="text-white font-semibold">报告声明：</strong>
              本报告基于多维度统计模型分析，结果仅供参考；检测算法持续迭代中，与各类官方查重系统的结果可能存在差异。
              学术成果的最终判定请以所在机构的官方系统为准。
              本平台不保留原文，所有上传文件在检测完成 24 小时内自动从服务器删除。
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function MetaItem({ icon, label, value }) {
  return (
    <span className="inline-flex items-center gap-1.5">
      <span className="text-ink-400">{icon}</span>
      <span className="text-ink-400">{label}</span>
      <span className="text-ink-700 font-medium num">{value}</span>
    </span>
  );
}

function SectionTitle({ n, title, subtitle, extra }) {
  return (
    <div className="mt-12 mb-4 flex items-end justify-between gap-4 flex-wrap">
      <div>
        <div className="text-[11px] tracking-[0.18em] uppercase text-brand-600 font-semibold mono">{n} · SECTION</div>
        <h2 className="mt-1 text-[22px] sm:text-[24px] font-bold text-navy-900 tracking-tight">{title}</h2>
        {subtitle && <p className="mt-1 text-[13.5px] text-ink-500 max-w-2xl">{subtitle}</p>}
      </div>
      {extra}
    </div>
  );
}

function MetricChip({ label, value, tone = 'ink' }) {
  const tones = {
    ink:   'bg-ink-50',
    ok:    'bg-ok-50 text-ok-600 ring-ok-100',
    brand: 'bg-brand-50 text-brand-600 ring-brand-100',
  };
  return (
    <div className={`p-3 rounded-lg ring-1 ring-ink-200 ${tones[tone]}`}>
      <div className="text-[10.5px] uppercase tracking-wider text-ink-500 font-semibold">{label}</div>
      <div className="mt-1 num text-[20px] font-bold text-navy-900">{value}</div>
    </div>
  );
}

function getRisk(aigc) {
  if (aigc < 30) return { label: '低风险',    color: '#059669', dotClass: 'bg-ok-500',   tone: 'ok'   };
  if (aigc < 55) return { label: '中风险',    color: '#D97706', dotClass: 'bg-warn-500', tone: 'warn' };
  if (aigc < 75) return { label: '中高风险',  color: '#EF4444', dotClass: 'bg-bad-500',  tone: 'bad'  };
  return            { label: '高风险',         color: '#DC2626', dotClass: 'bg-bad-600',  tone: 'bad'  };
}

function RiskIcon({ level }) {
  if (level === 'low')   return <div className="w-12 h-12 rounded-xl bg-ok-50   text-ok-600 ring-1 ring-ok-100 flex items-center justify-center"><I.CheckCircle size={22} /></div>;
  if (level === 'mid')   return <div className="w-12 h-12 rounded-xl bg-warn-50 text-warn-600 ring-1 ring-warn-100 flex items-center justify-center"><I.AlertTriangle size={22} /></div>;
  return <div className="w-12 h-12 rounded-xl bg-bad-50 text-bad-600 ring-1 ring-bad-100 flex items-center justify-center"><I.AlertTriangle size={22} /></div>;
}

function ReportHeader({ report, navigate, pdfUrl }) {
  return (
    <header className="sticky top-0 z-40 backdrop-blur bg-white/85 border-b border-ink-200">
      <div className="max-w-[1200px] mx-auto h-16 px-6 lg:px-8 flex items-center justify-between">
        <button onClick={() => navigate('home')} className="flex items-center gap-2.5">
          <I.Logo size={28} />
          <div className="leading-tight">
            <div className="font-semibold text-navy-900 text-[14px]">墨鉴 · Mojian</div>
            <div className="text-[10px] text-ink-500 -mt-0.5 tracking-wide">ACADEMIC AI DETECTOR</div>
          </div>
        </button>
        <div className="hidden md:flex items-center gap-2 text-[12px] text-ink-500">
          <I.FileText size={13} />
          <span className="mono">{(report.id || '').toUpperCase()}</span>
          <span className="text-ink-300">·</span>
          <span>生成于 {report.submitted}</span>
        </div>
        <div className="flex items-center gap-2">
          <Button kind="ghost" size="sm" onClick={() => navigate('home')}>关闭</Button>
          {pdfUrl ? (
            <Button kind="primary" size="sm" icon={<I.Download size={14} />} onClick={() => window.open(pdfUrl, '_blank')}>下载 PDF</Button>
          ) : (
            <Button kind="primary" size="sm" icon={<I.Download size={14} />} disabled>下载 PDF</Button>
          )}
        </div>
      </div>
    </header>
  );
}

function DimensionList({ dims }) {
  const [open, setOpen] = useSR(null);
  return (
    <div className="space-y-3">
      {MOCK.DIMENSIONS.map(d => {
        const val = dims[d.key];
        const isOpen = open === d.key;
        const tone = d.key === 'voice'
          ? (val >= 60 ? 'ok' : val >= 40 ? 'warn' : 'bad')
          : (val < 40 ? 'ok' : val < 65 ? 'warn' : 'bad');
        const accent = tone === 'ok' ? '#10B981' : tone === 'warn' ? '#F59E0B' : '#EF4444';
        return (
          <Card key={d.key} className="overflow-hidden">
            <button onClick={() => setOpen(isOpen ? null : d.key)} className="w-full p-4 flex items-center gap-4 text-left">
              <div className="flex-1 min-w-0">
                <div className="flex items-center gap-2">
                  <span className="text-[14.5px] font-semibold text-navy-900">{d.label}</span>
                  <span className="text-[11px] text-ink-400 mono">{d.en}</span>
                </div>
                <div className="mt-2 h-1.5 bg-ink-100 rounded-full overflow-hidden">
                  <div className="h-full rounded-full" style={{ width: `${val}%`, background: accent, transition: 'width 1s ease-out' }} />
                </div>
              </div>
              <div className="num text-right">
                <div className="text-[22px] font-bold text-navy-900 leading-none">{val}</div>
                <div className="text-[10px] text-ink-400 mt-1">/ 100</div>
              </div>
              <I.ChevronDown size={16} className={`text-ink-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
            </button>
            <div className="overflow-hidden transition-[max-height,opacity] duration-300" style={{ maxHeight: isOpen ? 200 : 0, opacity: isOpen ? 1 : 0 }}>
              <div className="px-4 pb-4 pt-1 text-[12.5px] text-ink-500 leading-relaxed border-t border-ink-200">
                {d.desc}
              </div>
            </div>
          </Card>
        );
      })}
    </div>
  );
}

function RiskLegend() {
  return (
    <div className="flex items-center gap-3 text-[12px] flex-wrap">
      {[
        { color: '#10B981', bg: '#ECFDF5', label: '安全' },
        { color: '#F59E0B', bg: '#FFFBEB', label: '中风险' },
        { color: '#EF4444', bg: '#FEF2F2', label: '高风险' },
      ].map(l => (
        <div key={l.label} className="inline-flex items-center gap-1.5">
          <span className="inline-block w-3.5 h-3.5 rounded-sm ring-1" style={{ background: l.bg, borderColor: l.color, color: l.color, ringColor: l.color }} />
          <span className="text-ink-500">{l.label}</span>
        </div>
      ))}
    </div>
  );
}

function ParagraphSection({ paragraphs, metaStats }) {
  // 计数：用 prose_total（参与判定）而不是 total（含 skip）
  const dist = (metaStats && metaStats.paragraph_dist) || {};
  const prose_total = dist.prose_total ?? paragraphs.filter(p => p.risk !== 'skip').length;
  const skipCount = dist.skip ?? paragraphs.filter(p => p.risk === 'skip').length;
  const high = dist.high ?? paragraphs.filter(p => p.risk === 'high').length;
  const med  = dist.med  ?? paragraphs.filter(p => p.risk === 'med').length;
  const safe = dist.safe ?? paragraphs.filter(p => p.risk === 'safe').length;

  // 过滤器：默认只看 med + high（信号最强的部分）
  const [filter, setFilter] = useSR('risky'); // 'risky' | 'high' | 'med' | 'safe' | 'all' | 'skip'
  const [renderLimit, setRenderLimit] = useSR(200);

  const filtered = useMR(() => {
    let arr = paragraphs;
    if (filter === 'risky') arr = paragraphs.filter(p => p.risk === 'med' || p.risk === 'high');
    else if (filter === 'all') arr = paragraphs.filter(p => p.risk !== 'skip');
    else arr = paragraphs.filter(p => p.risk === filter);
    return [...arr].sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
  }, [paragraphs, filter]);

  const shown = filtered.slice(0, renderLimit);
  const hiddenCount = filtered.length - shown.length;

  return (
    <div className="space-y-5">
      {/* 风险分布概览 */}
      <div className="grid grid-cols-4 gap-3">
        <DistChip count={high} total={prose_total} label="高风险段" tone="bad" />
        <DistChip count={med}  total={prose_total} label="中风险段" tone="warn" />
        <DistChip count={safe} total={prose_total} label="低风险段" tone="ok" />
        <DistChip count={prose_total} total={prose_total} label="参与判定段" tone="ink" />
      </div>
      {skipCount > 0 && (
        <p className="text-[11.5px] text-ink-400 italic -mt-1">
          ※ 另有 {skipCount} 段为标题、列表项、代码段、参考条目等，由于样本特征不适合风险评分而被排除。
        </p>
      )}

      {/* 过滤器 */}
      <div className="flex items-center gap-2 flex-wrap">
        <span className="text-[11.5px] tracking-wider uppercase text-ink-400 font-semibold mr-1">查看</span>
        {[
          { k: 'risky', label: '中/高风险段', count: high + med, recommend: true },
          { k: 'high',  label: '仅高风险',    count: high },
          { k: 'med',   label: '仅中风险',    count: med },
          { k: 'safe',  label: '低风险段',    count: safe },
          { k: 'all',   label: '全部论述段',  count: prose_total },
          ...(skipCount > 0 ? [{ k: 'skip', label: '非论述段', count: skipCount }] : []),
        ].map(opt => (
          <button
            key={opt.k}
            onClick={() => { setFilter(opt.k); setRenderLimit(200); }}
            className={`h-8 px-3 rounded-full text-[12px] inline-flex items-center gap-1.5 ring-1 transition-colors
              ${filter === opt.k
                ? 'bg-navy-900 text-white ring-navy-900'
                : 'bg-white text-ink-700 ring-ink-200 hover:ring-ink-400'}`}>
            {opt.label}
            <span className={`num text-[11px] font-bold ${filter === opt.k ? 'text-white opacity-80' : 'text-ink-400'}`}>
              {opt.count}
            </span>
            {opt.recommend && filter !== opt.k && (
              <span className="text-[10px] text-brand-600 font-semibold">推荐</span>
            )}
          </button>
        ))}
      </div>

      {/* 列表 */}
      {filtered.length === 0 ? (
        <div className="p-6 rounded-xl bg-ok-50 ring-1 ring-ok-100 text-center">
          <div className="text-[14.5px] text-ok-600 font-semibold">
            <I.CheckCircle size={18} className="inline mr-1.5 -mt-0.5" />
            无符合筛选条件的段落
          </div>
          <div className="text-[12px] text-ink-500 mt-1">
            {filter === 'risky' && '本地算法未在全文中发现中/高风险段。'}
          </div>
        </div>
      ) : (
        <div className="space-y-2.5">
          {shown.map((p, i) => <Para key={p.idx ?? i} para={p} idx={(p.idx ?? i) + 1} />)}
          {hiddenCount > 0 && (
            <button
              onClick={() => setRenderLimit(l => l + 200)}
              className="w-full py-3 rounded-lg ring-1 ring-ink-200 bg-ink-50 hover:bg-ink-100 text-[13px] text-ink-700 font-medium">
              展开剩余 {hiddenCount} 段（共 {filtered.length} 段）
            </button>
          )}
        </div>
      )}
    </div>
  );
}

function DistChip({ count, total, label, tone }) {
  const tones = {
    bad:  { bg: 'bg-bad-50',  color: 'text-bad-600',  bar: 'bg-bad-500'  },
    warn: { bg: 'bg-warn-50', color: 'text-warn-600', bar: 'bg-warn-500' },
    ok:   { bg: 'bg-ok-50',   color: 'text-ok-600',   bar: 'bg-ok-500'   },
    ink:  { bg: 'bg-ink-50',  color: 'text-ink-700',  bar: 'bg-ink-400'  },
  }[tone] || { bg: 'bg-ink-50', color: 'text-ink-700', bar: 'bg-ink-400' };
  const pct = total > 0 ? Math.round((count / total) * 100) : 0;
  return (
    <div className={`${tones.bg} rounded-xl p-4 ring-1 ring-ink-200`}>
      <div className={`num text-[28px] font-bold leading-none ${tones.color}`}>
        {count}
        <span className="text-[12px] font-medium opacity-60 ml-1">/ {total}</span>
      </div>
      <div className="mt-2 text-[11.5px] text-ink-500 font-semibold tracking-wide">{label}</div>
      <div className="mt-2 h-1 rounded-full bg-white/60 overflow-hidden">
        <div className={`${tones.bar} h-full rounded-full`} style={{ width: `${pct}%`, transition: 'width 0.8s ease-out' }} />
      </div>
      <div className="mt-1 text-[10px] num text-ink-400">{pct}%</div>
    </div>
  );
}

function ParagraphList({ paragraphs }) {
  // 兼容老用法（demo 模式直接传 paragraphs 数组）
  return (
    <div className="space-y-2.5">
      {paragraphs.map((p, i) => <Para key={p.idx ?? i} para={p} idx={(p.idx ?? i) + 1} />)}
    </div>
  );
}

function Para({ para, idx }) {
  const [hover, setHover] = useSR(false);
  const TYPE_LABELS = { short: '标题/短段', list: '列表项', code: '代码/英文段', citation: '参考条目', prose: '论述段' };
  const isSkip = para.risk === 'skip';
  const tone = isSkip                  ? { bg: 'bg-ink-50',  border: 'border-ink-200',  bar: '#94A3B8', text: 'text-ink-500', label: TYPE_LABELS[para.type] || '非论述段', icon: <I.FileText size={14} /> }
              : para.risk === 'safe' ? { bg: 'bg-ok-50',   border: 'border-ok-100',   bar: '#10B981', text: 'text-ok-600',  label: '低风险', icon: <I.CheckCircle size={14} /> }
              : para.risk === 'med'  ? { bg: 'bg-warn-50', border: 'border-warn-100', bar: '#F59E0B', text: 'text-warn-600', label: '中风险', icon: <I.AlertTriangle size={14} /> }
                                     : { bg: 'bg-bad-50',  border: 'border-bad-100',  bar: '#EF4444', text: 'text-bad-600',  label: '高风险', icon: <I.AlertTriangle size={14} /> };
  const tooShort = isSkip;
  const hasReason = para.llm_reviewed && para.reason;
  return (
    <div
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      className={`relative rounded-lg border ${tone.border} ${tone.bg} transition-shadow ${hover ? 'shadow-card' : ''} ${tooShort ? 'opacity-60' : ''}`}>
      <div className="absolute top-0 bottom-0 left-0 w-1 rounded-l-lg" style={{ background: tone.bar }} />
      <div className="pl-5 pr-5 py-4 grid grid-cols-[40px_1fr] gap-3">
        <div className="num text-[11.5px] text-ink-400 mt-0.5 font-medium">§{idx}</div>
        <div className="min-w-0">
          <p className={`${tooShort ? 'text-[13px] italic text-ink-500' : 'text-[14.5px] text-ink-700 leading-[1.85]'}`}
             style={tooShort ? {} : { textIndent: '2em' }}>
            {para.text}
            {tooShort && <span className="ml-2 text-[11px] text-ink-400 not-italic">（段落过短，未参与判定）</span>}
          </p>
          <div className="mt-3 flex items-center gap-2.5 flex-wrap text-[11.5px]">
            <span className={`inline-flex items-center gap-1.5 px-2 h-6 rounded-full bg-white ring-1 font-medium ${tone.text}`} style={{ borderColor: tone.bar }}>
              {tone.icon}{tone.label}
            </span>
            {!isSkip && para.score != null && (
              <span className="text-ink-500">
                AIGC 得分 <span className="num font-semibold" style={{ color: tone.bar }}>{para.score}</span>
                <span className="text-ink-400"> / 100</span>
              </span>
            )}
            {isSkip && (
              <span className="text-ink-400 italic text-[11px]">不参与风险评分</span>
            )}
            {para.char_count != null && (
              <>
                <span className="text-ink-300">·</span>
                <span className="text-ink-400 num">{para.char_count} 字</span>
              </>
            )}
            {hasReason && (
              <>
                <span className="text-ink-300">·</span>
                <span className="inline-flex items-center gap-1 px-2 h-5 rounded-full bg-brand-50 text-brand-600 text-[10.5px] font-semibold ring-1 ring-brand-100">
                  <I.Sparkles size={10} />LLM 已点评
                </span>
              </>
            )}
          </div>
          {hasReason && (
            <div className="mt-3 p-3 rounded-md bg-white ring-1 ring-ink-200">
              <div className="text-[10px] tracking-[0.15em] uppercase font-semibold text-brand-600 mb-1">LLM 点评</div>
              <div className="text-[13px] text-ink-700 leading-relaxed">{para.reason}</div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function StatBox({ label, value, unit, highlight }) {
  return (
    <div className={`p-3 rounded-lg ring-1 ${highlight ? 'bg-navy-50 ring-navy-200' : 'bg-ink-50 ring-ink-200'}`}
         style={highlight ? { background: '#EFF6FF', borderColor: '#DBEAFE' } : {}}>
      <div className="text-[10.5px] uppercase tracking-wider text-ink-500 font-semibold">{label}</div>
      <div className={`mt-1 num font-bold leading-none ${highlight ? 'text-brand-600' : 'text-navy-900'}`}
           style={{ fontSize: '22px' }}>
        {value}{unit && <span className="text-[11px] text-ink-400 font-medium ml-1">{unit}</span>}
      </div>
    </div>
  );
}

function HitsList({ title, tone, items, emptyText }) {
  const accent = tone === 'bad' ? 'text-bad-600 bg-bad-50' : 'text-ok-600 bg-ok-50';
  return (
    <div>
      <h4 className="text-[12px] font-semibold text-ink-700 mb-2 tracking-wide">{title}</h4>
      {items && items.length > 0 ? (
        <div className="space-y-1">
          {items.map(([word, count], i) => (
            <div key={i} className="flex items-center justify-between px-3 py-1.5 rounded-md bg-white ring-1 ring-ink-200">
              <span className="text-[13px] text-ink-700 font-medium">{word}</span>
              <span className={`num text-[12px] font-bold px-2 py-0.5 rounded ${accent}`}>×{count}</span>
            </div>
          ))}
        </div>
      ) : (
        <div className="text-[12px] text-ink-400 italic px-3 py-3 rounded-md bg-ink-50">{emptyText}</div>
      )}
    </div>
  );
}

window.ReportPage = ReportPage;
