// screens-checkout.jsx — Cart, QRIS payment, Dispense, Result

// ─────────────────────────────────────────────────────────────
// CART — items belong to one machine (no cross-machine cart)
// ─────────────────────────────────────────────────────────────
const ScreenCart = ({ cart, machine, onBack, onAdd, onRemove, onClear, onCheckout, user, useVoucher, setUseVoucher, reservationLeft, onVoucherPicker }) => {
  const lines = Object.entries(cart).map(([pid, qty]) => ({
    p: MOCK_PRODUCTS.find(x => x.id === pid),
    qty,
  })).filter(l => l.p);

  const subtotal = lines.reduce((s, l) => s + l.p.price * l.qty, 0);
  const voucher = useVoucher ? user?.vouchers.find(v => v.id === useVoucher) : null;
  const voucherAmount = voucher ? Math.min(voucher.amount, subtotal) : 0;
  const total = subtotal - voucherAmount;

  return (
    <div className="app">
      <Topbar
        title="Keranjang"
        sub={`${lines.length} produk · habis ${mmss(reservationLeft)}`}
        onBack={onBack}
        right={
          lines.length > 0 && (
            <button onClick={onClear} style={{ fontSize: 12, color: 'var(--eb-danger)', fontWeight: 600, padding: '0 8px' }}>
              Kosongkan
            </button>
          )
        }
      />

      {lines.length === 0 ? (
        <div className="app-scroll">
          <div className="empty" style={{ paddingTop: 80 }}>
            <Icon name="cart" size={32} color="var(--eb-faint)" />
            <div style={{ marginTop: 10, fontWeight: 600, fontSize: 15, color: 'var(--eb-ink)' }}>Belum ada produk</div>
            <div style={{ fontSize: 12, marginTop: 4 }}>Pindai QR di mesin untuk mulai belanja</div>
            <button className="btn btn-primary btn-sm" onClick={onBack} style={{ marginTop: 16 }}>
              Kembali pindai
            </button>
          </div>
        </div>
      ) : (
        <>
          <div className="app-scroll">
            {/* Machine reference + reservation */}
            <div style={{ padding: '0 16px 12px' }}>
              {machine && (
                <div style={{
                  padding: 10, borderRadius: 12,
                  background: 'var(--eb-bg-elev)',
                  border: '1px solid var(--eb-line-soft)',
                  display: 'flex', alignItems: 'center', gap: 10,
                  marginBottom: 10,
                }}>
                  <div style={{
                    width: 32, height: 32, borderRadius: 8,
                    background: 'var(--eb-navy)', color: 'var(--eb-amber)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
                  }}>
                    <SpinnerIcon size={20} slots={PRODUCTS_FOR_MACHINE(machine.id).length} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--eb-ink)' }}>
                      Mesin {machine.name}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--eb-muted)' }}>{machine.location}</div>
                  </div>
                </div>
              )}
              <div style={{
                padding: 12, borderRadius: 12,
                background: reservationLeft < 30 ? 'var(--eb-danger-soft)' : 'var(--eb-amber-soft)',
                display: 'flex', alignItems: 'center', gap: 10,
              }}>
                <Icon name="clock" size={18} color={reservationLeft < 30 ? 'var(--eb-danger)' : 'var(--eb-amber-strong)'} />
                <div style={{ flex: 1, fontSize: 12, color: 'var(--eb-navy)' }}>
                  <div style={{ fontWeight: 600 }}>Slot kamu di-reserve</div>
                  <div style={{ opacity: 0.7, marginTop: 1, fontSize: 11 }}>Selesaikan dalam {mmss(reservationLeft)} agar tidak diambil pembeli lain</div>
                </div>
              </div>
            </div>

            {/* Cart lines — flat list (single machine) */}
            <div style={{ padding: '0 16px 12px' }}>
              <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
                {lines.map((l, idx) => (
                  <div key={l.p.id} style={{
                    display: 'flex', alignItems: 'center', gap: 12, padding: 12,
                    borderTop: idx > 0 ? '1px solid var(--eb-line-soft)' : 'none',
                  }}>
                    <ProductThumb product={l.p} size={52} />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.2, color: 'var(--eb-ink)' }}>{l.p.name}</div>
                      <div style={{ fontSize: 11, color: 'var(--eb-muted)', marginTop: 2 }}>{l.p.unit}</div>
                      <span className="price" style={{ fontSize: 13, marginTop: 4, display: 'inline-block' }}>{fmtRp(l.p.price)}</span>
                    </div>
                    <div style={{
                      display: 'flex', alignItems: 'center', gap: 2,
                      background: 'var(--eb-bg-muted)', borderRadius: 10, padding: 2,
                    }}>
                      <button onClick={() => onRemove(l.p)} style={{
                        width: 30, height: 28, color: 'var(--eb-navy)',
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        borderRadius: 8, background: l.qty === 1 ? 'transparent' : 'var(--eb-bg-elev)',
                      }}>
                        <Icon name={l.qty === 1 ? 'close' : 'minus'} size={14} stroke={2.5} />
                      </button>
                      <div style={{
                        minWidth: 20, textAlign: 'center', fontWeight: 700,
                        fontFamily: 'IBM Plex Mono', fontSize: 13, color: 'var(--eb-navy)',
                      }}>{l.qty}</div>
                      <button onClick={() => onAdd(l.p)} disabled={l.qty >= l.p.stock} style={{
                        width: 30, height: 28, color: 'var(--eb-navy)',
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        borderRadius: 8, background: 'var(--eb-bg-elev)',
                        opacity: l.qty >= l.p.stock ? 0.4 : 1,
                      }}>
                        <Icon name="plus" size={14} stroke={2.5} />
                      </button>
                    </div>
                  </div>
                ))}
              </div>
            </div>

            {/* Voucher */}
            {user && (
              <div style={{ padding: '6px 16px 12px' }}>
                <button onClick={onVoucherPicker} className="card" style={{
                  width: '100%', padding: 12,
                  display: 'flex', alignItems: 'center', gap: 12, textAlign: 'left',
                }}>
                  <div style={{
                    width: 38, height: 38, borderRadius: 10,
                    background: voucher ? 'var(--eb-amber)' : 'var(--eb-bg-muted)',
                    color: voucher ? 'var(--eb-navy)' : 'var(--eb-muted)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
                  }}>
                    <Icon name="ticket" size={20} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--eb-ink)' }}>
                      {voucher ? `Voucher ${fmtRp(voucher.amount)}` : 'Pakai voucher'}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--eb-muted)', marginTop: 1 }}>
                      {voucher ? voucher.code : `${user.vouchers.length} voucher tersedia`}
                    </div>
                  </div>
                  <Icon name="chevron" size={16} color="var(--eb-faint)" />
                </button>
              </div>
            )}

            {/* Summary */}
            <div style={{ padding: '6px 16px 24px' }}>
              <div className="card" style={{ padding: 14 }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--eb-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 10 }}>
                  Rincian
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
                    <span style={{ color: 'var(--eb-muted)' }}>Subtotal ({lines.reduce((s,l) => s+l.qty, 0)} pcs)</span>
                    <span className="price" style={{ fontSize: 13 }}>{fmtRp(subtotal)}</span>
                  </div>
                  {voucher && (
                    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: 'var(--eb-success)' }}>
                      <span>Voucher {voucher.code}</span>
                      <span className="price" style={{ fontSize: 13, color: 'var(--eb-success)' }}>−{fmtRp(voucherAmount)}</span>
                    </div>
                  )}
                  <div style={{ height: 1, background: 'var(--eb-line-soft)', margin: '4px 0' }} />
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                    <span style={{ fontSize: 14, fontWeight: 600 }}>Total bayar</span>
                    <span className="price" style={{ fontSize: 20, color: 'var(--eb-navy)' }}>{fmtRp(total)}</span>
                  </div>
                </div>
              </div>
            </div>

            <div style={{ height: 84 }} />
          </div>

          {/* Bottom CTA */}
          <div className="bottom-bar">
            <button className="btn btn-primary btn-block" onClick={() => onCheckout(total)}>
              <div style={{
                width: 22, height: 22, borderRadius: 5, background: 'var(--eb-navy)',
                color: 'var(--eb-amber)', display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontFamily: 'IBM Plex Mono', fontWeight: 700, fontSize: 10,
              }}>Q</div>
              Bayar QRIS {fmtRp(total)}
            </button>
          </div>
        </>
      )}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// QRIS PAYMENT
// ─────────────────────────────────────────────────────────────
const ScreenQris = ({ total, onBack, onPaid, onCancel }) => {
  const [secs, setSecs] = React.useState(180); // 3 minute window
  const [status, setStatus] = React.useState('waiting'); // waiting | paid

  React.useEffect(() => {
    const t = setInterval(() => setSecs(s => Math.max(0, s - 1)), 1000);
    return () => clearInterval(t);
  }, []);

  React.useEffect(() => {
    if (status === 'paid') {
      const t = setTimeout(onPaid, 900);
      return () => clearTimeout(t);
    }
  }, [status, onPaid]);

  const simulatePay = () => setStatus('paid');

  return (
    <div className="app" style={{ background: 'var(--eb-bg)' }}>
      <Topbar title="Bayar QRIS" onBack={onBack} />

      <div className="app-scroll">
        <div style={{ padding: '8px 16px 14px', textAlign: 'center' }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--eb-muted)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
            Total tagihan
          </div>
          <div className="price" style={{ fontSize: 32, color: 'var(--eb-navy)', marginTop: 4 }}>
            {fmtRp(total)}
          </div>
          <div className="timer-pip" data-tone={secs < 60 ? 'urgent' : secs < 120 ? 'warn' : 'ok'} style={{ marginTop: 10 }}>
            <Icon name="clock" size={12} />
            QR berlaku {mmss(secs)}
          </div>
        </div>

        {/* QR */}
        <div style={{ display: 'flex', justifyContent: 'center', padding: '0 16px 14px' }}>
          <div style={{
            background: '#fff', borderRadius: 24, padding: 20,
            boxShadow: 'var(--eb-shadow-lg)',
            position: 'relative',
          }}>
            {/* QRIS header strip */}
            <div style={{
              position: 'absolute', top: -1, left: 20, right: 20,
              height: 36, background: '#fff', borderRadius: '24px 24px 0 0',
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '0 8px',
            }}>
              <div style={{
                fontFamily: 'IBM Plex Mono', fontWeight: 700, fontSize: 11,
                color: '#D32F2F', letterSpacing: '-0.02em',
                display: 'inline-flex', alignItems: 'center', gap: 4,
              }}>
                <span style={{
                  width: 18, height: 18, borderRadius: 4, background: '#D32F2F',
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  color: '#fff', fontSize: 9, fontWeight: 700,
                }}>Q</span>
                QRIS
              </div>
              <div style={{ fontSize: 9, color: '#666', fontWeight: 500 }}>NMID: ID2020 0234</div>
            </div>
            <div style={{ height: 16 }} />
            {status === 'paid' ? (
              <div style={{
                width: 240, height: 240,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                flexDirection: 'column', gap: 12,
                background: 'var(--eb-success-soft)', borderRadius: 12,
              }}>
                <div style={{
                  width: 72, height: 72, borderRadius: 36,
                  background: 'var(--eb-success)', color: '#fff',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  <Icon name="check" size={36} stroke={3} />
                </div>
                <div style={{ fontWeight: 700, fontSize: 16, color: 'var(--eb-success)' }}>Pembayaran berhasil</div>
              </div>
            ) : (
              <QrMock size={240} seed={total} />
            )}
            <div style={{
              textAlign: 'center', marginTop: 10,
              fontSize: 10, color: '#666', fontFamily: 'IBM Plex Mono',
            }}>
              MERCHANT · {(window.BRAND && window.BRAND.merchantName) || 'ELEKTROBEE VENDING'} — KOS SAKURA
            </div>
          </div>
        </div>

        {/* Wallet logos hint */}
        <div style={{ padding: '0 16px 14px', textAlign: 'center' }}>
          <div style={{ fontSize: 12, color: 'var(--eb-muted)' }}>
            Pindai dengan dompet digital atau m-banking apa pun
          </div>
          <div style={{
            display: 'flex', justifyContent: 'center', gap: 8, marginTop: 10,
            flexWrap: 'wrap',
          }}>
            {['GoPay', 'OVO', 'DANA', 'ShopeePay', 'BCA', 'Mandiri', 'BRI', 'BNI'].map(w => (
              <span key={w} style={{
                fontSize: 10, padding: '4px 8px', borderRadius: 6,
                background: 'var(--eb-bg-elev)', border: '1px solid var(--eb-line)',
                color: 'var(--eb-ink-2)', fontWeight: 500,
              }}>{w}</span>
            ))}
          </div>
        </div>

        {/* Steps */}
        <div style={{ padding: '0 16px 14px' }}>
          <div className="card" style={{ padding: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--eb-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 8 }}>
              Cara bayar
            </div>
            {[
              'Buka aplikasi dompet digital / m-banking',
              'Pilih menu pindai QRIS',
              'Arahkan kamera ke QR di atas',
              'Konfirmasi nominal dan bayar',
            ].map((s, i) => (
              <div key={i} style={{ display: 'flex', gap: 10, padding: '4px 0', fontSize: 13, color: 'var(--eb-ink-2)' }}>
                <span style={{
                  width: 18, height: 18, borderRadius: 9,
                  background: 'var(--eb-navy)', color: '#fff',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontWeight: 700, fontSize: 10, fontFamily: 'IBM Plex Mono', flexShrink: 0,
                }}>{i + 1}</span>
                {s}
              </div>
            ))}
          </div>
        </div>

        <div style={{ height: 80 }} />
      </div>

      <div className="bottom-bar">
        <button className="btn btn-primary btn-block" onClick={simulatePay} disabled={status === 'paid'}>
          {status === 'paid' ? 'Memverifikasi…' : 'Simulasikan pembayaran berhasil'}
        </button>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// DISPENSE — the magic moment (spinner rotation per item)
// ─────────────────────────────────────────────────────────────
const ScreenDispense = ({ cart, machine, onFinish }) => {
  const lines = Object.entries(cart).map(([pid, qty]) => ({
    p: MOCK_PRODUCTS.find(x => x.id === pid),
    qty,
  })).filter(l => l.p);

  // Per-item queue
  const queue = React.useMemo(() => {
    const q = [];
    lines.forEach(l => {
      for (let i = 0; i < l.qty; i++) q.push({ p: l.p, idx: i });
    });
    return q;
  }, []);

  const machineProducts = machine ? PRODUCTS_FOR_MACHINE(machine.id) : [];
  const slotCount = machineProducts.length;

  const [stepIdx, setStepIdx] = React.useState(-1);
  const [phase, setPhase] = React.useState('idle'); // idle | rotating | dispensing | done
  const [results, setResults] = React.useState({});
  const [rotation, setRotation] = React.useState(0);
  const [dropped, setDropped] = React.useState([]); // pids that landed in the tray
  const [done, setDone] = React.useState(false);

  // Force item at index 0 in cart to fail (demo voucher refund)
  const failPolicy = (p, occurrence) => p.id === 'C2-0' && occurrence === 0;

  // Index of current product on the machine's ring
  const currentSlotIdx = stepIdx >= 0 && stepIdx < queue.length
    ? machineProducts.findIndex(p => p.id === queue[stepIdx].p.id)
    : -1;

  React.useEffect(() => {
    if (stepIdx === -1) {
      const t = setTimeout(() => setStepIdx(0), 600);
      return () => clearTimeout(t);
    }
    if (stepIdx >= queue.length) {
      const t = setTimeout(() => { setPhase('done'); setDone(true); }, 700);
      return () => clearTimeout(t);
    }
    const item = queue[stepIdx];
    const targetSlot = machineProducts.findIndex(p => p.id === item.p.id);
    if (targetSlot === -1) {
      // Product not on this machine — shouldn't happen but skip safely
      setStepIdx(stepIdx + 1);
      return;
    }
    // Rotate so target slot is at top (12 o'clock). Each slot occupies 360/N degrees.
    // Wedge i centers at angle (i * wedgeAngle). To bring it to top (0deg = top),
    // we rotate the ring by -(i * wedgeAngle).
    const wedge = 360 / slotCount;
    // Accumulate rotation to always spin same direction (looks nicer)
    setRotation(prev => {
      const targetRot = -targetSlot * wedge;
      // Normalize so the spin is forward (negative): find the equivalent angle
      // less than current, then add a full extra turn for drama
      let next = targetRot;
      const turns = Math.floor((prev - next) / 360);
      next = next + (turns + 1) * -360; // ensure next < prev (one extra full rotation)
      return next;
    });
    setPhase('rotating');

    const willFail = failPolicy(item.p, item.idx);

    const t1 = setTimeout(() => {
      setPhase('dispensing');
    }, 1500);
    const t2 = setTimeout(() => {
      if (!willFail) {
        setDropped(prev => [...prev, { id: `${item.p.id}-${stepIdx}`, p: item.p }]);
      }
      setResults(prev => {
        const cur = prev[item.p.id] || { success: 0, failed: 0, total: 0 };
        return {
          ...prev,
          [item.p.id]: {
            ...cur,
            success: cur.success + (willFail ? 0 : 1),
            failed: cur.failed + (willFail ? 1 : 0),
            total: cur.total + 1,
          },
        };
      });
      setPhase('idle');
    }, 2300);
    const t3 = setTimeout(() => setStepIdx(stepIdx + 1), 2700);
    return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); };
  }, [stepIdx, slotCount]);

  React.useEffect(() => {
    if (done) {
      const t = setTimeout(() => onFinish(results), 900);
      return () => clearTimeout(t);
    }
  }, [done]);

  const currentItem = stepIdx >= 0 && stepIdx < queue.length ? queue[stepIdx] : null;
  const progress = Math.min(stepIdx + 1, queue.length) / queue.length;

  return (
    <div className="app" style={{
      background: 'linear-gradient(180deg, var(--eb-navy) 0%, var(--eb-navy) 100%)',
      color: '#fff',
    }}>
      <Topbar
        title={<span style={{ color: '#fff' }}>Sedang mengeluarkan</span>}
        sub={<span style={{ color: 'rgba(255,255,255,0.6)' }}>
          {machine ? `${machine.name} · ${queue.length} barang` : `${queue.length} barang`}
        </span>}
        transparent
      />

      <div className="app-scroll">
        {/* Progress bar */}
        <div style={{ padding: '0 16px 14px' }}>
          <div style={{
            height: 4, borderRadius: 999, background: 'rgba(255,255,255,0.10)',
            overflow: 'hidden',
          }}>
            <div style={{
              height: '100%', width: `${progress * 100}%`,
              background: 'var(--eb-amber)', borderRadius: 999,
              transition: 'width 0.4s ease',
              boxShadow: '0 0 12px var(--eb-amber)',
            }} />
          </div>
          <div style={{ marginTop: 8, fontSize: 11, color: 'rgba(255,255,255,0.6)' }}>
            {Math.min(stepIdx + 1, queue.length)} dari {queue.length} selesai
          </div>
        </div>

        {/* SPINNER STAGE */}
        <div className="spinner-stage">
          <SpinnerRing
            products={machineProducts}
            rotation={rotation}
            currentSlotIdx={currentSlotIdx}
            phase={phase}
          />
          {/* Dispense tray (catches dropped items) */}
          <div className="dispense-tray">
            <div className="dispense-tray-shelf" />
            <div className="dispense-tray-items">
              {dropped.slice(-6).map((d, i) => (
                <div key={d.id} className="tray-item" style={{
                  zIndex: dropped.length - i,
                  marginLeft: i === 0 ? 0 : -14,
                }}>
                  <ProductThumb product={d.p} size={36} />
                </div>
              ))}
              {dropped.length > 6 && (
                <span style={{ marginLeft: 6, fontSize: 11, color: 'rgba(255,255,255,0.55)', fontFamily: 'IBM Plex Mono', fontWeight: 600 }}>
                  +{dropped.length - 6}
                </span>
              )}
            </div>
          </div>
        </div>

        {/* Current item card */}
        {currentItem && !done && (
          <div style={{ padding: '0 16px 14px' }}>
            <div style={{
              background: 'rgba(var(--eb-amber-rgb), 0.10)',
              border: '1.5px solid rgba(var(--eb-amber-rgb), 0.35)',
              borderRadius: 18, padding: 14,
              display: 'flex', alignItems: 'center', gap: 12,
            }}>
              <ProductThumb product={currentItem.p} size={52} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--eb-amber)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
                  {phase === 'rotating' ? 'Memutar spinner' :
                   phase === 'dispensing' ? 'Sedang keluar' : 'Mempersiapkan'}
                </div>
                <div style={{ fontSize: 15, fontWeight: 700, marginTop: 2, color: '#fff' }}>{currentItem.p.name}</div>
                <div style={{ fontSize: 11, color: 'rgba(255,255,255,0.65)', marginTop: 2 }}>
                  posisi {currentSlotIdx + 1}/{slotCount}
                </div>
              </div>
              <div className="loader" style={{ color: 'var(--eb-amber)' }}>
                <span /><span /><span />
              </div>
            </div>
          </div>
        )}

        {done && (
          <div style={{ padding: '0 16px 14px' }}>
            <div style={{
              background: 'rgba(31,138,91,0.18)',
              border: '1.5px solid rgba(31,138,91,0.5)',
              borderRadius: 18, padding: 14,
              display: 'flex', alignItems: 'center', gap: 14,
            }}>
              <div style={{
                width: 44, height: 44, borderRadius: 22,
                background: 'var(--eb-success)', color: '#fff',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                flexShrink: 0,
              }}>
                <Icon name="check" size={22} stroke={3} />
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 15, fontWeight: 700, color: '#fff' }}>Selesai</div>
                <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>
                  Ambil barang dari pintu mesin
                </div>
              </div>
            </div>
          </div>
        )}

        {/* Per-product progress list */}
        <div style={{ padding: '0 16px 14px' }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.55)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 8 }}>
            Daftar barang
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {lines.map(l => {
              const r = results[l.p.id] || { success: 0, failed: 0, total: 0 };
              const remaining = l.qty - r.total;
              let state = remaining > 0 && r.total === 0 ? 'waiting' : remaining > 0 ? 'inprogress' : r.failed > 0 ? 'partial' : 'success';
              if (r.failed === l.qty) state = 'failed';
              return (
                <div key={l.p.id} style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  padding: 10, borderRadius: 12,
                  background: 'rgba(255,255,255,0.04)',
                  border: '1px solid rgba(255,255,255,0.08)',
                }}>
                  <ProductThumb product={l.p} size={40} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>{l.p.name}</div>
                    <div style={{ fontSize: 11, color: 'rgba(255,255,255,0.55)', marginTop: 1 }}>
                      {l.qty}× dipesan
                    </div>
                  </div>
                  <div style={{ minWidth: 76, textAlign: 'right' }}>
                    {state === 'waiting' && (
                      <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.5)' }}>Menunggu</span>
                    )}
                    {state === 'inprogress' && (
                      <span className="loader" style={{ color: 'var(--eb-amber)' }}><span /><span /><span /></span>
                    )}
                    {state === 'success' && (
                      <span style={{ fontSize: 11, color: 'var(--eb-success)', fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                        <Icon name="check" size={12} stroke={3} /> {l.qty}/{l.qty}
                      </span>
                    )}
                    {state === 'partial' && (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 2, alignItems: 'flex-end' }}>
                        <span style={{ fontSize: 11, color: 'var(--eb-amber)', fontWeight: 600 }}>{r.success}/{l.qty} keluar</span>
                        <span style={{ fontSize: 10, color: 'rgba(255,255,255,0.55)' }}>+ voucher Rp {(r.failed * l.p.price).toLocaleString('id-ID')}</span>
                      </div>
                    )}
                    {state === 'failed' && (
                      <span style={{ fontSize: 11, color: 'var(--eb-danger)', fontWeight: 600 }}>Gagal — voucher</span>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        <div style={{ height: 30 }} />
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// SpinnerRing — turntable view: products arranged in a circle,
// dispense arrow at top (12 o'clock), ring rotates via CSS transform.
// ─────────────────────────────────────────────────────────────
const SpinnerRing = ({ products, rotation, currentSlotIdx, phase }) => {
  const N = products.length;
  const wedge = 360 / N;
  const SIZE = 280;
  const RADIUS = SIZE / 2 - 14;
  const HUB_R = RADIUS * 0.32;
  const THUMB_DIST = RADIUS * 0.66; // distance from center to thumb center
  const THUMB_SIZE = Math.min(54, RADIUS * 0.42);

  return (
    <div className="spinner-ring-wrap" style={{ width: SIZE, height: SIZE }}>
      {/* Outer chassis */}
      <div className="spinner-chassis" style={{ width: SIZE, height: SIZE }} />

      {/* Top dispense arrow (fixed, doesn't rotate) */}
      <div className="spinner-arrow">
        <svg width="32" height="22" viewBox="0 0 32 22">
          <path d="M16 22 L4 4 L28 4 Z" fill="var(--eb-amber)" stroke="var(--eb-navy)" strokeWidth="1.2"/>
        </svg>
      </div>

      {/* Rotating ring */}
      <div className="spinner-ring" style={{
        width: SIZE - 18, height: SIZE - 18,
        transform: `rotate(${rotation}deg)`,
        transition: 'transform 1.45s cubic-bezier(0.22, 1.05, 0.32, 1)',
      }}>
        {/* Background disc */}
        <svg width={SIZE - 18} height={SIZE - 18} viewBox={`0 0 ${SIZE - 18} ${SIZE - 18}`} style={{ position: 'absolute', inset: 0 }}>
          <defs>
            <radialGradient id="discGrad" cx="0.5" cy="0.5" r="0.5">
              <stop offset="0%" stopColor="var(--eb-navy)"/>
              <stop offset="100%" stopColor="var(--eb-navy)"/>
            </radialGradient>
          </defs>
          <circle cx={(SIZE - 18) / 2} cy={(SIZE - 18) / 2} r={(SIZE - 18) / 2 - 2}
            fill="url(#discGrad)" stroke="rgba(var(--eb-amber-rgb), 0.3)" strokeWidth="1" />
          {/* wedge separators */}
          {Array.from({ length: N }).map((_, i) => {
            const a = (i * wedge - wedge / 2 - 90) * Math.PI / 180;
            const r = (SIZE - 18) / 2;
            const x1 = r + Math.cos(a) * HUB_R;
            const y1 = r + Math.sin(a) * HUB_R;
            const x2 = r + Math.cos(a) * (r - 4);
            const y2 = r + Math.sin(a) * (r - 4);
            return (
              <line key={i} x1={x1} y1={y1} x2={x2} y2={y2}
                stroke="rgba(var(--eb-amber-rgb), 0.18)" strokeWidth="1" />
            );
          })}
          {/* center hub */}
          <circle cx={(SIZE - 18) / 2} cy={(SIZE - 18) / 2} r={HUB_R}
            fill="#03152e" stroke="rgba(var(--eb-amber-rgb), 0.5)" strokeWidth="1.5"/>
          <circle cx={(SIZE - 18) / 2} cy={(SIZE - 18) / 2} r={HUB_R * 0.45}
            fill="var(--eb-amber)" opacity={phase === 'rotating' ? 0.8 : 0.4} />
        </svg>

        {/* Product thumbnails — positioned around the ring, counter-rotate so labels stay readable */}
        {products.map((p, i) => {
          const a = (i * wedge - 90) * Math.PI / 180; // -90 puts slot 0 at top
          const cx = (SIZE - 18) / 2 + Math.cos(a) * THUMB_DIST;
          const cy = (SIZE - 18) / 2 + Math.sin(a) * THUMB_DIST;
          const isActive = i === currentSlotIdx;
          return (
            <div key={p.id} className={`spinner-thumb ${isActive ? 'active' : ''}`}
              style={{
                width: THUMB_SIZE, height: THUMB_SIZE,
                left: cx - THUMB_SIZE / 2,
                top: cy - THUMB_SIZE / 2,
                transform: `rotate(${-rotation}deg)`,
                transition: 'transform 1.45s cubic-bezier(0.22, 1.05, 0.32, 1)',
              }}>
              <ProductThumb product={p} size={THUMB_SIZE} />
            </div>
          );
        })}
      </div>

      {/* Active wedge glow at top */}
      {phase === 'dispensing' && (
        <div className="spinner-dispense-flash" />
      )}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────
// RESULT — receipt + voucher claim
// ─────────────────────────────────────────────────────────────
const ScreenResult = ({ cart, results, total, onDone, onScanAgain, user, onRegisterPrompt }) => {
  const lines = Object.entries(cart).map(([pid, qty]) => ({
    p: MOCK_PRODUCTS.find(x => x.id === pid),
    qty,
  })).filter(l => l.p);

  const failedLines = lines.filter(l => (results[l.p.id]?.failed || 0) > 0);
  const totalFailedRupiah = failedLines.reduce((s, l) => s + l.p.price * (results[l.p.id]?.failed || 0), 0);
  const anyFailed = totalFailedRupiah > 0;
  const allFailed = lines.every(l => (results[l.p.id]?.success || 0) === 0);

  const headline = allFailed ? 'Pesanan gagal' : anyFailed ? 'Sebagian berhasil' : 'Berhasil';
  const headlineColor = allFailed ? 'var(--eb-danger)' : anyFailed ? 'var(--eb-warn)' : 'var(--eb-success)';
  const tagline = allFailed
    ? 'Voucher ganti rugi sudah kami siapkan.'
    : anyFailed
      ? `${failedLines.length} barang gagal keluar. Voucher penggantian sudah masuk.`
      : 'Semua barang sudah keluar dari kotak. Selamat menikmati 🐝';

  return (
    <div className="app">
      <div className="app-scroll">
        {/* Hero status */}
        <div style={{
          padding: '60px 24px 24px',
          background: anyFailed
            ? 'linear-gradient(180deg, var(--eb-warn-soft) 0%, var(--eb-bg) 100%)'
            : 'linear-gradient(180deg, var(--eb-success-soft) 0%, var(--eb-bg) 100%)',
          textAlign: 'center',
        }}>
          <div style={{
            width: 72, height: 72, borderRadius: 36,
            background: '#fff', display: 'inline-flex',
            alignItems: 'center', justifyContent: 'center',
            color: headlineColor,
            boxShadow: '0 8px 24px rgba(0,0,0,0.10)',
          }}>
            <Icon name={anyFailed ? 'info' : 'check'} size={36} stroke={3} />
          </div>
          <div style={{ fontSize: 22, fontWeight: 700, marginTop: 14, color: 'var(--eb-ink)', letterSpacing: '-0.02em' }}>
            {headline}
          </div>
          <div style={{ fontSize: 13, color: 'var(--eb-muted)', marginTop: 6, lineHeight: 1.45, textWrap: 'pretty' }}>
            {tagline}
          </div>
        </div>

        {/* Voucher claim card */}
        {anyFailed && (
          <div style={{ padding: '0 16px 14px' }}>
            <div className="voucher-card" style={{ background: 'linear-gradient(135deg, #FDF1D6 0%, #FFD58A 100%)' }}>
              <div className="voucher-icon">
                <Icon name="ticket" size={26} />
              </div>
              <div style={{ flex: 1, paddingLeft: 12 }}>
                <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--eb-navy)', textTransform: 'uppercase', letterSpacing: '0.06em', opacity: 0.65 }}>
                  Voucher penggantian
                </div>
                <div className="price" style={{ fontSize: 22, color: 'var(--eb-navy)', marginTop: 2 }}>
                  {fmtRp(totalFailedRupiah)}
                </div>
                <div style={{ fontSize: 11, color: 'var(--eb-navy)', opacity: 0.7, marginTop: 4 }}>
                  {user ? 'Sudah masuk ke akun kamu' : 'Daftar untuk mengklaim'}
                </div>
              </div>
            </div>
            {!user && (
              <button onClick={onRegisterPrompt} className="btn btn-secondary btn-block" style={{ marginTop: 10 }}>
                Daftar 30 detik — klaim voucher
                <Icon name="arrow-right" size={16} stroke={2.5} />
              </button>
            )}
          </div>
        )}

        {/* Items */}
        <div style={{ padding: '8px 16px 0' }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--eb-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 8 }}>
            Detail transaksi
          </div>
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            {lines.map((l, idx) => {
              const r = results[l.p.id] || { success: 0, failed: 0 };
              return (
                <div key={l.p.id} style={{
                  display: 'flex', alignItems: 'center', gap: 12, padding: 12,
                  borderTop: idx > 0 ? '1px solid var(--eb-line-soft)' : 'none',
                }}>
                  <ProductThumb product={l.p} size={44} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--eb-ink)' }}>{l.p.name}</div>
                    <div style={{ fontSize: 11, color: 'var(--eb-muted)', marginTop: 1 }}>
                      {l.qty}×
                    </div>
                  </div>
                  <div style={{ textAlign: 'right' }}>
                    <span className="price" style={{ fontSize: 13 }}>{fmtRp(l.p.price * l.qty)}</span>
                    {r.failed > 0 && (
                      <div style={{ fontSize: 10, color: 'var(--eb-danger)', marginTop: 2 }}>
                        {r.failed} gagal
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        {/* Meta */}
        <div style={{ padding: '14px 16px 0' }}>
          <div className="card" style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
            <Row k="No. Transaksi" v="EB-25140-A78K" />
            <Row k="Mesin" v="Kos Sakura · Lobby" />
            <Row k="Waktu" v="14 Mei 2026 · 23:18" />
            <Row k="Metode" v="QRIS · GoPay" />
            <div style={{ height: 1, background: 'var(--eb-line-soft)', margin: '4px 0' }} />
            <Row k={<b>Total bayar</b>} v={<b className="price" style={{ fontSize: 14 }}>{fmtRp(total)}</b>} />
          </div>
        </div>

        <div style={{ padding: '14px 16px 0' }}>
          <button className="btn btn-ghost btn-block" style={{ height: 42 }}>
            <Icon name="mail" size={16} />
            Kirim struk via Email
          </button>
        </div>

        <div style={{ height: 110 }} />
      </div>

      <div className="bottom-bar" style={{ display: 'flex', gap: 10 }}>
        <button className="btn btn-ghost" style={{ flex: 1 }} onClick={onDone}>
          Selesai
        </button>
        <button className="btn btn-primary" style={{ flex: 1.2 }} onClick={onScanAgain}>
          <Icon name="qr" size={16} />
          Pindai lagi
        </button>
      </div>
    </div>
  );
};

const Row = ({ k, v }) => (
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12, minHeight: 18 }}>
    <span style={{ color: 'var(--eb-muted)', whiteSpace: 'nowrap', flexShrink: 0 }}>{k}</span>
    <span style={{ color: 'var(--eb-ink)', fontWeight: 500, textAlign: 'right', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{v}</span>
  </div>
);

Object.assign(window, { ScreenCart, ScreenQris, ScreenDispense, ScreenResult, SpinnerRing });
