// Inventory Count Work Session — step through items, enter physical counts, commit discrepancies
const { useState: useStateIC, useEffect: useEffectIC, useMemo: useMemoIC, useRef: useRefIC } = React;

// Variant names are "Base Product — 63.2mm / Black Anodize / 206mm". Split for grouping.
const _baseName = (name) => { const p = String(name || '').split(/[—–]/); return (p[0] || name || '').trim(); };
const _variantLabel = (name) => { const p = String(name || '').split(/[—–]/); return p.length > 1 ? p.slice(1).join(' – ').trim() : ''; };

function InventoryCheckSession({ wo, user, sessionId, onExit, onComplete }) {
  // refreshTick bumps when an overlay edit changes an item — re-filter so a part flipped to
  // in-development / archived drops out of the count immediately.
  const [refreshTick, setRefreshTick] = useStateIC(0);
  // Scope: wo.sku is 'ALL' or a type string like 'hardware'
  const allItems = useMemoIC(() => {
    const scope = (wo.sku || 'ALL').toLowerCase();
    // On-hand is the staged/finished bucket for products, raw qty for everything else. Normalize each
    // item's `qty` to that on-hand value (on a COPY, never the shared window.INVENTORY object) so the
    // whole session — system display, discrepancy, and the baseline we send — is correct for every type.
    const onHandOf = (i) => (i.type === 'product') ? (i.qty_packaged || 0) : (i.qty || 0);
    return window.INVENTORY.filter(i =>
      (i.lifecycle || 'active') === 'active' &&   // skip in-development / archived parts
      (scope === 'all' || (i.type || 'hardware').toLowerCase() === scope)
    ).map(i => ({ ...i, qty: onHandOf(i) }))
     .sort((a, b) => (a.type || '').localeCompare(b.type || '') || (a.sku || '').localeCompare(b.sku || ''));
  }, [wo.sku, refreshTick]);

  // Group variants of the same product into ONE step (a grid of combos); everything else stays
  // a single step. Preserves the original order (a product's group appears where its first
  // variant would have been).
  const entries = useMemoIC(() => {
    // Group variants by product_id, else product_group, else a shared base name — but only
    // when the name actually has a "Base — combo" variant suffix (so plain parts never group).
    const keyOf = (i) => {
      if (i.product_id) return 'pid:' + i.product_id;
      if (i.product_group) return 'pg:' + i.product_group;
      return _variantLabel(i.name) ? 'nm:' + _baseName(i.name).toLowerCase() : null;
    };
    const byKey = {};
    allItems.forEach(i => { const k = keyOf(i); if (k) (byKey[k] = byKey[k] || []).push(i); });
    const seen = new Set();
    const out = [];
    allItems.forEach(i => {
      const k = keyOf(i);
      if (k && byKey[k].length >= 2) {
        if (!seen.has(k)) { seen.add(k); out.push({ kind: 'group', key: k, base: _baseName(i.name), variants: byKey[k] }); }
      } else {
        out.push({ kind: 'single', key: i.sku, item: i });
      }
    });
    return out;
  }, [allItems]);

  const [counts, setCounts] = useStateIC({});       // { sku: countedQty }
  const [idx, setIdx] = useStateIC(0);              // current item in step mode
  const [mode, setMode] = useStateIC('step');       // 'step' | 'list'
  const [phase, setPhase] = useStateIC('count');    // 'count' | 'summary'
  const [elapsed, setElapsed] = useStateIC(0);
  const [committing, setCommitting] = useStateIC(false);
  const [committed, setCommitted] = useStateIC(false);
  const [editSku, setEditSku] = useStateIC(null);        // sku being edited in the overlay (null = closed)
  const [binDraft, setBinDraft] = useStateIC('');       // editable shelf/bin for the current item
  const [binSaving, setBinSaving] = useStateIC(false);
  const [binSavedSku, setBinSavedSku] = useStateIC(null);

  useEffectIC(() => {
    const t = setInterval(() => setElapsed(e => e + 1), 1000);
    return () => clearInterval(t);
  }, []);

  function elapsedFmt() {
    const h = Math.floor(elapsed / 3600);
    const m = Math.floor((elapsed % 3600) / 60);
    const s = elapsed % 60;
    return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
  }

  // Persist each adjustment as it's entered (debounced), not only at Finish. This means a long
  // count can't be lost to an expired token / crash / walk-away, and the writes keep the session
  // alive. commitAll at the end still runs as a final reconcile (idempotent absolute set).
  const saveTimers = useRefIC({});          // sku -> debounce timeout id
  const [saveState, setSaveState] = useStateIC({});  // sku -> 'saving' | 'saved' | 'error'
  const itemBySku = useMemoIC(() => Object.fromEntries(allItems.map(i => [i.sku, i])), [allItems]);

  async function persistCount(sku, n) {
    const it = itemBySku[sku];
    if (!it) return;
    setSaveState(s => ({ ...s, [sku]: 'saving' }));
    try {
      // Concurrency-safe count write: send the counted value plus the baseline we measured against
      // (it.qty at session start). The server applies the delta to the LIVE on-hand and tracks it
      // per (session, sku), so a movement during the count is merged (not overwritten) and repeated
      // saves — including editing a value back down — never double-apply.
      const res = await fetch(`/api/inventory/${encodeURIComponent(sku)}/count`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ counted: n, baseline: it.qty, session_id: sessionId }),
      });
      setSaveState(s => ({ ...s, [sku]: res.ok ? 'saved' : 'error' }));
    } catch (_) {
      setSaveState(s => ({ ...s, [sku]: 'error' }));
    }
  }
  function queueSave(sku, n) {
    const timers = saveTimers.current;
    if (timers[sku]) clearTimeout(timers[sku]);
    timers[sku] = setTimeout(() => { delete timers[sku]; persistCount(sku, n); }, 1000);
  }
  // Flush timers on unmount so a pending edit isn't silently dropped.
  useEffectIC(() => () => { Object.values(saveTimers.current).forEach(clearTimeout); }, []);

  function setCount(sku, val) {
    const n = Math.max(0, parseInt(val) || 0);
    setCounts(c => ({ ...c, [sku]: n }));
    queueSave(sku, n);
  }

  const discrepancies = useMemoIC(() => {
    return allItems
      .filter(i => counts[i.sku] !== undefined && counts[i.sku] !== i.qty)
      .map(i => ({ ...i, counted: counts[i.sku], diff: counts[i.sku] - i.qty }));
  }, [counts, allItems]);

  const checkedCount = Object.keys(counts).length;
  const entry = entries[idx] || null;                                    // current step (single or variant group)
  const cur = entry && entry.kind === 'single' ? entry.item : null;      // convenience for single-item steps
  const isManager = ['admin', 'manager'].includes(user?.role);
  // Fill any un-entered counts for this step with the system qty (so "Next" defaults to no-change).
  const fillStep = () => setCounts(c => {
    const next = { ...c };
    const items = entry ? (entry.kind === 'group' ? entry.variants : [entry.item]) : [];
    items.forEach(it => { if (next[it.sku] === undefined) next[it.sku] = it.qty; });
    return next;
  });

  // If the current item dropped out of the list (e.g. flipped to in-development mid-count),
  // clamp the step index so we don't land on an empty slot.
  useEffectIC(() => {
    if (idx > 0 && idx >= entries.length) setIdx(Math.max(0, entries.length - 1));
  }, [entries.length]);

  // Reset the shelf draft whenever the current item changes.
  useEffectIC(() => { setBinDraft(cur?.bin || ''); setBinSavedSku(null); }, [cur?.sku]);

  // Save a corrected shelf/bin. Persists to the product definition (part) AND the inventory row.
  async function saveBin() {
    if (!cur) return;
    const val = binDraft.trim();
    if (val === (cur.bin || '')) return;
    setBinSaving(true);
    try {
      await fetch(`/api/inventory/${encodeURIComponent(cur.sku)}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json', 'Idempotency-Key': `bin:${wo.id}:${cur.sku}:${val}` },
        body: JSON.stringify({ bin: val, user_id: user?.id }),
      });
      // reflect locally so the change sticks across this session + the inventory tab (same object ref)
      const inv = (window.INVENTORY || []).find(i => i.sku === cur.sku);
      if (inv) { inv.bin = val; inv.location = val; }
      setBinSavedSku(cur.sku);
    } catch (_) {}
    setBinSaving(false);
  }

  // Manager: edit this product without leaving the count. We open the BOM editor in an in-page
  // overlay (iframe) so the count stays mounted underneath — Save/Cancel in the editor posts a
  // message back here, which closes the overlay and refreshes the item. No popup windows.
  function openEditProduct() { if (cur) setEditSku(cur.sku); }

  async function refreshItem(sku) {
    if (!sku) return;
    try {
      const p = await fetch(`/api/parts/${encodeURIComponent(sku)}`).then(r => r.ok ? r.json() : null);
      if (p) {
        const inv = (window.INVENTORY || []).find(i => i.sku === sku);
        if (inv) { inv.name = p.name; if (p.image_url) inv.image_url = p.image_url; inv.bin = p.bin; inv.location = p.bin; inv.lifecycle = p.status || inv.lifecycle; }
        if (cur && cur.sku === sku) setBinDraft(p.bin || '');
        setRefreshTick(t => t + 1);
      }
    } catch (_) {}
  }

  function closeEditOverlay(sku) { setEditSku(null); refreshItem(sku); }

  useEffectIC(() => {
    const onMsg = (e) => {
      if (e.origin !== window.location.origin) return;
      if (!e.data || e.data.type !== 'gps-edit-done') return;
      closeEditOverlay(e.data.sku || editSku);
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, [editSku, cur?.sku]);

  async function commitAll() {
    setCommitting(true);
    // Flush any debounced per-item saves so we don't race the final reconcile.
    Object.values(saveTimers.current).forEach(clearTimeout);
    saveTimers.current = {};
    let failed = 0;
    for (const d of discrepancies) {
      // Final reconcile through the same concurrency-safe, per-session-idempotent count endpoint —
      // a no-op for items already saved incrementally, and the catch-all for any that didn't.
      const res = await fetch(`/api/inventory/${encodeURIComponent(d.sku)}/count`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ counted: d.counted, baseline: d.qty, session_id: sessionId }),
      }).catch(() => null);
      if (!res || !res.ok) failed++;
    }
    // Don't claim success on a silent failure — the whole point of this session is not to lose counts.
    if (failed > 0) {
      window.gpsToast && window.gpsToast({ message: `${failed} of ${discrepancies.length} counts didn't save — check your connection and finish again`, tone: 'warn' });
      setCommitting(false);
      return;   // stay on the count so the worker can retry; nothing is lost
    }
    // Mark WO complete
    await fetch(`/api/work-orders/${wo.id}/complete`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Idempotency-Key': `complete:${wo.id}:${sessionId || 'na'}`, // GPS-006
      },
      body: JSON.stringify({ session_id: sessionId, qty_built: checkedCount }),
    }).catch(() => {});
    setCommitting(false);
    setCommitted(true);
    onComplete && onComplete({ counts, discrepancies });
  }

  // ── Summary phase ───────────────────────────────────────────────────────────
  if (phase === 'summary') {
    return (
      <div className="min-h-screen bg-[#09090b] text-zinc-100 flex flex-col">
        <header className="flex-shrink-0 h-[60px] px-6 flex items-center justify-between border-b border-zinc-800">
          <span className="font-display text-[20px] tracking-wide">INVENTORY COUNT — REVIEW</span>
          <span className="font-mono text-zinc-500 text-[13px]">{elapsedFmt()}</span>
        </header>
        <div className="flex-1 px-8 py-8 max-w-[800px] mx-auto w-full">
          <div className="grid grid-cols-3 gap-4 mb-8">
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5 text-center">
              <div className="text-[11px] uppercase tracking-wider text-zinc-500 mb-2">Items checked</div>
              <div className="font-display text-[48px] text-zinc-100">{checkedCount}</div>
              <div className="text-[12px] text-zinc-500">of {allItems.length}</div>
            </div>
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5 text-center">
              <div className="text-[11px] uppercase tracking-wider text-zinc-500 mb-2">Discrepancies</div>
              <div className={`font-display text-[48px] ${discrepancies.length > 0 ? 'text-red-400' : 'text-emerald-400'}`}>
                {discrepancies.length}
              </div>
            </div>
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-5 text-center">
              <div className="text-[11px] uppercase tracking-wider text-zinc-500 mb-2">Unchanged</div>
              <div className="font-display text-[48px] text-zinc-400">{checkedCount - discrepancies.length}</div>
            </div>
          </div>

          {discrepancies.length > 0 && (
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden mb-6">
              <div className="px-4 py-3 border-b border-zinc-800 text-[11px] uppercase tracking-wider text-zinc-500">
                Discrepancies to commit
              </div>
              <div className="divide-y divide-zinc-800">
                {discrepancies.map(d => (
                  <div key={d.sku} className="px-4 py-3 grid grid-cols-[2fr_1fr_1fr_1fr_1fr] items-center gap-3 text-[13px]">
                    <div className="flex items-center gap-3 min-w-0">
                      <div className="h-9 w-9 flex-shrink-0 rounded-md bg-zinc-800 border border-zinc-700 overflow-hidden flex items-center justify-center">
                        {d.image_url ? <img src={d.image_url} alt="" className="w-full h-full object-contain"/> : <span className="text-zinc-600 text-[12px]">📦</span>}
                      </div>
                      <div className="min-w-0">
                        <div className="font-medium truncate">{d.name}</div>
                        <div className="font-mono text-[11px] text-zinc-500 truncate">{d.sku}</div>
                      </div>
                    </div>
                    <div className="text-zinc-400 text-right">{d.qty} (sys)</div>
                    <div className="text-zinc-100 text-right font-semibold">{d.counted} (counted)</div>
                    <div className={`text-right font-bold ${d.diff > 0 ? 'text-emerald-400' : 'text-red-400'}`}>
                      {d.diff > 0 ? '+' : ''}{d.diff}
                    </div>
                    <div className={`text-right text-[11px] font-mono ${d.diff > 0 ? 'text-emerald-300' : 'text-red-300'}`}>
                      {d.qty} → {d.counted}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          )}

          {committed ? (
            <div className="text-center py-8">
              <div className="text-emerald-400 font-display text-[32px] mb-4">✓ COMMITTED</div>
              <div className="text-zinc-400 text-[14px] mb-6">{discrepancies.length} adjustment{discrepancies.length !== 1 ? 's' : ''} saved</div>
              <button onClick={onExit}
                className="h-14 px-8 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-200 font-display text-[18px] tracking-wider hover:bg-zinc-700">
                DONE
              </button>
            </div>
          ) : (
            <div className="flex gap-3">
              <button onClick={() => setPhase('count')}
                className="h-14 px-6 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-300 font-display text-[18px] tracking-wider hover:bg-zinc-700">
                ← Back
              </button>
              <window.HoldButton
                onComplete={commitAll}
                color="emerald"
                className="flex-1 h-14 text-[20px]"
                subText={discrepancies.length > 0 ? `Will update ${discrepancies.length} item${discrepancies.length !== 1 ? 's' : ''}` : 'No changes to commit'}>
                {committing ? 'COMMITTING…' : discrepancies.length > 0 ? 'COMMIT ALL →' : 'FINISH (no changes)'}
              </window.HoldButton>
            </div>
          )}
        </div>
      </div>
    );
  }

  // ── Step mode ───────────────────────────────────────────────────────────────
  if (mode === 'step') {
    if (!entry) return (
      <div className="min-h-screen bg-[#09090b] text-zinc-100 flex items-center justify-center">
        <div className="text-zinc-500 text-[15px]">No items to count.</div>
      </div>
    );
    const counted = cur ? (counts[cur.sku] ?? cur.qty) : 0;
    const diff = cur ? counted - cur.qty : 0;
    const hasDiff = cur ? counted !== cur.qty : false;

    return (
      <div className="min-h-screen bg-[#09090b] text-zinc-100 flex flex-col">
        {editSku && (
          <div className="fixed inset-0 z-[60] bg-zinc-950 flex flex-col">
            <div className="h-12 flex-shrink-0 flex items-center justify-between px-4 border-b border-zinc-800 bg-zinc-900">
              <span className="text-[13px] text-zinc-400">Editing <span className="font-mono text-zinc-200">{editSku}</span> · Save or Cancel returns to the count</span>
              <button onClick={() => closeEditOverlay(editSku)}
                className="h-8 px-3 rounded-lg bg-zinc-800 border border-zinc-700 text-[12px] text-zinc-300 hover:bg-zinc-700">✕ Close</button>
            </div>
            <iframe src={`bom-editor.html?edit=${encodeURIComponent(editSku)}`} title="Edit product"
              className="flex-1 w-full border-0" />
          </div>
        )}
        <header className="flex-shrink-0 h-[60px] px-6 flex items-center justify-between border-b border-zinc-800">
          <div className="flex items-center gap-4">
            <button onClick={onExit} className="text-zinc-500 hover:text-zinc-300 text-[13px]">✕ Exit</button>
            <span className="font-display text-[20px] tracking-wide">INVENTORY COUNT</span>
            <span className="text-zinc-600 text-[13px]">{wo.name}</span>
          </div>
          <div className="flex items-center gap-4">
            <button onClick={() => setMode('list')}
              className="h-8 px-3 rounded-lg bg-zinc-800 border border-zinc-700 text-zinc-300 text-[12px] hover:bg-zinc-700">
              List mode
            </button>
            <span className="font-mono text-zinc-500 text-[13px]">{elapsedFmt()}</span>
          </div>
        </header>

        {/* Progress bar */}
        <div className="h-1 bg-zinc-800">
          <div className="h-full bg-[#C41230] transition-all" style={{ width: `${(idx / entries.length) * 100}%` }} />
        </div>

        <div className="flex-1 flex flex-col items-center justify-center px-8 py-6">
          <div className="w-full max-w-[500px]">
            {/* Item counter */}
            <div className="text-[12px] uppercase tracking-[0.22em] text-zinc-500 text-center mb-6">
              {entry.kind === 'group' ? `Product ${idx + 1} of ${entries.length} · ${entry.variants.length} variants` : `Item ${idx + 1} of ${entries.length}`} · {checkedCount} checked
            </div>

            {/* Variant group — one product, a grid of its combos */}
            {entry.kind === 'group' && (
              <div>
                <div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-5 mb-4">
                  <div className="text-[11px] uppercase tracking-[0.2em] text-zinc-500 mb-1">{entry.variants.length} variants · count each below</div>
                  <div className="font-display text-[24px] leading-tight">{entry.base}</div>
                </div>
                <div className="bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden">
                  <div className="grid grid-cols-[2.2fr_0.7fr_1.5fr_0.8fr] gap-2 px-4 py-2 border-b border-zinc-800 text-[10px] uppercase tracking-wider text-zinc-600">
                    <div>Variant</div><div className="text-right">Sys</div><div className="text-center">Counted</div><div className="text-right">Diff</div>
                  </div>
                  <div className="divide-y divide-zinc-800/60 max-h-[46vh] overflow-y-auto">
                    {entry.variants.map(v => {
                      const c = counts[v.sku]; const has = c !== undefined; const d = has ? c - v.qty : null;
                      return (
                        <div key={v.sku} className="grid grid-cols-[2.2fr_0.7fr_1.5fr_0.8fr] gap-2 items-center px-4 py-2.5">
                          <div className="min-w-0">
                            <div className="text-[13px] truncate" title={v.name}>{_variantLabel(v.name) || v.name}</div>
                            <div className="font-mono text-[10px] text-zinc-500 truncate">{v.sku}{v.bin ? ` · ${v.bin}` : ''}</div>
                          </div>
                          <div className="text-right tabular-nums text-zinc-400 text-[13px]">{v.qty}</div>
                          <div className="flex items-center gap-1">
                            <button onClick={() => setCount(v.sku, (counts[v.sku] ?? v.qty) - 1)} className="h-8 w-8 flex-shrink-0 rounded bg-zinc-800 border border-zinc-700 text-zinc-400 hover:bg-zinc-700 text-[15px]">−</button>
                            <input type="number" min={0} value={has ? c : ''} placeholder="—" onChange={e => setCount(v.sku, e.target.value)}
                              className={`w-full h-8 rounded px-1 text-center font-mono text-[13px] outline-none border ${has ? (d === 0 ? 'bg-emerald-500/5 border-emerald-500/30 text-emerald-300' : 'bg-red-500/5 border-red-500/30 text-red-300') : 'bg-zinc-800 border-zinc-700 text-zinc-200'}`} />
                            <button onClick={() => setCount(v.sku, (counts[v.sku] ?? v.qty) + 1)} className="h-8 w-8 flex-shrink-0 rounded bg-zinc-800 border border-zinc-700 text-zinc-400 hover:bg-zinc-700 text-[15px]">+</button>
                          </div>
                          <div className={`text-right tabular-nums text-[13px] font-semibold ${d === null ? 'text-zinc-600' : d === 0 ? 'text-emerald-400' : d > 0 ? 'text-emerald-300' : 'text-red-400'}`}>{d === null ? '—' : (d > 0 ? '+' : '') + d}</div>
                        </div>
                      );
                    })}
                  </div>
                </div>
                <div className="mt-3 flex justify-end">
                  <button onClick={() => entry.variants.forEach(v => { if (counts[v.sku] === undefined) setCount(v.sku, v.qty); })}
                    className="h-9 px-3 rounded-lg bg-zinc-800 border border-zinc-700 text-zinc-400 text-[12px] hover:bg-zinc-700">Set all to system</button>
                </div>
              </div>
            )}

            {/* Single item — photo + count. Confirm the exact part before counting (reduces miscount) */}
            {entry.kind === 'single' && (<>
            <div className="w-full h-[230px] rounded-2xl bg-zinc-900 border border-zinc-800 overflow-hidden flex items-center justify-center mb-4">
              {cur.image_url
                ? <img src={cur.image_url} alt={cur.name} className="w-full h-full object-contain" />
                : <div className="flex flex-col items-center gap-2 text-zinc-600">
                    <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                    <span className="text-[11px] uppercase tracking-[0.2em]">No photo</span>
                  </div>}
            </div>

            {/* Part info */}
            <div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-6 mb-6">
              <div className="flex items-start justify-between gap-3">
                <div className="font-mono text-[12px] text-zinc-500 mb-1">{cur.sku}</div>
                {isManager && (
                  <button onClick={openEditProduct} title="Edit this product in the BOM editor"
                    className="flex-shrink-0 h-8 px-3 rounded-lg bg-zinc-800 border border-zinc-700 text-[12px] text-zinc-300 hover:bg-zinc-700 -mt-1 flex items-center gap-1.5">
                    ✎ Edit product <span className="text-zinc-500">↗</span>
                  </button>
                )}
              </div>
              <div className="font-display text-[28px] leading-tight mb-1">{cur.name}</div>
              <div className="flex items-center gap-4 text-[13px] text-zinc-400 mt-3">
                <span>System qty: <span className="font-mono text-zinc-200">{cur.qty}</span></span>
                <span className="uppercase text-[11px] tracking-wider text-zinc-600">{cur.type}</span>
              </div>
              {/* Shelf / bin — editable here; saving updates the product definition */}
              <div className="flex items-center gap-2 mt-4 pt-4 border-t border-zinc-800">
                <span className="text-[11px] uppercase tracking-[0.18em] text-zinc-500 flex-shrink-0">📦 Shelf / Bin</span>
                <input value={binDraft} onChange={e => setBinDraft(e.target.value)} placeholder="e.g. A-12"
                  className="h-9 w-32 bg-zinc-950 border border-zinc-700 rounded-lg px-2 text-center font-mono text-[14px] text-zinc-100 outline-none focus:border-cyan-500/60"/>
                {binDraft.trim() !== (cur.bin || '') ? (
                  <button onClick={saveBin} disabled={binSaving}
                    className="h-9 px-3 rounded-lg bg-cyan-600 text-zinc-950 text-[12px] font-semibold hover:bg-cyan-500 disabled:opacity-50">
                    {binSaving ? 'Saving…' : 'Save shelf'}
                  </button>
                ) : binSavedSku === cur.sku ? (
                  <span className="text-[12px] text-emerald-400">✓ Updated</span>
                ) : null}
              </div>
            </div>

            {/* Count input */}
            <div className="mb-6">
              <div className="text-[11px] uppercase tracking-wider text-zinc-500 text-center mb-3">Counted qty</div>
              <div className="flex items-center gap-3">
                <button onClick={() => setCount(cur.sku, counted - 1)}
                  className="h-16 w-16 flex-shrink-0 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-200 text-[28px] hover:bg-zinc-700">−</button>
                <input type="number" value={counted} min={0}
                  onChange={e => setCount(cur.sku, e.target.value)}
                  className="flex-1 h-16 bg-zinc-900 border border-zinc-700 rounded-xl px-4 text-center font-display text-[48px] outline-none focus:border-zinc-500"/>
                <button onClick={() => setCount(cur.sku, counted + 1)}
                  className="h-16 w-16 flex-shrink-0 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-200 text-[28px] hover:bg-zinc-700">+</button>
              </div>
              {hasDiff && (
                <div className={`text-center mt-3 font-mono text-[15px] font-semibold ${diff > 0 ? 'text-emerald-400' : 'text-red-400'}`}>
                  {diff > 0 ? '+' : ''}{diff} vs system
                </div>
              )}
              {!hasDiff && counts[cur.sku] !== undefined && (
                <div className="text-center mt-3 text-emerald-500 text-[13px]">✓ Matches system</div>
              )}
              {saveState[cur.sku] && (
                <div className="text-center mt-1.5 text-[12px]">
                  {saveState[cur.sku] === 'saving' && <span className="text-zinc-500">Saving…</span>}
                  {saveState[cur.sku] === 'saved' && <span className="text-emerald-500">✓ Saved</span>}
                  {saveState[cur.sku] === 'error' && <span className="text-red-400">Couldn't save — will retry at Finish</span>}
                </div>
              )}
            </div>
            </>)}

            {/* Navigation */}
            <div className="flex gap-3">
              <button onClick={() => setIdx(i => Math.max(0, i - 1))} disabled={idx === 0}
                className="h-14 px-6 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-300 font-display text-[18px] tracking-wider hover:bg-zinc-700 disabled:opacity-30">
                ← PREV
              </button>
              {idx < entries.length - 1 ? (
                <button onClick={() => { fillStep(); setIdx(i => i + 1); }}
                  className="flex-1 h-14 rounded-xl bg-[#C41230] text-white font-display text-[22px] tracking-wider hover:bg-red-700">
                  NEXT →
                </button>
              ) : (
                <button onClick={() => { fillStep(); setPhase('summary'); }}
                  className="flex-1 h-14 rounded-xl bg-emerald-600 text-zinc-950 font-display text-[20px] tracking-wider hover:bg-emerald-500">
                  REVIEW & COMMIT →
                </button>
              )}
            </div>
            <button onClick={() => setPhase('summary')} className="w-full mt-3 text-[12px] text-zinc-600 hover:text-zinc-400 text-center">
              Skip to summary ({checkedCount} checked so far)
            </button>
          </div>
        </div>
      </div>
    );
  }

  // ── List mode ───────────────────────────────────────────────────────────────
  return (
    <div className="min-h-screen bg-[#09090b] text-zinc-100 flex flex-col">
      <header className="flex-shrink-0 h-[60px] px-6 flex items-center justify-between border-b border-zinc-800">
        <div className="flex items-center gap-4">
          <button onClick={onExit} className="text-zinc-500 hover:text-zinc-300 text-[13px]">✕ Exit</button>
          <span className="font-display text-[20px] tracking-wide">INVENTORY COUNT</span>
          <span className="text-zinc-600 text-[13px]">{wo.name}</span>
        </div>
        <div className="flex items-center gap-4">
          <span className="text-zinc-500 text-[13px]">{checkedCount} / {allItems.length} checked</span>
          <button onClick={() => setMode('step')}
            className="h-8 px-3 rounded-lg bg-zinc-800 border border-zinc-700 text-zinc-300 text-[12px] hover:bg-zinc-700">
            Step mode
          </button>
          <button onClick={() => setPhase('summary')}
            className="h-8 px-3 rounded-lg bg-emerald-600/20 border border-emerald-500/30 text-emerald-300 text-[12px] hover:bg-emerald-600/30">
            Review →
          </button>
          <span className="font-mono text-zinc-500 text-[13px]">{elapsedFmt()}</span>
        </div>
      </header>
      <div className="flex-1 overflow-y-auto">
        <div className="px-6 py-3 border-b border-zinc-800 grid grid-cols-[2fr_1fr_1fr_1.2fr_0.8fr] text-[10px] uppercase tracking-wider text-zinc-600 gap-3">
          <div>Part</div>
          <div className="text-right">System qty</div>
          <div className="text-right">Counted</div>
          <div className="text-center">Adjust</div>
          <div className="text-right">Diff</div>
        </div>
        <div className="divide-y divide-zinc-800/60">
          {allItems.map((it, i) => {
            const counted = counts[it.sku] ?? '';
            const hasCounted = counts[it.sku] !== undefined;
            const diff = hasCounted ? counts[it.sku] - it.qty : null;
            return (
              <div key={it.sku} className="grid grid-cols-[2fr_1fr_1fr_1.2fr_0.8fr] items-center px-6 py-3 gap-3 hover:bg-zinc-900/40">
                <div className="flex items-center gap-3 min-w-0">
                  <div className="h-11 w-11 flex-shrink-0 rounded-lg bg-zinc-800 border border-zinc-700 overflow-hidden flex items-center justify-center">
                    {it.image_url
                      ? <img src={it.image_url} alt="" className="w-full h-full object-contain" />
                      : <span className="text-zinc-600 text-[14px]">📦</span>}
                  </div>
                  <div className="min-w-0">
                    <div className="font-medium text-[13px] truncate">{it.name}</div>
                    <div className="font-mono text-[11px] text-zinc-500 truncate">{it.sku}{it.bin ? ` · ${it.bin}` : ''}</div>
                  </div>
                </div>
                <div className="text-right tabular-nums text-zinc-400 text-[13px]">{it.qty}</div>
                <div>
                  <input type="number" value={counted} min={0} placeholder="—"
                    onChange={e => setCount(it.sku, e.target.value)}
                    className={`w-full h-9 rounded-lg px-2 text-center font-mono text-[14px] outline-none border transition ${
                      hasCounted
                        ? diff === 0 ? 'bg-emerald-500/5 border-emerald-500/30 text-emerald-300' : 'bg-red-500/5 border-red-500/30 text-red-300'
                        : 'bg-zinc-800 border-zinc-700 text-zinc-200'
                    } focus:border-zinc-500`}/>
                </div>
                <div className="flex items-center justify-center gap-1">
                  <button onClick={() => setCount(it.sku, (counts[it.sku] ?? it.qty) - 1)}
                    className="h-8 w-8 rounded bg-zinc-800 border border-zinc-700 text-zinc-400 hover:bg-zinc-700 text-[14px]">−</button>
                  <button onClick={() => setCount(it.sku, it.qty)}
                    className="h-8 px-2 rounded bg-zinc-800 border border-zinc-700 text-zinc-500 hover:bg-zinc-700 text-[11px]">SYS</button>
                  <button onClick={() => setCount(it.sku, (counts[it.sku] ?? it.qty) + 1)}
                    className="h-8 w-8 rounded bg-zinc-800 border border-zinc-700 text-zinc-400 hover:bg-zinc-700 text-[14px]">+</button>
                </div>
                <div className={`text-right tabular-nums text-[13px] font-semibold ${
                  diff === null ? 'text-zinc-600' : diff === 0 ? 'text-emerald-400' : diff > 0 ? 'text-emerald-300' : 'text-red-400'
                }`}>
                  {diff === null ? '—' : diff === 0 ? '✓' : `${diff > 0 ? '+' : ''}${diff}`}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

window.InventoryCheckSession = InventoryCheckSession;
