/* QR gift-card scanner / verifier / redemption page.
   Lives at /qr. Gated behind the same PosAuth pattern as /pos
   (device pairing via manager email+password, then staff PIN).
   v1 ships with: fragment-strip-on-load (security), manual-entry
   keypad, code lookup, client-side transactional redemption,
   table picker. Camera scanner + Cloud Function callables are
   added in later steps so each step can ship independently.
*/

// === FRAGMENT-STRIP-ON-LOAD =================================================
// Synchronously parse the QR URL fragment BEFORE React mounts and before any
// analytics/observability SDK can ever observe location.href. The plaintext
// token only ever lives in this module-scope variable; the URL bar is clean
// from frame 1.
const QR_INITIAL_FRAGMENT = (() => {
  if (typeof window === 'undefined') return null;
  try {
    const hash = window.location.hash || '';
    if (!hash) return null;
    // Expect "#t=...&c=...&k=..."
    const trimmed = hash.replace(/^#/, '');
    const params = new URLSearchParams(trimmed);
    const t = params.get('t');
    const c = params.get('c');
    const k = params.get('k');
    if (t && c && k && /^[A-Za-z0-9_-]{20,}$/.test(k)) {
      // Strip the fragment immediately — no React, no later scripts will
      // see it. Keep the query string (carries ?table= from the POS).
      if (window.history && window.history.replaceState) {
        window.history.replaceState(null, '', window.location.pathname + window.location.search);
      }
      return { t, c, k };
    }
  } catch (e) { /* ignore */ }
  return null;
})();

// Arriving from a POS table ("Scan gavekort" in the order header) carries
// the table along — redemption then skips the table picker entirely and
// applies straight to that bill.
const QR_PREF_TABLE = (() => {
  try {
    const p = new URLSearchParams(window.location.search);
    const id = p.get('table');
    if (!id) return null;
    return { id, name: p.get('tableName') || id };
  } catch (e) { return null; }
})();

// === Helpers ================================================================
const QR_STAFF_KEY = 'seatiq.pos.staff';

function fmtKr(n) {
  return (Number(n) || 0).toLocaleString('da-DK') + ' kr';
}

// Crockford-style alphabet for typed entries — excludes confusable I/L/O/0/1.
const QR_CODE_ALPHABET = '23456789ABCDEFGHJKMNPQRSTUVWXYZ';
function isValidCodeChar(ch) {
  return QR_CODE_ALPHABET.includes(ch.toUpperCase());
}

// === Root ===================================================================
function QrApp() {
  const auth = window.useAuth();
  const [staffMember, setStaffMember] = React.useState(() => {
    try {
      const raw = localStorage.getItem(QR_STAFF_KEY);
      return raw ? JSON.parse(raw) : null;
    } catch (e) { return null; }
  });

  if (!auth || !auth.tenantId) {
    return (
      <div className="qr-shell">
        <div className="auth-spinner"/>
      </div>
    );
  }

  if (!staffMember) {
    const PosStaffLogin = window.PosStaffLogin;
    if (!PosStaffLogin) return <div className="qr-shell">Indlæser…</div>;
    return (
      <PosStaffLogin onLogin={(m) => {
        try { localStorage.setItem(QR_STAFF_KEY, JSON.stringify(m)); } catch (e) {}
        setStaffMember(m);
      }}/>
    );
  }

  return <QrMain staffMember={staffMember} onLogoutStaff={() => {
    try { localStorage.removeItem(QR_STAFF_KEY); } catch (e) {}
    setStaffMember(null);
  }} initialQr={QR_INITIAL_FRAGMENT}/>;
}

// === Main ===================================================================
function QrMain({ staffMember, onLogoutStaff, initialQr }) {
  const auth = window.useAuth();
  const tenantId = auth.tenantId;
  // 'scan' | 'manual' | 'result' | 'tablepick' | 'confirm' | 'done'
  const [view, setView] = React.useState('scan');
  const [lookup, setLookup] = React.useState(null); // { code, token? }
  const [card, setCard] = React.useState(null);     // resolved giftIssued doc
  const [lookupError, setLookupError] = React.useState('');
  const [pickedTable, setPickedTable] = React.useState(null);
  const [pickedOrder, setPickedOrder] = React.useState(null);
  const [applying, setApplying] = React.useState(false);
  const [applyError, setApplyError] = React.useState('');
  const [lastResult, setLastResult] = React.useState(null); // { applied, newRemaining, tableName }

  // If we landed with a QR fragment, jump straight to verification.
  React.useEffect(() => {
    if (initialQr && initialQr.t && initialQr.c && initialQr.k) {
      if (initialQr.t !== tenantId) {
        setLookupError('Dette gavekort tilhører en anden restaurant.');
        return;
      }
      verifyAndShow({ code: initialQr.c, token: initialQr.k });
    }
  }, [initialQr, tenantId]);

  async function verifyAndShow({ code, token }) {
    setLookupError('');
    setCard(null);
    setLookup({ code, token });
    const fb = window.fb;
    try {
      // Use the callable so the server enforces tenant-membership,
      // rate-limit and constant-time token compare.
      const verify = fb.httpsCallable(fb.functions, 'verifyGiftCard');
      const res = await verify({ tenantId, code, token, staffName: staffMember?.name || '' });
      const view = res.data;
      // Shape: { code, amount, remaining, status, expiresAt (ms|null),
      //          productName, recipientName, codeLast4, redemptions[] }
      setCard({
        code: view.code,
        kind: view.kind || 'fixed',
        percent: view.percent || null,
        amount: view.amount,
        remaining: view.remaining,
        status: view.status,
        productName: view.productName,
        recipient: { name: view.recipientName },
        expiresAt: view.expiresAt ? { toMillis: () => view.expiresAt, toDate: () => new Date(view.expiresAt) } : null,
        redemptions: (view.redemptions || []).map(r => ({
          ...r,
          appliedAt: r.appliedAt ? { toMillis: () => r.appliedAt, toDate: () => new Date(r.appliedAt) } : null,
        })),
        token, // kept in memory only — passed to redeem callable.
      });
      setView('result');
    } catch (e) {
      console.error(e);
      const msg = e?.message || 'Kunne ikke slå gavekortet op.';
      setLookupError(msg);
      setView('manual');
    }
  }

  // POS handoff: fetch the carried table's doc + live order and jump
  // straight to the confirm sheet — no table picker.
  async function applyToPrefTable() {
    const fb = window.fb;
    try {
      const [tSnap, oSnap] = await Promise.all([
        fb.getDoc(fb.doc(fb.db, 'tenants', tenantId, 'tables', QR_PREF_TABLE.id)),
        fb.getDoc(fb.doc(fb.db, 'tenants', tenantId, 'orders', QR_PREF_TABLE.id)),
      ]);
      const tbl = tSnap.exists()
        ? { id: tSnap.id, ...tSnap.data() }
        : { id: QR_PREF_TABLE.id, name: QR_PREF_TABLE.name };
      setPickedTable(tbl);
      setPickedOrder(oSnap.exists() ? { id: oSnap.id, ...oSnap.data() } : null);
      setView('confirm');
    } catch (e) {
      console.error(e);
      setView('tablepick'); // fall back to manual pick
    }
  }

  async function applyToTable() {
    if (!card || !pickedTable) return;
    setApplying(true);
    setApplyError('');
    const fb = window.fb;
    try {
      const subtotal = (pickedOrder?.items || []).reduce((a, x) => a + (x.price || 0) * (x.qty || 1), 0);
      const existingDisc = (pickedOrder?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
      const orderTotal = Math.max(0, subtotal - existingDisc);
      const cap = Math.min(card.remaining || 0, orderTotal > 0 ? orderTotal : (card.remaining || 0));
      const idempotencyKey = (crypto.randomUUID && crypto.randomUUID()) ||
        ('rd' + Date.now() + Math.random().toString(36).slice(2));
      const redeem = fb.httpsCallable(fb.functions, 'redeemGiftCard');
      const res = await redeem({
        tenantId,
        code: card.code,
        token: card.token || null,
        orderId: pickedTable.id,
        tableName: pickedTable.name,
        amountToApply: cap || card.remaining,
        idempotencyKey,
        createIfMissing: !pickedOrder, // staff confirmed empty-table flow
        staffName: staffMember?.name || '',
      });
      setLastResult(res.data);
      setView('done');
    } catch (e) {
      console.error(e);
      setApplyError(e?.message || 'Indløsning fejlede.');
    } finally {
      setApplying(false);
    }
  }

  return (
    <div className="qr-shell">
      <header className="qr-topbar">
        <div className="qr-topbar-brand">SEATIQ QR<span style={{color: 'var(--accent)'}}>.</span></div>
        <div className="qr-topbar-staff">
          <span className="qr-topbar-staff-name">{staffMember.name}</span>
          <button className="btn btn-ghost btn-sm" onClick={onLogoutStaff}>Skift</button>
        </div>
      </header>

      <main className="qr-main">
        {view === 'scan' && (
          <QrScanLanding
            onPickManual={() => setView('manual')}
            onScanned={(parsed) => {
              if (parsed.t !== tenantId) {
                setLookupError('Dette gavekort tilhører en anden restaurant.');
                setView('manual');
                return;
              }
              verifyAndShow({ code: parsed.c, token: parsed.k });
            }}/>
        )}

        {view === 'manual' && (
          <QrManualEntry
            initialCode={lookup?.code || ''}
            error={lookupError}
            onBack={() => { setLookupError(''); setView('scan'); }}
            onSubmit={(code) => verifyAndShow({ code: code.toUpperCase(), token: null })}/>
        )}

        {view === 'result' && card && (
          <QrResultCard card={card} tenantId={tenantId}
            applyLabel={QR_PREF_TABLE ? `Anvend på ${QR_PREF_TABLE.name} →` : 'Anvend på bord →'}
            onScanAnother={() => { setCard(null); setLookup(null); setView('scan'); }}
            onApply={() => QR_PREF_TABLE ? applyToPrefTable() : setView('tablepick')}/>
        )}

        {view === 'tablepick' && card && (
          <QrTablePicker tenantId={tenantId}
            onBack={() => setView('result')}
            onPick={async (tbl, ord) => {
              setPickedTable(tbl); setPickedOrder(ord);
              setView('confirm');
            }}/>
        )}

        {view === 'confirm' && card && pickedTable && (
          <QrConfirmSheet
            card={card}
            table={pickedTable}
            order={pickedOrder}
            applying={applying}
            error={applyError}
            onCancel={() => { setApplyError(''); setView(QR_PREF_TABLE ? 'result' : 'tablepick'); }}
            onConfirm={applyToTable}/>
        )}

        {view === 'done' && lastResult && (
          <QrDoneCard
            result={lastResult}
            onScanAnother={() => {
              setCard(null); setLookup(null);
              setPickedTable(null); setPickedOrder(null);
              setLastResult(null);
              setView('scan');
            }}/>
        )}
      </main>
    </div>
  );
}

// === Scan landing (camera) — step 7 swaps in real html5-qrcode ============
function QrScanLanding({ onScanned, onPickManual }) {
  const [permState, setPermState] = React.useState('idle');
  const containerRef = React.useRef(null);
  const scannerRef = React.useRef(null);
  // Guards the gap between "start requested" and "instance stored" — on
  // iPad the permission API reports granted, so the button AND the
  // auto-start effect could both launch a scanner into the same
  // container (two stacked camera feeds).
  const startingRef = React.useRef(false);

  // html5-qrcode THROWS synchronously ("Cannot stop, scanner is not
  // running or paused") if stop() is called on an already-stopped
  // instance — which happens when a successful scan stops the camera
  // and the unmount cleanup then stops it again. Only stop when the
  // scanner reports SCANNING (2) or PAUSED (3).
  const safeStop = (s) => {
    if (!s) return;
    try {
      const st = s.getState ? s.getState() : 2;
      if (st === 2 || st === 3) {
        s.stop().then(() => { try { s.clear(); } catch (e) {} }).catch(() => {});
        return;
      }
    } catch (e) { /* fall through */ }
    try { s.clear(); } catch (e) {}
  };

  React.useEffect(() => {
    // Camera permission state machine: idle → prompting → granted/denied/unavailable.
    let cancelled = false;
    (async () => {
      if (!navigator.mediaDevices?.getUserMedia) {
        setPermState('unavailable');
        return;
      }
      if (navigator.permissions?.query) {
        try {
          const status = await navigator.permissions.query({ name: 'camera' });
          if (cancelled) return;
          if (status.state === 'granted') setPermState('granted');
          else if (status.state === 'denied') setPermState('denied');
          else setPermState('prompt');
        } catch (e) {
          setPermState('prompt'); // not supported on some iOS — fall through to user-gesture
        }
      } else {
        setPermState('prompt');
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const startScanner = async () => {
    if (typeof Html5Qrcode === 'undefined') {
      setPermState('unavailable');
      return;
    }
    // Idempotent: a second call (button + effect racing) is a no-op.
    if (startingRef.current || scannerRef.current) return;
    startingRef.current = true;
    setPermState('granted');
    // Wait one tick so the viewfinder div is mounted.
    await new Promise(r => setTimeout(r, 0));
    if (scannerRef.current) { startingRef.current = false; return; }
    try {
      const id = 'qr-camera';
      // Belt & braces: clear any stale video left in the container.
      const host = document.getElementById(id);
      if (host) host.innerHTML = '';
      const inst = new window.Html5Qrcode(id);
      scannerRef.current = inst;
      await inst.start(
        { facingMode: 'environment' },
        // No qrbox: scan the full frame and skip the shaded focus-box
        // overlay entirely.
        { fps: 12 },
        (decodedText) => {
          try {
            const u = new URL(decodedText);
            if (!u.hash) throw new Error('no fragment');
            const params = new URLSearchParams(u.hash.replace(/^#/, ''));
            const t = params.get('t'); const c = params.get('c'); const k = params.get('k');
            if (!t || !c || !k) throw new Error('missing fields');
            // Stop scanner and bubble up.
            safeStop(inst);
            scannerRef.current = null;
            try { navigator.vibrate?.(10); } catch (e) {}
            onScanned({ t, c, k });
          } catch (e) {
            // ignore non-gift QRs
          }
        },
        () => { /* per-frame scan miss — ignore */ }
      );
    } catch (e) {
      console.error(e);
      scannerRef.current = null;
      setPermState('denied');
    } finally {
      startingRef.current = false;
    }
  };

  React.useEffect(() => {
    // Auto-start when permission is already granted (startScanner is
    // idempotent, so a button-triggered start can't double it).
    if (permState === 'granted') startScanner();
    return () => {
      safeStop(scannerRef.current);
      scannerRef.current = null;
    };
  }, [permState]);

  return (
    <div className="qr-scan-landing">
      {permState === 'granted' && (
        <div id="qr-camera" ref={containerRef} className="qr-viewfinder"/>
      )}
      {(permState === 'idle' || permState === 'prompt') && (
        <div className="qr-permission-card">
          <Icon name="bolt" size={40}/>
          <h2>Scan et gavekort</h2>
          <p>Tryk for at åbne kameraet og scanne gavekortets QR-kode.</p>
          <button className="btn btn-accent btn-lg" onClick={startScanner}>
            Åbn kamera
          </button>
        </div>
      )}
      {permState === 'denied' && (
        <div className="qr-permission-card qr-permission-blocked">
          <h2>Kameraet er blokeret</h2>
          <p>For at scanne et gavekort skal kameraet have lov på denne enhed:</p>
          <ol style={{textAlign: 'left', maxWidth: 360, margin: '12px auto'}}>
            <li>Indstillinger → Safari → Kamera → <b>Tillad</b>.</li>
            <li>Genindlæs denne side.</li>
          </ol>
          <button className="btn btn-soft" onClick={() => window.location.reload()}>Genindlæs</button>
        </div>
      )}
      {permState === 'unavailable' && (
        <div className="qr-permission-card">
          <h2>Intet kamera tilgængeligt</h2>
          <p>Du kan indtaste gavekortets kode manuelt i stedet.</p>
        </div>
      )}

      <button className="btn btn-soft qr-manual-link" onClick={onPickManual}>
        <Icon name="edit" size={14}/> Indtast kode i stedet
      </button>
    </div>
  );
}

// === Manual entry — real input (PC keyboard / native mobile keyboard)
// with the on-screen keypad kept as a touch-friendly supplement.
function QrManualEntry({ initialCode, error, onSubmit, onBack }) {
  const [code, setCode] = React.useState(initialCode || '');
  const inputRef = React.useRef(null);

  // Uppercase + restrict to the code alphabet. Hyphen is allowed for
  // legacy codes (e.g. HJ-XXXX) even though new codes never contain it.
  const clean = (raw) => String(raw || '')
    .toUpperCase()
    .split('')
    .filter(ch => QR_CODE_ALPHABET.includes(ch) || ch === '-')
    .join('')
    .slice(0, 12);

  const push = (ch) => { setCode(c => clean(c + ch)); inputRef.current?.focus(); };
  const pop  = () => { setCode(c => c.slice(0, -1)); inputRef.current?.focus(); };
  const submit = () => { if (code.length >= 4) onSubmit(code); };

  return (
    <div className="qr-manual">
      <button className="btn btn-ghost btn-sm" onClick={onBack}>
        <Icon name="chev-left" size={14}/> Tilbage
      </button>
      <h2 style={{fontFamily: 'var(--display)', fontWeight: 800, fontSize: 24, letterSpacing: '-0.02em', textAlign: 'center', marginTop: 12}}>
        Indtast gavekort-kode
      </h2>
      <p style={{textAlign: 'center', color: 'var(--muted)', fontSize: 14, marginTop: 4}}>
        Står på gavekortet og i bekræftelses-mailen.
      </p>
      <input
        ref={inputRef}
        className="qr-code-input"
        value={code}
        onChange={e => setCode(clean(e.target.value))}
        onKeyDown={e => { if (e.key === 'Enter') submit(); }}
        placeholder="F.eks. X7K4P2QA"
        autoFocus
        autoComplete="off"
        autoCapitalize="characters"
        autoCorrect="off"
        spellCheck={false}
        inputMode="text"
        aria-label="Gavekort-kode"
      />
      {error && <div className="qr-error">{error}</div>}
      <div className="qr-keypad">
        {QR_CODE_ALPHABET.split('').slice(0, 30).map(ch => (
          <button key={ch} type="button" tabIndex={-1}
            onPointerDown={(e) => { e.preventDefault(); push(ch); }}>{ch}</button>
        ))}
        <button type="button" tabIndex={-1} className="qr-key-back"
          onPointerDown={(e) => { e.preventDefault(); pop(); }}><Icon name="chev-left" size={14}/></button>
        <button type="button" onClick={submit}
          disabled={code.length < 4}
          className="qr-key-go">Bekræft</button>
      </div>
    </div>
  );
}

// === Result card ============================================================
function QrResultCard({ card, tenantId, onScanAnother, onApply, applyLabel = 'Anvend på bord →' }) {
  const isPercent = card.kind === 'percent';
  const remaining = Number(card.remaining) || 0;
  const amount = Number(card.amount) || 0;
  const expiresMs = card.expiresAt?.toMillis?.() || 0;
  const expired = !!(expiresMs && expiresMs < Date.now());
  // Expired cards are NOT redeemable even if status still says active.
  // Percent cards have no balance — redeemability is just active + not
  // expired (single use, status flips to redeemed afterwards).
  const isRedeemable = card.status === 'active' && !expired && (isPercent || remaining > 0);
  return (
    <div className="qr-result-card">
      <div className={`qr-result-badge ${isRedeemable ? 'ok' : 'used'}`}>
        {isRedeemable ? 'GYLDIGT' : (expired ? 'UDLØBET' : 'BRUGT OP')}
      </div>
      <div className="qr-result-code">{card.code}</div>
      {isPercent ? (
        <>
          <div className="qr-result-amount">{card.percent}%</div>
          <div className="qr-result-meta">rabat på regningen</div>
        </>
      ) : (
        <>
          <div className="qr-result-amount">{fmtKr(remaining)}</div>
          <div className="qr-result-meta">af {fmtKr(amount)} oprindelig</div>
        </>
      )}
      {card.recipient?.name && (
        <div className="qr-result-recipient">Til: <b>{card.recipient.name}</b></div>
      )}
      {expired && card.expiresAt?.toDate && (
        <div className="qr-result-expiry">Udløb: {new Intl.DateTimeFormat('da-DK', {dateStyle: 'long'}).format(card.expiresAt.toDate())}</div>
      )}
      {!isRedeemable && card.redemptions?.length > 0 && (
        <div className="qr-result-history">
          <h4>Sidste indløsning</h4>
          {card.redemptions.slice(-3).reverse().map((r, i) => (
            <div key={i} className="qr-result-history-row">
              <span>{fmtKr(r.amount)} · {r.tableName}</span>
              <span style={{color: 'var(--muted)'}}>{r.appliedBy}</span>
            </div>
          ))}
        </div>
      )}
      <div className="qr-result-actions">
        {isRedeemable && <button className="btn btn-accent btn-lg" onClick={onApply}>{applyLabel}</button>}
        <button className="btn btn-soft" onClick={onScanAnother}>Scan et andet</button>
        <a className="btn btn-ghost" href="/pos" style={{justifyContent: 'center', textDecoration: 'none'}}>
          Tilbage til POS
        </a>
      </div>
    </div>
  );
}

// === Table picker ===========================================================
function QrTablePicker({ tenantId, onBack, onPick }) {
  const fb = window.fb;
  const [tables, setTables] = React.useState([]);
  const [orders, setOrders] = React.useState({});
  React.useEffect(() => {
    if (!tenantId) return;
    const u1 = fb.onSnapshot(
      fb.collection(fb.db, 'tenants', tenantId, 'tables'),
      (snap) => {
        const out = []; snap.forEach(d => out.push({ id: d.id, ...d.data() }));
        setTables(out);
      }
    );
    const u2 = fb.onSnapshot(
      fb.collection(fb.db, 'tenants', tenantId, 'orders'),
      (snap) => {
        const map = {}; snap.forEach(d => { map[d.id] = { id: d.id, ...d.data() }; });
        setOrders(map);
      }
    );
    return () => { u1(); u2(); };
  }, [tenantId]);

  const withOrder  = tables.filter(t => orders[t.id]);
  const withoutOrder = tables.filter(t => !orders[t.id]);

  return (
    <div className="qr-table-picker">
      <button className="btn btn-ghost btn-sm" onClick={onBack}>
        <Icon name="chev-left" size={14}/> Tilbage
      </button>
      <h2 style={{fontFamily: 'var(--display)', fontWeight: 800, fontSize: 22, letterSpacing: '-0.02em', marginTop: 8}}>
        Vælg bord
      </h2>
      {withOrder.length > 0 && (
        <>
          <h4 className="qr-table-section">Åbne regninger</h4>
          <div className="qr-table-grid">
            {withOrder.map(t => {
              const ord = orders[t.id];
              const sub = (ord.items || []).reduce((a, x) => a + (x.price || 0) * (x.qty || 1), 0);
              const disc = (ord.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
              return (
                <button key={t.id} className="qr-table-card open" onClick={() => onPick(t, ord)}>
                  <div className="qr-table-name">{t.name}</div>
                  <div className="qr-table-bill">{fmtKr(sub - disc)}</div>
                </button>
              );
            })}
          </div>
        </>
      )}
      {withoutOrder.length > 0 && (
        <>
          <h4 className="qr-table-section">Tomme borde</h4>
          <div className="qr-table-grid">
            {withoutOrder.map(t => (
              <button key={t.id} className="qr-table-card empty"
                onClick={() => {
                  if (!window.confirm(`${t.name} har ingen åben regning. Skal jeg åbne en og lægge gavekortet på som forskud?`)) return;
                  onPick(t, null);
                }}>
                <div className="qr-table-name">{t.name}</div>
                <div className="qr-table-bill" style={{color: 'var(--muted)'}}>Tomt</div>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// === Confirm sheet ==========================================================
function QrConfirmSheet({ card, table, order, applying, error, onCancel, onConfirm }) {
  const isPercent = card.kind === 'percent';
  const subtotal = (order?.items || []).reduce((a, x) => a + (x.price || 0) * (x.qty || 1), 0);
  const existingDiscounts = (order?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
  const orderTotal = Math.max(0, subtotal - existingDiscounts);
  // Percent card: X% of the outstanding total (needs a bill). Value card
  // on an empty bill = prepaid (full balance, table starts at -X);
  // otherwise capped at the outstanding total, leftover stays on card.
  const prepaid = !isPercent && orderTotal === 0;
  const cap = isPercent
    ? Math.round(orderTotal * (card.percent || 0) / 100)
    : (prepaid ? (card.remaining || 0) : Math.min(card.remaining || 0, orderTotal));
  const last4 = (card.code || '').slice(-4);

  return (
    <div className="qr-confirm-sheet">
      <div className="qr-confirm-header">
        <div className="qr-confirm-eyebrow">Bekræft indløsning</div>
        <div className="qr-confirm-recipient">Til: <b>{card.recipient?.name || '—'}</b> · …{last4}</div>
      </div>
      <div className="qr-confirm-table">
        <div className="qr-confirm-table-name">{table.name}</div>
        {isPercent ? (
          <>
            <div className="qr-confirm-math">
              {card.percent}% af <b>{fmtKr(orderTotal)}</b>
              {' → '}
              <b>{fmtKr(Math.max(0, orderTotal - cap))}</b>
            </div>
            <div className="qr-confirm-applied">Trækkes: <b>−{fmtKr(cap)}</b></div>
            {orderTotal <= 0 && (
              <div className="qr-confirm-warning">Procent-gavekort kræver en regning at trække fra.</div>
            )}
          </>
        ) : prepaid ? (
          <>
            <div className="qr-confirm-math">
              Bordet starter på <b>−{fmtKr(cap)}</b>
            </div>
            <div className="qr-confirm-leftover">
              Beløbet modregnes automatisk når der bestilles på bordet.
            </div>
          </>
        ) : (
          <>
            <div className="qr-confirm-math">
              Regning: <b>{fmtKr(orderTotal)}</b>
              {' → '}
              <b>{fmtKr(Math.max(0, orderTotal - cap))}</b>
            </div>
            <div className="qr-confirm-applied">Trækkes: <b>−{fmtKr(cap)}</b></div>
            {cap < (card.remaining || 0) && (
              <div className="qr-confirm-leftover">{fmtKr((card.remaining || 0) - cap)} bliver tilbage på gavekortet</div>
            )}
          </>
        )}
      </div>
      {error && <div className="qr-error">{error}</div>}
      <div className="qr-confirm-actions">
        <button className="btn btn-ghost" onClick={onCancel} disabled={applying}>Annuller</button>
        <button className="btn btn-accent btn-lg" onClick={onConfirm} disabled={applying || cap <= 0}>
          {applying ? 'Indløser…' : `Anvend ${fmtKr(cap)}`}
        </button>
      </div>
    </div>
  );
}

// === Done card ==============================================================
function QrDoneCard({ result, onScanAnother }) {
  return (
    <div className="qr-done">
      <div className="qr-done-check">
        <Icon name="check" size={48} stroke={3}/>
      </div>
      <h2>Gavekort anvendt</h2>
      <p>{fmtKr(result.applied)} er trukket fra <b>{result.tableName}</b>.</p>
      {result.newRemaining > 0
        ? <p style={{color: 'var(--muted)'}}>{fmtKr(result.newRemaining)} tilbage på gavekortet.</p>
        : <p style={{color: 'var(--muted)'}}>Gavekortet er nu opbrugt.</p>}
      <div className="qr-done-actions">
        <button className="btn btn-accent" onClick={onScanAnother}>Scan et andet</button>
        <a className="btn btn-soft" href="/pos">→ POS</a>
      </div>
    </div>
  );
}

window.QrApp = QrApp;
