const { useState, useEffect, useRef } = React;
const cx = (...a) => a.filter(Boolean).join(' ');
const q = (k) => new URLSearchParams(location.search).get(k);
const S2 = q('s') === '2'; // 评审：直接进结果
const G = q('g') === '1';  // 评审：冻结在合成中
const REDUCE = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);

function Btn({ v = 'glass', size, icon, block, children, ...rest }) {
  return (
    <button className={cx('btn', v, size, block && 'block', !children && 'icon')} {...rest}>
      {icon && <Icon name={icon} size={size === 'xl' ? 20 : 18} />}
      {children}
    </button>
  );
}
function Phone({ children }) {
  return (
    <div className="phone">
      <div className="pscreen">{children}</div>
      <div className="island"></div>
      <div className="phome"></div>
    </div>
  );
}

/* ============ 在我家试电视：AR 量房 → 实时摆放 → AI 精细效果 ============ */
const MODELS = [
  { id: 'e8s', name: '海信 85 E8S', short: '海信 E8S', panel: 'RGB-MiniLED' },
  { id: 'g5', name: 'LG 65 G5', short: 'LG G5', panel: 'OLED evo' },
  { id: 'sny', name: 'Sony BRAVIA 8 II', short: 'Sony B8 II', panel: 'QD-OLED' },
  { id: 'tcl', name: 'TCL 85 Q10K', short: 'TCL Q10K', panel: 'Mini LED' },
];
const REF = 85;                     // 预览样片里那台电视的实际尺寸（英寸）
const SIZES = [55, 65, 75, 85, 98]; // 尺寸档位（place 阶段的可选项，deep link sz 吸附到最近档）
const DISTS = [240, 280, 320];      // 沙发距离候选（cm），默认 280
// 电视在照片卡中的位置（卡与照片同为 4:3，坐标即百分比）
const TVRECT = { wall: { cx: 62, cy: 40.5, w: 33.5, h: 28 }, stand: { cx: 61.5, cy: 45.5, w: 34, h: 28 } };
const viewAngle = (size, dist) => Math.round(2 * Math.atan((size * 2.54 * 0.8716 / 2) / dist) * 180 / Math.PI);
const fitLabel = (a) => a < 28 ? { t: '偏小', cls: 'warn' } : a <= 40 ? { t: '推荐', cls: 'ok' } : { t: '偏大', cls: 'warn' };
const T = { toResult: 1300 }; // 需 ≥ 所有合成动画收尾（bloom .3+.9=1.2s / scan .1+1.15=1.25s）；reduced 时快进
const ROOM_WALL_CM = 226;
const ROOM_DIST_CM = 280;

function RoomApp({ onBack, onComplete, initialTv, initialMount, standSelection, initialPhase: phaseOverride }) {
  const phaseParam = q('rp');
  const initialPhase = phaseOverride || (G ? 'gen' : S2 ? 'result' : ['scan', 'place', 'gen', 'result'].includes(phaseParam) ? phaseParam : 'capture');
  const roomModels = initialTv
    ? [{ id: `agent-${initialTv.id}`, name: `${initialTv.brand} ${initialTv.model}`, short: initialTv.model, panel: initialTv.panel }, ...MODELS.filter((m) => !`${initialTv.brand} ${initialTv.model}`.includes(m.short))]
    : MODELS;
  const sizeChoices = initialTv && !SIZES.includes(initialTv.size) ? [...SIZES, initialTv.size].sort((a, b) => a - b) : SIZES;
  const [phase, setPhase] = useState(initialPhase); // capture | scan | place | gen | result
  const [size, setSize] = useState(() => {
    if (initialTv && Number.isFinite(initialTv.size)) return initialTv.size;
    const sz = parseFloat(q('sz')); // 深链尺寸吸附到最近档位：无效值回 REF，避免 NaN 传遍派生值
    return Number.isFinite(sz) ? SIZES.reduce((a, b) => Math.abs(b - sz) < Math.abs(a - sz) ? b : a) : REF;
  });
  const [midx, setMidx] = useState(0);
  const [mount, setMount] = useState(initialMount === 'floor' || initialMount === 'stand' || q('m') === 'stand' ? 'stand' : 'wall');
  const [dist, setDist] = useState(ROOM_DIST_CM);
  const timers = useRef([]);
  const clear = () => { timers.current.forEach(clearTimeout); timers.current = []; };
  useEffect(() => clear, []);
  /* 深链直入 scan 只用于展示扫描态：补上 scan→place 推进定时器，否则永久停在无按钮的扫描屏 */
  useEffect(() => {
    if (initialPhase === 'scan') timers.current.push(setTimeout(() => setPhase('place'), REDUCE ? 360 : 1550));
  }, []);

  const startScan = () => {
    if (phase !== 'capture') return;
    clear();
    setPhase('scan');
    timers.current.push(setTimeout(() => setPhase('place'), REDUCE ? 360 : 1550));
  };
  const makeAI = () => {
    if (phase !== 'place') return;
    clear();
    setPhase('gen');
    if (selectedRoomSrc) {
      const startedAt = Date.now();
      let settled = false;
      const finish = () => {
        if (settled) return;
        settled = true;
        const minWait = REDUCE ? 320 : T.toResult;
        timers.current.push(setTimeout(() => setPhase('result'), Math.max(0, minWait - (Date.now() - startedAt))));
      };
      const preloader = new Image();
      preloader.onload = finish;
      preloader.onerror = finish;
      preloader.src = selectedRoomSrc;
      timers.current.push(setTimeout(finish, 5000));
    } else {
      timers.current.push(setTimeout(() => setPhase('result'), REDUCE ? 320 : T.toResult));
    }
  };
  const reset = () => { clear(); setSize(initialTv && Number.isFinite(initialTv.size) ? initialTv.size : REF); setDist(ROOM_DIST_CM); setPhase('capture'); };

  const model = roomModels[midx];
  const angle = viewAngle(size, dist), angleFit = fitLabel(angle);
  const wCm = Math.round(size * 2.54 * 0.8716), hCm = Math.round(wCm * 9 / 16);
  const clearance = ROOM_WALL_CM - wCm;
  const fit = clearance < 16 ? { t: '墙面过满', cls: 'warn' } : angleFit;
  const aiShown = phase === 'gen' || phase === 'result';
  const arShown = phase === 'place';
  /* 预生成图只代表 85″ 与提示词里指定的支架颜色；其他组合回退到可缩放、可着色的实时摆放，避免图像与选择不一致。 */
  const selectedRoomSrc = standSelection && size === REF && standSelection.style && standSelection.style.roomImage && standSelection.style.roomColor === standSelection.color.v
    ? standSelection.style.roomImage
    : null;
  const resultSrc = selectedRoomSrc || (standSelection ? IMG.orig : mount === 'wall' ? IMG.wall : IMG.stand);
  const arSource = mount === 'wall' ? IMG.wall : IMG.stand;

  // AR 电视：以样片实际尺寸(REF)为基准，按所选尺寸等比缩放；样片裁切只显示电视模型区域
  const tvr = TVRECT[mount], k = size / REF;
  const fp = { left: tvr.cx - tvr.w * k / 2, top: tvr.cy - tvr.h * k / 2, width: tvr.w * k, height: tvr.h * k };
  const crop = {
    backgroundImage: `url(${arSource})`,
    backgroundSize: `${10000 / tvr.w}% ${10000 / tvr.h}%`,
    backgroundPosition: `${((tvr.cx - tvr.w / 2) / (100 - tvr.w)) * 100}% ${((tvr.cy - tvr.h / 2) / (100 - tvr.h)) * 100}%`,
  };

  // 回传 Agent 的结构化载荷（chip 只是它的可视摘要）
  const payload = {
    wall_width_cm: ROOM_WALL_CM, size_in: size, width_cm: wCm, mount, model_id: model.id,
    panel: model.panel, distance_cm: dist, view_angle: angle, fit: fit.t,
    mount_selection: standSelection ? { style: standSelection.style.name, lift: standSelection.lift, power: standSelection.power, color: standSelection.color.label } : null,
  };
  const mountName = standSelection ? standSelection.style.name : mount === 'wall' ? '挂墙' : '底座';
  const chips = [`墙宽 ${ROOM_WALL_CM}cm`, `${size}″`, model.panel, mountName, `${(dist / 100).toFixed(1)}m`, fit.t];
  const resultTitle = clearance < 0
    ? `${size}″ 已超出电视墙宽度`
    : clearance < 16
      ? `${size}″ 能放，但安装余量太小`
      : `${size}″ 放得下，观看尺寸${fit.t === '推荐' ? '正合适' : '需要再斟酌'}`;
  const step = phase === 'capture' || phase === 'scan' ? 0 : phase === 'place' ? 1 : 2;
  const phaseLabel = {
    capture: '量空间',
    scan: '扫描中',
    place: '看大小',
    gen: '生成中',
    result: '看效果',
  }[phase];

  return (
    <div className={cx('m-app', standSelection && 'with-mount')} data-screen-label={'在我家试电视-' + phase}>
      <div className="vf-bg" style={{ backgroundImage: `url(${aiShown ? resultSrc : IMG.orig})` }}></div>

      {/* 顶栏 */}
      <div className="m-head">
        {onBack && <button className="mh-back" onClick={onBack} aria-label={onComplete ? '返回对话' : '返回主页'}><Icon name="arrowLeft" size={16} /></button>}
        <span className="mh-logo"><Icon name="cube" size={16} /></span>
        <div className="mh-txt">
          <div className="mh-t">在我家试电视</div>
        </div>
        <span className={cx('mh-step', (phase === 'scan' || phase === 'gen') && 'busy')}>{phaseLabel}</span>
      </div>

      <div className="room-progress" aria-label="量空间、看大小、看效果">
        {['量空间', '看大小', '看效果'].map((label, i) => (
          <div key={label} className={cx('rp-step', i < step && 'done', i === step && 'on')}>
            <span>{i < step ? <Icon name="check" size={10} /> : i + 1}</span><b>{label}</b>
          </div>
        ))}
      </div>

      {/* 照片卡 */}
      <div className={cx('vf-wrap', 'p-' + phase)}>
        <div className={cx('vf', 'phase-' + phase)}>
          <img className="vf-img base" src={IMG.orig} alt="客厅原图" draggable="false" />
          {standSelection
            ? aiShown && <img className="vf-img res" src={resultSrc} alt={standSelection.style.name + '客厅 AI 效果'} draggable="false" decoding="async" />
            : <React.Fragment>
                <img className="vf-img res" src={IMG.wall} alt="挂墙 AI 效果" draggable="false" style={{ opacity: aiShown && mount === 'wall' ? 1 : 0 }} />
                <img className="vf-img res" src={IMG.stand} alt="底座 AI 效果" draggable="false" style={{ opacity: aiShown && mount === 'stand' ? 1 : 0 }} />
              </React.Fragment>}

          {phase === 'capture' && (
            <div className="vf-guide">
              <span className="gc tl"></span><span className="gc tr"></span><span className="gc bl"></span><span className="gc br"></span>
              <span className="vf-hint">镜头对准电视墙 · 缓慢移动</span>
            </div>
          )}

          {phase === 'scan' && (
            <React.Fragment>
              <span className="vf-scan room-scanline"></span>
              <span className="vf-grid room-grid"></span>
              <div className="scan-plane"><span></span><span></span><span></span><span></span></div>
              <div className="scan-readout">
                <span><Icon name="ruler" size={12} />电视墙净宽 <b>{ROOM_WALL_CM} cm</b></span>
                <span><Icon name="crosshair" size={12} />观看距离 <b>{(ROOM_DIST_CM / 100).toFixed(1)} m</b></span>
              </div>
            </React.Fragment>
          )}

          {phase === 'gen' && (
            <React.Fragment>
              <span className="vf-flash"></span>
              <span className="vf-scan"></span>
              <span className="vf-bloom" style={{ left: tvr.cx + '%', top: tvr.cy + '%' }}></span>
              <span className="vf-grid"></span>
            </React.Fragment>
          )}

          {(arShown || (phase === 'result' && standSelection && !selectedRoomSrc)) && (
            <React.Fragment>
              {arShown && <div className="room-ar-meta"><span>墙宽 {ROOM_WALL_CM}cm</span><span>视距 {(dist / 100).toFixed(1)}m</span></div>}
              <div className={cx('ar-tv', mount, phase === 'result' && 'result-preview')} style={{ left: fp.left + '%', top: fp.top + '%', width: fp.width + '%', height: fp.height + '%', ...crop }}>
                <span className="ar-glint"></span>
                {arShown && <React.Fragment><span className="ar-handle tl"></span><span className="ar-handle tr"></span><span className="ar-handle bl"></span><span className="ar-handle br"></span></React.Fragment>}
                {mount === 'stand' && <span className="ar-stand"></span>}
                {arShown && <span className="fp-lbl">{size}″ · {wCm}×{hCm}cm</span>}
              </div>
              {arShown && <div className="ar-anchor"><span></span><b>已吸附电视墙</b></div>}
            </React.Fragment>
          )}

          {phase === 'result' && (
            <React.Fragment>
              {/* AI 结果仍保留实测尺寸框，确保精细效果不丢尺度可信度 */}
              {!selectedRoomSrc && <div className={cx('size-fp', size === REF && 'ref')} style={{ left: fp.left + '%', top: fp.top + '%', width: fp.width + '%', height: fp.height + '%' }}>
                <span className="fp-c tl"></span><span className="fp-c tr"></span><span className="fp-c bl"></span><span className="fp-c br"></span>
                <span className="fp-lbl">{size}″ · {wCm}×{hCm}cm</span>
              </div>}
              <div className="fit-badge">
                <Icon name="crosshair" size={13} />
                <span><b>{size}″</b> ≈ 视角 {angle}°</span>
                <span className={cx('fit-tag', fit.cls)}>{fit.t}</span>
              </div>
            </React.Fragment>
          )}

          {standSelection && (arShown || (phase === 'result' && !selectedRoomSrc)) && mount === 'stand' && (
            <div
              className={cx('room-selected-mount', standSelection.style.id, phase === 'result' && 'result')}
              style={{
                left: tvr.cx + '%',
                top: (fp.top + fp.height - 1) + '%',
                width: Math.max(11, fp.width * .52) + '%',
                height: Math.max(11, fp.height * .75) + '%',
                '--mount-color': standSelection.color.hex,
              }}
              aria-hidden="true"
            ><i></i><i></i><i></i></div>
          )}
          {standSelection && (arShown || phase === 'result') && (
            <div className="room-mount-label"><Icon name="sparkle" size={11} />{standSelection.style.name} · {standSelection.color.label}</div>
          )}

          <div className={cx('gen-pill', phase === 'gen' && 'on')}>
            <span className="gp-spin"><Icon name="sparkle" size={14} /></span>
            <span>正在精细合成 <b>{model.short}</b></span>
          </div>
        </div>
      </div>

      {/* 扫描条：AR 量房成为主流程，不再是旁路入口 */}
      <div className={cx('sheet cap-sheet room-start-sheet', phase !== 'capture' && phase !== 'scan' && 'gone')}>
        {phase === 'capture' ? (
          <React.Fragment>
            <div className="room-start-copy"><b>先量一下，后面看到的大小才是真的</b></div>
            <div className="cap-row">
              <button className="cap-side"><Icon name="image" size={20} /><small>用照片</small></button>
              <button className="shutter room-shutter" onClick={startScan} aria-label="开始扫描房间"><span><Icon name="scan" size={24} /></span></button>
              <button className="cap-side"><Icon name="database" size={20} /><small>我的房间</small></button>
            </div>
          </React.Fragment>
        ) : (
          <div className="room-scanning">
            <div className="room-scan-title"><span className="gp-spin"><Icon name="scan" size={15} /></span><b>正在建立客厅空间</b><em>ARKit</em></div>
            <div className="room-scan-progress"><span></span></div>
            <div className="room-scan-stats"><span>墙面平面 <b>已识别</b></span><span>空间尺度 <b>校准中</b></span></div>
          </div>
        )}
      </div>

      {/* 实时摆放：便宜环，尺寸/机型/安装方式都即时变化 */}
      <div className={cx('sheet room-place-sheet', phase === 'place' && 'on')}>
        <div className="sheet-grip"></div>
        <div className="room-measures">
          <span><small>电视墙净宽</small><b>{ROOM_WALL_CM} cm</b></span>
          <span><small>观看距离</small><b>{(dist / 100).toFixed(1)} m</b></span>
          <span className={fit.cls}><small>当前适配</small><b>{fit.t}</b></span>
        </div>
        {initialTv ? (
          <div className="room-locked-selection">
            <span><Icon name="monitor" size={13} /><small>电视已锁定</small><b>{model.short}</b></span>
            <span><Icon name={mount === 'wall' ? 'cube' : 'layers'} size={13} /><small>支架已锁定</small><b>{mountName}</b></span>
          </div>
        ) : <React.Fragment>
          <div className="mseg big">
            {[['wall', '挂墙', 'monitor'], ['stand', '底座', 'layers']].map(([kk, l, ic]) => (
              <button key={kk} className={mount === kk ? 'on' : ''} onClick={() => setMount(kk)}><Icon name={ic} size={15} />{l}</button>
            ))}
          </div>
          <div className="mchips">
            {roomModels.map((m, i) => (
              <button key={m.id} className={cx('mchip', i === midx && 'on')} onClick={() => setMidx(i)}>
                <b>{m.short}</b><small>{m.panel}</small>
              </button>
            ))}
          </div>
        </React.Fragment>}
        <div className="fld">
          <div className="fld-top"><span className="fld-l">目标尺寸</span><span className="fld-v">{size}″ · {wCm}×{hCm}cm</span></div>
          {initialTv
            ? <div className="room-fixed-size"><Icon name="check" size={11} />已按当前候选锁定 {size}″</div>
            : <div className="size-options">{sizeChoices.map((s) => <button key={s} className={size === s ? 'on' : ''} onClick={() => setSize(s)}>{s}″</button>)}</div>}
        </div>
        <div className="dist-row">
          <span className="fld-l">沙发距离</span>
          <div className="dist-seg">
            {DISTS.map((d) => <button key={d} className={dist === d ? 'on' : ''} onClick={() => setDist(d)}>{(d / 100).toFixed(1)}m</button>)}
          </div>
          <span className="dist-fit">视角 {angle}° <em className={fit.cls}>{fit.t}</em></span>
        </div>
        <div className="ret-line">
          <span className="ret-k"><Icon name="check" size={12} /> 回传 Agent</span>
          <div className="ret-chips">{chips.map((c) => <span key={c} className="ret-chip">{c}</span>)}</div>
        </div>
        <div className="m-actions">
          <Btn v="glass" icon="refresh" onClick={reset}>重新量</Btn>
          <Btn v="pri" block icon="sparkle" onClick={makeAI}>生成精细效果</Btn>
        </div>
      </div>

      {/* AI 精细结果：昂贵环只在用户确认 AR 摆放后触发 */}
      <div className={cx('sheet room-result-sheet', phase === 'result' && 'on')}>
        <div className="sheet-grip"></div>
        <div className="room-result-hero">
          <span><Icon name="check" size={18} /></span>
          <div><b>{resultTitle}</b><small>{model.name} · {standSelection ? standSelection.style.name : mount === 'wall' ? '挂墙' : '底座摆放'}方案</small></div>
        </div>
        <div className="room-result-grid">
          <span><small>墙面余量</small><b className={clearance < 16 ? 'warn' : ''}>{Math.max(0, clearance)} cm</b></span>
          <span><small>观看视角</small><b>{angle}°</b></span>
          <span><small>尺度来源</small><b className="ok">AR 实测</b></span>
        </div>
        {standSelection && <div className="room-accessory-summary">
          <img src={standSelection.style.image} alt={standSelection.style.name + '支架参考图'} />
          <div><small>本次合成支架</small><b>{standSelection.style.name} · {standSelection.color.label}</b><span>{standSelection.lift} · {standSelection.power}</span></div>
          <i style={{ background: standSelection.color.hex }}></i>
        </div>}
        <div className="ret-line">
          <span className="ret-k"><Icon name="check" size={12} /> 回传 Agent</span>
          <div className="ret-chips">{chips.map((c) => <span key={c} className="ret-chip">{c}</span>)}</div>
        </div>
        <div className="m-actions">
          <Btn v="glass" icon="ruler" onClick={() => setPhase('place')}>调整摆放</Btn>
          <Btn v="pri" block icon="check" onClick={() => onComplete && onComplete(payload)}>{onComplete ? '保存并回到对话' : '保存这套方案'}</Btn>
        </div>
      </div>
    </div>
  );
}

/* 旧名称保留给历史深链/评审页，产品入口统一使用 RoomApp。 */
const ComposeApp = RoomApp;

/* ============ 采光 · 测光（前台工具 #3） ============
   两轴分离：照度轴 = 锁曝光 EV 反推 lux（快测=估算档，补齐三时段=可信档）；
   反光轴 = AI 识别光源位置（对屏/侧向/顶部）。两轴合成抗反与亮度建议。 */
const LMIN = 30, LMAX = 3000;
const logT = (v) => Math.min(1, Math.max(0, (Math.log(v) - Math.log(LMIN)) / (Math.log(LMAX) - Math.log(LMIN))));
const lux = (ev) => Math.round(2.5 * Math.pow(2, ev)); // ISO 2720：E ≈ 2.5·2^EV（ISO100）
const SLOTS = [
  { k: 'day', l: '白天', ic: 'sun', ev: 7.0 },
  { k: 'dusk', l: '傍晚', ic: 'sunset', ev: 4.5 },
  { k: 'night', l: '夜晚', ic: 'moon', ev: 3.8 },
];
/* agent 识别出的反光源（box 为照片内百分比，按真实样片标注） */
const LSRC = [
  { id: 'win2', ph: 2, n: '对屏侧窗', risk: 'hi', box: { x: 66, y: 10, w: 27, h: 52 } },
  { id: 'lamp2', ph: 2, n: '落地灯', risk: 'mid', box: { x: 1, y: 25, w: 11.5, h: 38 } },
  { id: 'spot2', ph: 2, n: '筒灯×2', risk: 'mid', box: { x: 12.5, y: 1.5, w: 11.5, h: 8.5 } },
  { id: 'win1', ph: 1, n: '侧窗', risk: 'mid', box: { x: 0.6, y: 8, w: 20, h: 50 } },
  { id: 'spot1', ph: 1, n: '筒灯×3', risk: 'mid', box: { x: 43, y: 1.5, w: 21, h: 11.5 } },
  { id: 'cove1', ph: 1, n: '灯带', risk: 'lo', box: { x: 26, y: 6.5, w: 71, h: 6 } },
];
const RISKL = { hi: '高', mid: '中', lo: '低' };

function ABox({ s, i }) {
  const b = s.box;
  return (
    <div className={cx('abox', s.risk)}
      style={{ left: b.x + '%', top: b.y + '%', width: b.w + '%', height: b.h + '%', animationDelay: (0.08 + i * 0.12) + 's' }}>
      <span className="abox-l">{s.n} · {RISKL[s.risk]}</span>
    </div>
  );
}

function LightApp({ onBack }) {
  const LPS = ['turn', 'cap2', 'ana', 'result'];
  const lp0 = LPS.includes(q('lp')) ? q('lp') : 'cap1';
  const [phase, setPhase] = useState(lp0); // cap1 | m1 | turn | cap2 | m2 | ana | result
  const [ev, setEv] = useState(null);
  const [pv, setPv] = useState(q('pv') === '1' ? 1 : 2);       // 结果页当前照片
  const [busySlot, setBusySlot] = useState(null);
  const [meas, setMeas] = useState(() => {
    const m = { day: null, dusk: null, night: null };
    if (lp0 !== 'cap1') m.day = lux(SLOTS[0].ev);
    if (q('slots') === '3') SLOTS.forEach((s) => { m[s.k] = lux(s.ev); });
    return m;
  });
  const timers = useRef([]);
  const T2 = (fn, ms) => timers.current.push(setTimeout(fn, REDUCE ? Math.min(ms, 240) : ms));
  useEffect(() => () => timers.current.forEach(clearTimeout), []);

  // 锁曝光测光：EV 读数 ease-out 逼近目标，落点回调（clearTimeout 亦可清 interval）
  const runMeter = (target, done) => {
    let i = 0; const N = REDUCE ? 4 : 18;
    setEv(Math.round((target - 3.6) * 10) / 10);
    const id = setInterval(() => {
      i++; const k = 1 - i / N;
      setEv(Math.round((target - 3.6 * k * k) * 10) / 10);
      if (i >= N) { clearInterval(id); done(); }
    }, 46);
    timers.current.push(id);
  };
  const shoot1 = () => {
    if (phase !== 'cap1') return; setPhase('m1');
    runMeter(SLOTS[0].ev, () => { setMeas((m) => ({ ...m, day: lux(SLOTS[0].ev) })); T2(() => { setEv(null); setPhase('turn'); }, 460); });
  };
  const shoot2 = () => {
    if (phase !== 'cap2') return; setPhase('m2');
    runMeter(7.4, () => T2(() => {
      setEv(null); setPhase('ana');
      T2(() => setPhase('result'), REDUCE ? 320 : 1450);
    }, 400));
  };
  const reset = () => { timers.current.forEach(clearTimeout); timers.current = []; setMeas({ day: null, dusk: null, night: null }); setEv(null); setPv(2); setBusySlot(null); setPhase('cap1'); };
  const fillSlot = (k) => {
    if (meas[k] != null || busySlot) return; setBusySlot(k);
    T2(() => { setMeas((m) => ({ ...m, [k]: lux(SLOTS.find((s) => s.k === k).ev) })); setBusySlot(null); }, 900);
  };

  const filled = SLOTS.filter((s) => meas[s.k] != null);
  const cred = filled.length === 3;                              // 置信阶梯：3 时段 → 可信档
  const dayLux = meas.day ?? lux(SLOTS[0].ev);
  const band = [Math.round(dayLux / 1.414), Math.round(dayLux * 1.414)]; // ±半档 EV
  const vmin = Math.min(...filled.map((s) => meas[s.k])), vmax = Math.max(...filled.map((s) => meas[s.k]));

  // 二维结论：反光轴（光源位置）× 照度轴（白天上限）
  const hiG = LSRC.some((s) => s.risk === 'hi'), midG = LSRC.some((s) => s.risk === 'mid');
  const arV = hiG ? '必要' : midG ? '建议' : '非必需';
  const brV = dayLux > 200 ? '峰值 ≥1000 nits' : dayLux > 100 ? '峰值 ≥600 nits' : '无高亮需求';
  // 回传 Agent 的结构化摘要（工具核心契约：data.jsx TOOLS_FRONT.ret 承诺的返回项）
  const chips = [cred ? `全天 ${vmin}–${vmax}lx·可信档` : `白天 ${dayLux}lx·估算档`, '对屏侧窗·高', `抗反·${arV}`, dayLux > 200 ? '≥1000nits' : brV];
  const capturing = ['cap1', 'm1', 'turn', 'cap2', 'm2'].includes(phase);
  const img2on = phase === 'result' ? pv === 2 : ['cap2', 'm2', 'ana'].includes(phase);
  const bg = img2on ? (IMG.rev || IMG.orig) : IMG.orig;
  const wrapCls = phase === 'result' ? 'p-lresult' : phase === 'ana' ? 'p-gen' : 'p-capture';
  const stepT = { cap1: '测光①', m1: '测光①', turn: '转身', cap2: '测光②', m2: '测光②', ana: '识别中', result: '结果' }[phase];

  return (
    <div className="m-app">
      <div className="vf-bg" style={{ backgroundImage: `url(${bg})` }}></div>

      <div className="m-head">
        {onBack && <button className="mh-back" onClick={onBack} aria-label="返回主页"><Icon name="arrowLeft" size={16} /></button>}
        <span className="mh-logo"><Icon name="sun" size={16} /></span>
        <div className="mh-txt">
          <div className="mh-t">采光 · 环境光检测</div>
        </div>
        <span className={cx('mh-step', ['m1', 'm2', 'ana'].includes(phase) && 'busy')}>{stepT}</span>
      </div>

      <div className={cx('vf-wrap', wrapCls)}>
        <div className="vf">
          <img className="vf-img base" src={IMG.orig} alt="朝电视墙" draggable="false" />
          <div className="vf-l2" style={{ opacity: img2on ? 1 : 0 }}>
            {IMG.rev
              ? <img className="vf-img" src={IMG.rev} alt="朝沙发方向" draggable="false" />
              : <div className="vf-ph"><b>「朝沙发方向」样片待生成</b><small>prompts/01-scene-reverse-sofa.md<br />生成后放 aiimage/朝沙发.png，跑 python _mkimg.py</small></div>}
          </div>

          {(phase === 'cap1' || phase === 'cap2') && (
            <div className="vf-guide">
              <span className="gc tl"></span><span className="gc tr"></span><span className="gc bl"></span><span className="gc br"></span>
              <span className="vf-hint">{phase === 'cap1' ? '① 朝电视墙 · 含吸顶灯' : '② 对屏方向 · 含窗与灯'}</span>
            </div>
          )}

          {(phase === 'm1' || phase === 'm2') && (
            <div className="meter">
              <span className="meter-ring"></span>
              <span className="ev-chip">AE-L · EV <b>{(ev ?? 0).toFixed(1)}</b> ≈ {lux(ev ?? 0)} lx</span>
            </div>
          )}

          {phase === 'turn' && (
            <div className="turn-card" onClick={() => setPhase('cap2')}>
              <span className="turn-ic"><Icon name="turn" size={30} /></span>
              <div className="turn-t">转身 180°</div>
              <div className="turn-s">屏幕反射的是它<b>对面</b>的东西——窗、落地灯都在你身后这一侧，拍下来交给 AI 认</div>
              <Btn v="pri" icon="camera" onClick={(e) => { e.stopPropagation(); setPhase('cap2'); }}>已转身 · 拍对屏方向</Btn>
            </div>
          )}

          {phase === 'ana' && (
            <React.Fragment>
              <span className="vf-flash"></span>
              <span className="vf-scan"></span>
              <span className="vf-grid"></span>
            </React.Fragment>
          )}

          {phase === 'result' && LSRC.filter((s) => s.ph === pv).map((s, i) => <ABox key={s.id} s={s} i={i} />)}

          {phase === 'result' && (
            <div className="ppager">
              {[[1, '① 电视墙'], [2, '② 对屏']].map(([n, l]) => (
                <button key={n} className={pv === n ? 'on' : ''} onClick={() => setPv(n)}>{l}</button>
              ))}
            </div>
          )}

          <div className={cx('gen-pill', phase === 'ana' && 'on')}>
            <span className="gp-spin"><Icon name="sparkle" size={14} /></span>
            <span>AI 正在识别<b>反光源</b>与位置…</span>
          </div>
        </div>
      </div>

      {/* 拍摄条 */}
      <div className={cx('sheet cap-sheet', !capturing && 'gone')}>
        <div className="cap-row">
          <button className="cap-side"><Icon name="image" size={20} /><small>相册</small></button>
          <button className="shutter" onClick={phase === 'cap2' ? shoot2 : shoot1} disabled={phase !== 'cap1' && phase !== 'cap2'} aria-label="测光拍摄"><span></span></button>
          <button className="cap-side"><Icon name="lock" size={20} /><small>锁曝光</small></button>
        </div>
      </div>

      {/* 结果面板 */}
      <div className={cx('sheet lres-sheet', phase === 'result' && 'on')}>
        <div className="sheet-grip"></div>

        <div className="lux-top">
          <div>
            <div className="lux-v">{cred ? `${vmin}–${vmax} lx` : `${dayLux} lx`}</div>
            <div className="lux-sub">{cred ? '全天照度跨度 · 三时段实测' : `白天快测 · 约 ${band[0]}–${band[1]} lx（±半档 EV）`}</div>
          </div>
          <span className={cx('conf-tag', cred && 'ok')}>{cred ? '可信档 · 3/3' : `估算档 · ${filled.length}/3 时段`}</span>
        </div>

        <div className="lgauge">
          <div className="lg-track"></div>
          {!cred && <div className="lg-band" style={{ left: logT(band[0]) * 100 + '%', width: (logT(band[1]) - logT(band[0])) * 100 + '%' }}></div>}
          {[50, 200].map((v) => <span key={v} className="lg-tick" style={{ left: logT(v) * 100 + '%' }}></span>)}
          {[['暗', 40], ['中', 100], ['亮', 700]].map(([l, v]) => <span key={l} className="lg-lbl" style={{ left: logT(v) * 100 + '%' }}>{l} {l === '暗' ? '<50' : l === '中' ? '50–200' : '>200'}</span>)}
          {filled.map((s) => <span key={s.k} className="lg-dot" style={{ left: logT(meas[s.k]) * 100 + '%' }} title={`${s.l} ${meas[s.k]}lx`}></span>)}
        </div>

        <div className="slot-row">
          {SLOTS.map((s) => (
            <button key={s.k} className={cx('slot', meas[s.k] != null && 'done', busySlot === s.k && 'busy')} onClick={() => fillSlot(s.k)}>
              <b><Icon name={s.ic} size={13} />{s.l}</b>
              <small>{busySlot === s.k ? '测光中…' : meas[s.k] != null ? `${meas[s.k]} lx` : '＋补测'}</small>
            </button>
          ))}
        </div>

        <div className="lsec"><Icon name="eye" size={13} />反光源 · AI 识别 {LSRC.length} 处</div>
        <div className="gl-leg">
          {LSRC.map((s) => (
            <span key={s.id} className="gl-it">
              <span className={'dot ' + s.risk}></span><b>{s.n}</b><small>照片{s.ph === 1 ? '①' : '②'}·{RISKL[s.risk]}</small>
            </span>
          ))}
        </div>

        <div className="lrec">
          <div className={cx('lrec-card', hiG && 'hot')}><small>抗反涂层</small><b>{arV}</b></div>
          <div className="lrec-card acc"><small>亮度需求</small><b>{brV}</b></div>
        </div>
        <div className="lrec-why">白天 &gt;200 lx，且对屏方向有大面积侧窗直射入屏——光面屏会出现清晰镜面反射{cred ? `；夜晚 ${meas.night} lx 属暗房，OLED 黑位受益` : ''}</div>
        <div className="gl-row">
          <span className="mfit ok">海信 E8S · 高峰值亮度</span>
          <span className="mfit ok">LG G5 · 抗反膜</span>
          <span className="mfit warn">Sony B8 II · 光面屏慎选</span>
        </div>
        <div className="lnote">照度＝锁曝光 EV 反推（估算，非照度计）；反光＝光源位置判定，非实测眩光</div>
        <div className="ret-line">
          <span className="ret-k"><Icon name="check" size={12} /> 回传 Agent</span>
          <div className="ret-chips">{chips.map((c) => <span key={c} className="ret-chip">{c}</span>)}</div>
        </div>
        <div className="m-actions">
          <Btn v="glass" icon="refresh" onClick={reset}>重测</Btn>
          <Btn v="pri" block icon="check">满意 · 存入清单</Btn>
        </div>
      </div>
    </div>
  );
}

/* ============ 一键验机：五步陪验 + 双屏测试 + 证据化结论 ============ */
const INSPECT_STEPS = [
  { n: '型号与包装', icon: 'scan' },
  { n: '外观取证', icon: 'camera' },
  { n: '屏幕检测', icon: 'grid' },
  { n: '开机与遥控', icon: 'bolt' },
  { n: '签收结论', icon: 'check' },
];
const SCREEN_TESTS = [
  { n: '白', c: '#f7f9ff' }, { n: '黑', c: '#08090c' }, { n: '红', c: '#e51d2a' },
  { n: '绿', c: '#31bd3e' }, { n: '蓝', c: '#1458e8' }, { n: '灰', c: '#a5a9b0' },
];
const CAST_PROTOCOLS = [
  { id: 'airplay', n: 'AirPlay', icon: 'broadcast' },
  { id: 'dlna', n: 'DLNA', icon: 'wifi' },
];
const APPEARANCE_EVIDENCE = [
  { n: '正面整机', image: 'inspect-screen.webp', alt: '电视正面整机与边框取证' },
  { n: '背板与壁挂孔', image: 'inspect-back-panel.webp', alt: '电视背板与壁挂孔取证' },
  { n: '背板接口', image: 'inspect-ports.webp', alt: '电视 HDMI USB 网口等背板接口取证' },
  { n: '附件清点', image: 'inspect-accessories.webp', alt: '遥控器底座电源线与配件清点' },
];
const BOOT_CHECKS = [
  { n: '冷启动正常', icon: 'bolt' },
  { n: '遥控器响应', icon: 'turn' },
  { n: '扬声器有声', icon: 'activity' },
  { n: 'HDMI 1 可用', icon: 'plug' },
];

function InspectApp({ onBack }) {
  const shortcut = q('ip');
  const initialStep = shortcut === 'result' ? 4 : shortcut === 'boot' ? 3 : shortcut === 'screen' ? 2 : shortcut === 'appearance' ? 1 : 0;
  const [step, setStep] = useState(initialStep);
  const [labelScanned, setLabelScanned] = useState(initialStep > 0);
  const [appearanceShots, setAppearanceShots] = useState(initialStep > 1 ? APPEARANCE_EVIDENCE.length : 0);
  const [screenConnected, setScreenConnected] = useState(initialStep > 2);
  const [castProtocol, setCastProtocol] = useState('airplay');
  const [castConnecting, setCastConnecting] = useState(false);
  const [screenTest, setScreenTest] = useState(initialStep > 2 ? SCREEN_TESTS.length : 0);
  const [bootChecks, setBootChecks] = useState(initialStep > 3 ? BOOT_CHECKS.map(() => true) : BOOT_CHECKS.map(() => false));
  const [issues, setIssues] = useState([]); // 多处异常并存：外观磕碰与开机故障可同时写入报告
  const [reportReached, setReportReached] = useState(initialStep === 4); // 报告一旦生成就保持可回看，不随离开步骤 5 失效
  const [busy, setBusy] = useState(false);
  const [saved, setSaved] = useState(false);
  const [cloudBackup, setCloudBackup] = useState(true);
  const timer = useRef(null);
  useEffect(() => () => clearTimeout(timer.current), []);
  useEffect(() => { if (step === 4) setReportReached(true); }, [step]);

  const stepDone = [labelScanned, appearanceShots >= APPEARANCE_EVIDENCE.length, screenTest >= SCREEN_TESTS.length, bootChecks.every(Boolean), reportReached || step === 4];
  const completed = stepDone.filter(Boolean).length;
  const remaining = ['约 5 分钟', '约 4 分钟', '约 3 分钟', '约 1 分钟', '已完成'][step];

  const scanLabel = () => {
    setBusy(true);
    clearTimeout(timer.current);
    timer.current = setTimeout(() => { setLabelScanned(true); setBusy(false); }, REDUCE ? 120 : 650);
  };
  const nextAppearance = () => setAppearanceShots((n) => Math.min(APPEARANCE_EVIDENCE.length, n + 1));
  const toggleBoot = (i) => setBootChecks((old) => old.map((v, j) => j === i ? !v : v));
  const recordIssue = (text) => setIssues((old) => old.includes(text) ? old.filter((t) => t !== text) : [...old, text]);
  const addIssue = (text) => setIssues((old) => old.includes(text) ? old : [...old, text]);
  const connectScreen = () => {
    setCastConnecting(true);
    clearTimeout(timer.current);
    timer.current = setTimeout(() => { setScreenConnected(true); setCastConnecting(false); }, REDUCE ? 120 : 720);
  };
  const reset = () => {
    clearTimeout(timer.current);
    setStep(0); setLabelScanned(false); setAppearanceShots(0); setScreenConnected(false); setCastProtocol('airplay'); setCastConnecting(false);
    setScreenTest(0); setBootChecks(BOOT_CHECKS.map(() => false)); setIssues([]); setReportReached(false); setBusy(false); setSaved(false); setCloudBackup(true);
  };
  const chooseBackup = (value) => { setCloudBackup(value); setSaved(false); };

  const PackageStep = () => (
    <div className="inspect-step-content">
      <div className="inspect-media package-media">
        <img src="inspect-package.webp" alt="海信电视包装标签" draggable="false" />
        <span className="inspect-voice"><Icon name="activity" size={15} />{busy ? '正在识别标签' : '请对准包装标签'}</span>
        <span className="scan-corner tl"></span><span className="scan-corner tr"></span>
        <span className="scan-corner bl"></span><span className="scan-corner br"></span>
        {busy && <span className="inspect-scanline"></span>}
      </div>
      {labelScanned && (
        <div className="inspect-facts" aria-live="polite">
          <span><Icon name="cube" size={15} /><b>海信 85 E8S</b></span>
          <span><Icon name="monitor" size={15} /><b>85 英寸</b></span>
          <span><Icon name="scan" size={15} /><b>H85E8S2407</b></span>
          <span className="ok"><Icon name="check" size={15} /><b>与订单一致</b></span>
        </div>
      )}
      {!labelScanned
        ? <Btn v="pri" size="lg" block icon="camera" onClick={scanLabel} disabled={busy}>{busy ? '识别中…' : '扫描包装标签'}</Btn>
        : <React.Fragment>
          <Btn v="pri" size="lg" block icon="check" onClick={() => setStep(1)}>确认一致，下一步</Btn>
          <button className="inspect-quiet" onClick={() => { addIssue('型号与订单不一致'); setStep(4); }}>型号不一致</button>
        </React.Fragment>}
    </div>
  );

  const AppearanceStep = () => {
    const current = APPEARANCE_EVIDENCE[Math.min(appearanceShots, APPEARANCE_EVIDENCE.length - 1)];
    return (
    <div className="inspect-step-content">
      <div className="inspect-media appearance-media">
        <img src={current.image} alt={current.alt} draggable="false" />
        <span className="inspect-voice"><Icon name="camera" size={15} />{current.n}入镜</span>
        <span className="scan-corner tl"></span><span className="scan-corner tr"></span>
        <span className="scan-corner bl"></span><span className="scan-corner br"></span>
      </div>
      <div className="capture-list">
        {APPEARANCE_EVIDENCE.map((item, i) => (
          <span key={item.n} className={i < appearanceShots ? 'done' : i === appearanceShots ? 'on' : ''}>
            <img className="capture-thumb" src={item.image} alt="" aria-hidden="true" loading="lazy" decoding="async" />
            <Icon name={i < appearanceShots ? 'check' : 'camera'} size={15} /><b>{item.n}</b><em>{i < appearanceShots ? '已保存' : i === appearanceShots ? '待拍摄' : '稍后'}</em>
          </span>
        ))}
      </div>
      {appearanceShots < APPEARANCE_EVIDENCE.length
        ? <Btn v="pri" size="lg" block icon="camera" onClick={nextAppearance}>拍摄{APPEARANCE_EVIDENCE[appearanceShots].n}</Btn>
        : <Btn v="pri" size="lg" block icon="check" onClick={() => setStep(2)}>完成外观取证，下一步</Btn>}
      <button className={cx('inspect-quiet', issues.includes('外观存在磕碰或破损') && 'warn')} onClick={() => recordIssue('外观存在磕碰或破损')}>{issues.includes('外观存在磕碰或破损') ? '已记录外观异常' : '发现磕碰或破损'}</button>
    </div>
    );
  };

  const ScreenStep = () => {
    const current = SCREEN_TESTS[Math.min(screenTest, SCREEN_TESTS.length - 1)];
    const cast = CAST_PROTOCOLS.find((item) => item.id === castProtocol);
    return (
      <div className="inspect-step-content">
        {!screenConnected ? (
          <React.Fragment>
            <div className="cast-setup-card">
              <div className="cast-open-step"><span>1</span><div><b>在电视打开无线投屏</b><small>停留在投屏接收界面</small></div><Icon name="tv" size={20} /></div>
              <div className="cast-route" aria-label={`本机网页通过 ${cast.n} 连接客厅电视`}>
                <span className="cast-endpoint"><Icon name="phone" size={23} /><em>本机网页</em></span>
                <span className="cast-route-link"><b>{cast.n}</b><Icon name="chevRight" size={14} /></span>
                <span className="cast-endpoint"><Icon name="tv" size={25} /><em>客厅电视</em></span>
              </div>
              <div className="cast-protocols" role="group" aria-label="选择投屏协议">
                {CAST_PROTOCOLS.map((item) => <button key={item.id} className={item.id === castProtocol ? 'on' : ''} aria-pressed={item.id === castProtocol} disabled={castConnecting} onClick={() => setCastProtocol(item.id)}><Icon name={item.icon} size={16} />{item.n}</button>)}
              </div>
            </div>
            <div className="cast-device-card"><span><Icon name="tv" size={18} /></span><div><b>客厅电视 · 海信 85 E8S</b><small>同一 Wi-Fi</small></div><em><i></i>已发现</em></div>
            <Btn v="pri" size="lg" block icon="broadcast" onClick={connectScreen} disabled={castConnecting}>{castConnecting ? `正在通过 ${cast.n} 连接…` : '投到客厅电视'}</Btn>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div className="screen-connect-row"><b>客厅电视 · 海信 85 E8S</b><span className="ok"><i></i>{cast.n} 已连接</span></div>
            <div className="cast-live-strip"><span><Icon name="broadcast" size={17} /></span><div><b>网页信号发生器</b><small>{screenTest < SCREEN_TESTS.length ? `${current.n}色测试图正在电视全屏显示` : '六色测试信号已完成'}</small></div><em>{screenTest < SCREEN_TESTS.length ? '投放中' : '已完成'}</em></div>
            <div className="inspect-media screen-media">
              <img src="inspect-screen.webp" alt="电视纯色屏幕检测" draggable="false" />
              {screenTest < SCREEN_TESTS.length && <span className="screen-test-color" style={{ background: current.c }}></span>}
              <span className="scan-corner tl"></span><span className="scan-corner tr"></span>
              <span className="scan-corner bl"></span><span className="scan-corner br"></span>
              <span className="screen-score"><b>坏点 0</b><b>亮点 0</b></span>
              {screenTest > 0 && <span className="evidence-saved"><Icon name="image" size={13} />已保存原图</span>}
            </div>
            <div className="screen-test-title"><b>{screenTest < SCREEN_TESTS.length ? `${screenTest + 1} / 6 ${current.n}色检测` : '6 / 6 检测完成'}</b><span>逐张留证</span></div>
            <div className="screen-swatches" aria-label="六色屏幕测试进度">
              {SCREEN_TESTS.map((test, i) => <span key={test.n} className={cx(i === screenTest && 'on', i < screenTest && 'done')} style={{ background: test.c }} aria-label={`${test.n}色${i < screenTest ? '已完成' : i === screenTest ? '检测中' : '待检测'}`}></span>)}
            </div>
            {screenTest < SCREEN_TESTS.length
              ? <Btn v="pri" size="lg" block icon="camera" onClick={() => setScreenTest((n) => Math.min(SCREEN_TESTS.length, n + 1))}>拍摄{current.n}色画面</Btn>
              : <Btn v="pri" size="lg" block icon="check" onClick={() => setStep(3)}>完成屏幕检测，下一步</Btn>}
            <button className={cx('inspect-quiet', issues.includes('屏幕显示异常') && 'warn')} onClick={() => recordIssue('屏幕显示异常')}>{issues.includes('屏幕显示异常') ? '已记录屏幕异常' : '我看到异常'}</button>
          </React.Fragment>
        )}
      </div>
    );
  };

  const BootStep = () => (
    <div className="inspect-step-content">
      <div className="inspect-media boot-media">
        <img src="inspect-remote-boot.webp" alt="电视开机并使用遥控器测试音量与响应" draggable="false" />
        <span className="inspect-voice"><Icon name="turn" size={15} />按遥控，确认电视响应</span>
        <span className="evidence-saved"><Icon name="camera" size={13} />开机留证</span>
      </div>
      <div className="manual-note"><Icon name="activity" size={16} /><span><b>请逐项实际确认</b><small>只有你点过的项目才会写入报告</small></span></div>
      <div className="boot-checks">
        {BOOT_CHECKS.map((item, i) => (
          <button key={item.n} className={bootChecks[i] ? 'done' : ''} onClick={() => toggleBoot(i)} aria-pressed={bootChecks[i]}>
            <span><Icon name={bootChecks[i] ? 'check' : item.icon} size={17} /></span><b>{item.n}</b><em>{bootChecks[i] ? '已确认' : '点击确认'}</em>
          </button>
        ))}
      </div>
      {/* 已记录异常也能生成结论（走"建议现场复查"），不逼用户违心把故障项点成正常 */}
      <Btn v="pri" size="lg" block icon="check" disabled={!bootChecks.every(Boolean) && !issues.length} onClick={() => setStep(4)}>生成签收结论</Btn>
      <button className={cx('inspect-quiet', issues.includes('开机或基础功能异常') && 'warn')} onClick={() => recordIssue('开机或基础功能异常')}>{issues.includes('开机或基础功能异常') ? '已记录功能异常' : '发现开机或功能问题'}</button>
    </div>
  );

  const ResultStep = () => {
    const hasIssue = issues.length > 0;
    return (
      <div className="inspect-step-content result-content">
        <div className={cx('inspect-verdict', hasIssue ? 'warn' : 'ok')}>
          <span><Icon name={hasIssue ? 'activity' : 'check'} size={23} /></span>
          <div><small>签收建议</small><b>{hasIssue ? '建议现场复查' : '可以签收'}</b></div>
        </div>
        {hasIssue && <div className="issue-evidence"><Icon name="camera" size={17} /><div><b>{issues.join('；')}</b><small>异常已写入报告，请让送装人员现场确认</small></div></div>}
        <div className="report-list">
          <span><b>型号与包装</b><em>{issues.includes('型号与订单不一致') ? '不一致 · 1 张证据' : '一致 · 1 张证据'}</em></span>
          <span><b>外观取证</b><em>{appearanceShots ? `${appearanceShots} 张原图` : '未执行'}</em></span>
          <span><b>屏幕检测</b><em>{screenTest ? `${screenTest} 组测试` : '未执行'}</em></span>
          <span><b>开机与遥控</b><em>{bootChecks.some(Boolean) ? `1 张证据 · ${bootChecks.filter(Boolean).length} 项确认` : '未执行'}</em></span>
        </div>
        <div className="report-meta"><span>海信 85 E8S</span><span>H85E8S2407</span></div>
        <div className="inspect-backup-choice">
          <div className="ibc-title"><span><Icon name="database" size={17} /></span><b>验机数据是否上传先看数据库进行云备份？</b></div>
          <div className="ibc-options" role="group" aria-label="选择验机数据保存位置">
            <button className={cloudBackup ? 'on' : ''} aria-pressed={cloudBackup} onClick={() => chooseBackup(true)}><Icon name="upload" size={15} />上传云备份</button>
            <button className={!cloudBackup ? 'on' : ''} aria-pressed={!cloudBackup} onClick={() => chooseBackup(false)}><Icon name="download" size={15} />仅存本机</button>
          </div>
        </div>
        <Btn v="pri" size="lg" block icon={saved ? 'check' : cloudBackup ? 'upload' : 'download'} onClick={() => setSaved(true)}>{saved ? (cloudBackup ? '已上传并保存' : '已保存到本机') : (cloudBackup ? '上传并保存报告' : '保存报告到本机')}</Btn>
        {saved && cloudBackup && (
          <div className="inspect-encryption-proof" role="status" aria-live="polite">
            <span className="iep-lock"><Icon name="lock" size={21} /></span>
            <div><b>端到端非对称加密已启用</b><p>先看将对序列号等敏感信息采用端到端非对称加密，哪怕是我们也无法获取具体内容</p></div>
          </div>
        )}
        <button className="inspect-quiet" onClick={reset}>重新验机</button>
      </div>
    );
  };

  /* 步骤视图是组件体内定义的函数：以函数调用渲染（而非 <PackageStep/> 元素），
     否则每次 setState 都生成新组件类型，React 会整棵卸载重建当前步骤（焦点丢失、动画重播、图片重解码） */
  const renderStep = () => step === 0 ? PackageStep() : step === 1 ? AppearanceStep() : step === 2 ? ScreenStep() : step === 3 ? BootStep() : ResultStep();

  return (
    <div className="m-app utility-app inspect-app" data-screen-label={'一键验机-' + INSPECT_STEPS[step].n}>
      <div className="utility-bg inspect-bg"></div>
      <div className="m-head inspect-head">
        {onBack && <button className="mh-back" onClick={onBack} aria-label="返回主页"><Icon name="arrowLeft" size={16} /></button>}
        <span className="mh-logo"><Icon name="scan" size={16} /></span>
        <div className="mh-txt"><div className="mh-t">一键验机</div></div>
        <span className="mh-step">{remaining}</span>
      </div>

      <main className="inspect-flow">
        <div className={cx('inspect-device-strip', labelScanned || step > 0 ? 'verified' : '')}>
          <img src="tool-inspect.webp" alt="海信 85 E8S" />
          <div><b>{labelScanned || step > 0 ? '海信 85 E8S' : '待核对电视型号'}</b><small>{labelScanned || step > 0 ? '序列号 H85E8S2407' : '先扫描包装标签'}</small></div>
          <span><Icon name={labelScanned || step > 0 ? 'check' : 'scan'} size={18} /></span>
        </div>
        <div className="inspect-progress-summary">
          <div><b>{completed}</b><span>/ 5 已完成</span></div>
          <div className="inspect-progress-dots" aria-label={`验机完成 ${completed} 项，共 5 项`}>
            {INSPECT_STEPS.map((item, i) => <span key={item.n} className={cx(stepDone[i] && 'done', i === step && step < 4 && 'on')}></span>)}
          </div>
        </div>

        <div className="inspect-journey">
          {INSPECT_STEPS.map((item, i) => {
            const done = stepDone[i];
            const active = i === step;
            const reviewable = done && !active;
            return (
              <section className={cx('inspect-journey-step', done && 'done', active && 'active')} key={item.n}>
                <button className="ij-head" aria-current={active ? 'step' : undefined} onClick={() => reviewable && setStep(i)} disabled={!active && !reviewable}>
                  <span className="ij-index">{done ? <Icon name="check" size={15} /> : i + 1}</span>
                  <b>{item.n}</b>
                  <em>{active ? (i === 4 ? '查看报告' : '进行中') : done ? '已完成' : '待检测'}</em>
                  {reviewable && <Icon name="chevRight" size={14} />}
                </button>
                {active && <div className="ij-body">{renderStep()}</div>}
              </section>
            );
          })}
        </div>
      </main>
    </div>
  );
}

/* ============ 一键最佳设置：机型 + 设备 + 场景 → 设置卡 + 线材购买 ============ */
const SETUP_SOURCES = [
  { id: 'native', n: '内置应用', icon: 'tv', verdict: '不用买线', cable: '电视直接播放', buy: false },
  { id: 'console', n: 'PS5 / Xbox', icon: 'bolt', verdict: '先别买', cable: '优先用主机原装 HDMI 线', buy: false },
  { id: 'box', n: '电视盒子', icon: 'cube', verdict: '先别买', cable: '优先用盒内 HDMI 线', buy: false },
  { id: 'pc', n: '游戏电脑', icon: 'monitor', verdict: '买 1 根', cable: '超高速 HDMI 2.1 · 2米', buy: true },
];
const SETUP_SCENES = {
  movie: { n: '电影', light: '自动', motion: '关闭' },
  game: { n: '游戏', light: '自动 HDR', motion: 'VRR 开' },
  sport: { n: '体育', light: '65', motion: '中' },
  day: { n: '白天', light: '75', motion: '低' },
};
const SETUP_TUNING = {
  e8s: { modes: ['电影', '游戏', '体育', '标准'], warm: '暖 2' },
  g5: { modes: ['FILMMAKER', '游戏优化器', '标准', '影院主页'], warm: '暖 50' },
  sny: { modes: ['专业', '游戏', '标准', '自定义'], warm: '专家 1' },
  tcl: { modes: ['电影', '游戏大师', '体育', '标准'], warm: '暖色' },
};

function SetupApp({ onBack }) {
  const [phase, setPhase] = useState(q('sp') === 'result' ? 'result' : 'pick');
  const [midx, setMidx] = useState(0);
  /* 深链值先校验再入 state：未知值直接回默认，避免选中态失联；hasOwnProperty 挡掉原型链键（?scene=constructor） */
  const [sourceId, setSourceId] = useState(() => { const v = q('src'); return SETUP_SOURCES.some((s) => s.id === v) ? v : 'console'; });
  const [sceneId, setSceneId] = useState(() => { const v = q('scene'); return v && Object.prototype.hasOwnProperty.call(SETUP_SCENES, v) ? v : 'game'; });
  const [added, setAdded] = useState(false);
  const [saved, setSaved] = useState(false);
  const source = SETUP_SOURCES.find((s) => s.id === sourceId) || SETUP_SOURCES[1];
  const scene = SETUP_SCENES[sceneId] || SETUP_SCENES.game;
  const model = MODELS[midx];
  const tuning = SETUP_TUNING[model.id];
  const sceneIndex = ['movie', 'game', 'sport', 'day'].indexOf(sceneId);
  const settings = [
    ['图像模式', tuning.modes[sceneIndex < 0 ? 1 : sceneIndex]],
    ['屏幕亮度', scene.light],
    ['色温', tuning.warm],
    ['动态', scene.motion],
  ];
  const make = () => { setAdded(false); setSaved(false); setPhase('result'); };
  const reset = () => { setAdded(false); setSaved(false); setPhase('pick'); };

  return (
    <div className="m-app utility-app setup-app" data-screen-label={'一键最佳设置-' + phase}>
      <div className={cx('utility-bg', 'setup-bg', 'scene-' + sceneId)}></div>
      <div className="m-head">
        {onBack && <button className="mh-back" onClick={onBack} aria-label="返回主页"><Icon name="arrowLeft" size={16} /></button>}
        <span className="mh-logo"><Icon name="sliders" size={16} /></span>
        <div className="mh-txt"><div className="mh-t">一键最佳设置</div></div>
        <span className="mh-step">{phase === 'pick' ? '选择场景' : '设置卡'}</span>
      </div>

      <div className={cx('utility-stage', 'setup-stage', phase)}>
        <div className="setup-tv">
          <div className="setup-screen"><Icon name={source.icon} size={31} /><b>{scene.n}</b></div>
          <span className="setup-stand"></span>
        </div>
        <div className="setup-model">{model.short}</div>
      </div>

      <div className={cx('sheet', 'utility-sheet', 'setup-sheet', phase)}>
        {phase === 'pick' ? (
          <React.Fragment>
            <div className="pick-group">
              <div className="pick-title">电视型号</div>
              <div className="pick-grid models">
                {MODELS.map((m, i) => <button key={m.id} className={i === midx ? 'on' : ''} onClick={() => setMidx(i)}>{m.short}</button>)}
              </div>
            </div>
            <div className="pick-group">
              <div className="pick-title">连接设备</div>
              <div className="pick-grid sources">
                {SETUP_SOURCES.map((s) => (
                  <button key={s.id} className={s.id === sourceId ? 'on' : ''} onClick={() => setSourceId(s.id)}><Icon name={s.icon} size={16} />{s.n}</button>
                ))}
              </div>
            </div>
            <div className="pick-group">
              <div className="pick-title">主要场景</div>
              <div className="scene-picks">
                {Object.entries(SETUP_SCENES).map(([id, s]) => <button key={id} className={id === sceneId ? 'on' : ''} onClick={() => setSceneId(id)}>{s.n}</button>)}
              </div>
            </div>
            <Btn v="pri" block icon="sparkle" onClick={make}>生成最佳设置</Btn>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div className="utility-verdict"><span><Icon name="check" size={19} /></span><b>{model.short} · {scene.n}</b></div>
            <div className="setting-grid">
              {settings.map(([k, v]) => <span key={k}><em>{k}</em><b>{v}</b></span>)}
            </div>
            <div className={cx('cable-card', source.buy && 'buy')}>
              <span className="cable-icon"><Icon name="plug" size={20} /></span>
              <div><em>线材购买</em><b>{source.verdict}</b><strong>{source.cable}</strong></div>
              {source.buy && <button className={added ? 'added' : ''} onClick={() => setAdded(true)}>{added ? '已加入' : '加入清单'}</button>}
            </div>
            <div className="m-actions">
              <Btn v="glass" icon="refresh" onClick={reset}>重选</Btn>
              <Btn v="pri" block icon="download" onClick={() => setSaved(true)}>{saved ? '设置卡已保存' : '保存设置卡'}</Btn>
            </div>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

/* ============ stage ============ */
const APPMETA = {
  room: {
    t: '在我家试电视 · 移动端',
    p: '一条任务链完成 AR 量房、真实比例 3D 摆放与 AI 精细合成；房间是持久锚点，尺寸和机型可即时替换。',
    foot: '评审深链：?app=room&rp=place 实时摆放 · &rp=result 结果 · &m=stand 底座 · &sz=75 指定尺寸',
  },
  light: {
    t: '采光 · 环境光检测 · 移动端',
    p: '两拍测两轴：朝电视墙锁曝光反推照度（快测＝估算档，补齐傍晚 / 夜晚升可信档）；转身拍对屏方向，AI 识别反光源。合成抗反与亮度建议，回传 Agent。',
    foot: '评审深链：?app=light&lp=result（&slots=3 补满时段 · &pv=1 电视墙标注）',
  },
  inspect: {
    t: '一键验机 · 移动端',
    p: '一次拍摄完成外观、屏幕、接口与开机检查，直接给出是否签收。',
    foot: '评审深链：?app=inspect&ip=result',
  },
  setup: {
    t: '一键最佳设置 · 移动端',
    p: '按机型、连接设备和观看场景生成设置卡，同时判断线材是否需要购买。',
    foot: '评审深链：?app=setup&sp=result&src=pc&scene=game',
  },
};
function Stage() {
  const app0 = ['room', 'light', 'inspect', 'setup'].includes(q('app')) ? q('app') : 'room';
  const [app, setApp] = useState(app0);
  const pick = (a) => {
    if (a === app) return;
    const u = new URL(location); ['app', 'lp', 'slots', 'pv', 's', 'g', 'rp', 'm', 'sz', 'ip', 'sp', 'src', 'scene'].forEach((k) => u.searchParams.delete(k));
    u.searchParams.set('app', a);
    history.replaceState(null, '', u); setApp(a);
  };
  const M = APPMETA[app];
  return (
    <div className="stage">
      {q('from') === 'home' && (
        <a className="stage-back" href="index.html"><Icon name="arrowLeft" size={14} />主页</a>
      )}
      <div className="stage-head">
        <h1>{M.t}</h1>
        <p>{M.p}</p>
        <div className="app-seg" role="tablist">
          <button className={app === 'room' ? 'on' : ''} onClick={() => pick('room')}><Icon name="cube" size={15} />在我家试电视</button>
          <button className={app === 'light' ? 'on' : ''} onClick={() => pick('light')}><Icon name="sun" size={15} />采光 · 环境光检测</button>
          <button className={app === 'inspect' ? 'on' : ''} onClick={() => pick('inspect')}><Icon name="scan" size={15} />一键验机</button>
          <button className={app === 'setup' ? 'on' : ''} onClick={() => pick('setup')}><Icon name="sliders" size={15} />最佳设置</button>
        </div>
        <div className="tag-row">
          <span className="tag ok">Liquid Glass</span>
          <span className="chip">一镜到底动效</span>
          <span className="chip accent">前台交互工具</span>
        </div>
      </div>
      <div className="frames"><Phone key={app}>{app === 'light' ? <LightApp /> : app === 'inspect' ? <InspectApp /> : app === 'setup' ? <SetupApp /> : <RoomApp />}</Phone></div>
      <div className="stage-foot">{M.foot}</div>
    </div>
  );
}

/* 供 电视数据库主页(home.jsx) 壳内嵌入调用；ComposeApp 只保留历史兼容 */
Object.assign(window, { RoomApp, ComposeApp, LightApp, InspectApp, SetupApp });
if (!window.__TV_EMBED) ReactDOM.createRoot(document.getElementById('root')).render(<Stage />);
