// 镜像回答主屏 — 用户搜索后看到的页面.
//
// 结构:
//   ┌─── 顶部 ─────────────────────────────┐
//   │ 返回   搜索栏 (显示当前 query)         │
//   ├─── 提示框 (状态 B/C 时出现) ───────────┤
//   │ 这个问题维度比较具体, 找到 N 条 95+      │
//   ├─── 真人答主卡片 0-5 张 ───────────────┤
//   │ 每张含 EvalPanel + 答主原话              │
//   ├─── 刘看山的总结 ───────────────────────┤
//   │ 🐺 刘看山 (AI 综合)                     │
//   │   综合 / AI 调研补充                      │
//   ├─── 查看更多 ──────────────────────────┤
//   │ [↓ 查看更多 — 解锁 90-94 区间]           │
//   └────────────────────────────────────┘
//
// 演示数据: 三个 demo query 分别对应状态 A/B/C.

const MA_BG       = '#0E0F12';
const MA_CARD     = '#16181D';
const MA_TEXT     = '#EDEEF0';
const MA_MUTED    = '#7A8089';
const MA_DIM      = '#5A5F66';
const MA_DIVIDER  = '#1B1D22';
const MA_BLUE     = '#1772F6';
const MA_BLUE_SOFT= '#1B3556';

// ─── Demo 场景数据 ─────────────────────────────────────────────
// 真实生产里这部分由 Orchestrator + Workers + Judge pipeline 产出
// (4 个 prompt 已经设计好了). 这里 hard-code 是为了不依赖 LLM 也能演示.
const DEMO_SCENARIOS = {
  // 状态 B (sparse): 女儿超重场景, 命中 q_w06 的 2 条 valuable 答案
  daughter: {
    query: '我女儿小学六年级超重, 老师说太胖, 该怎么管控让她恢复健康?',
    state: 'B',
    qualifying: [
      {
        answerId: 'q_w06-v1',
        relevance: 96,
        composite: 88,
        recommendation: {
          source_intro: '这条来自上海六院临床营养科的陈砚秋医生.',
          expertise_evidence: '她在儿童肥胖话题下输出稳定, 这条 6512 赞同, 是该话题历史前 3.',
          relevance_to_user: '她在文中处理的 9 岁男孩, 妈妈也面临"老师说太胖"的处境, 跟你女儿的场景同源.',
        },
      },
      {
        answerId: 'q_w06-v2',
        relevance: 93,
        composite: 78,
        recommendation: {
          source_intro: '这条来自答主"呼吸机蛋糕", 她以妈妈的第一人称分享自家儿子的减重过程.',
          expertise_evidence: '她在亲历者类话题持续输出, 这条 3923 赞同, 是该话题真实经验类前 5.',
          relevance_to_user: '她也犯过第一周直说"你太胖"的错误, 然后从全家戒含糖饮料切入 — 你做家长可以直接拿这条 timeline 借鉴.',
        },
      },
    ],
    pushedAuthors: 3,
    scoutFindings: [
      '国家卫健委等 13 部门 2026 年发布《儿童青少年"五健"促进行动计划》, 把"体重"列为五个健康维度之首.',
    ],
    liukanshanSummary: {
      sectionSynthesis:
        '两位答主一致认为, 儿童减重的核心不是让孩子减肥, 是全家调整生活方式. ' +
        '陈砚秋医生强调"低热量饮食会影响青春期发育, 必须先做 BMI-Z 评分而不是套成人 BMI"; ' +
        '呼吸机蛋糕的经验是"先让家长戒含糖饮料, 用语言把「你太胖」换成「我们家最近要吃健康一点」". ' +
        '我的综合建议: 第一步先带女儿去儿童保健科或儿内分泌门诊评估, ' +
        '第二步把家庭饮食 + 屏幕时间作为干预重点, 而不是给孩子单独定目标.',
      sectionSupplement:
        '我在调研中发现, 国家卫健委 2026 年刚发布《儿童青少年"五健"促进行动计划 (2026-2030)》, ' +
        '把儿童体重列为五个核心健康维度的第一位, 学校层面要求每日 ≥ 2 小时综合体育活动. ' +
        '答主们的回答写于 2024-2025, 还没引用这条最新政策. ' +
        '你跟学校沟通时可以拿这条政策做后盾, 让校方分担一部分干预责任.',
      supplementSource: '国家卫健委「五健」计划 (2025-12 发布)',
    },
  },

  // 状态 A (充裕): 减肥 75kg 场景, 命中多条高分答案
  loseweight: {
    query: '182cm 想 4 个月减到 75kg, 配合健身, 有什么好方案 + 注意事项?',
    state: 'A',
    qualifying: [
      {
        answerId: 'q_w04-v1',
        relevance: 96,
        composite: 87,
        recommendation: {
          source_intro: '这条来自持证健身教练"听风者", 已经带过 200+ 减脂学员.',
          expertise_evidence: '他是知乎"健身 · 减脂" 话题创作者, 这条 9234 赞同, 是该话题历史 top 5.',
          relevance_to_user: '他按 BMI 给出分层方案: 你 182cm 想到 75kg 大概率是 BMI 26-29, 正好对应他文中"减脂保肌一起做" 的路径.',
        },
      },
      {
        answerId: 'q_w11-v3',
        relevance: 95,
        composite: 85,
        recommendation: {
          source_intro: '这条来自北京协和医院内分泌科的苏予安医生.',
          expertise_evidence: '协和内分泌科 15 年门诊, 跟踪过 300+ 例长期减重患者, 这条 4381 赞同.',
          relevance_to_user: '他从基础代谢角度警示"4 个月减太快会砸代谢底座, 第 5 年要复胖" — 直接给你的时间表打了个修正提醒.',
        },
      },
      {
        answerId: 'q_w14-v1',
        relevance: 95,
        composite: 82,
        recommendation: {
          source_intro: '这条来自健身教练"听风者", 跟第 1 条同一作者从另一角度切入.',
          expertise_evidence: '该话题创作者, 这条 5287 赞同, 数据来自他真实带教的学员 InBody 体测.',
          relevance_to_user: '他用一个 60kg 女总监的 InBody 数据演示: 单跑步会掉 40-60% 肌肉. 你说要配合健身, 这条解释为什么必须力量训练而不是只跑步.',
        },
      },
      {
        answerId: 'q_w04-v2',
        relevance: 95,
        composite: 76,
        recommendation: {
          source_intro: '这条来自"老周的减重日记", 从 98kg 减到 72kg 的亲历者.',
          expertise_evidence: '他在减重话题下持续输出第一人称经验, 这条 4287 赞同.',
          relevance_to_user: '他自己走的就是先减重 (98→86) 然后才加力量训练的路径, 中间踩过"瘦但塌"的坑. 跟你的目标节奏接近, 弯路他已经替你走过.',
        },
      },
      {
        answerId: 'q_w11-v1',
        relevance: 95,
        composite: 75,
        recommendation: {
          source_intro: '这条来自"老周的减重日记", 26 个月稳定不复胖的真实经验.',
          expertise_evidence: '亲历者 5 年的真实路径, 19421 赞同, 是减重维持类话题的代表性回答.',
          relevance_to_user: '他讲反弹 3 次之后才稳住. 你要"4 个月达标" 是短期目标, 这条提醒你怎么把 4 个月之后的下一个 5 年也守住.',
        },
      },
    ],
    pushedAuthors: 0,
    scoutFindings: [],
    liukanshanSummary: {
      sectionSynthesis:
        '5 位答主在两个点上有共识: (1) 4 个月减到 75kg 数学上可行, 但代谢风险需要管理; ' +
        '(2) 必须力量训练保肌, 不能只有氧. 分歧在司美格鲁肽 — 你的 BMI 估计 26-29, ' +
        '正好在适应症 (≥30) 的临界外, 没有医生答主推荐, 亲历者也警示停药反弹. ' +
        '综合建议: 把目标节奏从 4 个月放宽到 6 个月, 蛋白拉到 1.8g/kg, 每周 2 次力量, ' +
        '可以在 BMI 降到 24 之前不动药.',
      sectionSupplement: null,
      supplementSource: null,
    },
  },

  // 状态 C (空): 一个特别独到的问题, 没有 95+ 命中
  obscure: {
    query: '如何用 Rust 写一个能并发处理 100 万 WebSocket 连接的服务端?',
    state: 'C',
    qualifying: [],
    pushedAuthors: 5,
    scoutFindings: [
      'Rust 高并发 WebSocket 在 2026 主流是 tokio-tungstenite + axum 或 actix-web 4.x',
      'Cloudflare 2026 公开过的"million-connection" 实践用 Rust 是常见参考',
    ],
    liukanshanSummary: {
      sectionSynthesis: null,
      sectionSupplement:
        '目前我没有看到知乎答主直接写过"Rust 100 万 WebSocket"的具体实践. 但调研阶段我发现: ' +
        'Rust 高并发 WebSocket 在 2026 主流栈是 tokio-tungstenite + axum, ' +
        '社区性能基线大约是单机 50-80 万连接 (取决于 fd 上限 + 内存). ' +
        '想到 100 万通常需要做 SO_REUSEPORT + 多进程 + 优化 epoll 的组合. ' +
        '建议你先看 Cloudflare 工程博客 + axum 仓库的 example, 再来这边问具体踩坑.',
      supplementSource: 'Cloudflare 工程博客 / axum GitHub example',
    },
  },
};

// ─── Match a user query to one of the demo scenarios via keywords ──
// Real pipeline (Orchestrator + Workers + Judge) would replace this
// with actual LLM + Zhihu retrieval. For demo, simple keyword routing.
function matchScenario(query) {
  const q = (query || '').toLowerCase();
  if (/(孩子|女儿|儿子|儿童|小学|超重|老师说)/.test(q)) return 'daughter';
  if (/(减肥|瘦|健身|公斤|kg|代餐|司美格鲁肽|减重)/.test(q)) return 'loseweight';
  return 'obscure';
}

// ─── Inline mirror panel — 直接嵌进 QuestionCreatedPage 用 ──────
// 不带 chrome (没有 ‹ 返回 / 邀请回答 / 写回答 顶栏), 由宿主页面提供.
// embedded=true (默认) 时 panel 自身不绝对定位, 跟着父容器布局走.
// cache / setCache: 由 LeftPhone 持有的缓存 (跨屏 mount/unmount 也保留).
// 命中 (同 query + 有 scenario) 时直接读缓存, 不再跑 pipeline.
// onAuthorTap: 点击答主头像跳答主主页, 跟 onSourceTap 一样由宿主页面保存 scrollTop 再调.
// onLike: 用户在某条镜像答案上点赞 — 用做 tour 触发右屏唤醒序列.
//
// newRealAnswer: { likedAnswerId, body, author, ts, persona } — 答主二次作答.
// realtime reply 内嵌在被点赞答主的 AnswerCard 里 (评估卡下方);
// 顶部同时显示一个 sticky banner 提示"真人博主更新了你的问题, 请向上滑动查看",
// 用户点 × 关闭 banner 时调用 onBannerDismiss → 父组件触发 P6.
//
// onSynthesisVisibilityChange(inView: boolean): synthesis 卡片入/离视口都报告,
// 父组件用这个状态控制 P7 何时弹出 (避免用户没看到总结就提示"已掌握").
function InlineMirrorPanel({
  query = '', onCollapse, onSourceTap, onAuthorTap, onLike, cache, setCache,
  newRealAnswer, einsteinAnswer, leftBanner, onBannerDismiss, onSynthesisVisibilityChange,
}) {
  const cacheHit = cache && cache.query === query && cache.scenario;

  const [loading, setLoading]     = React.useState(!cacheHit);
  const [scenario, setScenario]   = React.useState(cacheHit ? cache.scenario : null);
  const [activeTab, setActiveTab] = React.useState(cacheHit && cache.activeTab ? cache.activeTab : 'mirror');
  const [showComments, setShowComments] = React.useState(null);
  const [thoughts, setThoughts]   = React.useState(cacheHit && cache.thoughts ? cache.thoughts : []);

  // activeTab 变化时同步进缓存, 这样返回后还停在同一个 tab
  React.useEffect(() => {
    if (cacheHit && setCache) {
      setCache(prev => (prev && prev.query === query) ? { ...prev, activeTab } : prev);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeTab]);

  React.useEffect(() => {
    if (cacheHit) return; // 命中缓存, 跳过 pipeline 不重跑

    let cancelled = false;
    setLoading(true);
    setScenario(null);
    setThoughts([]);

    (async () => {
      try {
        if (typeof runMirrorAnswerPipeline !== 'function') throw new Error('pipeline 未就绪');
        const collectedThoughts = [];
        const result = await runMirrorAnswerPipeline(query, {
          onProgress: (p) => {
            if (cancelled) return;
            const entry = { ...p, t: Date.now() };
            collectedThoughts.push(entry);
            setThoughts(ts => [...ts, entry]);
          },
        });
        if (cancelled) return;
        setScenario(result);
        if (setCache) {
          setCache({ query, scenario: result, thoughts: collectedThoughts, activeTab: 'mirror', scrollTop: 0 });
        }
      } catch (e) {
        if (cancelled) return;
        console.warn('[镜像回答] pipeline 失败, 走 fallback:', e);
        const key = matchScenario(query);
        const fallback = DEMO_SCENARIOS[key] || DEMO_SCENARIOS.daughter;
        setScenario(fallback);
        if (setCache) {
          setCache({ query, scenario: fallback, thoughts: [], activeTab: 'mirror', scrollTop: 0 });
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [query]);

  // synthesis 卡片入/离视口的 IntersectionObserver — 给父组件报告状态,
  // 父组件用这个判断什么时候可以弹 P7. 必须等 scenario 已加载 (synthesis 卡才会渲染).
  const synthesisRef = React.useRef(null);
  React.useEffect(() => {
    if (!onSynthesisVisibilityChange) return;
    if (!scenario) return;
    const el = synthesisRef.current;
    if (!el || typeof IntersectionObserver === 'undefined') return;
    const ob = new IntersectionObserver((entries) => {
      for (const e of entries) onSynthesisVisibilityChange(e.isIntersecting);
    }, { threshold: 0.3 });
    ob.observe(el);
    return () => {
      ob.disconnect();
      // 卸掉时报告 false, 避免父组件以为还在视口里
      onSynthesisVisibilityChange(false);
    };
  }, [onSynthesisVisibilityChange, scenario]);

  // (旧版本在这里监听父级 counter 触发 scrollIntoView 到 synthesis. 已删:
  // 用户反馈强制滚动体验糟糕, 现在改成手动滚 — banner × 弹 P6, 用户自己滑.)

  // 点击 synthesis 段里被加粗成蓝色链接的答主名 → 滚回该答主的 AnswerCard.
  // AnswerCard 渲染时挂了 data-author-id; 这里只要查 DOM 就能定位.
  // 注意: 这是**用户主动点击**触发的, 不是被动 — 跟自动滚动是两码事.
  const handleSynthesisAuthorTap = React.useCallback((authorId) => {
    if (!authorId) return;
    const el = document.querySelector(`[data-author-id="${authorId}"]`);
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }, []);

  if (loading || !scenario) {
    return <InlineLoader thoughts={thoughts} onCollapse={onCollapse}/>;
  }

  const mirrorAnswers = scenario.qualifying || [];

  return (
    <div style={{ background: MA_BG, color: MA_TEXT }}>
      {/* 收起栏 — 让用户能折回邀请列表 */}
      <div style={{
        padding: '8px 12px 6px',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        borderTop: `6px solid #07080A`, borderBottom: `1px solid ${MA_DIVIDER}`,
      }}>
        <div style={{ fontSize: 11, color: MA_TEXT, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 5 }}>
          <span>镜像回答 · 为你召集的答主</span>
          <MirrorBadge/>
        </div>
        {onCollapse && (
          <div onClick={onCollapse} style={{
            fontSize: 9, color: MA_MUTED, cursor: 'pointer',
            padding: '2px 8px', borderRadius: 8, border: `1px solid ${MA_DIVIDER}`,
          }}>收起</div>
        )}
      </div>

      {/* Tab 切换 */}
      <TabStrip activeTab={activeTab} onChange={setActiveTab}
        mirrorCount={mirrorAnswers.length}/>

      {activeTab === 'mirror' && (
        <React.Fragment>
          {/* tour 后期: 顶部 sticky banner. 无论用户滚到哪个位置都顶在屏幕上方,
             直到用户点 × 关闭. 关闭时 onBannerDismiss → 父级触发 P6 ("滑到最底下"). */}
          {leftBanner && (
            <div style={{
              position: 'sticky',
              top: 0,
              zIndex: 30,
              margin: 0,
              padding: '10px 12px',
              background: 'linear-gradient(135deg, #1772F6 0%, #0858C0 100%)',
              color: '#fff',
              fontSize: 11.5,
              fontWeight: 600,
              letterSpacing: 0.4,
              display: 'flex', alignItems: 'center', gap: 8,
              boxShadow: '0 4px 14px rgba(23, 114, 246, 0.45)',
              animation: 'mirror-banner-slide 480ms cubic-bezier(0.22, 1.4, 0.4, 1)',
            }}>
              <div style={{
                width: 22, height: 22, borderRadius: 11,
                background: 'rgba(255,255,255,0.22)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 11, animation: 'arr-bounce 1.4s ease-in-out infinite',
                flexShrink: 0,
              }}>↑</div>
              <span style={{ flex: 1 }}>{leftBanner.text || '真人博主更新了你的问题, 请向上滑动查看'}</span>
              {onBannerDismiss && (
                <div onClick={onBannerDismiss} style={{
                  width: 22, height: 22, borderRadius: 11,
                  background: 'rgba(255,255,255,0.22)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 13, lineHeight: 1, fontWeight: 600,
                  cursor: 'pointer', flexShrink: 0,
                }}>×</div>
              )}
              <style>{`
                @keyframes mirror-banner-slide {
                  0%   { opacity: 0; transform: translateY(-10px); }
                  100% { opacity: 1; transform: translateY(0); }
                }
                @keyframes arr-bounce {
                  0%, 100% { transform: translateY(0); }
                  50%      { transform: translateY(-3px); }
                }
              `}</style>
            </div>
          )}

          {mirrorAnswers.length === 0 ? (
            // 离题分支 + 爱因斯坦回答已经到了 → 替换"知识海洋"看板, 直接显示爱因斯坦卡
            // (用户拍板的方案 C). 否则照常显示空态看板.
            einsteinAnswer ? (
              <EinsteinAnswerCard entry={einsteinAnswer}
                onAuthorTap={onAuthorTap ? () => onAuthorTap('einstein') : null}/>
            ) : (
              <EmptyStateMirror pushedAuthors={scenario.pushedAuthors || 5}/>
            )
          ) : (() => {
            // 答主二次作答的 reply 直接透传给 AnswerCard, 由它渲染在 EvalPanel 下方,
            // 不再单独插入一张 RealtimeAuthorAnswer 卡片 (那种方式视觉冲突 + 位置错).
            return mirrorAnswers.map((entry, i) => {
              const ans = entry.body ? entry : lookupAnswer(entry.answerId);
              const persona = entry._persona
                || (ans && typeof getPersona === 'function' ? getPersona(ans.authorId) : null);
              if (!ans || !persona) return null;
              const isLiked = !!(newRealAnswer && newRealAnswer.likedAnswerId === ans.id);
              return (
                <AnswerCard
                  key={(ans.id || entry.answerId || i)}
                  answer={ans}
                  persona={persona}
                  last={false}
                  isTopAnswer={false}
                  isMirror={true}
                  onCommentTap={() => setShowComments(ans.id)}
                  onSourceTap={onSourceTap}
                  onAuthorTap={onAuthorTap ? () => onAuthorTap(ans.authorId) : null}
                  onLike={onLike ? (info) => onLike({ ...info, persona }) : null}
                  realtimeReply={isLiked ? newRealAnswer : null}/>
              );
            });
          })()}

          {/* synthesisRef 包在外层 div, IntersectionObserver 用它来判断 synthesis 入视口 */}
          <div ref={synthesisRef}>
            <LiukanshanSummaryCard
              state={scenario.state}
              summary={scenario.liukanshanSummary}
              authors={mirrorAnswers.map(entry => {
                const ans = entry.body ? entry : lookupAnswer(entry.answerId);
                const p = entry._persona
                  || (ans && typeof getPersona === 'function' ? getPersona(ans.authorId) : null);
                return p ? { authorId: ans?.authorId, name: p.name, persona: p } : null;
              }).filter(Boolean)}
              onAuthorTap={handleSynthesisAuthorTap}/>
          </div>
        </React.Fragment>
      )}

      {activeTab === 'real' && <EmptyStateReal/>}
    </div>
  );
}

// ─── 嵌入式 loader — 显示思考过程, 不占满全屏 ─────────────────
function InlineLoader({ thoughts = [], onCollapse }) {
  const [dots, setDots] = React.useState('');
  React.useEffect(() => {
    const t = setInterval(() => setDots(d => (d.length >= 3 ? '' : d + '·')), 380);
    return () => clearInterval(t);
  }, []);
  const last = thoughts[thoughts.length - 1] || { message: '在准备…' };

  return (
    <div style={{
      background: MA_BG, color: MA_TEXT,
      borderTop: `6px solid #07080A`,
      padding: '14px 14px 16px',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12,
      }}>
        <LiukanshanAvatar size={36}/>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 11, color: MA_TEXT, fontWeight: 600 }}>
            刘看山正在思索<span style={{ display: 'inline-block', minWidth: 14, textAlign: 'left' }}>{dots}</span>
          </div>
          <div style={{ fontSize: 9, color: MA_MUTED, marginTop: 2 }}>
            {last.message || ''}
          </div>
        </div>
        {onCollapse && (
          <div onClick={onCollapse} style={{
            fontSize: 9, color: MA_MUTED, cursor: 'pointer',
            padding: '2px 8px', borderRadius: 8, border: `1px solid ${MA_DIVIDER}`,
          }}>收起</div>
        )}
      </div>

      {/* 思考过程流 — 倒序, 最新在上 */}
      <div style={{
        maxHeight: 320, overflowY: 'auto',
        padding: '4px 0', fontSize: 10, lineHeight: 1.6,
      }}>
        {[...thoughts].reverse().map((t, i) => (
          <ThoughtRow key={t.t + '-' + i} thought={t} isLatest={i === 0}/>
        ))}
      </div>
    </div>
  );
}

// ─── 全屏 wrapper — 给老路由 (phone.jsx setScreen('mirrorAnswer')) 留个备份 ─
function MirrorAnswerScreen({ query = '', onBack }) {
  return (
    <div style={{
      position: 'absolute', inset: 0, background: MA_BG, color: MA_TEXT,
      display: 'flex', flexDirection: 'column',
      fontFamily: '"PingFang SC", "Noto Sans SC", -apple-system, system-ui',
    }}>
      <div style={{ height: 38, flexShrink: 0 }}/>
      <div style={{
        padding: '4px 12px 6px', display: 'flex', alignItems: 'center', gap: 8,
      }}>
        <div onClick={onBack} style={{
          fontSize: 18, color: MA_TEXT, cursor: 'pointer', width: 16,
        }}>‹</div>
        <div style={{ flex: 1, fontSize: 13, fontWeight: 700, color: MA_TEXT,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          {query}
        </div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto' }}>
        <InlineMirrorPanel query={query} onCollapse={onBack}/>
      </div>
    </div>
  );
}

// ─── Tab 切换条 — 镜像回答 | 真人博主 ───────────────────────────
function TabStrip({ activeTab, onChange, mirrorCount }) {
  const Tab = ({ id, label, count }) => {
    const active = activeTab === id;
    return (
      <div onClick={() => onChange(id)} style={{
        position: 'relative', fontSize: 12,
        fontWeight: active ? 700 : 500,
        color: active ? MA_TEXT : MA_MUTED,
        paddingBottom: 8, cursor: 'pointer',
        display: 'flex', alignItems: 'center', gap: 4,
      }}>
        <span>{label}</span>
        {count != null && (
          <span style={{ fontSize: 9, color: active ? MA_BLUE : MA_DIM }}>{count}</span>
        )}
        {active && (
          <div style={{
            position: 'absolute', left: 0, right: 0, bottom: 0,
            height: 2, background: MA_BLUE, borderRadius: 1,
          }}/>
        )}
      </div>
    );
  };
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 22,
      padding: '6px 12px 0', borderBottom: `1px solid ${MA_DIVIDER}`,
      marginBottom: 2,
    }}>
      <Tab id="mirror" label="镜像回答" count={mirrorCount}/>
      <Tab id="real"   label="真人博主" count={0}/>
    </div>
  );
}

// ─── 真人博主 tab 的空状态 ───────────────────────────────────
function EmptyStateReal() {
  return (
    <div style={{
      padding: '60px 24px', textAlign: 'center',
      color: MA_MUTED, fontSize: 11, lineHeight: 1.7,
    }}>
      <div style={{
        width: 32, height: 32, margin: '0 auto 12px',
        border: `1px solid ${MA_DIVIDER}`, borderRadius: 16,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <div style={{
          width: 14, height: 14, borderRadius: 7,
          border: `1px solid ${MA_DIM}`,
        }}/>
      </div>
      还没有答主回答这个问题
      <div style={{ marginTop: 6, fontSize: 9, color: MA_DIM }}>
        可以邀请知友, 或者写下第一个回答
      </div>
    </div>
  );
}

// ─── 可点击的来源链接列表 ─────────────────────────────────
function SourceList({ sources }) {
  if (!sources || sources.length === 0) return null;
  return (
    <div style={{ marginTop: 10 }}>
      <div style={{ fontSize: 9, color: MA_MUTED, marginBottom: 4 }}>
        来源 (点击跳出查看)
      </div>
      {sources.slice(0, 4).map((s, i) => (
        <a key={i} href={s.url} target="_blank" rel="noopener noreferrer" style={{
          display: 'block', padding: '5px 8px', marginBottom: 4,
          background: 'rgba(23, 114, 246, 0.06)',
          border: `1px solid ${MA_BLUE_SOFT}`,
          borderRadius: 4, textDecoration: 'none',
        }}>
          <div style={{
            fontSize: 9.5, color: MA_BLUE, fontWeight: 500,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          }}>
            {s.title || s.url}
          </div>
          {s.snippet && (
            <div style={{
              fontSize: 8.5, color: MA_MUTED, marginTop: 2, lineHeight: 1.4,
              overflow: 'hidden', textOverflow: 'ellipsis',
              display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
            }}>
              {s.snippet}
            </div>
          )}
          <div style={{ fontSize: 8, color: MA_DIM, marginTop: 2 }}>
            {(() => { try { return new URL(s.url).hostname.replace(/^www\./, ''); } catch { return ''; } })()} ↗
          </div>
        </a>
      ))}
    </div>
  );
}

// ─── 镜像 tab 的空状态 ────────────────────────────────────────
// 不分 in-scope / out-of-scope, 永远是这一个版本: "大海边界" 金句 + "推给了 N 位答主".
// 用户场景的区别由刘看山的 supplement 段去承担 — 主题超范围时它会写
// "虽然我们暂时没人正面回答过这个, 但我联网看到 X / Y 几个相关词".
function EmptyStateMirror({ pushedAuthors }) {
  return (
    <div style={{
      margin: '14px 12px 10px',
      background: '#1A1F2A',
      border: `1px solid ${MA_DIVIDER}`,
      borderRadius: 8,
      padding: '14px 14px 12px',
      fontSize: 11, color: MA_TEXT, lineHeight: 1.6,
    }}>
      <div style={{ marginBottom: 10 }}>
        <span style={{ fontSize: 12, fontWeight: 600 }}>这是一个特别独到的问题</span>
      </div>
      <p style={{ margin: '6px 0 16px' }}>
        目前还没找到跟你这个问题高度匹配的真人讨论.
      </p>

      {/* 经典金句 — 杂志 pull-quote 风格, 衬线, 居中, 上下细线 */}
      <div style={{
        margin: '20px 0',
        padding: '20px 8px',
        textAlign: 'center',
        position: 'relative',
      }}>
        <div style={{
          position: 'absolute', top: 0, left: '20%', right: '20%',
          height: 1, background: MA_DIVIDER,
        }}/>
        <div style={{
          position: 'absolute', bottom: 0, left: '20%', right: '20%',
          height: 1, background: MA_DIVIDER,
        }}/>
        <div style={{
          fontFamily: '"Instrument Serif", "Noto Serif SC", Georgia, serif',
          fontSize: 17, lineHeight: 1.45, color: MA_TEXT,
          letterSpacing: 0.5, fontWeight: 400,
        }}>
          知识的海洋无穷无尽
        </div>
        <div style={{
          marginTop: 8,
          fontFamily: '"Instrument Serif", "Noto Serif SC", Georgia, serif',
          fontSize: 14, lineHeight: 1.5, color: MA_MUTED,
          letterSpacing: 0.5, fontWeight: 400,
        }}>
          但你刚好站在了它的边界上
        </div>
      </div>

      <p style={{ margin: '16px 0 0' }}>
        我们已经把这条问题推给了 <span style={{ color: MA_BLUE, fontWeight: 600 }}>
          {pushedAuthors}
        </span> 位相关领域的优秀答主, 通常 24-48 小时内会有第一手回答.
      </p>
    </div>
  );
}

// ─── Mirror loader — 展示真实的"思考过程" ─────────────────────
// thoughts: 从 runMirrorAnswerPipeline.onProgress 推上来的事件流,
//   {phase, message, detail?} — 让用户能看到 pipeline 真的在跑.
function ThoughtRow({ thought, isLatest }) {
  const { phase, message, detail } = thought;
  const isDone = phase.endsWith('-done');
  return (
    <div style={{
      padding: '6px 4px',
      borderLeft: `2px solid ${isLatest ? MA_BLUE : MA_DIVIDER}`,
      paddingLeft: 10, marginBottom: 4,
      opacity: isLatest ? 1 : 0.6,
    }}>
      <div style={{
        color: isDone ? '#7BBE7E' : MA_TEXT, fontSize: 10,
      }}>
        {isDone ? '✓ ' : '○ '}{message}
      </div>
      {detail && (
        <div style={{ fontSize: 9, color: MA_MUTED, marginTop: 3, lineHeight: 1.5 }}>
          {detail.rootTopic && <div>话题: {detail.rootTopic}</div>}
          {detail.popularMethods && detail.popularMethods.length > 0 && (
            <div>联网得到的主流方法: {detail.popularMethods.slice(0, 4).join(' · ')}</div>
          )}
          {detail.scoutFindings && <div>事实: {detail.scoutFindings.slice(0, 80)}…</div>}
          {detail.dimensions && (
            <div>方向: {detail.dimensions.slice(0, 5).join(' · ')}</div>
          )}
          {detail.candidatesByDim && (
            <div>各维度命中: {detail.candidatesByDim.map(c => `${c.name}(${c.n})`).join(' · ')}</div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── 解析 synthesis, 把答主名字变成内联的蓝色可点击文字 ────
// authors = [{ authorId, name, persona }]. 文本里每一处 name 都换成 <span> —
// 不换行不加头像不加箭头, 就是同一段文字里把名字描蓝标可点. 用户反馈之前的
// chip 卡片把句子切碎了, 现在改成最轻的形式: 名字本身变蓝, 句子流不变.
//
// 重名/多次出现处理: LLM 可能在同一段里提到答主两次 (例: "X 强调…X 还说…"),
// 我们保留所有出现, 每次都是单独可点的蓝字 — 跟文章里链接的处理一样.
function renderSynthesisWithAuthorLinks(text, authors, onAuthorTap) {
  if (!text || !Array.isArray(authors) || authors.length === 0) {
    return [<React.Fragment key="0">{text || ''}</React.Fragment>];
  }
  // 长名优先匹配, 避免短名吃掉长名前缀.
  const sorted = authors.slice().filter(a => a && a.name).sort((a, b) => b.name.length - a.name.length);
  // 防御: sorted 为空时, 正则 () 匹配空串无限循环 → OOM. 直接 fallback.
  if (sorted.length === 0) {
    return [<React.Fragment key="0">{text}</React.Fragment>];
  }
  const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const re = new RegExp('(' + sorted.map(a => escapeRe(a.name)).join('|') + ')', 'g');

  const out = [];
  let cursor = 0;
  let m;
  let i = 0;
  while ((m = re.exec(text)) !== null) {
    if (m.index > cursor) {
      out.push(<React.Fragment key={'t' + i}>{text.slice(cursor, m.index)}</React.Fragment>);
    }
    const author = sorted.find(a => a.name === m[1]);
    out.push(
      <span
        key={'a' + i}
        onClick={() => onAuthorTap && onAuthorTap(author?.authorId)}
        style={{
          color: '#5BB0FF',
          cursor: 'pointer',
          fontWeight: 600,
        }}
      >{m[1]}</span>
    );
    cursor = m.index + m[1].length;
    i++;
  }
  if (cursor < text.length) {
    out.push(<React.Fragment key={'t' + i}>{text.slice(cursor)}</React.Fragment>);
  }
  return out;
}

// ─── Liu Kanshan summary card ─────────────────────────────────
function LiukanshanSummaryCard({ state, summary, authors, onAuthorTap }) {
  if (!summary) return null;
  // Fix #4: 当 synthesizer 返回 null/空 时给一个兜底, 避免只显示"刘看山 · 为你看完上面的回答" 而没有内容
  const hasSynth = !!(summary.sectionSynthesis && summary.sectionSynthesis.trim());
  const hasSupp  = !!(summary.sectionSupplement && summary.sectionSupplement.trim());
  const fallbackSynthesis = !hasSynth && !hasSupp
    ? '上面这几位答主都是真人, 各自从不同切面对这个话题做过深度回答. 综合来看, 大家共识是: 别套单一模板, 把不同维度的建议组合起来用. 我建议你优先从赞同最高的那条入手, 配合"为什么推荐" 里的 caveat 看一遍.'
    : null;
  return (
    <div style={{
      borderBottom: `6px solid #07080A`,
      paddingBottom: 8,
      background: '#11141A',
    }}>
      {/* Avatar row */}
      <div style={{ padding: '12px 12px 4px', display: 'flex', alignItems: 'center', gap: 8 }}>
        <LiukanshanAvatar size={32}/>
        <div style={{ flex: 1 }}>
          <div style={{
            fontSize: 11, color: MA_TEXT, fontWeight: 600,
            display: 'flex', alignItems: 'center', gap: 5,
          }}>
            <span>刘看山</span>
            <MirrorBadge/>
          </div>
          <div style={{ fontSize: 9, color: MA_MUTED, marginTop: 1.5 }}>
            为你看完上面的回答
          </div>
        </div>
      </div>

      <div style={{ padding: '4px 12px 12px', fontSize: 10.5, color: MA_TEXT, lineHeight: 1.7 }}>
        {/* 第一段: 综合判断 — 直接 body 文字, 不加标签, 像答主写的一样.
           凡是出现答主 name 的地方, 切出一个独立蓝色 chip (头像 + 名字 + "跳到 TA"),
           点击就 scrollIntoView 到该答主的 AnswerCard. */}
        {(summary.sectionSynthesis || fallbackSynthesis) && (
          <p style={{ margin: 0 }}>
            {renderSynthesisWithAuthorLinks(
              summary.sectionSynthesis || fallbackSynthesis,
              authors || [],
              onAuthorTap
            )}
          </p>
        )}

        {/* 第二段: 答主没提到的新东西 — 用细分隔线 + 小灰字 caption, 不用颜色块 */}
        {summary.sectionSupplement && (
          <div style={{ marginTop: summary.sectionSynthesis ? 14 : 0 }}>
            <div style={{
              height: 1, background: MA_DIVIDER,
              margin: '0 0 10px',
            }}/>
            <div style={{
              fontSize: 9, color: MA_MUTED, marginBottom: 6,
              letterSpacing: 1.2, textTransform: 'none',
            }}>
              · 答主没提到, 联网时另外看到的
            </div>
            <p style={{
              margin: 0, fontSize: 10.5, color: MA_TEXT, lineHeight: 1.7,
            }}>
              {summary.sectionSupplement}
            </p>
          </div>
        )}

        {/* web 来源, 可点 — 只在 supplement 存在时才显示 (跟它是一体的) */}
        {summary.sectionSupplement && <SourceList sources={summary.sources}/>}
      </div>
    </div>
  );
}

// ─── Liu Kanshan avatar (CSS-cropped from the four-view PNG) ──
function LiukanshanAvatar({ size = 28 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: '#F4F5F7',
      backgroundImage: 'url(assets/liukanshan-views.png)',
      backgroundSize: '400% 100%',
      backgroundPosition: '0% center',
      backgroundRepeat: 'no-repeat',
      border: '1.5px solid #E5E7EB',
      flexShrink: 0,
    }}/>
  );
}

// ─── Helper: lookup an answer by id across all questions ───────
function lookupAnswer(answerId) {
  for (const qid in ANSWERS) {
    const found = ANSWERS[qid].find(a => a.id === answerId);
    if (found) return found;
  }
  return null;
}

// ─── 答主二次作答 (实时回复) 卡片 ─────────────────────────────
// 答主在右屏点了"更新我的回答" 之后, 新答案以特殊卡片插进 mirror panel 顶部.
// 视觉上跟普通镜像答案区分开: 顶部一条彩色条 + "答主真人回复" 徽章 + 时间戳"刚刚".
function RealtimeAuthorAnswer({ entry }) {
  if (!entry || !entry.body) return null;
  return (
    <div style={{
      margin: '8px 12px 0',
      borderRadius: 10,
      overflow: 'hidden',
      border: '1.5px solid #1772F6',
      background: '#121417',
      boxShadow: '0 8px 24px rgba(23, 114, 246, 0.22)',
      animation: 'realtime-pop 520ms cubic-bezier(0.22, 1.4, 0.4, 1)',
    }}>
      <div style={{
        background: 'linear-gradient(135deg, #1772F6 0%, #0858C0 100%)',
        padding: '6px 12px',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        color: '#fff',
        fontSize: 10.5, fontWeight: 600, letterSpacing: 0.5,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
          <span style={{ fontSize: 11 }}>⚡</span>
          <span>答主真人回复 · 刚刚</span>
        </div>
        <span style={{ fontSize: 9.5, opacity: 0.85 }}>{entry.author || '答主'}</span>
      </div>
      <div style={{ padding: '10px 14px 12px' }}>
        <div style={{
          fontSize: 11.5, color: '#EDEEF0',
          lineHeight: 1.65, whiteSpace: 'pre-wrap',
        }}>{entry.body}</div>
      </div>
      <style>{`
        @keyframes realtime-pop {
          0%   { opacity: 0; transform: translateY(14px) scale(0.96); }
          100% { opacity: 1; transform: translateY(0) scale(1); }
        }
      `}</style>
    </div>
  );
}

// ─── 离题分支专属: 爱因斯坦被镜像系统召集来的回答卡 ─────────────────
// entry = { author, persona, body, ts }. 视觉跟普通 AnswerCard 区分:
// 金色边框, 顶部一条"📡 被镜像系统召集" 标识带, 头像 + 名字 + credential,
// 然后正文. 不带评级面板 (他不是库里答主, 没有评级维度).
function EinsteinAnswerCard({ entry, onAuthorTap }) {
  if (!entry || !entry.body) return null;
  const persona = entry.persona || {};
  return (
    <div data-author-id={persona.id || 'einstein'} style={{
      margin: '8px 12px 0',
      borderRadius: 10,
      overflow: 'hidden',
      border: '1.5px solid #E8B447',
      background: '#121417',
      boxShadow: '0 8px 28px rgba(232, 180, 71, 0.18)',
      animation: 'einstein-pop 560ms cubic-bezier(0.22, 1.4, 0.4, 1)',
    }}>
      {/* 顶部召集标识带 */}
      <div style={{
        background: 'linear-gradient(135deg, #E8B447 0%, #C9962A 100%)',
        padding: '6px 12px',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        color: '#0E0F12',
        fontSize: 10.5, fontWeight: 700, letterSpacing: 0.5,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
          <span style={{ fontSize: 12 }}>📡</span>
          <span>被镜像系统召集的回答 · 刚刚</span>
        </div>
        <span style={{ fontSize: 9.5, opacity: 0.85 }}>神秘答主</span>
      </div>

      {/* 头像 + 名字 + credential — 可点击跳 AuthorProfile */}
      <div onClick={onAuthorTap} style={{
        display: 'flex', alignItems: 'flex-start',
        padding: '8px 12px 4px',
        cursor: onAuthorTap ? 'pointer' : 'default',
      }}>
        {typeof AuthorAvatar === 'function' && persona.name && (
          <AuthorAvatar persona={persona} size={28}/>
        )}
        <div style={{ flex: 1, marginLeft: 9, minWidth: 0 }}>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 4,
            fontSize: 11, color: MA_TEXT, fontWeight: 600,
          }}>
            <span>{persona.name || '爱因斯坦'}</span>
            {persona.credential === 'verified' && (
              <span style={{
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                width: 11, height: 11, borderRadius: '50%',
                background: '#1A3E5C', color: '#5BB0FF', fontSize: 7, fontWeight: 700,
              }}>✓</span>
            )}
          </div>
          {persona.credentialText && (
            <div style={{ fontSize: 9, color: MA_MUTED, marginTop: 2, lineHeight: 1.4 }}>
              {persona.credentialText}
            </div>
          )}
        </div>
      </div>

      <div style={{
        padding: '6px 14px 12px',
        fontSize: 11.5, color: '#EDEEF0',
        lineHeight: 1.7, whiteSpace: 'pre-wrap',
      }}>{entry.body}</div>

      <style>{`
        @keyframes einstein-pop {
          0%   { opacity: 0; transform: translateY(14px) scale(0.97); }
          100% { opacity: 1; transform: translateY(0) scale(1); }
        }
      `}</style>
    </div>
  );
}

Object.assign(window, { MirrorAnswerScreen, InlineMirrorPanel, InlineLoader, LiukanshanAvatar, LiukanshanSummaryCard, DEMO_SCENARIOS, RealtimeAuthorAnswer, EinsteinAnswerCard });
