/* POS — seatiq.ooniq.app/pos
   Touch-first point-of-sale: layout your floor plan from scratch, tap a
   table to open its bill, add items by category, see the running total.

   Builds on the same Firebase tenant as the dashboard: shares the
   tables collection so booking and POS see the same physical tables,
   and stores active bills under /tenants/{id}/orders/{tableId}.

   Login is the standard manager email/password (AuthGate). On top of
   that, an optional PIN identifies which staff member is taking the
   order — purely for audit, not security. PIN management lives under
   Settings on the dashboard. */

const { useState: posS, useEffect: posE, useMemo: posM, useRef: posR } = React;

function PosApp() {
  const fb = window.fb;
  const auth = window.useAuth();
  const tenantId = auth?.tenantId;
  const [view, setView] = posS('floor'); // 'floor' | 'editor' | 'order'
  const [activeTable, setActiveTable] = posS(null);
  const [staffMember, setStaffMember] = posS(() => {
    try { return JSON.parse(localStorage.getItem('seatiq.pos.staff') || 'null'); } catch (e) { return null; }
  });
  // Sell gift cards from POS — existing products only, no product
  // creation (that's manager-only on the dashboard).
  const [sellGift, setSellGift] = posS(false);
  const [giftProducts, setGiftProducts] = posS([]);
  const [giftIssued, setGiftIssued] = posS([]);
  posE(() => {
    if (!tenantId) return;
    const u1 = fb.onSnapshot(fb.collection(fb.db, 'tenants', tenantId, 'giftProducts'),
      (s) => { const o = []; s.forEach(d => o.push({ id: d.id, ...d.data() })); setGiftProducts(o); }, () => {});
    const u2 = fb.onSnapshot(fb.collection(fb.db, 'tenants', tenantId, 'giftIssued'),
      (s) => { const o = []; s.forEach(d => o.push({ id: d.id, ...d.data() })); setGiftIssued(o); }, () => {});
    return () => { u1(); u2(); };
  }, [tenantId]);

  // Same pending→CF mint flow the dashboard uses. staffName is the PIN
  // operator (display only); createdBy is the shared manager uid.
  const sellGiftCard = async (sale, onMintedCode) => {
    if (!tenantId || !auth?.user?.uid) throw new Error('Ikke logget ind');
    const isPercent = sale.kind === 'percent';
    const tempId = 'gp' + Date.now() + Math.random().toString(36).slice(2, 6);
    const pendingRef = fb.doc(fb.db, 'tenants', tenantId, 'giftPending', tempId);
    await fb.setDoc(pendingRef, {
      productId: sale.productId || null,
      productName: sale.productName || null,
      kind: sale.kind || 'fixed',
      amount: isPercent ? 0 : sale.amount,
      percent: isPercent ? sale.percent : null,
      buyer: sale.buyer || { name: '', email: '' },
      recipient: sale.recipient || { name: '', email: '' },
      message: sale.message || '',
      paymentMethod: sale.paymentMethod || 'Kort',
      staffName: staffMember?.name || '',
      status: 'pending',
      createdBy: auth.user.uid,
      createdAt: fb.serverTimestamp(),
    });
    return new Promise((resolve, reject) => {
      const resultRef = fb.doc(fb.db, 'tenants', tenantId, 'giftPendingResults', tempId);
      const timeout = setTimeout(() => { unsubR(); unsubP(); reject(new Error('Gavekortet kunne ikke oprettes — prøv igen.')); }, 30000);
      const unsubR = fb.onSnapshot(resultRef, (snap) => {
        if (snap.exists()) { const code = snap.data().code; clearTimeout(timeout); unsubR(); unsubP(); onMintedCode && onMintedCode(code); resolve(code); }
      }, () => {});
      const unsubP = fb.onSnapshot(pendingRef, (snap) => {
        if (snap.exists() && snap.data().status === 'failed') { clearTimeout(timeout); unsubR(); unsubP(); reject(new Error(snap.data().errorMessage || 'Gavekortet blev afvist.')); }
      }, () => {});
    });
  };

  posE(() => {
    document.documentElement.dataset.theme = 'light';
  }, []);

  posE(() => {
    if (staffMember) {
      localStorage.setItem('seatiq.pos.staff', JSON.stringify(staffMember));
    } else {
      localStorage.removeItem('seatiq.pos.staff');
    }
  }, [staffMember]);

  // The stored staff identity is client-side state — re-validate it
  // against the live /staff collection so a deleted or renamed staff
  // member can't keep operating under a stale localStorage blob.
  // NOTE: the session object deliberately does NOT contain the PIN
  // (never persisted client-side), so only existence + name can be
  // compared here — comparing the PIN kicked every valid login back
  // to the PIN screen as soon as this lookup resolved.
  posE(() => {
    if (!tenantId || !staffMember?.id) return;
    let cancelled = false;
    fb.getDoc(fb.doc(fb.db, 'tenants', tenantId, 'staff', staffMember.id))
      .then(snap => {
        if (cancelled) return;
        if (!snap.exists()) { setStaffMember(null); return; }
        const live = snap.data();
        if (staffMember.name && live.name !== staffMember.name) {
          setStaffMember(null);
        }
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [tenantId, staffMember?.id]);

  // If no staff member is logged in, show the PIN screen first.
  if (!staffMember) {
    return <PosStaffLogin onLogin={setStaffMember}/>;
  }

  return (
    <div className={`pos-shell view-${view}`}>
      <PosTopBar
        staffMember={staffMember}
        onLogoutStaff={() => setStaffMember(null)}
        onSignOut={auth?.signOut}
        view={view}
        onView={setView}
        onSellGift={() => setSellGift(true)}
        tenantName={auth?.tenantDoc?.name}/>

      <div className="pos-main">
        {view === 'floor' && (
          <PosFloor onTapTable={(t) => { setActiveTable(t); setView('order'); }}/>
        )}
        {view === 'editor' && <PosLayoutEditor onDone={() => setView('floor')}/>}
        {view === 'order' && activeTable && (
          <PosOrder table={activeTable} staffMember={staffMember}
            onClose={() => { setActiveTable(null); setView('floor'); }}/>
        )}
      </div>

      {sellGift && (
        window.GiftSaleModal
          ? <window.GiftSaleModal products={giftProducts} issuedCards={giftIssued}
              onSale={async (sale, onMintedCode) => { await sellGiftCard(sale, onMintedCode); }}
              onClose={() => setSellGift(false)} />
          : null
      )}
    </div>
  );
}

// ============================================================
// STAFF LOGIN
// ============================================================
function PosStaffLogin({ onLogin }) {
  const fb = window.fb;
  const auth = window.useAuth();
  const tenantId = auth?.tenantId;
  const [staff, setStaff] = posS([]);
  const [pin, setPin] = posS('');
  const [error, setError] = posS('');
  const [loading, setLoading] = posS(true);

  posE(() => {
    if (!tenantId) return;
    const unsub = fb.onSnapshot(
      fb.collection(fb.db, 'tenants', tenantId, 'staff'),
      (snap) => {
        const out = [];
        snap.forEach(d => out.push({ id: d.id, ...d.data() }));
        setStaff(out.sort((a, b) => (a.name || '').localeCompare(b.name || '')));
        setLoading(false);
      },
      () => setLoading(false)
    );
    return () => unsub();
  }, [tenantId]);

  const slug = auth?.tenantDoc?.slug || 'restaurant';
  // Username = restaurant slug + underscore + staff slug. Compare
  // case-insensitively, ignore extra whitespace.
  const usernameSlug = (s) => `${slug}_${(s.username || s.name || '').toLowerCase().trim().replace(/\s+/g, '')}`;

  const tryLogin = () => {
    setError('');
    const wantUser = username.trim().toLowerCase();
    if (!wantUser) { setError('Indtast brugernavn'); return; }
    if (pin.length !== 4) { setError('PIN skal være 4 cifre'); return; }
    const match = staff.find(s => usernameSlug(s) === wantUser && s.pin === pin);
    if (match) {
      onLogin({ id: match.id, name: match.name, role: match.role });
      setPin('');
      setUsername('');
    } else {
      setError('Forkert brugernavn eller PIN');
      setTimeout(() => setError(''), 1800);
      setPin('');
    }
  };

  // Functional updater — two FAST taps used to read the same stale
  // `pin` from the closure, so the second tap overwrote the first and
  // only half the presses registered.
  const addDigit = (d) => {
    setPin(p => (p.length >= 4 ? p : p + d));
  };

  // Fire on pointerdown instead of click: click waits for pointer-up
  // (and on touch screens for double-tap disambiguation), which made
  // the keypad feel laggy. preventDefault stops the synthetic click
  // from double-firing and keeps focus where it is.
  const press = (fn) => (e) => {
    e.preventDefault();
    try { navigator.vibrate?.(8); } catch (err) {}
    fn();
  };

  const [username, setUsername] = posS('');

  // Allow physical keyboards too. Ignore keystrokes that are happening
  // inside the username input or other form fields.
  posE(() => {
    const onKey = (e) => {
      const target = e.target;
      const inField = target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable);
      if (inField) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      if (/^[0-9]$/.test(e.key)) {
        e.preventDefault();
        if (pin.length < 4) setPin(prev => (prev.length < 4 ? prev + e.key : prev));
      } else if (e.key === 'Backspace') {
        e.preventDefault();
        setPin(prev => prev.slice(0, -1));
      } else if (e.key === 'Escape') {
        e.preventDefault();
        setPin('');
      } else if (e.key === 'Enter') {
        e.preventDefault();
        // tryLogin reads current pin from state via closure — it's
        // recomputed each render but we use a ref-style read here.
        if (username.trim() && pin.length === 4) tryLogin();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [pin, username, staff]);

  return (
    <div className="pos-login">
      <div className="pos-login-card">
        <div className="pos-login-brand">SEATIQ POS<span style={{color: 'var(--accent)'}}>.</span></div>
        <div className="pos-login-tenant">{auth?.tenantDoc?.name || 'Restaurant'}</div>

        {loading ? (
          <div className="pos-login-loading">Indlæser personale…</div>
        ) : staff.length === 0 ? (
          <div className="pos-login-empty">
            <h3>Ingen POS-brugere endnu</h3>
            <p>Opret medarbejdere under Indstillinger → POS-brugere på dashboardet, så de kan logge ind her med deres PIN.</p>
            <div style={{display: 'flex', gap: 8, justifyContent: 'center', marginTop: 14}}>
              <a href="/" className="btn btn-soft">← Til dashboard</a>
              <button className="btn btn-soft" onClick={() => window.fb.signOut(window.fb.auth)}>Frakobl enhed</button>
            </div>
          </div>
        ) : (
          <>
            <div className="pos-login-username">
              <label>Brugernavn</label>
              <div className="pos-login-username-row">
                <span className="pos-login-prefix">{slug}_</span>
                <input
                  type="text"
                  autoComplete="off"
                  autoCapitalize="none"
                  spellCheck={false}
                  value={username.replace(new RegExp(`^${slug}_`, 'i'), '')}
                  onChange={e => setUsername(`${slug}_${e.target.value}`)}
                  onKeyDown={e => { if (e.key === 'Enter') tryLogin(); }}
                  placeholder="brugernavn"/>
              </div>
            </div>

            <div className="pos-login-prompt">PIN-kode <span style={{opacity: 0.5, fontSize: 11, marginLeft: 4}}>· tryk på tasterne eller brug tastaturet</span></div>
            <div className={`pos-pin-dots ${error ? 'error' : ''}`}>
              {[0,1,2,3].map(i => <span key={i} className={pin.length > i ? 'filled' : ''}/>)}
            </div>
            {error && <div className="pos-pin-error">{error}</div>}
            <div className="pos-keypad">
              {[1,2,3,4,5,6,7,8,9].map(n => (
                <button key={n} className="pos-key" onPointerDown={press(() => addDigit(String(n)))}>{n}</button>
              ))}
              <button className="pos-key ghost" onPointerDown={press(() => setPin(''))}>C</button>
              <button className="pos-key" onPointerDown={press(() => addDigit('0'))}>0</button>
              <button className="pos-key ghost" onPointerDown={press(() => setPin(p => p.slice(0, -1)))}>⌫</button>
            </div>
            <button className="btn pos-pairing-cta" style={{width: '100%', justifyContent: 'center', marginBottom: 14}}
              onClick={tryLogin} disabled={pin.length !== 4 || !username.trim()}>
              Log ind
            </button>
            <div className="pos-login-hint">
              Brugernavn er <strong>{slug}_dit-navn</strong>. Spørg en manager hvis du ikke har et.
            </div>
            <div style={{marginTop: 14, paddingTop: 14, borderTop: '1px solid var(--pos-line)'}}>
              <button className="btn btn-soft" style={{width: '100%', justifyContent: 'center', fontSize: 11}}
                onClick={() => {
                  if (confirm('Frakobl denne enhed fra restauranten? En manager skal logge ind igen næste gang POS bruges.')) {
                    window.fb.signOut(window.fb.auth);
                  }
                }}>
                Frakobl enhed
              </button>
            </div>
          </>
        )}
      </div>

    </div>
  );
}

// ============================================================
// TOP BAR
// ============================================================
function PosTopBar({ staffMember, onLogoutStaff, onSignOut, view, onView, onSellGift, tenantName }) {
  return (
    <div className="pos-topbar">
      <div className="pos-topbar-left">
        <div className="pos-brandmark">SEATIQ POS<span style={{color: 'var(--accent)'}}>.</span></div>
        <span className="pos-tenant-name">{tenantName}</span>
      </div>
      <div className="pos-topbar-tabs">
        <button className={`pos-tab ${view === 'floor' ? 'active' : ''}`} onClick={() => onView('floor')}>
          <Icon name="grid" size={16}/> Bordplan
        </button>
        {/* Drag-and-drop layout editing is unusable on a phone — the tab
            is hidden ≤640px (CSS) and kept on iPad/desktop. */}
        <button className={`pos-tab pos-tab-editor ${view === 'editor' ? 'active' : ''}`} onClick={() => onView('editor')}>
          <Icon name="edit" size={16}/> Rediger layout
        </button>
      </div>
      <div className="pos-topbar-right">
        <button className="pos-tab pos-sell-gift-btn" onClick={onSellGift} title="Sælg gavekort">
          <Icon name="bolt" size={16}/> <span className="pos-sell-gift-label">Sælg gavekort</span>
        </button>
        <div className="pos-staff-badge" onClick={onLogoutStaff} title="Skift bruger">
          <span className="pos-staff-initials">{(staffMember.name || '?').slice(0, 2).toUpperCase()}</span>
          <span className="pos-staff-name">{staffMember.name}</span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// FLOOR — read-only, tap a table to open its bill
// ============================================================
function PosFloor({ onTapTable }) {
  const fb = window.fb;
  const auth = window.useAuth();
  const tenantId = auth?.tenantId;
  const [tables, setTables] = posS([]);
  const [orders, setOrders] = posS({}); // tableId -> order doc
  // Both survive reloads: you come back to the area you work in and
  // the view you prefer. Phones default to the list — far easier to
  // hit than map tiles on a small screen.
  const [areaFilter, setAreaFilter] = window.usePersistedState('seatiq.pos.area', null,
    v => v === null || typeof v === 'string');
  const [floorMode, setFloorMode] = window.usePersistedState('seatiq.pos.floormode',
    () => (typeof window !== 'undefined' && window.innerWidth <= 820 ? 'list' : 'map'),
    v => ['list', 'map'].includes(v));
  // Phones get the LIST only — map tiles are too small to hit reliably.
  // Tracked reactively so rotating a tablet behaves.
  const [isPhone, setIsPhone] = posS(() => typeof window !== 'undefined' && window.innerWidth <= 640);
  posE(() => {
    const onResize = () => setIsPhone(window.innerWidth <= 640);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  const [rooms, setRooms] = posS([]);

  posE(() => {
    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, 'rooms'),
      (snap) => { const out = []; snap.forEach(d => out.push({ id: d.id, ...d.data() })); setRooms(out); },
      () => {}
    );
    const u3 = fb.onSnapshot(
      fb.query(
        fb.collection(fb.db, 'tenants', tenantId, 'orders'),
        fb.where('status', '==', 'open'),
      ),
      (snap) => {
        const m = {};
        snap.forEach(d => {
          const data = d.data();
          // A joined-group order is keyed on its primary table but
          // occupies every table in groupTables — show it on each so
          // both halves of a joined booking read as "open".
          const ids = (Array.isArray(data.groupTables) && data.groupTables.length)
            ? data.groupTables : [data.tableId];
          ids.forEach(tid => { m[tid] = { id: d.id, ...data }; });
        });
        setOrders(m);
      },
      () => {}
    );
    return () => { u1(); u2(); u3(); };
  }, [tenantId]);

  const canvasW = auth?.tenantDoc?.canvasW || 1080;
  const canvasH = auth?.tenantDoc?.canvasH || 720;
  // A remembered area that has since been deleted falls back to "alle"
  // for THIS render without clobbering the stored preference.
  const effFilter = areaFilter && rooms.length && !rooms.some(r => r.id === areaFilter)
    ? null : areaFilter;
  const visibleTables = effFilter ? tables.filter(t => t.room === effFilter) : tables;
  const sortedTables = [...visibleTables].sort((a, b) =>
    (a.name || '').localeCompare(b.name || '', 'da', { numeric: true }));
  const roomLabel = (id) => rooms.find(r => r.id === id)?.label || '';

  const orderSummary = (t) => {
    const order = orders[t.id];
    if (!order) return null;
    const items = order.items || [];
    // Negative = prepaid gift-card credit on the table.
    const total = items.reduce((a, i) => a + (i.price || 0) * (i.qty || 1), 0)
      - (order.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
    const count = items.reduce((a, i) => a + (i.qty || 1), 0);
    const unsent = items.filter(i => !i.sentAt).reduce((a, i) => a + (i.qty || 1), 0);
    return { total, count, unsent };
  };

  return (
    <>
      <div className="pos-areas">
        <button className={`pos-area-chip ${!effFilter ? 'active' : ''}`} onClick={() => setAreaFilter(null)}>
          Alle <span className="pos-area-count">{tables.length}</span>
        </button>
        {rooms.map(r => {
          const count = tables.filter(t => t.room === r.id).length;
          if (count === 0) return null;
          return (
            <button key={r.id}
              className={`pos-area-chip ${effFilter === r.id ? 'active' : ''}`}
              onClick={() => setAreaFilter(r.id)}>
              {r.label || r.id} <span className="pos-area-count">{count}</span>
            </button>
          );
        })}
        {!isPhone && (
          <div className="pos-floormode-toggle">
            <button className={floorMode === 'list' ? 'active' : ''} onClick={() => setFloorMode('list')}>Liste</button>
            <button className={floorMode === 'map' ? 'active' : ''} onClick={() => setFloorMode('map')}>Kort</button>
          </div>
        )}
      </div>

      {tables.length === 0 ? (
        <div className="pos-empty">
          <Icon name="grid" size={48} stroke={1.5}/>
          <h2>Ingen borde endnu</h2>
          <p>Skift til <strong>Rediger layout</strong> for at placere borde på din plantegning.</p>
        </div>
      ) : (isPhone || floorMode === 'list') ? (
        <div className="pos-table-list">
          {sortedTables.map(t => {
            const sum = orderSummary(t);
            return (
              <button key={t.id} className={`pos-table-row ${sum ? 'open' : ''}`}
                onClick={() => onTapTable(t)}>
                <div className="pos-table-row-main">
                  <span className="pos-table-row-name">{t.name || '?'}</span>
                  <span className="pos-table-row-meta">
                    {t.cap?.[0]}–{t.cap?.[1]} pers{!effFilter && roomLabel(t.room) ? ` · ${roomLabel(t.room)}` : ''}
                  </span>
                </div>
                {sum ? (
                  <div className="pos-table-row-status">
                    <span className="pos-table-row-chip open">Åben</span>
                    <span className="pos-table-row-total">{sum.total.toLocaleString('da-DK')} kr</span>
                    <span className="pos-table-row-items">
                      {sum.count} {sum.count === 1 ? 'vare' : 'varer'}{sum.unsent > 0 ? ` · ${sum.unsent} ikke sendt` : ''}
                    </span>
                  </div>
                ) : (
                  <div className="pos-table-row-status">
                    <span className="pos-table-row-chip free">Ledig</span>
                  </div>
                )}
                <Icon name="chev-right" size={18}/>
              </button>
            );
          })}
        </div>
      ) : (
        <div className="pos-floor-wrap">
          <div className="pos-floor" style={{width: canvasW, height: canvasH}}>
            {visibleTables.map(t => {
              const order = orders[t.id];
              const state = order ? 'occupied' : 'free';
              const total = (order?.items?.reduce((a, i) => a + (i.price || 0) * (i.qty || 1), 0) || 0)
                - (order?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
              return (
                <button key={t.id}
                  className={`pos-table ${t.shape || 'rect'} ${state}`}
                  style={{
                    left: t.x, top: t.y,
                    width: t.w || 90, height: t.h || 90,
                  }}
                  onClick={() => onTapTable(t)}>
                  <div className="pos-table-name">{t.name?.replace('Bord ', '') || '?'}</div>
                  {order ? (
                    <div className="pos-table-total">{total.toLocaleString('da-DK')} kr</div>
                  ) : (
                    <div className="pos-table-cap">{t.cap?.[0]}–{t.cap?.[1]}</div>
                  )}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </>
  );
}

// ============================================================
// LAYOUT EDITOR — clean rewrite, drag-and-drop only
// ============================================================
function PosLayoutEditor({ onDone }) {
  const fb = window.fb;
  const auth = window.useAuth();
  const tenantId = auth?.tenantId;
  const [tables, setTables] = posS([]);
  const [rooms, setRooms] = posS([]);
  const [selId, setSelId] = posS(null);
  const [drag, setDrag] = posS(null);
  const canvasRef = posR(null);

  posE(() => {
    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, 'rooms'),
      (snap) => { const out = []; snap.forEach(d => out.push({ id: d.id, ...d.data() })); setRooms(out); },
      () => {}
    );
    return () => { u1(); u2(); };
  }, [tenantId]);

  const canvasW = auth?.tenantDoc?.canvasW || 1080;
  const canvasH = auth?.tenantDoc?.canvasH || 720;
  const snap = (n) => Math.round(n / 10) * 10;

  // Pointer drag — works for mouse and touch.
  const pt = (e) => {
    if (e.touches?.length) return { x: e.touches[0].clientX, y: e.touches[0].clientY };
    if (e.changedTouches?.length) return { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY };
    return { x: e.clientX, y: e.clientY };
  };

  const startDrag = (e, table) => {
    if (e.cancelable) e.preventDefault();
    e.stopPropagation();
    const { x, y } = pt(e);
    setSelId(table.id);
    setDrag({ id: table.id, startX: x, startY: y, origX: table.x || 0, origY: table.y || 0 });
  };

  posE(() => {
    if (!drag) return;
    const onMove = (e) => {
      const { x, y } = pt(e);
      const dx = x - drag.startX;
      const dy = y - drag.startY;
      setTables(prev => prev.map(t => t.id === drag.id ? { ...t, x: Math.max(0, snap(drag.origX + dx)), y: Math.max(0, snap(drag.origY + dy)) } : t));
      if (e.touches && e.cancelable) e.preventDefault();
    };
    const onUp = async () => {
      const final = tables.find(t => t.id === drag.id);
      if (final) {
        await fb.updateDoc(
          fb.doc(fb.db, 'tenants', tenantId, 'tables', drag.id),
          { x: final.x, y: final.y }
        );
      }
      setDrag(null);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    window.addEventListener('touchmove', onMove, { passive: false });
    window.addEventListener('touchend', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onUp);
    };
  }, [drag, tenantId, tables]);

  const addTable = async (shape) => {
    const id = 'nt' + Math.random().toString(36).slice(2, 7);
    const room = rooms[0]?.id || null;
    const newT = {
      // Area initial + per-area number (T1, L1, …) — same scheme as
      // the dashboard floor editor.
      name: window.SEATIQ_HELPERS.nextAreaTableName(rooms, tables, room, id),
      cap: [2, 4],
      room,
      shape,
      x: Math.round(canvasW / 2 - 50),
      y: Math.round(canvasH / 2 - 50),
      w: shape === 'round' ? 100 : 90,
      h: shape === 'round' ? 100 : 90,
    };
    await fb.setDoc(fb.doc(fb.db, 'tenants', tenantId, 'tables', id), newT);
    setSelId(id);
  };

  const updateTable = async (id, patch) => {
    await fb.updateDoc(fb.doc(fb.db, 'tenants', tenantId, 'tables', id), patch);
  };

  const deleteTable = async (id) => {
    if (!window.confirm('Slet dette bord?')) return;
    await fb.deleteDoc(fb.doc(fb.db, 'tenants', tenantId, 'tables', id));
    setSelId(null);
  };

  const addRoom = async () => {
    const name = window.prompt('Navn på nyt område:');
    if (!name?.trim()) return;
    const id = 'rm' + Math.random().toString(36).slice(2, 6);
    await fb.setDoc(fb.doc(fb.db, 'tenants', tenantId, 'rooms', id), {
      label: name.trim(),
      labelX: 30, labelY: 80,
    });
  };

  const sel = tables.find(t => t.id === selId);

  return (
    <div className="pos-editor">
      <div className="pos-editor-toolbar">
        <button className="btn btn-soft" onClick={onDone}>
          <Icon name="chev-left" size={14}/> Færdig
        </button>
        <button className="btn btn-primary" onClick={() => addTable('rect')}>
          <Icon name="plus" size={14}/> Firkantet bord
        </button>
        <button className="btn btn-soft" onClick={() => addTable('round')}>
          <Icon name="plus" size={14}/> Rundt bord
        </button>
        <button className="btn btn-soft" onClick={addRoom}>
          <Icon name="plus" size={14}/> Nyt område
        </button>
        <div style={{flex: 1}}/>
        <span className="floor-stat">{tables.length} borde</span>
      </div>

      <div className="pos-editor-body">
        <div className="pos-editor-canvas-wrap">
          <div className="pos-editor-canvas" ref={canvasRef}
            style={{ width: canvasW, height: canvasH }}
            onClick={() => setSelId(null)}>
            {tables.map(t => {
              const isSel = selId === t.id;
              return (
                <div key={t.id}
                  className={`pos-table editor ${t.shape || 'rect'} ${isSel ? 'selected' : ''}`}
                  style={{
                    left: t.x || 0, top: t.y || 0,
                    width: t.w || 90, height: t.h || 90,
                    cursor: drag?.id === t.id ? 'grabbing' : 'grab',
                  }}
                  onMouseDown={(e) => startDrag(e, t)}
                  onTouchStart={(e) => startDrag(e, t)}
                  onClick={(e) => { e.stopPropagation(); setSelId(t.id); }}>
                  <div className="pos-table-name">{t.name?.replace('Bord ', '') || '?'}</div>
                  <div className="pos-table-cap">{t.cap?.[0]}–{t.cap?.[1]}</div>
                </div>
              );
            })}
            {tables.length === 0 && (
              <div className="pos-editor-hint">
                <Icon name="plus" size={40} stroke={1.5}/>
                <h3>Tom plantegning</h3>
                <p>Tryk „Firkantet bord“ eller „Rundt bord“ for at tilføje dit første bord.</p>
              </div>
            )}
          </div>
        </div>

        {sel && (
          <aside className="pos-editor-side">
            <div style={{fontSize: 11, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '0.1em', fontWeight: 700, marginBottom: 12}}>
              Bordets egenskaber
            </div>
            <div className="field">
              <label>Navn</label>
              <input value={sel.name || ''} onChange={e => updateTable(sel.id, { name: e.target.value })}/>
            </div>
            <div className="field">
              <label>Område</label>
              <select value={sel.room || ''} onChange={e => {
                const room = e.target.value || null;
                updateTable(sel.id, { room, name: window.SEATIQ_HELPERS.nextAreaTableName(rooms, tables, room, sel.id) });
              }}>
                <option value="">Intet område</option>
                {rooms.map(r => <option key={r.id} value={r.id}>{r.label || r.id}</option>)}
              </select>
            </div>
            <div className="field">
              <label>Kapacitet (min – max)</label>
              <div style={{display: 'flex', gap: 8}}>
                <input type="number" value={sel.cap?.[0] || 1} min={1}
                  onChange={e => updateTable(sel.id, { cap: [Math.max(1, +e.target.value || 1), sel.cap?.[1] || 4] })}/>
                <input type="number" value={sel.cap?.[1] || 4} min={1}
                  onChange={e => updateTable(sel.id, { cap: [sel.cap?.[0] || 1, Math.max(sel.cap?.[0] || 1, +e.target.value || 4)] })}/>
              </div>
            </div>
            <div className="field">
              <label>Størrelse (px)</label>
              <div style={{display: 'flex', gap: 8}}>
                <input type="number" value={sel.w || 90} min={40} step={10}
                  onChange={e => updateTable(sel.id, { w: Math.max(40, +e.target.value || 40) })}/>
                <input type="number" value={sel.h || 90} min={40} step={10}
                  onChange={e => updateTable(sel.id, { h: Math.max(40, +e.target.value || 40) })}/>
              </div>
            </div>
            <button className="btn btn-ghost"
              style={{color: 'var(--danger)', borderColor: 'rgba(224,83,58,0.3)', width: '100%', justifyContent: 'center', marginTop: 12}}
              onClick={() => deleteTable(sel.id)}>
              <Icon name="x" size={14}/> Slet bord
            </button>
          </aside>
        )}
      </div>
    </div>
  );
}

// ============================================================
// ORDER — tap on table opens this, add items by category
// ============================================================
function PosOrder({ table, staffMember, onClose }) {
  const fb = window.fb;
  const auth = window.useAuth();
  const tenantId = auth?.tenantId;
  const [items, setItems] = posS([]);
  const [order, setOrder] = posS(null);
  const [activeCat, setActiveCat] = posS(null);
  // Joined tables share ONE bill. Find today's active reservation that
  // includes this table; if it spans several tables, the bill lives on
  // the booking's PRIMARY table (tables[0]) so tapping any joined table
  // opens and updates the same order.
  const [groupTables, setGroupTables] = posS([table.id]);
  posE(() => {
    if (!tenantId || !table) { setGroupTables([table.id]); return; }
    const todayKey = window.SEATIQ_HELPERS.todayKey();
    const q = fb.query(
      fb.collection(fb.db, 'tenants', tenantId, 'reservations'),
      fb.where('bookedFor', '==', todayKey),
    );
    const unsub = fb.onSnapshot(q, (snap) => {
      let joined = null;
      snap.forEach(d => {
        const r = { id: d.id, ...d.data() };
        if (r.status === 'cancelled' || r.status === 'finished') return;
        const ts = window.SEATIQ_HELPERS.resTables(r);
        if (ts.length > 1 && ts.includes(table.id)) joined = ts;
      });
      setGroupTables(joined || [table.id]);
    }, () => setGroupTables([table.id]));
    return () => unsub();
  }, [tenantId, table.id]);

  // The shared order doc id: primary (first) table of the joined set.
  const orderId = groupTables[0] || table.id;
  const orderRef = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
  const isJoined = groupTables.length > 1;

  // Table names for the joined-group label (e.g. "Bord 1 + Bord 2").
  const [allTables, setAllTables] = posS([]);
  posE(() => {
    if (!tenantId) return;
    const unsub = fb.onSnapshot(fb.collection(fb.db, 'tenants', tenantId, 'tables'),
      (snap) => { const o = []; snap.forEach(d => o.push({ id: d.id, ...d.data() })); setAllTables(o); }, () => {});
    return () => unsub();
  }, [tenantId]);
  const groupName = isJoined
    ? groupTables.map(id => allTables.find(t => t.id === id)?.name || id).join(' + ')
    : table.name;

  // Load menu items
  posE(() => {
    if (!tenantId) return;
    const unsub = fb.onSnapshot(
      fb.collection(fb.db, 'tenants', tenantId, 'items'),
      (snap) => {
        const out = [];
        snap.forEach(d => out.push({ id: d.id, ...d.data() }));
        setItems(out);
      },
      () => {}
    );
    return () => unsub();
  }, [tenantId]);

  // Load (or create) open order for this table (or joined group)
  posE(() => {
    if (!tenantId || !table) return;
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    const unsub = fb.onSnapshot(ref, (snap) => {
      if (snap.exists()) {
        setOrder({ id: snap.id, ...snap.data() });
      } else {
        setOrder(null);
      }
    }, () => {});
    return () => unsub();
  }, [tenantId, orderId]);

  const categories = posM(() => {
    const present = new Set();
    items.forEach(i => present.add(i.category || 'Ukategoriseret'));
    // Respect the manual ordering set on the dashboard
    // (tenant.itemCategoryOrder) so POS and dashboard stay in sync.
    const manualOrder = auth?.tenantDoc?.itemCategoryOrder || [];
    const ordered = [];
    manualOrder.forEach(c => { if (present.has(c)) ordered.push(c); });
    Array.from(present)
      .filter(c => !ordered.includes(c) && c !== 'Ukategoriseret')
      .sort((a, b) => a.localeCompare(b))
      .forEach(c => ordered.push(c));
    if (present.has('Ukategoriseret') && !ordered.includes('Ukategoriseret')) {
      ordered.push('Ukategoriseret');
    }
    return ordered;
  }, [items, auth?.tenantDoc?.itemCategoryOrder]);

  posE(() => {
    // Also recover when the active category disappears (renamed/deleted)
    // — otherwise the item grid goes permanently blank.
    if ((!activeCat || !categories.includes(activeCat)) && categories.length > 0) {
      setActiveCat(categories[0]);
    }
  }, [categories, activeCat]);

  // Free-text item search across ALL categories — during rush it's
  // faster to type 3 letters than to hunt through category tabs.
  const [search, setSearch] = posS('');

  // Phone-only: the bill starts collapsed to a summary bar so the item
  // grid gets the whole screen. No effect ≥641px (CSS-gated).
  const [billCollapsed, setBillCollapsed] = posS(true);

  // Items inside a category keep oldest-first ordering so POS matches
  // the dashboard view exactly. Items without a createdAt (legacy data)
  // sort AFTER the timestamped ones — same tie-breaker as MenuPage so
  // the two screens never disagree on order.
  const itemsInCat = (search.trim()
    ? items.filter(i => (i.name || '').toLowerCase().includes(search.trim().toLowerCase()))
    : items.filter(i => (i.category || 'Ukategoriseret') === activeCat))
    .sort((a, b) => {
      const aT = a.createdAt || '';
      const bT = b.createdAt || '';
      if (aT && bT) return aT.localeCompare(bT);
      if (aT) return -1;
      if (bT) return 1;
      return (a.name || '').localeCompare(b.name || '');
    });

  // All order writes run as Firestore transactions on the LIVE doc —
  // two waiters in the same order no longer clobber each other's items
  // (the React-state `order` can be seconds stale).
  const addItem = async (it) => {
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    try { navigator.vibrate?.(15); } catch (e) {}
    try {
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        const current = snap.exists() ? snap.data() : null;
        const existing = current?.items || [];
        // Merge qty ONLY into a line that hasn't been fired to the
        // kitchen yet — sent lines are frozen, so the kitchen never sees
        // a line silently grow after it started cooking. A re-order of
        // the same dish becomes a fresh unsent line instead.
        const idx = existing.findIndex(x => x.itemId === it.id && !x.sentAt);
        let newItems;
        if (idx >= 0) {
          newItems = existing.map((x, i) => i === idx ? { ...x, qty: (x.qty || 1) + 1 } : x);
        } else {
          newItems = [...existing, {
            itemId: it.id,
            name: it.name,
            price: it.price || 0,
            qty: 1,
            category: it.category || null,
            addedBy: staffMember.name,
            addedAt: new Date().toISOString(),
          }];
        }
        tx.set(ref, {
          tableId: orderId,
          tableName: groupName,
          groupTables: groupTables,
          status: 'open',
          items: newItems,
          openedAt: current?.openedAt || new Date().toISOString(),
          openedBy: current?.openedBy || staffMember.name,
          lastUpdatedAt: new Date().toISOString(),
        }, { merge: true });
      });
    } catch (e) {
      alert(e?.message || 'Kunne ikke tilføje varen — prøv igen.');
      return;
    }
    // Atomic popularity counter (non-blocking) — read-modify-write lost
    // taps when two devices ordered the same dish simultaneously.
    fb.updateDoc(fb.doc(fb.db, 'tenants', tenantId, 'items', it.id), {
      orderCount: fb.increment(1),
    }).catch(() => {});
  };

  const changeQty = async (idx, delta) => {
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    // Removing the last item deletes the whole bill — confirm first so a
    // stray "−" tap can't silently wipe an order.
    const localItems = order?.items || [];
    const wouldEmpty = localItems.length === 1 && idx === 0 && ((localItems[0].qty || 1) + delta) <= 0;
    if (wouldEmpty && !window.confirm('Fjern sidste vare og annullér regningen?')) return;
    try {
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        if (!snap.exists()) return;
        const current = snap.data();
        const existing = current.items || [];
        let newItems = existing.map((x, i) => i === idx ? { ...x, qty: (x.qty || 1) + delta } : x);
        newItems = newItems.filter(x => (x.qty || 0) > 0);
        if (newItems.length === 0 && !(current.discounts || []).length) {
          tx.delete(ref);
        } else {
          tx.set(ref, { items: newItems, lastUpdatedAt: new Date().toISOString() }, { merge: true });
        }
      });
    } catch (e) {
      alert(e?.message || 'Kunne ikke opdatere antallet — prøv igen.');
    }
  };

  // Fire all unsent lines to the kitchen display (/kokken). Stamps
  // sentAt/sentBy on each — the KDS only shows stamped lines, and its
  // timers run from the moment the waiter sent, not when it was typed.
  const unsentCount = (order?.items || []).filter(x => !x.sentAt).reduce((a, x) => a + (x.qty || 1), 0);
  const sendToKitchen = async () => {
    if (!unsentCount) return;
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    try { navigator.vibrate?.(20); } catch (e) {}
    try {
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        if (!snap.exists()) return;
        const cur = snap.data();
        const ts = new Date().toISOString();
        const items = (cur.items || []).map(x => x.sentAt ? x : ({
          ...x, sentAt: ts, sentBy: staffMember.name,
        }));
        tx.set(ref, { items, lastUpdatedAt: ts }, { merge: true });
      });
    } catch (e) {
      alert(e?.message || 'Kunne ikke sende til køkkenet — prøv igen.');
    }
  };

  // The in-app payment panel (pos-pay-sheet) IS the confirmation step —
  // no browser popups. Picking a method closes the bill directly.
  const [payOpen, setPayOpen] = posS(false);
  const [closing, setClosing] = posS(false);
  const closeBill = async (paymentMethod) => {
    if (!order?.items?.length) return;
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    const histId = 'h' + Date.now() + Math.random().toString(36).slice(2, 5);
    const histRef = fb.doc(fb.db, 'tenants', tenantId, 'orderHistory', histId);
    try {
      // Read the FRESH order inside the transaction — closing from stale
      // React state used to drop items a colleague added seconds earlier.
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        if (!snap.exists()) throw new Error('Regningen findes ikke længere.');
        const current = snap.data();
        if (current.status !== 'open') throw new Error('Regningen er allerede lukket.');
        const closed = {
          ...current,
          status: 'closed',
          paymentMethod,
          closedAt: new Date().toISOString(),
          closedBy: staffMember.name,
        };
        tx.set(histRef, closed);
        tx.delete(ref);
      });
      onClose();
    } catch (e) {
      alert(e?.message || 'Kunne ikke lukke regningen — prøv igen.');
    }
  };

  const subtotal = (order?.items || []).reduce((a, x) => a + (x.price || 0) * (x.qty || 1), 0);
  const discountTotal = (order?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
  // Can go NEGATIVE: a gift card scanned onto an empty table starts the
  // bill at -X kr (prepaid credit that dishes then eat into).
  const total = subtotal - discountTotal;

  // Removing a card from a LIVE bill returns the money to the card —
  // any staff member may do it (the server still requires manager for
  // bills that are already closed).
  const removeDiscount = async (d) => {
    // Gift-card discounts route through the void Cloud Function so the
    // balance returns to the card. Manual discounts are just a line on
    // the order — remove them directly.
    if (d?.kind === 'giftCard') {
      if (!d?.redemptionId || !d?.code) return;
      if (!window.confirm(`Fjern gavekort ${d.code}? Beløbet sættes tilbage på kortet.`)) return;
      try {
        const voidFn = fb.httpsCallable(fb.functions, 'voidGiftRedemption');
        await voidFn({ tenantId, code: d.code, redemptionId: d.redemptionId, staffName: staffMember?.name || '' });
      } catch (e) {
        alert(e?.message || 'Kunne ikke fjerne gavekortet.');
      }
      return;
    }
    // Manual discount — drop the line from the order.
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    try {
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        if (!snap.exists()) return;
        const cur = snap.data();
        const next = (cur.discounts || []).filter(x => (x.id || x.redemptionId) !== (d.id || d.redemptionId));
        tx.update(ref, { discounts: next, lastUpdatedAt: new Date().toISOString() });
      });
    } catch (e) {
      alert(e?.message || 'Kunne ikke fjerne rabatten.');
    }
  };

  // Manual discount sheet (kr ⇄ % in one field, max 100%).
  const [discOpen, setDiscOpen] = posS(false);
  const [discMode, setDiscMode] = posS('kr'); // 'kr' | 'pct'
  const [discVal, setDiscVal] = posS('');
  const applyManualDiscount = async () => {
    const raw = parseFloat(discVal);
    if (isNaN(raw) || raw <= 0) { alert('Indtast et beløb'); return; }
    let amount;
    if (discMode === 'pct') {
      const pct = Math.min(100, raw);
      amount = Math.round(subtotal * pct / 100);
    } else {
      amount = Math.round(raw);
    }
    // Total discounts can never exceed the subtotal (max 100% off).
    const existing = (order?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
    amount = Math.min(amount, Math.max(0, subtotal - existing));
    if (amount <= 0) { alert('Regningen er allerede fuldt rabatteret.'); return; }
    const ref = fb.doc(fb.db, 'tenants', tenantId, 'orders', orderId);
    const entry = {
      id: 'md' + Date.now() + Math.random().toString(36).slice(2, 5),
      kind: 'manual',
      label: discMode === 'pct' ? `Rabat ${Math.min(100, raw)}%` : 'Rabat',
      amount,
      appliedBy: staffMember.name,
      appliedAt: new Date().toISOString(),
    };
    try {
      await fb.runTransaction(fb.db, async (tx) => {
        const snap = await tx.get(ref);
        if (!snap.exists()) throw new Error('Åbn en regning først (tilføj en vare).');
        tx.update(ref, { discounts: [...(snap.data().discounts || []), entry], lastUpdatedAt: new Date().toISOString() });
      });
      setDiscOpen(false); setDiscVal('');
    } catch (e) {
      alert(e?.message || 'Kunne ikke tilføje rabat.');
    }
  };

  return (
    <div className="pos-order">
      <div className="pos-order-left">
        <header className="pos-order-header">
          <button className="btn btn-soft" onClick={onClose}>
            <Icon name="chev-left" size={14}/> Bordplan
          </button>
          <div style={{minWidth: 0}}>
            <div className="pos-order-table-name">{groupName}{isJoined && <span style={{fontSize:11,fontWeight:600,color:"var(--pos-accent,var(--accent))",marginLeft:8}}>· sammenkædet</span>}</div>
            <div className="pos-order-meta">
              {order ? `Åbnet af ${order.openedBy}` : 'Ny regning'}
            </div>
          </div>
          {/* Top-right: scanning from here carries the table along, so
              the gift card lands on THIS bill without a table picker. */}
          <a className="btn btn-soft pos-order-qr"
            href={`/qr?table=${encodeURIComponent(table.id)}&tableName=${encodeURIComponent(table.name || '')}`}>
            <Icon name="bolt" size={14}/> Scan gavekort
          </a>
        </header>

        {items.length === 0 ? (
          <div className="pos-empty" style={{padding: 60}}>
            <Icon name="menu-book" size={40} stroke={1.5}/>
            <h3>Ingen retter på menuen</h3>
            <p>Tilføj retter under Menu → Items på dashboardet, så kan personalet vælge dem her.</p>
          </div>
        ) : (
          <>
            <div className="pos-item-search">
              <input value={search} onChange={e => setSearch(e.target.value)}
                placeholder="Søg ret…" aria-label="Søg i menuen"/>
              {search && (
                <button className="btn-icon" onClick={() => setSearch('')} aria-label="Ryd søgning">
                  <Icon name="x" size={14}/>
                </button>
              )}
            </div>
            {!search.trim() && (
              <div className="pos-cat-tabs">
                {categories.map(c => (
                  <button key={c}
                    className={`pos-cat-tab ${activeCat === c ? 'active' : ''}`}
                    onClick={() => setActiveCat(c)}>{c}</button>
                ))}
              </div>
            )}
            <div className="pos-items-grid">
              {itemsInCat.map(it => (
                <button key={it.id} className="pos-item-card" onClick={() => addItem(it)}>
                  <div className="pos-item-name">{it.name}</div>
                  <div className="pos-item-price">{it.price || 0} kr</div>
                </button>
              ))}
              {itemsInCat.length === 0 && (
                <div style={{gridColumn: '1 / -1', textAlign: 'center', color: 'var(--muted)', padding: 30}}>
                  {search.trim() ? `Ingen retter matcher „${search.trim()}“.` : `Ingen retter i kategorien „${activeCat}“.`}
                </div>
              )}
            </div>
          </>
        )}
      </div>

      <aside className={`pos-bill ${billCollapsed ? 'collapsed' : ''}`}>
        {/* Phone-only summary bar (hidden ≥641px via CSS): the bill stays
            out of the way while tapping dishes, with live count + total. */}
        <button className="pos-bill-mobile-toggle" onClick={() => setBillCollapsed(c => !c)}>
          <span>Regning · {(order?.items || []).reduce((a, x) => a + (x.qty || 1), 0)} varer</span>
          <strong>{total.toLocaleString('da-DK')} kr</strong>
          <Icon name={billCollapsed ? 'chev-up' : 'chev-down'} size={16}/>
        </button>
        <header className="pos-bill-header">
          <div>
            <div className="pos-bill-title">Regning</div>
            <div className="pos-bill-sub">{groupName}</div>
          </div>
        </header>

        <div className="pos-bill-items">
          {(!order || !order.items?.length) && (
            <div style={{textAlign: 'center', color: 'var(--muted)', padding: 30, fontSize: 14}}>
              Ingen items endnu. Tryk på en ret for at tilføje den.
            </div>
          )}
          {order?.items?.map((x, i) => (
            <div key={i} className="pos-bill-row">
              <div style={{flex: 1, minWidth: 0}}>
                <div className="pos-bill-row-name">
                  {x.name}
                  {!x.sentAt
                    ? <span className="pos-unsent-chip">ikke sendt</span>
                    : x.kdone
                      ? <span className="pos-sent-chip done" title={`Lavet af ${x.kdoneBy || 'køkken'}`}>✓ lavet</span>
                      : <span className="pos-sent-chip" title={`Sendt af ${x.sentBy || ''}`}>i køkken</span>}
                </div>
                <div className="pos-bill-row-price">{x.price} kr</div>
              </div>
              <div className="pos-qty">
                <button onClick={() => changeQty(i, -1)}>−</button>
                <span>{x.qty}</span>
                <button onClick={() => changeQty(i, 1)}>+</button>
              </div>
              <div className="pos-bill-row-total">{(x.price * x.qty).toLocaleString('da-DK')} kr</div>
            </div>
          ))}
        </div>

        {(order?.discounts || []).length > 0 && (
          <div className="pos-bill-discounts">
            <div className="pos-bill-subtotal">
              <span>Subtotal</span>
              <strong>{subtotal.toLocaleString('da-DK')} kr</strong>
            </div>
            {(order.discounts || []).map((d, i) => (
              <div key={d.redemptionId || d.id || i} className="pos-bill-discount-row">
                <Icon name={d.kind === 'manual' ? 'percent' : 'bolt'} size={14}/>
                <div style={{flex: 1, minWidth: 0}}>
                  <div className="pos-bill-discount-name">{d.kind === 'manual' ? (d.label || 'Rabat') : `Gavekort ${d.code}`}</div>
                  <div className="pos-bill-discount-meta">Af {d.appliedBy}</div>
                </div>
                <div className="pos-bill-discount-amount">−{(d.amount || 0).toLocaleString('da-DK')} kr</div>
                <button className="pos-bill-discount-remove"
                  title={d.kind === 'manual' ? 'Fjern rabat' : 'Fjern gavekortet — beløbet ryger tilbage på kortet'}
                  onClick={() => removeDiscount(d)}>×</button>
              </div>
            ))}
          </div>
        )}

        <footer className="pos-bill-footer">
          <button className="btn btn-primary pos-send-kitchen"
            disabled={!unsentCount}
            onClick={sendToKitchen}>
            🔔 Send til køkken{unsentCount > 0 ? ` (${unsentCount})` : ''}
          </button>
          <div className="pos-bill-total">
            <span>I alt</span>
            <strong>{total.toLocaleString('da-DK')} kr</strong>
          </div>
          <div className="pos-bill-actions" style={{gridTemplateColumns: '1fr 1.6fr'}}>
            <button className="btn btn-soft"
              disabled={!order?.items?.length}
              onClick={() => { setDiscMode('kr'); setDiscVal(''); setDiscOpen(true); }}>
              Rabat
            </button>
            <button className="btn btn-accent"
              disabled={!order?.items?.length}
              onClick={() => setPayOpen(true)}>
              Luk regning
            </button>
          </div>
        </footer>
      </aside>

      {discOpen && (
        <div className="pos-pay-backdrop" onClick={() => setDiscOpen(false)}>
          <div className="pos-pay-sheet" onClick={e => e.stopPropagation()}>
            <div className="pos-pay-total">
              <span>{groupName} · rabat</span>
              <strong>{subtotal.toLocaleString('da-DK')} kr</strong>
            </div>
            <div className="pos-disc-field">
              <input type="number" inputMode="decimal" autoFocus
                value={discVal} onChange={e => setDiscVal(e.target.value)}
                placeholder={discMode === 'pct' ? 'f.eks. 50' : 'f.eks. 100'}
                max={discMode === 'pct' ? 100 : undefined} min={0}/>
              <div className="pos-disc-toggle">
                <button className={discMode === 'kr' ? 'active' : ''} onClick={() => setDiscMode('kr')}>kr</button>
                <button className={discMode === 'pct' ? 'active' : ''} onClick={() => setDiscMode('pct')}>%</button>
              </div>
            </div>
            <div className="pos-disc-preview">
              {(() => {
                const raw = parseFloat(discVal);
                if (isNaN(raw) || raw <= 0) return 'Indtast et beløb eller en procent.';
                const existing = (order?.discounts || []).reduce((a, d) => a + (d.amount || 0), 0);
                let amount = discMode === 'pct' ? Math.round(subtotal * Math.min(100, raw) / 100) : Math.round(raw);
                amount = Math.min(amount, Math.max(0, subtotal - existing));
                return `Trækker −${amount.toLocaleString('da-DK')} kr fra regningen`;
              })()}
            </div>
            <div className="pos-pay-methods">
              <button className="pos-pay-btn" onClick={applyManualDiscount}>Tilføj rabat</button>
            </div>
            <button className="pos-pay-cancel" onClick={() => setDiscOpen(false)}>Annuller</button>
          </div>
        </div>
      )}

      {payOpen && (
        <div className="pos-pay-backdrop" onClick={() => !closing && setPayOpen(false)}>
          <div className="pos-pay-sheet" onClick={e => e.stopPropagation()}>
            <div className="pos-pay-total">
              <span>{groupName} · i alt</span>
              <strong>{total.toLocaleString('da-DK')} kr</strong>
            </div>
            {unsentCount > 0 && (
              <div className="pos-pay-warn">
                ⚠ {unsentCount} {unsentCount === 1 ? 'vare er' : 'varer er'} ikke sendt til køkkenet
              </div>
            )}
            {total < 0 && (
              <div className="pos-pay-warn">
                ⚠ Der er {Math.abs(total).toLocaleString('da-DK')} kr ubrugt gavekort-tilgodehavende på bordet. Lukkes regningen nu, fortabes det — en manager kan i stedet fjerne gavekortet fra regningen, så beløbet ryger tilbage på kortet.
              </div>
            )}
            <div className="pos-pay-methods">
              {total <= 0 ? (
                <button className="pos-pay-btn" disabled={closing}
                  onClick={async () => { if (closing) return; setClosing(true); try { await closeBill('Gavekort'); } finally { setClosing(false); setPayOpen(false); } }}>
                  {total < 0 ? 'Luk (Gavekort)' : 'Luk gratis (Gavekort)'}
                </button>
              ) : (
                ['Kort', 'MobilePay', 'Kontant'].map(m => (
                  <button key={m} className="pos-pay-btn" disabled={closing}
                    onClick={async () => { if (closing) return; setClosing(true); try { await closeBill(m); } finally { setClosing(false); setPayOpen(false); } }}>
                    {closing ? 'Lukker…' : m}
                  </button>
                ))
              )}
            </div>
            <button className="pos-pay-cancel" disabled={closing} onClick={() => setPayOpen(false)}>
              Annuller
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

window.PosApp = PosApp;
window.PosStaffLogin = PosStaffLogin;
