/* AI 选购助手（壳内层）— 9 个统一 Agent 工具的脚本化演示：
   工具 needs_input → 对话内 GUI → resume → 结构化结果卡 → Agent 继续编排。
   四个固定小工具仍可从“小工具”页独立打开；AR 量房 / 采光检测也可在同一对话内弹出。
   电视对比看板以紧凑交互卡嵌入对话，并保留展开完整看板入口。
   三条剧本：主线（标准选购闭环）· 反光 · 烧屏。对话为 demo 剧本，非真模型。
   深链：?tool=agent · &auto=1 自动替用户点选项（走 AR→设备兼容→对比→配件→链接核验，评审/截图用） */
const AG_RM = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
const AG_AUTO = new URLSearchParams(location.search).get('auto') === '1';
const agCx = (...a) => a.filter(Boolean).join(' ');
const AG_CMD = '我想买台客厅用的电视';
/* 主页 Agent 卡片的三个建议问题 —— 文案单一事实源（home.jsx 读 window.AG_ASKS），顺序即剧本路由 */
const AG_ASKS = ['预算 1.5 万，客厅怎么选', '白天客厅很亮，怕反光', 'OLED 会烧屏吗'];

/* 协议层保留英文 ID；展示层一律使用中文名称。 */
const AG_TOOL_LABELS = {
  research_products: '产品研究',
  compare_products: '电视对比',
  verify_model_and_offer: '购买链接核验',
  evaluate_room_and_size: '房间与尺寸评估',
  recommend_tv_accessories: '电视配件推荐',
  plan_device_setup: '设备与最佳设置',
  plan_installation_and_delivery: '安装与入户规划',
  evaluate_lifecycle_options: '长期成本与处置',
  diagnose_tv_problem: '验机与故障诊断',
};
const AG_RESULT_LABELS = {
  needs_confirmation: '等待确认',
  needs_input: '需要补充',
  partial: '部分完成',
  ok: '已完成',
  PurchaseQuestionnaire: '选购问卷',
  RoomMeasurementWizard: '空间测量',
  AmbientLightCapture: '环境光采集',
  UsageMixSelector: '使用习惯选择',
  PurchaseLinkInput: '商品链接输入',
  required: '必需',
  recommended: '建议',
  not_needed: '不需要',
};
const agToolLabel = (id) => AG_TOOL_LABELS[id] || id;
const agResultLabel = (text) => Object.entries(AG_RESULT_LABELS)
  .reduce((s, [raw, label]) => s.replaceAll(raw, label), String(text || ''));

/* 尺寸 → 在库命中数（dbdata.jsx 按尺寸实数：75″=7 · 85″=23 · 98″+=13）/ 包装箱最长边（入户预检用） */
const AG_HIT = { 75: 7, 85: 23, 98: 13 };
const AG_BOX = {
  75: { long: '1.86 m' },
  85: { long: '2.08 m' },
  98: { long: '2.36 m', warn: '箱体超 2.3 m：需斜立进梯，建议约 2 名搬运（免费）' },
};

/* 预算选项按尺寸给（价格全部来自机型库在售录价；85″ 从 7 千档起步——在库 7499 有雷鸟鹤6U/Vidda发现X/酷开K6 PRO 三台） */
const AG_BUDGETS = {
  75: [
    { label: '4 – 6 千', v: '75b1' },
    { label: '1 万左右', v: '75b2' },
    { label: '不确定，直接推荐', v: '75b2' },
  ],
  85: [
    { label: '7 – 8 千', v: '85v' },
    { label: '1 万左右', v: '85low' },
    { label: '1 – 1.5 万', v: '85mid' },
    { label: '不设上限', v: '85high' },
  ],
  98: [
    { label: '3 万左右', v: '98b1' },
    { label: '5 万以上', v: '98b2' },
    { label: '不确定，直接推荐', v: '98b1' },
  ],
};

/* Agent 里出现的机型只取两类证据：db/1.sql 的 brightness_plot，或仓库内完整评测报告。
   peak / full 分别为该模式窗口曲线最大值与 100% 窗口平均值；价格为报告录入快照，不是实时到手价。 */
const AG_TV_DATA = {
  vision5: { id: 10000228, brand: '华为', model: '智慧屏 Vision 5 75', panel: 'MiniLED', size: 75, peak: 1080, full: 592, mode: 'HDR 标准', source: 'DB 实测', price: 5647, hl: '生态与大屏体验', why: 'HDR 标准模式实测峰值 1080 nit、100% 窗口 592 nit。报告更看重智慧交互与家居生态；若只追求极端画质，不必为生态功能加预算。' },
  vision5se: { id: 10000233, brand: '华为', model: '智慧屏 Vision 5 SE 75', panel: 'MiniLED', size: 75, peak: 1088, full: 446, mode: 'HDR 标准', source: 'DB 实测', price: 4499, hl: '预算友好', why: 'HDR 标准模式实测峰值 1088 nit、100% 窗口 446 nit。峰值接近 Vision 5，但大面积亮场更弱；更适合预算优先、华为生态明确的家庭。' },
  he6: { id: 10000223, brand: '雷鸟', model: '85 鹤6 Ultra 25款', panel: 'QD-MiniLED', size: 85, peak: 2428, full: 685, mode: 'HDR 标准', source: 'DB 实测', price: 7499, hl: '7 千档均衡', why: 'HDR 标准模式实测峰值 2428 nit、100% 窗口 685 nit。库内报告认为它同价位均衡、控光好、系统干净；短板是画面略有提亮。' },
  viddaX: { id: 10000231, brand: 'VIDDA', model: '发现X 2026款 85', panel: 'QD-MiniLED', size: 85, peak: 1424, full: 500, mode: 'HDR 标准', source: 'DB 实测', price: 7499, hl: '雾面屏路线', why: 'HDR 标准模式实测峰值 1424 nit、100% 窗口 500 nit，亮度明显低于同价鹤6。优势是报告记录的雾面抗反与入门性价比，代价是清晰度和通透感。' },
  he7: { id: 10000219, brand: '雷鸟', model: '85 鹤7 PRO 25款', panel: 'QD-MiniLED', size: 85, peak: 4016, full: 708, mode: 'HDR 标准', source: 'DB 实测', price: 9799, hl: '影音游戏双修', why: 'HDR 标准模式实测峰值 4016 nit、100% 窗口 708 nit。报告把它列为大屏游戏与观影的高性价比选择；短板是提亮偏多、外壳做工一般。' },
  viddaXPro: { id: 10000222, brand: 'VIDDA', model: '发现X Pro 2026款 85', panel: 'QD-MiniLED', size: 85, peak: 1883, full: 721, mode: 'HDR 标准', source: 'DB 实测', price: 9999, hl: '调校较标准', why: 'HDR 标准模式实测峰值 1883 nit、100% 窗口 721 nit。报告评价是没有明显大短板、调校较标准；不足是画面偏清淡、对比感不强。' },
  q10l: { id: 10000225, brand: 'TCL', model: '85 Q10L', panel: 'QD-MiniLED', size: 85, peak: 4728, full: 835, mode: 'HDR 标准', source: 'DB 实测', price: 11999, hl: '持续亮度强', why: 'HDR 标准模式实测峰值 4728 nit、100% 窗口 835 nit。库内报告认可亮度、光晕控制和超窄边；像素响应与背光延迟不是它的强项。' },
  e8s: { id: 10000788, brand: '海信', model: '85 E8S', panel: 'RGB-MiniLED', size: 85, peak: 2631, full: null, refl: 1.732, mode: 'HDR 鲜艳', source: '本地报告实测', price: 14999, hl: 'RGB 控光均衡', why: '本地报告 HDR 鲜艳模式实测峰值 2631 nit、全反射率 1.732%。报告认为画面偏中性、光晕和延迟控制好；HDR 小窗口与白点仍有短板。' },
  q10lPro: { id: 10000346, brand: 'TCL', model: '98 Q10L PRO', panel: 'QD-MiniLED', size: 98, peak: 7810, full: 826, mode: 'HDR 标准', source: 'DB 实测', price: 28999, hl: '98″ 高亮旗舰', why: 'HDR 标准模式实测峰值 7810 nit、100% 窗口 826 nit。报告认为整体达到高端水准，但相较 85 Q10L 的提升有限，性价比一般。' },
  e8qPro: { id: 10000230, brand: '海信', model: '100 E8Q PRO', panel: 'MiniLED', size: 100, peak: 2887, full: 737, mode: 'HDR 标准', source: 'DB 实测', price: 28999, hl: '真正 100″', why: 'HDR 标准模式实测峰值 2887 nit、100% 窗口 737 nit。优势是比 98″ 更大的实际面积；报告也提醒它对空间、入户和安装要求更高。' },
  g5: { id: 10000735, brand: 'LG', model: '65 G5', panel: 'OLED evo', size: 65, peak: 2484, full: null, refl: 0.816, mode: 'HDR 影院', source: '本地报告实测', price: 21999, hl: 'OLED 暗场与抗反', why: '本地报告 HDR 影院模式实测峰值 2484 nit、全反射率 0.816%。暗场、响应和抗反是强项；中国区应用生态与长期静态画面风险需要接受。' },
};
const agTv = (key, extra = {}) => ({ ...AG_TV_DATA[key], ...extra });

/* 推荐会同时吃预算与用途。游戏不再把有明显延迟短板的机型排到前面，直播/体育优先看 100% 窗口。 */
function agPickFor(budgetKey, usage = []) {
  const movie = usage.includes('movie');
  const game = usage.includes('game');
  const live = usage.includes('live');
  const seniorFamily = usage.includes('senior') || usage.includes('family');
  if (budgetKey === '75b1' || budgetKey === '75b2') return {
    say: budgetKey === '75b2' ? '库内有完整实测的 75″ 里，不需要为了花满一万元硬上未知机型——' : '4–6 千的 75″，先看这组有完整实测的候选——',
    main: agTv('vision5'), altSay: '如果预算更优先：', alt: agTv('vision5se'),
  };
  if (budgetKey === '85v') return {
    say: '7 千档两台都实测过，亮度和综合完成度更好的这台排前面——',
    main: agTv('he6'), altSay: '如果更在意雾面屏：', alt: agTv('viddaX'),
  };
  if (budgetKey === '85low') {
    const calmFirst = (live || seniorFamily) && !game;
    return calmFirst
      ? { say: '你更重视长时间观看和家庭共用，我把调校较标准的放前面——', main: agTv('viddaXPro'), altSay: '想要更强 HDR 高光和游戏能力：', alt: agTv('he7') }
      : { say: '你包含游戏或电影需求，实测和报告都更支持这台——', main: agTv('he7'), altSay: '更偏好克制、均衡的调校：', alt: agTv('viddaXPro') };
  }
  if (budgetKey === '85mid' || budgetKey === '85high') {
    if (live && !game) return {
      say: '直播、体育和综艺更吃大面积持续亮度，100% 窗口实测更强的排前面——',
      main: agTv('q10l'), altSay: '如果更看重 RGB 控光与综合均衡：', alt: agTv('e8s'),
    };
    const sceneLead = game && movie
      ? '电影和游戏都要，我先选报告里延迟与控光更均衡的一台——'
      : game
        ? '游戏优先，我先选报告里延迟与控光更均衡的一台——'
        : movie
          ? '电影为主，我先选暗场、控光和画面调校更均衡的一台——'
          : seniorFamily
            ? '家庭共用更看重均衡与稳定，我先把综合完成度更高的一台放前面——'
            : '按当前用途和现有实测，我先把综合更均衡的一台放前面——';
    return {
      say: budgetKey === '85high' ? '85″ 不设上限也不等于要花满；按现有实测，这两台已经覆盖高端需求——' : sceneLead,
      main: agTv('e8s'), altSay: '想少花 3 千并换更高的实测亮度：', alt: agTv('q10l'),
    };
  }
  return {
    say: budgetKey === '98b2' ? '5 万档机型缺少完整实测，我不会拿标称值硬推；先回到这两台证据完整的百吋候选——' : '3 万左右的百吋档，按相同 HDR 标准模式实测先看这台——',
    main: agTv('q10lPro'), altSay: '如果你更在意真正的 100″ 面积：', alt: agTv('e8qPro'),
  };
}

/* 反光路线：有反射率实测才展示数值；缺失时明确标“未测”，不再用亮度冒充抗反。 */
const AG_GLARE = {
  g07: { say: '7 千档里，鹤6的亮度与报告结论更完整——', main: agTv('he6'), altSay: '想要雾面屏路线可以看：', alt: agTv('viddaX') },
  oled: { say: '混合画面路线里，G5 的抗反有本地实测支撑——', main: agTv('g5'), altSay: '想上 85″，就换这台 RGB-MiniLED：', alt: agTv('e8s') },
  g1: { say: '1 万左右先看 HDR 高光更强、影音游戏证据更完整的这台——', main: agTv('he7'), altSay: '想要调校更克制的备选：', alt: agTv('viddaXPro') },
  g15: { say: '满屏亮画面看得多，100% 窗口实测 835 nit 的这台更稳——', main: agTv('q10l'), altSay: '更看重 RGB 控光与已测反射率：', alt: agTv('e8s') },
  gbig: { say: '百吋路线里先按同模式实测亮度排序——', main: agTv('q10lPro'), altSay: '要真正 100″ 再看：', alt: agTv('e8qPro') },
};

/* 烧屏路线只使用仓库内两份完整报告给出的实测与定性结论。 */
const AG_BURN = {
  oled: agTv('g5', { hl: '暗场与响应优先', why: '逐像素控光、快速响应和 0.816% 全反射率是核心优势；报告也明确提醒，长期展示固定台标或游戏界面仍有烧屏风险。' }),
  mled: agTv('e8s', { hl: '固定画面更省心', why: 'MiniLED 背光没有 OLED 的像素老化路径，更适合台标或比分牌长时间固定；本地报告同时记录了不错的光晕与延迟控制。' }),
};

const agSleep = (ms) => new Promise((r) => setTimeout(r, AG_RM ? Math.min(ms, 80) : ms));

/* ---- 假测量浮层（AR 量房 / 采光检测共用骨架）：四相位扫描 → 两条标注 → 回传 ---- */
function AgScanOverlay({ bg, hints, l1, l2, runText, onDone }) {
  const { useState, useEffect } = React;
  const [ph, setPh] = useState(0);
  useEffect(() => {
    const k = AG_RM ? 0.1 : 1;
    const ts = [
      setTimeout(() => setPh(1), 1000 * k),
      setTimeout(() => setPh(2), 2300 * k),
      setTimeout(() => setPh(3), 3600 * k),
      setTimeout(onDone, 4500 * k),
    ];
    return () => ts.forEach(clearTimeout);
  }, []);
  return (
    <div className="arov">
      <img className="arov-bg" src={bg} alt="" draggable="false" />
      <div className="arov-dim"></div>
      <div className="arov-grid"></div>
      <span className="arc tl"></span><span className="arc tr"></span>
      <span className="arc bl"></span><span className="arc br"></span>
      <div className="arov-hint">{hints[ph]}</div>
      {ph >= 1 && <div className="ar-w"><span className="ar-lbl">{l1}</span></div>}
      {ph >= 2 && <div className="ar-d"><span className="ar-lbl">{l2}</span></div>}
      <div className={agCx('arov-pill', ph >= 3 && 'ok')}>
        {ph >= 3 ? <Icon name="check" size={14} /> : <span className="tc-spin"></span>}
        {ph >= 3 ? '测量完成' : runText}
      </div>
    </div>
  );
}
const AgArOverlay = (p) => (
  <AgScanOverlay bg={IMG.wall} l1="净宽 3.62 m" l2="视距 2.9 m" runText="AR 量房小工具运行中"
    hints={['正在识别电视墙平面…', '测量电视墙净宽…', '测量沙发观看距离…', '测量完成，正在生成建议']} {...p} />
);
const AgLuxOverlay = (p) => (
  <AgScanOverlay bg={IMG.rev || IMG.orig} l1="午后峰值 ≈1400 lux" l2="西晒直射 ≈2 h" runText="采光检测小工具运行中"
    hints={['锁定曝光，采样电视墙亮度…', '识别窗口方向与眩光…', '估算全天照度区间…', '检测完成，正在生成建议']} {...p} />
);

/* ---- 消息卡片 ---- */
const agPurchaseUrl = (tv) => 'https://search.jd.com/Search?keyword=' + encodeURIComponent(`${tv.brand}${tv.model.replace(/\s+/g, '')}电视`);
const agNit = (n) => n == null ? '未提供' : `${Number(n).toLocaleString()} nit`;
const agRefl = (n) => n == null ? '未测' : `${Number(n).toFixed(3).replace(/0+$/, '').replace(/\.$/, '')}%`;

function AgTvCard({ tv, tag }) {
  const color = (BRANDS.find((b) => b.zh === tv.brand) || {}).color || 'var(--accent)';
  const second = tv.full != null
    ? { label: '100% 窗口', value: agNit(tv.full) }
    : { label: '全反射率', value: agRefl(tv.refl) };
  return (
    <div className="agk tvk">
      <div className="tvk-top">
        <span className="tvk-b" style={{ color }}>{tv.brand}</span>
        <b className="tvk-n">{tv.model}</b>
        <span className="tvk-tag">{tag}</span>
      </div>
      <div className="tvk-tags"><span>{tv.panel}</span><span>{tv.size}″</span><span>{tv.hl}</span><span className="evidence">{tv.source}</span></div>
      <div className="tvk-mx">
        <div><small>HDR 峰值</small><b>{agNit(tv.peak)}</b></div>
        <div><small>{second.label}</small><b>{second.value}</b></div>
        <div><small>实测模式</small><b>{tv.mode}</b></div>
      </div>
      <p className="tvk-why">{tv.why}</p>
      <div className="tvk-buy">
        <div className="tvk-p"><b>¥{tv.price.toLocaleString()}</b><small>库内报告价</small></div>
        <a className="agk-btn" href="电视对比看板.html" target="_blank" rel="noreferrer">完整看板</a>
      </div>
    </div>
  );
}

function AgEntryCard({ size }) {
  const box = AG_BOX[size] || AG_BOX[85];
  return (
    <div className="agk entk">
      <div className="agk-h"><Icon name="cube" size={14} />入户可行性预检<span className="entk-ok">通过 ✓</span></div>
      <div className="entk-rows">
        <div className="entk-row"><Icon name="check" size={13} />包装箱最长边 {box.long}<em>电梯轿厢 1.6×1.5×2.3 m</em></div>
        <div className="entk-row"><Icon name="check" size={13} />入户门净宽 0.95 m<em>拆箱前可直进</em></div>
        <div className="entk-row"><Icon name="check" size={13} />楼道转角 · 无台阶<em>路径已核对</em></div>
      </div>
      {box.warn && <div className="entk-warn">{box.warn}</div>}
      <div className="agk-note">数据来自小区众包入户库（演示数据）</div>
    </div>
  );
}

function AgArCard() {
  return (
    <div className="agk ark">
      <div className="agk-h"><Icon name="scan" size={14} />AR 量房 · 测量结果</div>
      <div className="ark-vals">
        <div><small>电视墙净宽</small><b>3.62 m</b></div>
        <div><small>沙发视距</small><b>2.9 m</b></div>
        <div><small>推荐尺寸</small><b className="acc">85″</b></div>
      </div>
    </div>
  );
}

function AgLuxCard() {
  return (
    <div className="agk ark">
      <div className="agk-h"><Icon name="sun" size={14} />采光检测 · 测量结果</div>
      <div className="ark-vals">
        <div><small>午后峰值照度</small><b>≈1400 lux</b></div>
        <div><small>直射时段</small><b>西晒 ≈2 h</b></div>
        <div><small>抗反需求</small><b className="acc">高</b></div>
      </div>
    </div>
  );
}

function AgTradeCard() {
  return (
    <div className="agk trk">
      <div className="agk-h"><Icon name="cube" size={14} />旧机估值 · 回收抵扣</div>
      <div className="trk-v"><b>¥360 – 520</b><span>55″ 液晶 · 约 2017 年 · 屏幕完好</span></div>
      <div className="agk-note">含上门拆挂与搬运费 · 回收款下单直接抵扣</div>
      <button className="agk-btn">预约与新机同日上门</button>
    </div>
  );
}

function AgEndCard({ chips }) {
  return (
    <div className="agk endk">
      <div className="agk-h"><Icon name="check" size={14} />本次方案小结</div>
      <div className="endk-chips">{chips.map((c, i) => <span key={i}>{c}</span>)}</div>
    </div>
  );
}

/* ---- 电视对比的对话内结果 GUI：紧凑查看，必要时再展开完整看板 ---- */
function AgCompareCard({ tvs, scene = '客厅综合' }) {
  const { useState } = React;
  const [tab, setTab] = useState('evidence');
  const sameMode = new Set(tvs.map((t) => t.mode)).size === 1;
  const evidenceRows = [
    { n: 'HDR 峰值', get: (t) => agNit(t.peak), better: 'hi', raw: (t) => t.peak, modeBound: true },
    tvs.some((t) => t.full != null) && { n: '100% 窗口', get: (t) => agNit(t.full), better: 'hi', raw: (t) => t.full, modeBound: true },
    tvs.some((t) => t.refl != null) && { n: '全反射率', get: (t) => agRefl(t.refl), better: 'lo', raw: (t) => t.refl },
    { n: '实测模式', get: (t) => t.mode },
  ].filter(Boolean);
  const rows = tab === 'fit'
    ? [
        { n: '屏幕 / 背光', get: (t) => t.panel },
        { n: '尺寸', get: (t) => `${t.size}″` },
        { n: '场景亮点', get: (t) => t.hl },
      ]
    : tab === 'cost'
      ? [
          { n: '库内报告价', get: (t) => `¥${t.price.toLocaleString()}`, better: 'lo', raw: (t) => t.price },
          { n: '每英寸', get: (t) => `¥${Math.round(t.price / t.size)}`, better: 'lo', raw: (t) => t.price / t.size },
          { n: '价格口径', get: () => '报告录入快照' },
        ]
      : evidenceRows;
  const bestAt = (row) => {
    if (!row.better || (row.modeBound && !sameMode)) return -1;
    const valid = tvs.map((tv, i) => ({ i, v: row.raw(tv) })).filter(({ v }) => Number.isFinite(v));
    if (valid.length !== tvs.length) return -1;
    return valid.reduce((best, cur) => row.better === 'hi'
      ? (cur.v > best.v ? cur : best)
      : (cur.v < best.v ? cur : best)).i;
  };
  return (
    <div className="agk cmpk" data-screen-label="对话内电视对比看板">
      <div className="agk-h"><Icon name="crosshair" size={14} />电视对比看板</div>
      <div className="cmpk-scene"><b>{scene}</b><span>按当前需求权重排序</span></div>
      <div className="cmpk-tabs" role="tablist" aria-label="切换对比维度">
        {[['evidence', '关键实测'], ['fit', '场景适配'], ['cost', '价格']].map(([k, n]) => (
          <button key={k} className={tab === k ? 'on' : ''} onClick={() => setTab(k)}>{n}</button>
        ))}
      </div>
      <div className="cmpk-grid" style={{ '--cmp-n': tvs.length }}>
        <span></span>
        {tvs.map((tv, i) => <b key={i} className={i === 0 ? 'winner' : ''}>{i === 0 && <em>首选</em>}{tv.brand}<small>{tv.model}</small><small className="cmpk-source">{tv.source}</small></b>)}
        {rows.map((row) => {
          const best = bestAt(row);
          return <React.Fragment key={row.n}>
            <span className="cmpk-key">{row.n}</span>
            {tvs.map((tv, i) => <span key={i} className={best === i ? 'best' : ''}>{row.get(tv)}{best === i && <i></i>}</span>)}
          </React.Fragment>;
        })}
      </div>
      <div className="cmpk-foot"><span>{sameMode ? '同模式数据可比较；首选按当前用途排序' : '实测模式不同，保留原始口径，不把亮度强行判胜'}</span><a href="电视对比看板.html" target="_blank" rel="noreferrer">展开完整看板</a></div>
    </div>
  );
}

function AgAccessoryNeedsCard({ usage = [] }) {
  const stateLabels = { required: '必需', recommended: '建议', not_needed: '不需要' };
  const movieHeavy = usage.includes('movie');
  const familyUse = usage.includes('family');
  const liveUse = usage.includes('live');
  const gameUse = usage.includes('game');
  const rows = [
    ['电视支架', 'required', familyUse ? '除匹配 VESA 与承重，还需优先防倾倒' : '立式或壁挂都需匹配 VESA 与承重'],
    ['回音壁', movieHeavy ? 'recommended' : 'not_needed', movieHeavy ? '电影权重高，电视原生低频偏弱' : '当前用途不需要优先升级声音'],
    ['电视盒子', 'not_needed', liveUse ? '沿用运营商机顶盒，不重复购买' : '原生流媒体与 DRM 已满足'],
    ['HDMI 线', 'not_needed', gameUse ? '主机原装线带宽足够' : '当前连接设备无需另购'],
  ];
  return (
    <div className="agk acck">
      <div className="agk-h"><Icon name="layers" size={14} />配件需要性分析</div>
      <div className="acck-list">{rows.map(([n, state, why]) => (
        <div className="acck-row" key={n}><b>{n}</b><span className={'st-' + state}>{stateLabels[state]}</span><small>{why}</small></div>
      ))}</div>
    </div>
  );
}

const AG_MOUNT_STYLES = {
  floor: [
    { id: 'easel', name: '画架式', desc: '家具感最强', image: 'accessory-stand-easel.webp', roomImage: 'room-composites/room-mount-easel.webp', roomColor: 'walnut' },
    { id: 'pedestal', name: '立柱式', desc: '占地更小', image: 'accessory-stand-pedestal.webp', roomImage: 'room-composites/room-mount-pedestal.webp', roomColor: 'black' },
    { id: 'mobile', name: '移动式', desc: '带轮可推', image: 'accessory-stand-mobile.webp', roomImage: 'room-composites/room-mount-mobile.webp', roomColor: 'gray' },
  ],
  wall: [
    { id: 'slim', name: '超薄固定', desc: '贴墙最薄', image: 'accessory-mount-slim.webp', roomImage: 'room-composites/room-mount-slim.webp', roomColor: 'black' },
    { id: 'tilt', name: '俯仰可调', desc: '可调整上下角度', image: 'accessory-mount-slim.webp', roomImage: 'room-composites/room-mount-tilt.webp', roomColor: 'black' },
    { id: 'arm', name: '全向伸缩', desc: '可拉出与左右转向', image: 'accessory-mount-slim.webp', roomImage: 'room-composites/room-mount-arm.webp', roomColor: 'black' },
  ],
};

function AgMountResultCard({ mount, tv }) {
  return (
    <div className="agk mountk">
      <div className="agk-h"><Icon name="layers" size={14} />支架方案已选<span className="acck-demo">商品示意</span></div>
      <div className="mountk-main">
        <img src={mount.style.image} alt={mount.style.name + '电视支架示意图'} />
        <div>
          <b>{mount.modeLabel} · {mount.style.name}</b>
          <small>{mount.style.desc}</small>
          <div className="mountk-chips">
            <span>{mount.lift}</span><span>{mount.power}</span>
            <span><i style={{ background: mount.color.hex }}></i>{mount.color.label}</span>
          </div>
        </div>
      </div>
      <div className="mountk-check"><Icon name="check" size={12} /><span>已按 {tv.size}″ 预筛尺寸范围</span><em>下单前核对 VESA 与承重</em></div>
    </div>
  );
}

function AgSoundbarCard() {
  return (
    <div className="agk acck soundk">
      <div className="agk-h"><Icon name="broadcast" size={14} />回音壁方案<span className="acck-demo">商品示意</span></div>
      <div className="acck-soundbar">
        <img src="accessory-soundbar.webp" alt="5.1.2 声道回音壁套装示意图" />
        <div><b>5.1.2 声道套装</b><small>eARC · 杜比全景声 · 无线低音炮</small></div>
      </div>
    </div>
  );
}

function AgRoomPreviewCard({ tv, mount }) {
  const exactPreview = tv.size === 85 && mount.style.roomColor === mount.color.v;
  const bg = exactPreview && mount.style.roomImage ? mount.style.roomImage : (mount.mode === 'wall' ? IMG.wall : IMG.stand);
  return (
    <div className="agk roompk">
      <div className="agk-h"><Icon name="sparkle" size={14} />已保存客厅合成</div>
      <div className="roompk-shot"><img src={bg} loading="lazy" decoding="async" alt="电视与所选支架在客厅中的合成示意" /><span>{exactPreview ? `${mount.style.name} · ${mount.color.label}` : `摆放参考 · ${mount.style.name}/${mount.color.label}已记录`}</span></div>
      <b>{tv.brand} {tv.model} · {tv.size}″</b>
    </div>
  );
}

function AgReturnTvCard({ tv, mount }) {
  return (
    <div className="agk returnk">
      <span className="returnk-ic"><Icon name="arrowLeft" size={14} /></span>
      <div><small>回到电视方案</small><b>{tv.brand} {tv.model}</b></div>
      {mount && <em>{mount.style.name}已保存</em>}
    </div>
  );
}

function AgBuyLinkCard({ tv }) {
  return (
    <div className="agk linkk">
      <div className="agk-h"><Icon name="search" size={14} />推荐搜索入口</div>
      <div className="linkk-item">
        <span className="linkk-shop">京东</span>
        <div><b>{tv.brand} {tv.model} · {tv.size}″</b><small>型号关键词已锁定</small></div>
        <a href={agPurchaseUrl(tv)} target="_blank" rel="noreferrer" aria-label={'打开 ' + tv.brand + ' ' + tv.model + ' 推荐搜索入口'}>打开<Icon name="chevRight" size={12} /></a>
      </div>
    </div>
  );
}

function AgLinkInputCard({ onSubmit }) {
  const [value, setValue] = React.useState('');
  const valid = /^https?:\/\/\S+$/i.test(value.trim());
  const submit = () => { if (valid) onSubmit(value.trim()); };
  return (
    <div className="agi linki">
      <div className="agi-head">购买链接核验</div>
      <div className="agi-title">粘贴商品详情页链接</div>
      <div className="linki-row">
        <input
          type="url"
          inputMode="url"
          value={value}
          placeholder="https://item..."
          aria-label="商品详情页链接"
          onChange={(e) => setValue(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
        />
        <button type="button" disabled={!valid} onClick={submit}>核验</button>
      </div>
    </div>
  );
}

function AgOfferCard({ tv, source }) {
  const custom = source === '你提供的链接';
  const checks = custom
    ? ['型号 / 尺寸一致', '销售主体可开票', '国补地区匹配', '无缩水后缀']
    : ['型号关键词一致', '尺寸关键词一致', '进入商品页后核对店铺', '下单前确认地区补贴'];
  return (
    <div className="agk offerk">
      <div className="agk-h"><Icon name="search" size={14} />{custom ? '购买链接核验' : '搜索入口检查'}<span className="entk-ok">{custom ? '通过 ✓' : '入口可用 ✓'}</span></div>
      <div className="offerk-title">{tv.brand} {tv.model} · {tv.size}″</div>
      <div className="offerk-source">{source}</div>
      <div className="offerk-grid">{checks.map((check) => <span key={check}><Icon name="check" size={12} />{check}</span>)}</div>
      <div className="agk-note">价格和补贴会随地区与时间变化，下单当天建议再检查一次</div>
    </div>
  );
}

/* ---- 聊天主体：剧本引擎 ---- */
function AgentChatInner({ onBack, onRestart, ask }) {
  const { useState, useEffect, useRef } = React;
  const [msgs, setMsgs] = useState([]);
  const [ov, setOv] = useState(null); // 'ar' | 'lux' | 'room'
  const [ovData, setOvData] = useState(null);
  const seq = useRef(0);
  const alive = useRef(true);
  const pend = useRef(null);   // 选项或链接输入等待 {id, res}
  const ovRes = useRef(null);  // 浮层等待 resolve
  const scRef = useRef(null);

  useEffect(() => () => { alive.current = false; }, []);
  useEffect(() => {
    const el = scRef.current;
    if (el) el.scrollTo({ top: el.scrollHeight, behavior: AG_RM ? 'auto' : 'smooth' });
  }, [msgs, ov]);

  const push = (m) => { const id = ++seq.current; setMsgs((s) => [...s, { id, ...m }]); return id; };
  const patch = (id, p) => setMsgs((s) => s.map((m) => (m.id === id ? { ...m, ...p } : m)));
  const sleep = async (ms) => { await agSleep(ms); if (!alive.current) throw 0; };

  const ai = async (text, wait = 950) => {
    const id = push({ kind: 'ai', typing: true });
    await sleep(wait);
    patch(id, { typing: false, text });
    await sleep(260);
  };
  const tool = async (icon, name, detail, result, wait = 1400, mode = '') => {
    const id = push({ kind: 'tool', icon, name, mode, detail, state: 'run' });
    await sleep(wait);
    patch(id, { state: 'done', result });
    await sleep(320);
  };
  const opts = (items, meta = null) => new Promise((res) => {
    const id = push({ kind: 'opts', items, meta });
    pend.current = { id, res };
    if (AG_AUTO) agSleep(1100).then(() => {
      if (pend.current && pend.current.id === id)
        pick(items.find((it) => it.auto) || items.find((it) => it.wide) || items[0]);
    });
  });
  const multiOpts = (items, meta = null, max = 3) => new Promise((res) => {
    const id = push({ kind: 'multi-opts', items, meta, selected: [], max });
    pend.current = { id, res, items, selected: new Set(), max };
    if (AG_AUTO) agSleep(900).then(() => {
      const p = pend.current;
      if (!p || p.id !== id) return;
      p.selected = new Set(items.filter((it) => it.auto).map((it) => it.v));
      patch(id, { selected: [...p.selected] });
      agSleep(260).then(() => confirmMulti(id));
    });
  });
  const pick = (it) => {
    const p = pend.current; if (!p) return;
    pend.current = null;
    patch(p.id, { done: true });
    push({ kind: 'user', text: it.bubble || it.label });
    p.res(it);
  };
  const toggleMulti = (id, it) => {
    const p = pend.current;
    if (!p || p.id !== id) return;
    if (p.selected.has(it.v)) p.selected.delete(it.v);
    else if (p.selected.size < p.max) p.selected.add(it.v);
    patch(id, { selected: [...p.selected] });
  };
  const confirmMulti = (id) => {
    const p = pend.current;
    if (!p || p.id !== id || !p.selected.size) return;
    const chosen = p.items.filter((it) => p.selected.has(it.v));
    pend.current = null;
    patch(id, { done: true });
    push({ kind: 'user', text: chosen.map((it) => it.short || it.label).join(' + ') });
    p.res(chosen);
  };
  const submitLink = (id, value) => {
    const p = pend.current;
    if (!p || p.id !== id) return;
    pend.current = null;
    patch(id, { done: true, value });
    push({ kind: 'user', text: '核验我找到的商品链接' });
    p.res(value);
  };
  const linkInput = (tv) => new Promise((res) => {
    const demoUrl = agPurchaseUrl(tv);
    const id = push({ kind: 'link-input', demoUrl });
    pend.current = { id, res };
    if (AG_AUTO) agSleep(1100).then(() => submitLink(id, demoUrl));
  });
  const ovRun = (kind, data = null) => new Promise((res) => { ovRes.current = res; setOvData(data); setOv(kind); });
  const ovDone = (payload = null) => {
    setOv(null); setOvData(null);
    const r = ovRes.current; ovRes.current = null;
    if (r) r(payload);
  };

  /* 剧本路由：三个建议问题各走一条；其余（固定命令/自由输入）走主线 */
  useEffect(() => {
    const go = ask === AG_ASKS[1] ? runGlare : ask === AG_ASKS[2] ? runBurn : run;
    go().catch(() => {});
  }, []);

  async function runMountFlow(tv) {
    let selected = null;
    let previewSaved = false;
    let choosing = true;
    await ai('支架我单独帮你选。先定安装方式和款式；立式再看升降与电动，最后选颜色。选好后还能直接放进你家客厅看看。');
    while (choosing) {
      const mode = await opts([
        { label: '立式', sub: '不打墙，款式更多', v: 'floor', accent: true, auto: true },
        { label: '壁挂', sub: '更省地面空间', v: 'wall' },
      ], { tool: 'recommend_tv_accessories', title: '支架怎么安装？' });
      const styles = AG_MOUNT_STYLES[mode.v];
      const style = await opts(styles.map((item, i) => ({
        label: item.name, sub: item.desc, v: item.id, image: item.image, item, auto: i === 1,
      })), { tool: 'recommend_tv_accessories', title: `选择${mode.v === 'floor' ? '立式' : '壁挂'}款式` });
      let lift = { label: '固定安装', v: 'fixed' };
      let power = { label: '非电动', v: 'none' };
      if (mode.v === 'floor') {
        lift = await opts([
          { label: '固定高度', sub: '结构最简单', v: 'fixed', auto: true },
          { label: '需要升降', sub: '沙发与餐桌视线都能兼顾', v: 'lift', accent: true },
        ], { tool: 'recommend_tv_accessories', title: '需要升降吗？' });
      }
      if (lift.v === 'lift' && style.v === 'mobile') {
        power = await opts([
          { label: '手动调节', sub: '偶尔调一次即可', v: 'manual', auto: true },
          { label: '电动升降', sub: '遥控调高低', v: 'electric', accent: true },
        ], { tool: 'recommend_tv_accessories', title: '升降方式' });
      } else if (lift.v === 'lift') {
        power = { label: '手动调节', v: 'manual' };
      }
      const colorSet = style.v === 'easel'
        ? [
            { label: '胡桃木', v: 'walnut', hex: '#6B4430' },
            { label: '浅橡木', v: 'oak', hex: '#C49A68' },
            { label: '曜石黑', v: 'black', hex: '#24252A' },
          ]
        : mode.v === 'floor'
          ? [
              { label: '曜石黑', v: 'black', hex: '#24252A' },
              { label: '云母灰', v: 'gray', hex: '#777A80' },
              { label: '奶油白', v: 'white', hex: '#E9E2D6' },
            ]
          : [
              { label: '曜石黑', v: 'black', hex: '#24252A' },
              { label: '云母灰', v: 'gray', hex: '#777A80' },
              { label: '钛金色', v: 'titanium', hex: '#A59A88' },
            ];
      const color = await opts(colorSet.map((item, i) => ({ ...item, swatch: item.hex, auto: i === 0 })), {
        tool: 'recommend_tv_accessories', title: '选择颜色',
      });
      const wallAdjustment = style.v === 'tilt' ? '俯仰调节' : style.v === 'arm' ? '全向调节' : '固定安装';
      selected = {
        mode: mode.v,
        modeLabel: mode.v === 'floor' ? '立式' : '壁挂',
        style: style.item,
        lift: mode.v === 'wall' ? wallAdjustment : lift.v === 'lift' ? '可升降' : '固定高度',
        power: power.label,
        color,
      };
      await tool('layers', 'recommend_tv_accessories', `${tv.size}″ · ${selected.modeLabel} · ${selected.style.name} · ${selected.lift} · ${selected.power}`, 'needs_confirmation · 尺寸范围通过，VESA 与承重待商品页核对', 1100, 'recommend');
      push({ kind: 'mount-result', mount: selected, tv });
      await sleep(650);
      const next = await opts([
        { label: '放进我家看看', sub: '调用“在我家试电视”合成', v: 'preview', accent: true },
        { label: '换一个支架', v: 'again' },
        { label: '选这个，回到电视方案', v: 'done', auto: true },
      ], { tool: 'recommend_tv_accessories', title: '支架方案下一步' });
      if (next.v === 'again') continue;
      if (next.v === 'preview') {
        await tool('sparkle', 'evaluate_room_and_size', `${tv.brand} ${tv.model} + ${selected.style.name} + ${selected.color.label}`, 'needs_input · 请确认客厅摆放效果', 850, 'room_preview');
        const roomResult = await ovRun('room', { tv, mount: selected });
        if (roomResult) {
          previewSaved = true;
          push({ kind: 'room-preview', tv, mount: selected });
          await sleep(550);
          await ai('这套电视和支架已经按客厅尺度合成并保存。');
        } else {
          await ai('合成没有保存，但刚才选的支架方案还在。');
        }
        const afterPreview = await opts([
          { label: previewSaved ? '回到电视方案' : '保留支架并返回', v: 'done', accent: true, auto: true },
          { label: '换一个支架', v: 'again' },
        ], { tool: 'recommend_tv_accessories', title: '完成支架选择' });
        if (afterPreview.v === 'again') continue;
      }
      choosing = false;
    }
    return { mount: selected, previewSaved };
  }

  async function run() {
    push({ kind: 'user', text: ask || AG_CMD });
    await sleep(650);
    await tool('database', 'research_products', '先了解尺寸、家庭使用方式和预算', 'needs_input · 还差 3 个关键信息', 1200, 'candidate_search');
    await ai('没问题。要把客厅电视选准，我先确认三件事：多大、家里谁用和主要看什么、预算多少。如果尺寸拿不准，我也可以帮你量。');
    const s = await opts([
      { label: '75″', sub: '视距 2.3 – 2.8 m', v: 75 },
      { label: '85″', sub: '视距 2.8 – 3.4 m', v: 85 },
      { label: '98″ 以上', sub: '视距 3.4 m+', v: 98 },
      { label: '不知道怎么选，帮我量一下', sub: '用手机测电视墙和观看距离', v: 'ar', accent: true, wide: true, auto: true, bubble: '不知道怎么选' },
    ], { tool: 'research_products', title: '1 / 3 · 先确定尺寸或测量空间' });
    let size = s.v;
    if (size === 'ar') {
      await tool('cube', 'evaluate_room_and_size', '需要电视墙净宽和观看距离', 'needs_input · 请扫描电视墙', 850, 'room_preview');
      await ai('尺寸拿不准很正常。请把镜头对准电视墙，慢慢扫一圈；我会一起看墙宽和观看距离。', 750);
      await sleep(900);
      await ovRun('ar');
      push({ kind: 'ar' });
      await sleep(750);
      await ai('量好了：电视墙净宽 3.62 m，观看距离 2.9 m。85″ 最合适；98″ 虽然放得下，但坐得有点近。我先按 85″ 给你选。');
      size = 85;
    }
    const usage = await multiOpts([
      { label: '电影 / 流媒体', short: '电影', sub: 'HDR、暗场、杜比与应用兼容', v: 'movie', auto: true },
      { label: '主机 / PC 游戏', short: '游戏', sub: '4K 高刷、VRR 与低延迟', v: 'game', auto: true },
      { label: '电视直播 / 体育综艺', short: '直播体育', sub: '换台快、运动清晰、长时间亮屏', v: 'live' },
      { label: '长辈经常使用', short: '长辈使用', sub: '开机直达、少广告、语音好用', v: 'senior' },
      { label: '儿童 / 全家共看', short: '儿童家庭', sub: '护眼、家长控制与防倾倒', v: 'family' },
      { label: '手机投屏 / K歌', short: '投屏娱乐', sub: '投屏稳定、麦克风与家庭娱乐', v: 'cast' },
    ], { tool: 'research_products', title: '2 / 3 · 主要怎么用（最多选 3 项）' });
    const usageValues = usage.map((it) => it.v);
    const usageLabel = usage.map((it) => it.short || it.label).join(' + ');
    const usageSignals = [
      usageValues.includes('movie') && '流媒体 · HDR 音画',
      usageValues.includes('game') && '主机 / PC · 4K 高刷 · VRR',
      usageValues.includes('live') && '直播源 · 开机直达 · 长时稳定',
      usageValues.includes('senior') && '简洁系统 · 语音 · 少广告',
      usageValues.includes('family') && '护眼 · 家长控制 · 防倾倒',
      usageValues.includes('cast') && '投屏 · 麦克风兼容',
    ].filter(Boolean).join(' · ');
    /* AG_ASKS[0]「预算 1.5 万」：预算已知，不再重复追问，按在库真实价位直接落档 */
    const preBud = ask === AG_ASKS[0];
    let b;
    if (!preBud) {
      b = await opts(AG_BUDGETS[size].map((it) => ({ ...it, auto: it.v === '85mid' })), { tool: 'research_products', title: '3 / 3 · 电视本体预算' });
    } else if (size === 85) {
      await ai('预算 1.5 万已经在你的消息里，我不重复问。它正好落在 85″ 甜点档。');
      b = { label: '1 – 1.5 万', v: '85mid' };
    } else if (size === 75) {
      await ai('75″ 目前最贵的候选约 ¥9,749，1.5 万预算用不完。我会保留升级尺寸的余量，不为了花满而抬价。');
      b = { label: '1 万左右', v: '75b2' };
    } else {
      await ai('98″ 目前约 ¥28,999 起，1.5 万预算不够。你可以回到 85″ 选更好的画质，也可以增加预算上百吋。');
      const g = await opts([
        { label: '那就 85″ 里选最强', v: '85mid', accent: true, auto: true },
        { label: '加预算，上百吋', v: '98b1' },
      ], { tool: 'research_products', title: '硬条件冲突 · 请选择调整项', status: 'needs_confirmation' });
      if (g.v === '85mid') { size = 85; b = { label: '1 – 1.5 万', v: '85mid' }; }
      else b = { label: '3 万左右', v: '98b1' };
    }
    const P = agPickFor(b.v, usageValues);
    await tool('database', 'research_products', `${size}″ · ${usageLabel} · ${b.label} · 读取实测与证据`, `已完成 · 58 台评测 → 命中 ${AG_HIT[size]} 台 → 2 台证据完整`, 1500, 'candidate_search');
    await tool('bolt', 'plan_device_setup', usageSignals, 'ok · 端口、协议与内容兼容无硬伤', 1200, 'compatibility_check');
    await tool('cube', 'plan_installation_and_delivery', `${size}″ 包装箱 × 电梯 × 入户门 × 支架安装`, 'ok · 入户可行，立式 / 壁挂均可选', 1350, 'precheck');
    push({ kind: 'entry', size });
    await sleep(650);
    await tool('activity', 'compare_products', '候选 2 台 · 保留原始实测口径 · 当前场景权重', 'ok · 排序、优势、短板与妥协已生成', 1500, 'compare');
    push({ kind: 'compare', tvs: [P.main, P.alt], scene: `${usageLabel} · ${size}″ · ${b.label}` });
    await sleep(850);
    await ai(`${P.say} 我把两台最值得看的关键差异列在下面，你可以切换“关键实测、场景适配、价格”看看。`);
    const fb = await opts([
      { label: `选首选 · ${P.main.brand} ${P.main.model}`, v: 'main', accent: true, auto: true },
      { label: `选备选 · ${P.alt.brand} ${P.alt.model}`, v: 'alt' },
    ], { tool: 'compare_products', title: '根据对比结果确认候选', status: 'needs_confirmation' });
    const selected = fb.v === 'alt' ? P.alt : P.main;
    if (fb.v === 'alt') await ai(P.altSay, 700);
    push({ kind: 'tv', tv: selected, tag: fb.v === 'alt' ? '你的选择 · 备选' : '你的选择 · 首选' });
    await sleep(750);
    const needsSoundbar = usageValues.includes('movie');
    await tool('layers', 'recommend_tv_accessories', '根据电视、设备链、安装方式与已有配件分析缺口', `needs_confirmation · 1 required / ${needsSoundbar ? 1 : 0} recommended / ${needsSoundbar ? 2 : 3} not_needed`, 1200, 'needs_assessment');
    push({ kind: 'accessory-needs', usage: usageValues });
    await sleep(650);
    await ai('我先把该买、可买和不用买的配件分开，避免你多花冤枉钱。');
    const ac = await opts([
      { label: '继续推荐支架', v: 'y', accent: true, small: true, auto: true },
      { label: '支架以后再说', v: 'n', small: true },
    ], { tool: 'recommend_tv_accessories', title: '是否继续选支架？', status: 'needs_confirmation' });
    let mountPlan = null;
    let soundbarAdded = false;
    if (ac.v === 'y') {
      const mountResult = await runMountFlow(selected);
      mountPlan = mountResult.mount;
      await ai(`支架方案先收好。现在回到电视本体：你选的仍是 ${selected.brand} ${selected.model}。`);
      push({ kind: 'return-tv', tv: selected, mount: mountPlan });
      await sleep(650);
    }
    if (needsSoundbar) {
      const sb = await opts([
        { label: '继续看回音壁', v: 'y', small: true },
        { label: '声音以后再说', v: 'n', small: true, auto: true },
      ], { tool: 'recommend_tv_accessories', title: '电影使用较多，是否继续看声音方案？' });
      if (sb.v === 'y') {
        await tool('broadcast', 'recommend_tv_accessories', '电影权重 · eARC · 杜比全景声', 'ok · 回音壁类别建议已生成', 900, 'recommend');
        push({ kind: 'soundbar' });
        soundbarAdded = true;
        await sleep(550);
      }
    }
    await ai('我先给你一个匹配当前型号的搜索入口。你也可以把自己看到的商品链接贴进来，我会核对型号、尺寸、版本、店铺和补贴资格。');
    push({ kind: 'buy-link', tv: selected });
    await sleep(650);
    const vf = await opts([
      { label: '检查推荐入口', v: 'recommended', accent: true, auto: true },
      { label: '粘贴我的链接', v: 'custom' },
      { label: '暂时不用', v: 'skip' },
    ], { tool: 'verify_model_and_offer', title: '选择要检查的入口或链接', status: 'needs_confirmation' });
    let linkChecked = false;
    let linkSource = null;
    if (vf.v !== 'skip') {
      let source = '推荐搜索入口';
      if (vf.v === 'custom') {
        await tool('search', 'verify_model_and_offer', '需要商品详情页链接', 'needs_input · PurchaseLinkInput', 850, 'verify');
        await linkInput(selected);
        source = '你提供的链接';
      }
      const verifyResult = source === '你提供的链接'
        ? 'ok · 型号、尺寸、版本与销售条件一致'
        : 'ok · 推荐入口可用，具体店铺与补贴需进入商品页确认';
      await tool('search', 'verify_model_and_offer', `${source} · ${selected.brand} ${selected.model} · 上海地区`, verifyResult, 1400, 'verify');
      push({ kind: 'offer', tv: selected, source });
      linkSource = source;
      linkChecked = true;
      await sleep(650);
    }
    await ai(linkChecked
      ? linkSource === '你提供的链接'
        ? '你贴的商品链接与推荐机型一致，可以按核验结果继续；下单当天我建议再看一次最终价格和补贴。'
        : '推荐搜索入口已经按目标型号和尺寸匹配好；进入具体商品页后，再核对店铺、版本和当地补贴。'
      : '机型和配件方案已经整理好。你准备下单时，把商品链接发给我，我再帮你核验一次。');
    push({ kind: 'end', chips: [`${size}″`, b.label, `${selected.brand} ${selected.model}`, '对比已完成', mountPlan && `${mountPlan.style.name}支架`, soundbarAdded && '回音壁方案', linkChecked && (linkSource === '你提供的链接' ? '购买链接已核验 ✓' : '推荐入口已匹配')].filter(Boolean) });
  }

  /* 剧本「反光」（AG_ASKS[1]）：后台「光环境适配」引擎 + 前台「采光检测」小工具回传。
     口径：全反射率(done) × 窗口亮度曲线(done·1%–100% 分档) × 满屏亮度(done) × 朝向反光风险规则(ext)。
     结论逻辑：亮厅 ≠ 排除 OLED —— 混合画面下窗口高光 + 超低反射占优；满屏高亮内容才是 MiniLED 主场。 */
  async function runGlare() {
    push({ kind: 'user', text: ask });
    await sleep(650);
    await tool('sun', 'evaluate_room_and_size', '需要确认照度区间、直射方向和最差观看时段', 'needs_input · AmbientLightCapture', 1100, 'ambient_light');
    await ai('怕反光我会分开看：全反射率决定倒影有多重，窗口和满屏亮度决定画面能不能压住环境光。缺少反射率实测的机型我会直接标“未测”，不拿亮度冒充抗反。先摸清你家多亮：白天阳光会直射到电视墙吗？');
    const L = await opts([
      { label: '会，西晒 / 落地窗直射', v: 'hard' },
      { label: '挺亮，但晒不到屏幕', v: 'soft' },
      { label: '说不准，帮我测一下', sub: '拍两张照片测反光和照度', v: 'lux', accent: true, wide: true, auto: true, bubble: '说不准，帮我测一下' },
    ], { tool: 'evaluate_room_and_size', title: '采集客厅光环境', status: 'needs_input' });
    let lvl = L.v;
    if (lvl === 'lux') {
      await ai('说不准没关系。请分别对着电视墙和窗户拍一张，我帮你估照度和直射时段。', 750);
      await sleep(900);
      await ovRun('lux');
      push({ kind: 'lux' });
      await sleep(750);
      await ai('测好了：午后峰值约 1400 lux，西晒直射约 2 小时，属于强光客厅。我按最严格的抗反和亮度标准筛。');
      lvl = 'hard';
    } else {
      await ai(lvl === 'hard'
        ? '西晒直射是最难伺候的光，我按 1500 lux 高光害标准筛。'
        : '没有直射就好办，按 600–800 lux 明亮客厅的标准筛。', 800);
    }
    await tool('sun', 'evaluate_room_and_size', '照度区间 × 直射方向 × 反光源', 'ok · 抗反需求高 · 最差观看时段 15:00–17:00', 1200, 'ambient_light');
    await ai('亮厅不等于不能买 OLED。LG G5 在本地报告的 HDR 影院模式峰值是 2484 nit，全反射率 0.816%，看电影、剧和游戏这类混合画面很有优势；但如果经常看满屏明亮的体育和综艺，还要重点看 100% 窗口实测。');
    await ai('所以真正的分水岭是内容：满屏高亮画面看得多不多。白天开电视，你们主要看什么？', 850);
    const R = await opts([
      { label: '电影 / 剧 / 游戏为主', sub: '混合画面 · 高光都在窗口档', v: 'oled', auto: true },
      { label: '体育·综艺·新闻开一天', sub: '满屏亮画面多 · 吃持续输出', v: 'mled' },
    ], { tool: 'research_products', title: '补充内容类型，决定亮度路线' });
    let P, bud;
    if (R.v === 'oled') {
      P = AG_GLARE.oled; bud = '2 万档';
    } else {
      await ai('满屏亮画面更需要看 100% 窗口，而不是只看宣传峰值。预算大概什么范围？', 850);
      const b = await opts([
        { label: '7 – 8 千', v: 'g07' },
        { label: '1 万左右', v: 'g1' },
        { label: '1.5 万左右', v: 'g15', auto: true },
        { label: '3 万左右，上百吋', v: 'gbig' },
      ], { tool: 'research_products', title: '电视本体预算' });
      P = AG_GLARE[b.v]; bud = b.label;
    }
    await tool('database', 'research_products', `${R.v === 'oled' ? '已测反射率 + HDR 峰值' : '100% 窗口 + 抗反证据状态'} · ${bud}`, 'ok · 候选实测与缺失项均已标注', 1350, 'candidate_search');
    await tool('activity', 'compare_products', '全反射率 · HDR 峰值 / 100% 窗口 · 尺寸 · 价格', 'ok · 2 台按原始实测口径对比', 1450, 'compare');
    push({ kind: 'compare', tvs: [P.main, P.alt], scene: `强光客厅 · ${R.v === 'oled' ? '混合画面' : '满屏亮画面'}` });
    await sleep(800);
    await ai(`${P.say} 下面把两台的关键实测和价格放在一起，你可以直接比较。`);
    const fb = await opts([
      { label: '很合适，就它了', v: 'ok', accent: true, auto: true },
      { label: '再看看备选', v: 'alt' },
    ], { tool: 'compare_products', title: '确认当前路线', status: 'needs_confirmation' });
    const chosen = fb.v === 'alt' ? P.alt : P.main;
    if (fb.v === 'alt') {
      await ai(P.altSay, 700);
    }
    push({ kind: 'tv', tv: chosen, tag: fb.v === 'alt' ? '你的选择 · 备选' : '你的选择 · 首选' });
    await sleep(650);
    await ai('到货后告诉我电视型号和连接设备，我再按你家的光环境把亮度、HDR 和护眼设置调好。');
    push({ kind: 'end', chips: [`${chosen.size}″`, bud, `${chosen.brand} ${chosen.model}`, chosen.refl != null ? '反射率已实测 ✓' : '反射率待补测', L.v === 'lux' && '照度已实测 ✓', '对比已完成'].filter(Boolean) });
  }

  /* 剧本「烧屏」（AG_ASKS[2]）：evaluate_lifecycle_options 评估长期风险 → compare_products 对比 OLED / MiniLED。
     耐久库口径照实：质保条款已入库，烧屏/衰减实测 tier=todo（进行中）。 */
  async function runBurn() {
    push({ kind: 'user', text: ask });
    await sleep(650);
    await tool('gear', 'evaluate_lifecycle_options', '检索烧屏机制、防护能力、质保条款与使用风险', 'needs_input · UsageMixSelector', 1300, 'reliability_risk');
    await ai('直说结论：OLED 确实存在烧屏风险，但风险主要取决于使用习惯。台标、比分牌、游戏血条这类固定画面停留越久，风险越高；正常混着看，并保持像素位移和补偿刷新开启，风险会低很多。');
    await ai('风险大小主要看用法——你家电视平时开着干嘛？', 800);
    const u = await opts([
      { label: '剧 / 电影为主，内容很杂', v: 'mix', auto: true },
      { label: '新闻·体育台，一挂一整天', v: 'logo' },
      { label: '主机游戏比较多', v: 'game' },
    ], { tool: 'evaluate_lifecycle_options', title: '长期风险取决于固定画面与连续时长' });
    await tool('activity', 'evaluate_lifecycle_options', '使用结构 × ABL 行为 × 质保条款 × 耐久证据', 'partial · 条款已入库，长期衰减实测进行中', 1500, 'reliability_risk');
    const RISK = {
      mix: ['低风险用法 ✓', '你的内容一直在换，属于相对低风险的用法。OLED 可以优先考虑，但不要关闭系统自带的像素刷新。'],
      logo: ['高风险用法 ⚠', '固定台标 + 长时间开机是烧屏最高危组合，我不建议上 OLED——MiniLED 是背光方案，物理上不存在烧屏，这个用法选它更省心。'],
      game: ['中风险用法', '游戏属于中风险：血条和小地图位置固定，但 OLED 的响应速度确实更有优势。保持像素位移开启，并避免固定界面长时间连续显示，风险会更可控。'],
    }[u.v];
    await ai(RISK[1]);
    await ai('要不要我把两条路线各拉一台实测对比？看完你心里就有数了。', 800);
    const c = await opts([
      { label: '对比给我看', v: 'y', accent: true, auto: true },
      { label: '不用，我了解了', v: 'n' },
    ]);
    if (c.v === 'y') {
      await tool('database', 'research_products', 'OLED 与 MiniLED 各建立一个有完整证据的候选', 'ok · OLED 5 台 / MiniLED 30+ 台', 1200, 'candidate_search');
      await tool('activity', 'compare_products', '黑位 · HDR 峰值 · 响应 · 反射率 · 长期风险', 'ok · 2 台保留各自报告模式', 1400, 'compare');
      const burnTvs = u.v === 'logo' ? [AG_BURN.mled, AG_BURN.oled] : [AG_BURN.oled, AG_BURN.mled];
      push({ kind: 'compare', tvs: burnTvs, scene: `${RISK[0]} · OLED vs MiniLED` });
      await sleep(800);
      await ai('一句话总结：固定台标开一整天选 MiniLED；内容混着看、追求暗场和响应，可以选 OLED 并保留像素刷新。完整指标已经列在下面。');
      push({ kind: 'end', chips: ['烧屏答疑 ✓', RISK[0], 'OLED vs MiniLED', '实测对比 2 台'] });
    } else {
      await ai('行。送你三条防烧屏习惯：① 像素刷新保持开启；② 亮度别常年拉满；③ 长时间暂停记得息屏。真到要买的时候回来找我，我帮你核对具体机型的烧屏质保条款。');
      push({ kind: 'end', chips: ['烧屏答疑 ✓', RISK[0], '防烧屏习惯 ×3'] });
    }
  }

  return (
    <div className="agc" data-screen-label="Agent-统一工具对话">
      <div className="agc-head">
        <button className="mh-back" onClick={onBack} aria-label="返回主页"><Icon name="arrowLeft" size={16} /></button>
        <span className="agc-orb"><Icon name="sparkle" size={18} /></span>
        <div className="agc-ht"><b>AI 选购助手</b><small>已接入 {DB_STATS.inStock} 台电视评测库</small></div>
        <button className="agc-re" onClick={onRestart} aria-label="重新演示"><Icon name="refresh" size={15} /></button>
      </div>

      <div className="agc-scroll" ref={scRef}>
        {msgs.map((m) => {
          switch (m.kind) {
            case 'user': return <div key={m.id} className="msg user">{m.text}</div>;
            case 'ai': return (
              <div key={m.id} className="msg ai">
                {m.typing
                  ? <span className="agc-dots"><span></span><span></span><span></span></span>
                  : m.text}
              </div>
            );
            case 'tool': return (
              <div key={m.id} className={agCx('tcall', m.state === 'done' && 'done', m.result && m.result.startsWith('needs_input') && 'needs')}>
                <span className="tc-ic"><Icon name={m.icon} size={15} /></span>
                <div className="tc-tt">
                  <b>{agToolLabel(m.name)}</b>
                  <small className={agCx(m.state === 'done' && 'ok', m.result && m.result.startsWith('needs_input') && 'need')}>{agResultLabel(m.state === 'done' ? m.result : m.detail)}</small>
                </div>
                {m.state === 'run'
                  ? <span className="tc-spin"></span>
                  : <span className="tc-done"><Icon name={m.result && m.result.startsWith('needs_input') ? 'chevRight' : 'check'} size={13} /></span>}
              </div>
            );
            case 'opts': return m.done ? null : (
              <div key={m.id} className={agCx(m.meta && 'agi')}>
                {m.meta && <><div className="agi-head">{agToolLabel(m.meta.tool)}</div><div className="agi-title">{m.meta.title}</div></>}
                <div className="opts">
                  {m.items.map((it, i) => (
                    <button key={i} className={agCx('opt', it.accent && 'accent', it.wide && 'wide', it.small && 'small', it.image && 'visual', it.swatch && 'color')} onClick={() => pick(it)}>
                      {it.image && <img src={it.image} alt="" />}
                      <span className="opt-copy"><b>{it.label}</b>{it.sub && <small>{it.sub}</small>}</span>
                      {it.swatch && <i className="opt-swatch" style={{ background: it.swatch }}></i>}
                    </button>
                  ))}
                </div>
              </div>
            );
            case 'multi-opts': return m.done ? null : (
              <div key={m.id} className="agi">
                {m.meta && <><div className="agi-head">{agToolLabel(m.meta.tool)}</div><div className="agi-title">{m.meta.title}</div></>}
                <div className="multi-opts">
                  {m.items.map((it) => {
                    const selected = m.selected.includes(it.v);
                    const maxed = m.selected.length >= m.max && !selected;
                    return (
                      <button
                        key={it.v}
                        type="button"
                        className={agCx('multi-opt', selected && 'selected')}
                        aria-pressed={selected}
                        disabled={maxed}
                        onClick={() => toggleMulti(m.id, it)}
                      >
                        <span>{it.label}</span>
                        {it.sub && <small>{it.sub}</small>}
                        <i><Icon name="check" size={10} /></i>
                      </button>
                    );
                  })}
                </div>
                <button className="multi-done" type="button" disabled={!m.selected.length} onClick={() => confirmMulti(m.id)}>
                  {m.selected.length ? `选好了 · 已选 ${m.selected.length} 项` : '请至少选择 1 项'}
                </button>
              </div>
            );
            case 'ar': return <AgArCard key={m.id} />;
            case 'lux': return <AgLuxCard key={m.id} />;
            case 'entry': return <AgEntryCard key={m.id} size={m.size} />;
            case 'tv': return <AgTvCard key={m.id} tv={m.tv} tag={m.tag} />;
            case 'trade': return <AgTradeCard key={m.id} />;
            case 'compare': return <AgCompareCard key={m.id} tvs={m.tvs} scene={m.scene} />;
            case 'accessory-needs': return <AgAccessoryNeedsCard key={m.id} usage={m.usage} />;
            case 'mount-result': return <AgMountResultCard key={m.id} mount={m.mount} tv={m.tv} />;
            case 'soundbar': return <AgSoundbarCard key={m.id} />;
            case 'room-preview': return <AgRoomPreviewCard key={m.id} tv={m.tv} mount={m.mount} />;
            case 'return-tv': return <AgReturnTvCard key={m.id} tv={m.tv} mount={m.mount} />;
            case 'buy-link': return <AgBuyLinkCard key={m.id} tv={m.tv} />;
            case 'link-input': return m.done ? null : <AgLinkInputCard key={m.id} onSubmit={(value) => submitLink(m.id, value)} />;
            case 'offer': return <AgOfferCard key={m.id} tv={m.tv} source={m.source} />;
            case 'end': return <AgEndCard key={m.id} chips={m.chips} />;
            default: return null;
          }
        })}
      </div>

      <div className="agc-bar">
        <span>{msgs.some((m) => m.kind === 'link-input' && !m.done) ? '请粘贴商品链接继续' : msgs.some((m) => m.kind === 'opts' && !m.done) ? '请选择一个选项继续' : msgs.some((m) => m.kind === 'end') ? '方案已整理好 · 可以重新选择或返回主页' : '正在为你整理…'}</span>
        <span className="agc-mic"><Icon name="broadcast" size={15} /></span>
      </div>

      {ov === 'ar' && <AgArOverlay onDone={ovDone} />}
      {ov === 'lux' && <AgLuxOverlay onDone={ovDone} />}
      {ov === 'room' && <div className="agc-room-overlay">
        <RoomApp
          onBack={() => ovDone(null)}
          onComplete={ovDone}
          initialTv={ovData && ovData.tv}
          initialMount={ovData && ovData.mount && ovData.mount.mode}
          standSelection={ovData && ovData.mount}
          initialPhase="place"
        />
      </div>}
    </div>
  );
}

/* 外壳：epoch 换 key 实现「重新演示」整段重跑 */
function AgentChat(props) {
  const { useState } = React;
  const [ep, setEp] = useState(0);
  return <AgentChatInner key={ep} {...props} onRestart={() => setEp((e) => e + 1)} />;
}

Object.assign(window, { AgentChat, AG_ASKS });
