/* 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.
      if (window.history && window.history.replaceState) {
        window.history.replaceState(null, '', window.location.pathname);
      }
      return { t, c, k };
    }
  } catch (e) { /* ignore */ }
  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());
}

// Hash a plaintext token (base64url) into the SHA-256 base64url digest the
// server stores. Used for client-side verification in step 3; the Cloud
// Function callable replaces this in step 4.
async function sha256Base64Url(input) {
  const enc = new TextEncoder().encode(input);
  const buf = await crypto.subtle.digest('SHA-256', enc);
  // base64url
  const bytes = new Uint8Array(buf);
  let bin = '';
  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
  return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}

// === 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 });
      const view = res.data;
      // Shape: { code, amount, remaining, status, expiresAt (ms|null),
      //          productName, recipientName, codeLast4, redemptions[] }
      setCard({
        code: view.code,
        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');
    }
  }

  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
      });
      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}
            onScanAnother={() => { setCard(null); setLookup(null); setView('scan'); }}
            onApply={() => 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('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);

  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;
    }
    setPermState('granted');
    // Wait one tick so the viewfinder div is mounted.
    await new Promise(r => setTimeout(r, 0));
    try {
      const id = 'qr-camera';
      const inst = new window.Html5Qrcode(id);
      scannerRef.current = inst;
      await inst.start(
        { facingMode: 'environment' },
        { fps: 12, qrbox: { width: 240, height: 240 } },
        (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.
            inst.stop().then(() => inst.clear()).catch(() => {});
            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);
      setPermState('denied');
    }
  };

  React.useEffect(() => {
    // Auto-start when permission is already granted.
    if (permState === 'granted' && !scannerRef.current) startScanner();
    return () => {
      const s = scannerRef.current;
      if (s) {
        s.stop().then(() => s.clear()).catch(() => {});
        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 keypad ====================================================
function QrManualEntry({ initialCode, error, onSubmit, onBack }) {
  const [code, setCode] = React.useState(initialCode || '');

  const push = (ch) => setCode(c => (c + ch).slice(0, 12));
  const pop  = () => setCode(c => c.slice(0, -1));

  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>
      <div className="qr-code-display">
        {code || <span style={{color: 'var(--muted)'}}>•••••••</span>}
      </div>
      {error && <div className="qr-error">{error}</div>}
      <div className="qr-keypad">
        {QR_CODE_ALPHABET.split('').slice(0, 30).map(ch => (
          <button key={ch} onClick={() => push(ch)}>{ch}</button>
        ))}
        <button onClick={pop} className="qr-key-back"><Icon name="chev-left" size={14}/></button>
        <button onClick={() => onSubmit(code)}
          disabled={code.length < 4}
          className="qr-key-go">Bekræft</button>
      </div>
    </div>
  );
}

// === Result card ============================================================
function QrResultCard({ card, tenantId, onScanAnother, onApply }) {
  const remaining = Number(card.remaining) || 0;
  const amount = Number(card.amount) || 0;
  const isRedeemable = card.status === 'active' && remaining > 0;
  const expiresMs = card.expiresAt?.toMillis?.() || 0;
  const expired = expiresMs && expiresMs < Date.now();
  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>
      <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}>Anvend på bord →</button>}
        <button className="btn btn-soft" onClick={onScanAnother}>Scan et andet</button>
      </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(Math.max(0, 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 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);
  const cap = 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>
        <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>
        )}
        {cap <= 0 && (
          <div className="qr-confirm-warning">
            Intet at indløse — bordet har ingen åben regning.
          </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>
  );
}

// === Client-side transactional redemption (step 3 — Cloud Function takes
// over in step 4 to enforce server-resolved appliedBy and tighter validation)
async function runGiftTransaction(fb, {
  giftRef, orderRef, tenantId, card, pickedTable, staffMember,
  idempotencyKey, appliedByUid,
}) {
  // Firestore SDK runTransaction is not currently exposed on window.fb,
  // so we do best-effort atomic read+write using getDoc + setDoc with a
  // version check. Step 4 replaces this with a proper Cloud Function
  // runTransaction.
  const giftSnap = await fb.getDoc(giftRef);
  if (!giftSnap.exists()) throw new Error('Gavekortet findes ikke længere.');
  const gift = giftSnap.data();
  if (gift.status !== 'active') throw new Error('Gavekortet er ikke aktivt.');
  const remaining = Number(gift.remaining) || 0;
  if (remaining <= 0) throw new Error('Gavekortet er opbrugt.');
  const expiresMs = gift.expiresAt?.toMillis?.() || 0;
  if (expiresMs && expiresMs < Date.now()) throw new Error('Gavekortet er udløbet.');

  // Idempotency: same key → return prior result.
  const prior = (gift.redemptions || []).find(r => r.id === idempotencyKey);
  if (prior) {
    return { applied: prior.amount, newRemaining: remaining, tableName: pickedTable.name };
  }

  // Load (or create) the order.
  let orderSnap = await fb.getDoc(orderRef);
  let order;
  if (!orderSnap.exists()) {
    order = {
      tableId: pickedTable.id, tableName: pickedTable.name,
      status: 'open', items: [], discounts: [],
      openedAt: new Date().toISOString(),
      openedBy: staffMember.name,
      lastUpdatedAt: new Date().toISOString(),
    };
    await fb.setDoc(orderRef, order);
  } else {
    order = orderSnap.data();
    if (order.status !== 'open') throw new Error('Regningen er allerede lukket.');
  }
  const subtotal = (order.items || []).reduce((a, x) => a + (x.price || 0) * (x.qty || 1), 0);
  const existingDisc = (order.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
  const orderTotal = Math.max(0, subtotal - existingDisc);
  const cap = Math.min(remaining, orderTotal);
  if (cap <= 0) throw new Error('Bordet har ingen regning at trække fra.');

  const appliedAt = new Date().toISOString();
  const redemption = {
    id: idempotencyKey,
    kind: 'redeem',
    orderId: pickedTable.id,
    tableName: pickedTable.name,
    amount: cap,
    appliedBy: staffMember.name,
    appliedByUid,
    appliedAt,
  };
  const discount = {
    kind: 'giftCard',
    code: card.code,
    redemptionId: idempotencyKey,
    amount: cap,
    appliedBy: staffMember.name,
    appliedByUid,
    appliedAt,
  };
  const newRemaining = remaining - cap;
  const newStatus = newRemaining <= 0 ? 'redeemed' : 'active';

  await fb.updateDoc(giftRef, {
    remaining: newRemaining,
    status: newStatus,
    redemptions: [...(gift.redemptions || []), redemption],
  });
  await fb.updateDoc(orderRef, {
    discounts: [...(order.discounts || []), discount],
    lastUpdatedAt: appliedAt,
  });

  return { applied: cap, newRemaining, tableName: pickedTable.name };
}

window.QrApp = QrApp;
