const { useState, useEffect, useCallback, useRef, createContext, useContext } = React;

// ============================================================================
// WINSTASH POS — Operator Point-of-Sale Application
// ============================================================================
// Tablet-first React SPA for COAM location operators.
//
// Authentication: Operator card swipe + PIN (two-factor)
// Load Flow:
//   1. Player card swipe → lookup
//   2. Enter amount + ticket reference
//   3. Operator card swipe + PIN → authorize (generates single-use token)
//   4. Submit → execute load with fraud evaluation
// ============================================================================

const SUPABASE_URL = "https://vqlnyrumksdfdihwiamv.supabase.co";
const ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZxbG55cnVta3NkZmRpaHdpYW12Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzODM2NTksImV4cCI6MjA4Njk1OTY1OX0.j8gAJhvFccjlEQPJj5UC2zHYdNvyR3Xl50juGNlgSMg";

// ── SCANBOT WEB SDK ───────────────────────────────────────────────────────────
// Camera-based PDF417 scanning for US driver's licenses.
// License is scoped to pos.winstash.app.
const SCANBOT_LICENSE_KEY =
  "mcwTQe4ELFGwDrp7NIVe59Pp8i8uJf" +
  "XBz9r0hFoojnuxawzQYX3Be0w5ht1E" +
  "BhniSJugATfsLRwEKVAjILFdrWs40N" +
  "KlcFfGujxoNO9ybVoLUvf8j0Vho6VL" +
  "qrIomOaNN7D3AcDlEWt3jLHblc2v2I" +
  "BiDQcY6Tkb9DoWO87pOjtJc/LG+wgk" +
  "U0zq9BjO0wCJnkWevkLu/kJrY6XV7C" +
  "z37+HWOYV7BAovL/0gDrRFe/x8hWZq" +
  "8Zdh9CBLhX/d8rpn9wLNQaVueAhity" +
  "NffhZ3YQnwlmkTARs6ZLoeCDDvhfQi" +
  "Q9F4Hb0/jc77cipmdYHqVouA1tPAR4" +
  "winQWMkl3xow==\nU2NhbmJvdFNESw" +
  "psb2NhbGhvc3R8cG9zLndpbnN0YXNo" +
  "LmFwcAoxNzc1NTE5OTk5CjgzODg2MD" +
  "cKOA==\n";

// Initialized once at first use; reused across all components.
let scanbotSDKInstance = null;

async function initScanbot() {
  if (scanbotSDKInstance) return scanbotSDKInstance;
  if (!window.ScanbotSDK) throw new Error('Scanbot SDK script not loaded');
  scanbotSDKInstance = await window.ScanbotSDK.initialize({
    licenseKey: SCANBOT_LICENSE_KEY,
    enginePath: 'https://cdn.jsdelivr.net/npm/scanbot-web-sdk@8.0.1/bundle/bin/complete/',
  });
  return scanbotSDKInstance;
}

// Log an ID verification attempt to the dedicated compliance table.
// Non-fatal — failures are silently ignored so the POS flow is never blocked.
async function logIdVerification({ licenseNumber, operatorId, locationId, isOver21, isExpired, wasApproved }) {
  try {
    await fetch(`${SUPABASE_URL}/rest/v1/id_verifications`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'apikey': ANON_KEY,
        'Authorization': `Bearer ${ANON_KEY}`,
        'Prefer': 'return=minimal',
      },
      body: JSON.stringify({
        license_number: licenseNumber || 'unknown',
        operator_id: operatorId || null,
        location_id: locationId || null,
        is_over_21: isOver21,
        is_expired: isExpired,
        was_approved: wasApproved,
      }),
    });
  } catch (_) {}
}

// ── BRAND COLORS ─────────────────────────────────────────────────────────────
const COLORS = {
  bg: "#000000",
  surface: "#0A0A0A",
  surfaceLight: "#141414",
  inputBg: "#1a1a1a",
  border: "#222222",
  text: "#D4D4D4",
  textMuted: "#777777",
  heading: "#FFFFFF",
  orange: "#FF6B00",
  orangeGlow: "rgba(255, 107, 0, 0.15)",
  success: "#22c55e",
  successBg: "#052e16",
  error: "#ef4444",
  errorBg: "#450a0a",
  warning: "#f59e0b",
  warningBg: "#451a03",
  blue: "#3b82f6",
  blueBg: "#1e3a5f",
};

// ── LOCAL ACTION LOG (localStorage ring buffer, syncs to server on login) ────
const LOCAL_LOG_KEY = "winstash_pos_log";
const LOCAL_LOG_MAX = 1000;

function appendLocalLog(entry) {
  try {
    const raw = localStorage.getItem(LOCAL_LOG_KEY);
    const log = raw ? JSON.parse(raw) : [];
    log.push({ ...entry, ts: new Date().toISOString(), synced: false });
    if (log.length > LOCAL_LOG_MAX) log.splice(0, log.length - LOCAL_LOG_MAX);
    localStorage.setItem(LOCAL_LOG_KEY, JSON.stringify(log));
  } catch (_) {}
}

async function syncLocalLog(operatorToken) {
  try {
    const raw = localStorage.getItem(LOCAL_LOG_KEY);
    if (!raw) return;
    const log = JSON.parse(raw);
    const unsynced = log.filter((e) => !e.synced);
    if (unsynced.length === 0) return;

    const res = await fetch(`${SUPABASE_URL}/functions/v1/admin-activity/sync`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "apikey": ANON_KEY,
        "x-operator-token": operatorToken,
      },
      body: JSON.stringify({ entries: unsynced }),
    });

    if (res.ok) {
      const updated = log.map((e) => e.synced ? e : { ...e, synced: true });
      localStorage.setItem(LOCAL_LOG_KEY, JSON.stringify(updated));
    }
  } catch (_) {}
}

// ── OFFLINE MODE — SESSION CACHE, PLAYER CACHE, TRANSACTION QUEUE ────────────
const SESSION_CACHE_KEY = 'winstash_session_cache';
const SESSION_CACHE_TTL = 8 * 60 * 60 * 1000; // 8 hours
const INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
const INACTIVITY_EVENTS = ['mousedown', 'keydown', 'touchstart', 'scroll', 'mousemove'];
const PLAYER_CACHE_KEY = 'winstash_player_cache';
const PLAYER_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
const OFFLINE_QUEUE_KEY = 'winstash_offline_queue';

async function hashPin(pin) {
  const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(pin));
  return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}

function genUuid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0;
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}

function cachePlayerData(key, data) {
  try {
    const cache = JSON.parse(localStorage.getItem(PLAYER_CACHE_KEY) || '{}');
    cache[key] = { ...data, _cached_at: Date.now() };
    const entries = Object.entries(cache)
      .sort((a, b) => b[1]._cached_at - a[1]._cached_at).slice(0, 200);
    localStorage.setItem(PLAYER_CACHE_KEY, JSON.stringify(Object.fromEntries(entries)));
  } catch (_) {}
}

function getCachedPlayer(key) {
  try {
    const cache = JSON.parse(localStorage.getItem(PLAYER_CACHE_KEY) || '{}');
    const e = cache[key];
    if (!e || Date.now() - e._cached_at > PLAYER_CACHE_TTL) return null;
    return e;
  } catch (_) { return null; }
}

function getOfflineQueue() {
  try { return JSON.parse(localStorage.getItem(OFFLINE_QUEUE_KEY) || '[]'); }
  catch (_) { return []; }
}

function addToOfflineQueue(txn) {
  try {
    const q = getOfflineQueue();
    q.push({ ...txn, local_id: genUuid(), queued_at: new Date().toISOString(), status: 'pending' });
    localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(q));
  } catch (_) {}
}

function updateOfflineQueueResults(results) {
  try {
    const q = getOfflineQueue();
    for (const r of results) {
      const t = q.find(x => x.local_id === r.local_id);
      if (t) {
        t.status = r.success ? 'synced' : 'failed';
        t.error = r.error || null;
        t.server_transaction_id = r.transaction_id || null;
      }
    }
    localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(q));
  } catch (_) {}
}

function pendingBalanceFor(walletId) {
  return getOfflineQueue()
    .filter(t => t.wallet_id === walletId && t.status === 'pending')
    .reduce((sum, t) => sum + (t.type === 'load' ? t.amount : -t.amount), 0);
}

// ── LOCAL SYNC — Kiosk LAN Discovery + Wallet Snapshot ───────────────────────
// POS discovers the kiosk on the local network via mDNS hostname and caches its
// URL. Wallet snapshots are fetched every 2 minutes while online so the kiosk
// can operate offline with a recent starting balance.

const KIOSK_LOCAL_KEY     = 'winstash_kiosk_local';     // { url, discovered_at }
const WALLET_SNAPSHOT_KEY = 'winstash_wallet_snapshot'; // { wallets, location_limits, snapshot_at }

async function discoverKiosk(locationId) {
  try {
    const mdnsUrl = `http://winstash-kiosk-${locationId}.local:8765/local/v1/status`;
    const r = await fetch(mdnsUrl, { signal: AbortSignal.timeout(2000) });
    if (r.ok) {
      const base = `http://winstash-kiosk-${locationId}.local:8765`;
      localStorage.setItem(KIOSK_LOCAL_KEY, JSON.stringify({ url: base, discovered_at: Date.now() }));
    }
  } catch (_) {}
}

async function notifyKioskOfPendingLoad(txn, locationId) {
  try {
    const raw = localStorage.getItem(KIOSK_LOCAL_KEY);
    if (!raw) return;
    const k = JSON.parse(raw);
    // Consider stale if not discovered in last 10 minutes
    if (Date.now() - k.discovered_at > 10 * 60 * 1000) return;
    await fetch(`${k.url}/local/v1/pending-load`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Local-Secret': String(locationId),
      },
      body: JSON.stringify({
        wallet_id: txn.wallet_id,
        amount:    txn.amount,
        local_id:  txn.local_id,
      }),
      signal: AbortSignal.timeout(2000),
    });
  } catch (_) {} // fire-and-forget; failure is non-fatal
}

// ── ROLE HIERARCHY ───────────────────────────────────────────────────────────
// Role levels for permission checks
const ROLE_LEVELS = {
  attendant: 1,
  manager: 2,
  owner: 3,
  admin: 4,
  super_admin: 5,
};

// Helper to check if role has at least the required level
function hasRoleLevel(role, requiredLevel) {
  return (ROLE_LEVELS[role] || 0) >= requiredLevel;
}

// Helper to check specific role access
function canAccess(role, feature) {
  const level = ROLE_LEVELS[role] || 0;
  const features = {
    settings: level >= 2,           // Manager+
    basicSettings: level >= 2,      // Manager+
    feesSettings: level >= 3,       // Owner+
    limitsSettings: level >= 3,     // Owner+
    promosSettings: level >= 3,     // Owner+
    teamManagement: level >= 2,     // Manager+
  };
  return features[feature] || false;
}

// ── HID SCANNER — S370 in Bluetooth Keyboard Emulation mode ─────────────────

// — Device provisioning HMAC helpers ------------------------------------------
const DEVICE_TOKEN_ID_KEY = 'winstash_device_token_id';
const DEVICE_TOKEN_SECRET_KEY = 'winstash_device_token_secret';
const DEVICE_LOCATION_ID_KEY = 'winstash_device_location_id';

async function computeDeviceHMAC(secret, message) {
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message));
  return Array.from(new Uint8Array(sig))
    .map(b => b.toString(16).padStart(2, '0')).join('');
}

async function deviceHeaders(method, path, body) {
  const tokenId = localStorage.getItem(DEVICE_TOKEN_ID_KEY);
  const tokenSecret = localStorage.getItem(DEVICE_TOKEN_SECRET_KEY);
  if (!tokenId || !tokenSecret) return {};
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const canonical = `${timestamp}.${method}.${path}.${JSON.stringify(body)}`;
  const signature = await computeDeviceHMAC(tokenSecret, canonical);
  return {
    'X-Device-Id': tokenId,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
  };
}

// — API Helper ----------------------------------------------------------------
async function api(endpoint, body, token = null) {
  const path = `/functions/v1/${endpoint}`;
  const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${ANON_KEY}`,
  };
  if (token) headers["x-operator-token"] = token;

  // Add device HMAC headers when provisioned
  const devHeaders = await deviceHeaders("POST", path, body);
  Object.assign(headers, devHeaders);

  const res = await fetch(`${SUPABASE_URL}${path}`, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });

  const data = await res.json();
  if (!res.ok) throw { status: res.status, ...data };
  return data;
}

// — Format helpers ------------------------------------------------------------
function fmtPhone(v) {
  let d = v.replace(/\D/g, "");
  // Strip leading country code "1" if present (11 digits → 10)
  if (d.length === 11 && d[0] === "1") d = d.slice(1);
  d = d.slice(0, 10);
  if (d.length <= 3) return d;
  if (d.length <= 6) return `(${d.slice(0, 3)}) ${d.slice(3)}`;
  return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
}
// Strip country code for display, keep raw 10-digit for storage
function stripCountryCode(phone) {
  if (!phone) return "";
  let d = phone.replace(/\D/g, "");
  if (d.length === 11 && d[0] === "1") d = d.slice(1);
  return d;
}
function fmtCard(v) {
  return v.toUpperCase().replace(/[^A-Z0-9-]/g, "").slice(0, 12);
}
function fmtGiftCardSuffix(v) {
  // Only the 5-digit suffix (after WSH-)
  return v.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 5);
}
function fmtOperatorCardSuffix(v) {
  // Only the 5-digit suffix (after WSO-)
  return v.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 5);
}
function fmtCurrency(n) {
  return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(n);
}
// Generate an opaque supervisor review code from an alert external_id (UUID)
// Format: TK-XXXX where XXXX = first 4 hex chars of UUID, uppercased
function alertCode(externalId) {
  return 'TK-' + externalId.replace(/-/g, '').substring(0, 4).toUpperCase();
}

// ── HID SCANNER CONTEXT ──────────────────────────────────────────────────────
// S370 in Bluetooth HID (Keyboard Emulation) mode pairs to the tablet as a
// Bluetooth keyboard. Scanned data is typed into the hidden input element,
// terminated by Enter. No Companion app or SDK required.

const CaptureContext = createContext(null);

function useCaptureSDK() {
  return useContext(CaptureContext);
}

// ── SCANBOT CAMERA SCANNER HOOK ───────────────────────────────────────────────
// Opens the tablet camera to scan PDF417 barcodes from the back of a US
// driver's license. Completely independent from the S370 HID scanner.
function useIdScanner() {
  const [isScanning, setIsScanning] = useState(false);

  const scanId = useCallback(async ({ onSuccess, onError }) => {
    if (!window.ScanbotSDK) {
      onError?.('Camera scanning unavailable — use the Bluetooth scanner instead.');
      return;
    }
    setIsScanning(true);
    try {
      await initScanbot();
      const config = new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
      // Restrict to PDF417 only — the format used on the back of US driver's licenses
      config.scannerConfiguration.barcodeFormats = ['PDF_417'];
      const result = await ScanbotSDK.UI.createBarcodeScanner(config);
      setIsScanning(false);
      if (result?.items?.length > 0) {
        onSuccess(result.items[0].barcode.text);
      }
      // null result = user cancelled — no action needed
    } catch (err) {
      setIsScanning(false);
      onError?.(err?.message || 'Camera scanner failed to open');
    }
  }, []);

  return { scanId, isScanning };
}

// ── ID SCAN METHOD PICKER ─────────────────────────────────────────────────────
// Bluetooth scanner is primary (already listening via HID, no button needed).
// Camera is a secondary option the operator explicitly chooses.
function IdScanOptions({ onCameraClick, isCameraScanning, disabled, onCancel, cancelLabel = "Cancel" }) {
  return (
    <>
      <div style={{
        background: COLORS.orangeGlow, border: `2px solid ${COLORS.orange}`, borderRadius: 14,
        padding: "24px", textAlign: "center", marginBottom: 16,
      }}>
        <div style={{ fontSize: 36, marginBottom: 8 }}>📶</div>
        <div style={{ fontSize: 17, fontWeight: 700, color: COLORS.orange, marginBottom: 6 }}>
          Bluetooth Scanner
        </div>
        <div style={{ fontSize: 14, color: COLORS.textMuted }}>
          Point the scanner at the barcode on the back of the ID
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 12, margin: "16px 0" }}>
        <div style={{ flex: 1, height: 1, background: COLORS.border }} />
        <span style={{ fontSize: 12, color: COLORS.textMuted }}>or use camera</span>
        <div style={{ flex: 1, height: 1, background: COLORS.border }} />
      </div>
      <Btn onClick={onCameraClick} disabled={disabled || isCameraScanning} color={COLORS.blue} wide>
        {isCameraScanning ? "Opening camera…" : "📷 Scan with Camera"}
      </Btn>
      {onCancel && (
        <div style={{ marginTop: 12 }}>
          <Btn onClick={onCancel} variant="secondary" wide size="md">{cancelLabel}</Btn>
        </div>
      )}
    </>
  );
}

// ── QR SCAN METHOD PICKER ─────────────────────────────────────────────────────
// Bluetooth scanner is primary (already listening via HID, no button needed).
// Camera is a secondary option the operator explicitly chooses.
function QrScanOptions({ onCameraClick, isCameraScanning, disabled, onCancel, cancelLabel = "Back" }) {
  return (
    <>
      <div style={{
        background: COLORS.orangeGlow, border: `2px solid ${COLORS.orange}`, borderRadius: 14,
        padding: "24px", textAlign: "center", marginBottom: 16,
      }}>
        <div style={{ fontSize: 36, marginBottom: 8 }}>📶</div>
        <div style={{ fontSize: 17, fontWeight: 700, color: COLORS.orange, marginBottom: 6 }}>
          Bluetooth Scanner
        </div>
        <div style={{ fontSize: 14, color: COLORS.textMuted }}>
          Point the scanner at the player's QR code
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 12, margin: "16px 0" }}>
        <div style={{ flex: 1, height: 1, background: COLORS.border }} />
        <span style={{ fontSize: 12, color: COLORS.textMuted }}>or use camera</span>
        <div style={{ flex: 1, height: 1, background: COLORS.border }} />
      </div>
      <Btn onClick={onCameraClick} disabled={disabled || isCameraScanning} color={COLORS.blue} wide>
        {isCameraScanning ? "Opening camera…" : "📷 Scan with Camera"}
      </Btn>
      {onCancel && (
        <div style={{ marginTop: 12 }}>
          <Btn onClick={onCancel} variant="secondary" wide size="md">{cancelLabel}</Btn>
        </div>
      )}
    </>
  );
}

// Opens the tablet camera to scan a WinStash QR code from the player app.
function useQrScanner() {
  const [isScanning, setIsScanning] = useState(false);

  const scanQr = useCallback(async ({ onSuccess, onError } = {}) => {
    if (!window.ScanbotSDK) {
      onError?.('Camera scanning unavailable — Scanbot SDK not loaded.');
      return;
    }
    setIsScanning(true);
    try {
      await initScanbot();
      const config = new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
      config.scannerConfiguration.barcodeFormats = ['QR_CODE'];
      const result = await ScanbotSDK.UI.createBarcodeScanner(config);
      setIsScanning(false);
      if (result?.items?.length > 0) {
        onSuccess?.(result.items[0].barcode.text);
      }
      // null result = user cancelled
    } catch (err) {
      setIsScanning(false);
      onError?.(err?.message || 'Camera scanner failed to open');
    }
  }, []);

  return { scanQr, isScanning };
}

// Detect AAMVA PDF417 driver's license barcode
function isAAMVA(raw) {
  return raw.startsWith('@') ||
         raw.includes('ANSI ') ||
         raw.includes('AAMVA') ||
         (raw.includes('DL') && raw.includes('DAQ'));
}

// Detect WinStash NFC card number written as plain NDEF Text (e.g. "NFC-4DE9AA5D")
function isWinstashCard(raw) {
  return /^NFC-[A-F0-9]{8}$/i.test(raw.trim());
}

// Detect WinStash player QR code (JSON from player app)
function isWinstashQR(raw) {
  try { return JSON.parse(raw)?.type === 'winstash_player'; }
  catch { return false; }
}

// HID Scanner Provider — manages the hidden input that captures S370 keystrokes
// Uses timeout-based buffer completion (80 ms) so AAMVA PDF417 barcodes — which
// contain embedded \r\n delimiters — are not truncated at the first newline.
function HidScannerProvider({ children }) {
  const [lastScanType, setLastScanType] = useState(null); // 'nfc' | 'id' | null

  const onBarcodeRef   = useRef(null);
  const onNfcTagRef    = useRef(null);
  const inputRef       = useRef(null);
  const bufferRef      = useRef('');
  const timerRef       = useRef(null);
  // IME composition tracking: Android Chrome sends insertCompositionText events that may
  // be growing ("D" → "DL") or replacing ("A" then separate "N" replaces "A").
  // compositionRef holds the pending composition text; lastCompDataRef holds the last
  // insertCompositionText data value so we can detect whether the next event grows it.
  const compositionRef  = useRef('');
  const lastCompDataRef = useRef('');

  // Refocus the hidden input only when no real input field has focus
  const refocus = useCallback(() => {
    setTimeout(() => {
      const active = document.activeElement;
      const tag = active?.tagName;
      if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') {
        inputRef.current?.focus({ preventScroll: true });
      }
    }, 50);
  }, []);

  useEffect(() => { refocus(); }, [refocus]);

  // Called after the last keystroke — flushes any pending composition then reads bufferRef.
  const processBuffer = useCallback(() => {
    // Commit any composition text that never got a following insertText to finalize it
    if (compositionRef.current) {
      bufferRef.current += compositionRef.current;
      compositionRef.current = '';
      lastCompDataRef.current = '';
    }
    const rawBuffer = bufferRef.current || (inputRef.current?.value ?? '');
    console.log('[HID] raw buffer received:', JSON.stringify(rawBuffer), 'length:', rawBuffer.length);
    const raw = rawBuffer.trim();
    bufferRef.current = '';
    if (inputRef.current) inputRef.current.value = '';
    if (!raw) { refocus(); return; }

    console.log('[HID] scan received, length:', raw.length, 'preview:', JSON.stringify(raw.substring(0, 80)));

    if (isAAMVA(raw)) {
      console.log('[HID] → AAMVA/ID barcode');
      setLastScanType('id');
      onBarcodeRef.current?.(raw);
    } else if (isWinstashQR(raw)) {
      console.log('[HID] → WinStash QR');
      setLastScanType('qr');
      onBarcodeRef.current?.(raw);
    } else if (isWinstashCard(raw)) {
      // NFC card with plain card_number written as NDEF Text (e.g. "NFC-4DE9AA5D")
      console.log('[HID] → WinStash NFC card:', raw.trim().toUpperCase());
      setLastScanType('nfc');
      onNfcTagRef.current?.({ cardId: raw.trim().toUpperCase() });
    } else if (raw.length < 10 && /^\d+$/.test(raw)) {
      // Short all-digit string — NFC UID or noise. Drop silently.
      console.log('[HID] → short numeric ignored (len=' + raw.length + ')');
    } else {
      console.log('[HID] → unknown scan ignored, length:', raw.length, 'preview:', raw.substring(0, 20));
    }
    refocus();
  }, [refocus]);

  // Primary character capture — manually accumulates into bufferRef with IME composition
  // tracking. Android Chrome fires insertCompositionText for Shift+key sequences, which
  // can either be GROWING ("D" → "DL", same composition expanding) or REPLACING ("A" then
  // "N" as a new composition that replaces the previous single-char one). We detect which
  // case applies by checking whether the new data starts with the last composition value.
  const handleInputEvent = useCallback((e) => {
    const data = e.nativeEvent?.data ?? e.data ?? '';
    const inputType = e.nativeEvent?.inputType ?? e.inputType ?? '';
    console.log('[HID-input] onInput fired, data:', JSON.stringify(data), 'inputType:', inputType);
    if (!data) return; // null for Enter, Delete, etc. — handled by handleKey

    if (inputType === 'insertCompositionText') {
      if (compositionRef.current && data.startsWith(lastCompDataRef.current)) {
        // Growing composition: previous "D" → now "DL". Update in place.
        compositionRef.current = data;
      } else {
        // New composition started (replaces previous single-char composition).
        // Commit whatever was pending before starting fresh.
        if (compositionRef.current) bufferRef.current += compositionRef.current;
        compositionRef.current = data;
      }
      lastCompDataRef.current = data;
    } else {
      // insertText: commit any pending composition first, then append this char.
      if (compositionRef.current) {
        bufferRef.current += compositionRef.current;
        compositionRef.current = '';
        lastCompDataRef.current = '';
      }
      bufferRef.current += data;
    }

    if (timerRef.current) clearTimeout(timerRef.current);
    timerRef.current = setTimeout(processBuffer, 400);
  }, [processBuffer]);

  // Keydown handler — only used for Enter/CR suffix sent by scanner at end of scan.
  // Printable characters are captured by handleInputEvent above.
  //
  // IMPORTANT: Do NOT call processBuffer() immediately here. AAMVA (driver's license)
  // barcodes contain @\n at the start — the \n fires Enter mid-scan. Calling processBuffer
  // immediately on the first Enter would split the barcode into two incomplete reads.
  // Instead, use a short timer: if more chars follow within 400ms, handleInputEvent resets
  // the timer and the full scan accumulates. If Enter was truly the last char (QR/NFC),
  // the timer fires 150ms later with the complete buffer.
  const handleKey = useCallback((e) => {
    console.log('[HID-key] keydown fired, key:', JSON.stringify(e.key), 'code:', e.code);
    if (e.key === 'Enter') {
      // Commit any pending composition — Enter finalizes it
      if (compositionRef.current) {
        bufferRef.current += compositionRef.current;
        compositionRef.current = '';
        lastCompDataRef.current = '';
      }
      // Add the newline so AAMVA field codes are properly delimited in the buffer
      bufferRef.current += '\n';
      if (timerRef.current) clearTimeout(timerRef.current);
      timerRef.current = setTimeout(processBuffer, 600);
    }
    // All other printable chars handled by onInput — do nothing here
  }, [processBuffer]);

  // Global fallback: capture keystrokes when hidden input loses focus.
  // Uses e.key directly (acceptable fallback; focus loss should be rare).
  useEffect(() => {
    const onGlobalKey = (e) => {
      const tag = document.activeElement?.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
      if (e.key === 'Enter') {
        bufferRef.current += '\n';
      } else if (e.key.length === 1) {
        bufferRef.current += e.key;
      } else {
        return;
      }
      if (timerRef.current) clearTimeout(timerRef.current);
      timerRef.current = setTimeout(processBuffer, 2000);
    };
    document.addEventListener('keydown', onGlobalKey);
    return () => document.removeEventListener('keydown', onGlobalKey);
  }, [processBuffer]);

  // Clean up timer on unmount
  useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); }, []);

  // ── Web NFC API (tablet built-in NFC antenna) ────────────────────────────
  // Chrome on Android supports NDEFReader but scan() requires a user gesture.
  // startWebNfc() is called from NFC screens when the user taps a button,
  // satisfying the gesture requirement. State is exposed so screens can show
  // a "Tap to enable NFC" prompt before the card tap.
  const webNfcAbortRef = useRef(null);
  const [webNfcState, setWebNfcState] = useState(
    'NDEFReader' in window ? 'idle' : 'unsupported'
  ); // 'unsupported' | 'idle' | 'scanning' | 'error'

  const startWebNfc = useCallback(async () => {
    if (!('NDEFReader' in window)) return;
    if (webNfcAbortRef.current) webNfcAbortRef.current.abort();

    const abortCtrl = new AbortController();
    webNfcAbortRef.current = abortCtrl;

    try {
      const reader = new window.NDEFReader();
      await reader.scan({ signal: abortCtrl.signal });
      setWebNfcState('scanning');
      console.log('[WebNFC] tablet NFC scanning active');

      reader.addEventListener('reading', ({ message }) => {
        for (const record of message.records) {
          let text = null;
          if (record.recordType === 'text') {
            text = new TextDecoder(record.encoding || 'utf-8').decode(record.data);
          } else if (record.recordType === 'url') {
            text = new TextDecoder().decode(record.data);
          }
          if (text) {
            console.log('[WebNFC] NDEF text, length:', text.length, 'preview:', text.substring(0, 60));
            setLastScanType('nfc');
            onNfcTagRef.current?.({ cardId: text });
            return;
          }
        }
        console.warn('[WebNFC] card tapped — no text/url NDEF record (card may be blank)');
        onNfcTagRef.current?.({ cardId: null, blank: true });
      });

      reader.addEventListener('readingerror', () => {
        console.warn('[WebNFC] read error — card may be unreadable');
      });
    } catch (err) {
      if (err.name === 'AbortError') return;
      console.warn('[WebNFC] start failed:', err.message);
      // "NFC is not supported" = hardware absent or disabled in Android settings
      const isHardwareUnavailable = err.message?.toLowerCase().includes('not supported')
        || err.message?.toLowerCase().includes('not available')
        || err.name === 'NotSupportedError';
      setWebNfcState(isHardwareUnavailable ? 'unsupported' : 'error');
    }
  }, []);

  // Stop scanning when no NFC callback is registered (screen changed)
  const stopWebNfc = useCallback(() => {
    if (webNfcAbortRef.current) {
      webNfcAbortRef.current.abort();
      webNfcAbortRef.current = null;
    }
    setWebNfcState(prev => prev === 'scanning' ? 'idle' : prev);
  }, []);

  useEffect(() => () => { webNfcAbortRef.current?.abort(); }, []);

  const handleBlur = useCallback(() => { refocus(); }, [refocus]);
  const setOnBarcode = useCallback((cb) => { onBarcodeRef.current = cb; }, []);
  const setOnNfcTag  = useCallback((cb) => { onNfcTagRef.current  = cb; }, []);

  // Camera-based QR scanner — routes result through onBarcodeRef same as HID
  const [isQrCameraScanning, setIsQrCameraScanning] = useState(false);
  const scanQrWithCamera = useCallback(async ({ onError } = {}) => {
    if (!window.ScanbotSDK) {
      onError?.('Camera unavailable — Scanbot SDK not loaded.');
      return;
    }
    setIsQrCameraScanning(true);
    try {
      await initScanbot();
      const config = new ScanbotSDK.UI.Config.BarcodeScannerScreenConfiguration();
      config.scannerConfiguration.barcodeFormats = ['QR_CODE'];
      const result = await ScanbotSDK.UI.createBarcodeScanner(config);
      setIsQrCameraScanning(false);
      if (result?.items?.length > 0) {
        const raw = result.items[0].barcode.text;
        console.log('[Camera QR] scanned:', raw.substring(0, 60));
        setLastScanType('qr');
        onBarcodeRef.current?.(raw);
      }
    } catch (err) {
      setIsQrCameraScanning(false);
      onError?.(err?.message || 'Camera scanner failed to open');
    }
  }, []);

  const value = {
    deviceConnected: true,   // HID: always available (BT state not detectable in browser)
    lastScanType,
    setOnBarcode,
    setOnNfcTag,
    scanQrWithCamera,
    isQrCameraScanning,
    sdkStatus: 'ready',
    sdkError: null,
    deviceInfo: { name: 'S370' },
    refocus,
    // Web NFC (tablet built-in NFC antenna)
    webNfcState,      // 'unsupported' | 'idle' | 'scanning' | 'error'
    startWebNfc,
    stopWebNfc,
  };

  return (
    <CaptureContext.Provider value={value}>
      {/* Hidden input — captures S370 HID keyboard emulation scan data */}
      <input
        ref={inputRef}
        onKeyDown={handleKey}
        onInput={handleInputEvent}
        onBlur={handleBlur}
        inputMode="none"
        style={{ position: 'fixed', opacity: 0, pointerEvents: 'none', width: 1, height: 1, top: 0, left: 0 }}
        autoComplete="off"
        autoCorrect="off"
        autoCapitalize="off"
        spellCheck={false}
        aria-hidden="true"
      />
      {/* Scanbot scanner mount point — SDK renders its camera UI here when scanning */}
      <div id="scanbot-container" style={{ position: 'fixed', inset: 0, zIndex: 9999, display: 'none' }} />
      {children}
    </CaptureContext.Provider>
  );
}

// — Clipboard helper ----------------------------------------------------------
function copyToClipboard(text) {
  navigator.clipboard.writeText(text).catch(() => {
    try {
      const el = document.createElement("textarea");
      el.value = text;
      document.body.appendChild(el);
      el.select();
      document.execCommand("copy");
      document.body.removeChild(el);
    } catch (_) {}
  });
}

// — Toast Notification --------------------------------------------------------
function Toast({ message, type, onClose }) {
  const [copied, setCopied] = React.useState(false);

  useEffect(() => {
    const t = setTimeout(onClose, type === "error" ? 7000 : 4000);
    return () => clearTimeout(t);
  }, [onClose, type]);

  const bg =
    type === "success" ? COLORS.success : type === "error" ? COLORS.error : type === "warning" ? COLORS.warning : COLORS.orange;

  function handleCopy() {
    navigator.clipboard.writeText(message).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }).catch(() => {
      // Fallback for non-HTTPS
      try {
        const el = document.createElement("textarea");
        el.value = message;
        document.body.appendChild(el);
        el.select();
        document.execCommand("copy");
        document.body.removeChild(el);
        setCopied(true);
        setTimeout(() => setCopied(false), 2000);
      } catch (_) {}
    });
  }

  return (
    <div
      onClick={type === "error" ? handleCopy : undefined}
      style={{
        position: "fixed", top: 24, left: "50%", transform: "translateX(-50%)",
        background: bg, color: "#fff", padding: "14px 28px", borderRadius: 12,
        fontSize: 17, fontWeight: 600, zIndex: 9999, boxShadow: "0 8px 32px rgba(0,0,0,.5)",
        animation: "slideDown .3s ease", maxWidth: 520, textAlign: "center",
        cursor: type === "error" ? "pointer" : "default",
      }}
    >
      {message}
      {type === "error" && (
        <div style={{ fontSize: 11, fontWeight: 400, opacity: 0.75, marginTop: 4 }}>
          {copied ? "Copied!" : "Tap to copy"}
        </div>
      )}
    </div>
  );
}

// — Scanner Status Indicator --------------------------------------------------
// Shows last scan result; no connection state (HID mode — not detectable in browser)
function DeviceStatus() {
  const captureCtx = useCaptureSDK();
  if (!captureCtx) return null;

  const { lastScanType } = captureCtx;
  const cfg = lastScanType === 'nfc'
    ? { color: COLORS.success, bg: COLORS.successBg, border: COLORS.success, label: 'Last scan: Card' }
    : lastScanType === 'id'
    ? { color: COLORS.success, bg: COLORS.successBg, border: COLORS.success, label: 'Last scan: ID' }
    : { color: COLORS.textMuted, bg: COLORS.surfaceLight, border: COLORS.border, label: 'Scanner ready' };

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      padding: '6px 12px', borderRadius: 8,
      background: cfg.bg, border: `1px solid ${cfg.border}`,
      fontSize: 12, color: cfg.color,
    }}>
      <span style={{ width: 8, height: 8, borderRadius: '50%', background: cfg.color }} />
      <span>{cfg.label}</span>
    </div>
  );
}

// — Shared Button Component ---------------------------------------------------
function Btn({ children, onClick, color = COLORS.orange, disabled, wide, size = "md", variant = "primary", style = {} }) {
  const sizes = {
    sm: { padding: "10px 20px", fontSize: 15 },
    md: { padding: "16px 32px", fontSize: 18 },
    lg: { padding: "22px 44px", fontSize: 22 },
    xl: { padding: "28px 0", fontSize: 26 },
  };
  const bgColor = variant === "secondary" ? COLORS.surfaceLight : color;
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      style={{
        background: disabled ? COLORS.border : bgColor,
        color: COLORS.heading,
        border: variant === "secondary" ? `2px solid ${COLORS.border}` : "none",
        borderRadius: 14,
        fontWeight: 700,
        cursor: disabled ? "not-allowed" : "pointer",
        width: wide ? "100%" : "auto",
        transition: "all .15s ease",
        letterSpacing: ".3px",
        fontFamily: "inherit",
        opacity: disabled ? 0.6 : 1,
        ...sizes[size],
        ...style,
      }}
    >
      {children}
    </button>
  );
}

// — Input Component -----------------------------------------------------------
function Input({ label, value, onChange, type = "text", placeholder, required, autoFocus, maxLength, inputMode, disabled, uppercase }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
        {label} {required && <span style={{ color: COLORS.orange }}>*</span>}
      </label>
      <input
        type={type}
        value={value}
        onChange={(e) => onChange(uppercase ? e.target.value.toUpperCase() : e.target.value)}
        placeholder={placeholder}
        autoFocus={autoFocus}
        maxLength={maxLength}
        inputMode={inputMode}
        disabled={disabled}
        style={{
          width: "100%",
          padding: "14px 16px",
          fontSize: 18,
          border: `2px solid ${COLORS.border}`,
          borderRadius: 10,
          outline: "none",
          fontFamily: "inherit",
          boxSizing: "border-box",
          transition: "border-color .15s",
          background: disabled ? "#2A2A2A" : "#1E1E1E",
          color: disabled ? COLORS.textMuted : COLORS.heading,
          textTransform: uppercase ? "uppercase" : "none",
        }}
        onFocus={(e) => (e.target.style.borderColor = COLORS.orange)}
        onBlur={(e) => (e.target.style.borderColor = COLORS.border)}
      />
    </div>
  );
}

// — Select Component ----------------------------------------------------------
function Select({ label, value, onChange, options, required }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
        {label} {required && <span style={{ color: COLORS.orange }}>*</span>}
      </label>
      <select
        value={value}
        onChange={(e) => onChange(e.target.value)}
        style={{
          width: "100%", padding: "14px 16px", fontSize: 18,
          border: `2px solid ${COLORS.border}`, borderRadius: 10, outline: "none",
          fontFamily: "inherit", boxSizing: "border-box", background: "#1E1E1E",
          color: COLORS.heading, appearance: "auto",
        }}
      >
        {options.map((o) => (
          <option key={o.value} value={o.value}>{o.label}</option>
        ))}
      </select>
    </div>
  );
}

// — US States -----------------------------------------------------------------
const US_STATES = [
  { value: "", label: "Select state..." },
  { value: "AL", label: "AL" }, { value: "AK", label: "AK" }, { value: "AZ", label: "AZ" },
  { value: "AR", label: "AR" }, { value: "CA", label: "CA" }, { value: "CO", label: "CO" },
  { value: "CT", label: "CT" }, { value: "DE", label: "DE" }, { value: "FL", label: "FL" },
  { value: "GA", label: "GA" }, { value: "HI", label: "HI" }, { value: "ID", label: "ID" },
  { value: "IL", label: "IL" }, { value: "IN", label: "IN" }, { value: "IA", label: "IA" },
  { value: "KS", label: "KS" }, { value: "KY", label: "KY" }, { value: "LA", label: "LA" },
  { value: "ME", label: "ME" }, { value: "MD", label: "MD" }, { value: "MA", label: "MA" },
  { value: "MI", label: "MI" }, { value: "MN", label: "MN" }, { value: "MS", label: "MS" },
  { value: "MO", label: "MO" }, { value: "MT", label: "MT" }, { value: "NE", label: "NE" },
  { value: "NV", label: "NV" }, { value: "NH", label: "NH" }, { value: "NJ", label: "NJ" },
  { value: "NM", label: "NM" }, { value: "NY", label: "NY" }, { value: "NC", label: "NC" },
  { value: "ND", label: "ND" }, { value: "OH", label: "OH" }, { value: "OK", label: "OK" },
  { value: "OR", label: "OR" }, { value: "PA", label: "PA" }, { value: "RI", label: "RI" },
  { value: "SC", label: "SC" }, { value: "SD", label: "SD" }, { value: "TN", label: "TN" },
  { value: "TX", label: "TX" }, { value: "UT", label: "UT" }, { value: "VT", label: "VT" },
  { value: "VA", label: "VA" }, { value: "WA", label: "WA" }, { value: "WV", label: "WV" },
  { value: "WI", label: "WI" }, { value: "WY", label: "WY" },
];

// — WS Logo Component ---------------------------------------------------------
function WSLogo({ size = 72 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 32 32">
      <rect width="32" height="32" rx="6" fill={COLORS.bg}/>
      <text x="16" y="23" textAnchor="middle" fontFamily="DM Sans, -apple-system, sans-serif" fontWeight="700" fontSize="18">
        <tspan fill={COLORS.heading}>W</tspan><tspan fill={COLORS.orange}>S</tspan>
      </text>
    </svg>
  );
}

// ============================================================================
// PROVISIONING SCREEN — First-time POS device setup via QR scan
// ============================================================================
function ProvisioningScreen({ onProvisioned }) {
  const [scanning, setScanning] = useState(false);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(null);
  const videoRef = useRef(null);
  const streamRef = useRef(null);

  const stopCamera = useCallback(() => {
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(t => t.stop());
      streamRef.current = null;
    }
    setScanning(false);
  }, []);

  // Cleanup camera on unmount
  useEffect(() => () => stopCamera(), [stopCamera]);

  const startScan = async () => {
    setError(null);
    setScanning(true);
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: "environment" },
      });
      streamRef.current = stream;
      if (videoRef.current) {
        videoRef.current.srcObject = stream;
        videoRef.current.play();
      }
      // Use BarcodeDetector if available (Chrome/Android)
      if ('BarcodeDetector' in window) {
        const detector = new BarcodeDetector({ formats: ['qr_code'] });
        const scanLoop = async () => {
          if (!streamRef.current || !videoRef.current) return;
          try {
            const barcodes = await detector.detect(videoRef.current);
            if (barcodes.length > 0) {
              const tokenId = barcodes[0].rawValue.trim();
              stopCamera();
              await handleProvision(tokenId);
              return;
            }
          } catch (_) {}
          if (streamRef.current) requestAnimationFrame(scanLoop);
        };
        // Wait for video to start playing before scanning
        videoRef.current.onloadeddata = () => requestAnimationFrame(scanLoop);
      } else {
        setError("QR scanning requires Chrome or a Chromium-based browser on this device.");
        stopCamera();
      }
    } catch (err) {
      setError("Camera access denied. Please allow camera permissions and try again.");
      setScanning(false);
    }
  };

  const handleProvision = async (tokenId) => {
    // Validate UUID format
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    if (!uuidRegex.test(tokenId)) {
      setError("Invalid QR code. Please scan the provisioning QR from your administrator.");
      return;
    }
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(`${SUPABASE_URL}/functions/v1/pos-provision`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
        },
        body: JSON.stringify({ token_id: tokenId }),
      });
      const result = await res.json();
      if (!res.ok || !result.success) {
        const msg = result.error?.display_message || result.error?.message || "Provisioning failed.";
        setError(msg);
        return;
      }
      // Store device credentials
      localStorage.setItem(DEVICE_TOKEN_ID_KEY, result.data.token_id);
      localStorage.setItem(DEVICE_TOKEN_SECRET_KEY, result.data.token_secret);
      localStorage.setItem(DEVICE_LOCATION_ID_KEY, result.data.location_id);
      setSuccess(result.data);
      // Brief delay to show success, then continue
      setTimeout(() => onProvisioned(), 2000);
    } catch (err) {
      setError("Network error. Please check your connection and try again.");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{
      minHeight: "100vh",
      background: COLORS.bg,
      display: "flex", alignItems: "center", justifyContent: "center",
      fontFamily: "'DM Sans', -apple-system, sans-serif",
    }}>
      <div style={{
        background: COLORS.surface, borderRadius: 24, padding: "48px 44px", width: 460,
        boxShadow: "0 24px 80px rgba(0,0,0,.5)", border: `1px solid ${COLORS.border}`,
        textAlign: "center",
      }}>
        {/* Logo */}
        <div style={{ marginBottom: 32 }}>
          <div style={{ marginBottom: 16 }}>
            <WSLogo size={72} />
          </div>
          <h1 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 4px" }}>
            <span style={{ color: COLORS.heading }}>Win</span>
            <span style={{ color: COLORS.orange }}>Stash</span>
            <span style={{ color: COLORS.heading }}> POS Setup</span>
          </h1>
          <p style={{ fontSize: 15, color: COLORS.textMuted, margin: "8px 0 0" }}>
            This device needs to be configured before use.
          </p>
        </div>

        {/* Success state */}
        {success && (
          <div style={{
            background: COLORS.successBg, border: `1px solid ${COLORS.success}`,
            borderRadius: 12, padding: "20px 16px", marginBottom: 20,
          }}>
            <p style={{ color: COLORS.success, fontSize: 16, fontWeight: 600, margin: "0 0 8px" }}>
              {success.display_message}
            </p>
            <p style={{ color: COLORS.text, fontSize: 14, margin: 0 }}>
              Location: {success.location_name}
            </p>
          </div>
        )}

        {/* Error */}
        {error && (
          <div style={{
            background: COLORS.errorBg, border: `1px solid ${COLORS.error}`,
            borderRadius: 10, padding: "12px 16px", marginBottom: 20,
          }}>
            <p style={{ color: COLORS.error, fontSize: 14, fontWeight: 500, margin: 0 }}>{error}</p>
          </div>
        )}

        {/* Camera / QR area */}
        {!success && (
          <>
            {scanning ? (
              <div style={{ marginBottom: 24 }}>
                <div style={{
                  width: "100%", maxWidth: 340, margin: "0 auto",
                  borderRadius: 16, overflow: "hidden",
                  border: `2px solid ${COLORS.orange}`,
                  position: "relative",
                }}>
                  <video
                    ref={videoRef}
                    style={{ width: "100%", display: "block" }}
                    playsInline
                    muted
                  />
                  {/* Scanning indicator */}
                  <div style={{
                    position: "absolute", bottom: 12, left: "50%", transform: "translateX(-50%)",
                    background: "rgba(0,0,0,0.7)", borderRadius: 8, padding: "6px 16px",
                  }}>
                    <p style={{ color: COLORS.orange, fontSize: 13, fontWeight: 500, margin: 0, animation: "pulse 1.5s infinite" }}>
                      Scanning...
                    </p>
                  </div>
                </div>
                <button onClick={stopCamera} style={{
                  marginTop: 16, padding: "10px 24px", borderRadius: 10,
                  background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`,
                  color: COLORS.text, fontSize: 14, fontWeight: 500, cursor: "pointer",
                }}>
                  Cancel
                </button>
              </div>
            ) : (
              <div style={{ marginBottom: 24 }}>
                <div style={{
                  width: 200, height: 200, margin: "0 auto 24px",
                  border: `2px dashed ${COLORS.border}`, borderRadius: 20,
                  display: "flex", alignItems: "center", justifyContent: "center",
                  flexDirection: "column", gap: 8,
                }}>
                  <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke={COLORS.textMuted} strokeWidth="1.5">
                    <rect x="3" y="3" width="7" height="7" rx="1" />
                    <rect x="14" y="3" width="7" height="7" rx="1" />
                    <rect x="3" y="14" width="7" height="7" rx="1" />
                    <rect x="14" y="14" width="3" height="3" />
                    <line x1="21" y1="14" x2="21" y2="17" />
                    <line x1="17" y1="21" x2="21" y2="21" />
                    <line x1="21" y1="17" x2="21" y2="21" />
                  </svg>
                  <p style={{ color: COLORS.textMuted, fontSize: 13, margin: 0 }}>QR Code</p>
                </div>
                <p style={{ color: COLORS.text, fontSize: 14, marginBottom: 24, lineHeight: 1.5 }}>
                  Scan the provisioning QR code provided by your WinStash administrator.
                </p>
                <button onClick={startScan} disabled={loading} style={{
                  padding: "14px 32px", borderRadius: 12,
                  background: loading ? COLORS.surfaceLight : COLORS.orange,
                  border: "none", color: "#fff", fontSize: 16, fontWeight: 600,
                  cursor: loading ? "default" : "pointer",
                  opacity: loading ? 0.6 : 1,
                }}>
                  {loading ? "Configuring..." : "Scan QR Code"}
                </button>
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================================
// LOGIN SCREEN — Operator Card + PIN
// ============================================================================
function LoginScreen({ onLogin, logoutReason, onClearReason }) {
  const [cardSuffix, setCardSuffix] = useState("");
  const [pin, setPin] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const pinRef = useRef(null);

  const handleCardChange = (v) => {
    if (logoutReason && onClearReason) onClearReason();
    const formatted = fmtOperatorCardSuffix(v);
    setCardSuffix(formatted);
    if (formatted.length >= 5 && pinRef.current) {
      pinRef.current.focus();
    }
  };

  const handleLogin = async () => {
    if (!cardSuffix || cardSuffix.length < 5) return setError("Enter your 5-digit operator card number");
    if (!pin || pin.length < 4) return setError("Enter your 4-digit PIN");
    setLoading(true);
    setError(null);
    try {
      const fullCardNumber = "WSO-" + cardSuffix;
      const data = await api("operator-login", { card_number: fullCardNumber, pin });
      const pinHash = await hashPin(pin);
      onLogin({
        token: data.token,
        operator: data.operator,
        location: data.location,
        pinHash,
      });
    } catch (err) {
      console.error("Login error:", err);
      setError(err.error || "Login failed. Check your card and PIN.");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{
      minHeight: "100vh",
      background: COLORS.bg,
      display: "flex", alignItems: "center", justifyContent: "center",
      fontFamily: "'DM Sans', -apple-system, sans-serif",
    }}>
      <div style={{
        background: COLORS.surface, borderRadius: 24, padding: "48px 44px", width: 420,
        boxShadow: "0 24px 80px rgba(0,0,0,.5)", border: `1px solid ${COLORS.border}`,
      }}>
        {/* Logo */}
        <div style={{ textAlign: "center", marginBottom: 36 }}>
          <div style={{ marginBottom: 16 }}>
            <WSLogo size={72} />
          </div>
          <h1 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 4px" }}>
            <span style={{ color: COLORS.heading }}>Win</span><span style={{ color: COLORS.orange }}>Stash</span>
            <span style={{ color: COLORS.heading }}> POS</span>
          </h1>
          <p style={{ fontSize: 15, color: COLORS.textMuted, margin: 0 }}>Operator Login</p>
        </div>

        {/* Inactivity banner */}
        {logoutReason === "inactivity" && (
          <div style={{
            background: "rgba(255,107,0,0.1)", border: "1px solid rgba(255,107,0,0.3)",
            borderRadius: 10, padding: "12px 16px", marginBottom: 20, textAlign: "center",
          }}>
            <p style={{ color: COLORS.orange, fontSize: 14, fontWeight: 500, margin: 0 }}>
              You were signed out due to inactivity.
            </p>
          </div>
        )}

        {/* Card Input */}
        <div style={{ marginBottom: 20 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
            Operator Card <span style={{ color: COLORS.orange }}>*</span>
          </label>
          <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.orange}` }}>
            <span style={{
              padding: "16px 0 16px 18px", fontSize: 22, fontWeight: 700,
              fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px",
            }}>WSO-</span>
            <input
              type="text"
              inputMode="numeric"
              value={cardSuffix}
              onChange={(e) => handleCardChange(e.target.value)}
              placeholder="00000"
              autoFocus
              maxLength={5}
              style={{
                flex: 1, padding: "16px 18px 16px 0", fontSize: 22, fontWeight: 700,
                border: "none", outline: "none",
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                background: "transparent", letterSpacing: "2px",
                textTransform: "uppercase", color: COLORS.heading,
              }}
              onKeyDown={(e) => e.key === "Enter" && pinRef.current?.focus()}
            />
          </div>
          <p style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 6, textAlign: "center" }}>
            Swipe your operator card or type your 5-digit code
          </p>
        </div>

        {/* PIN */}
        <div style={{ marginBottom: 20 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
            PIN <span style={{ color: COLORS.orange }}>*</span>
          </label>
          <input
            ref={pinRef}
            type="password"
            value={pin}
            onChange={(e) => setPin(e.target.value.replace(/\D/g, "").slice(0, 4))}
            placeholder=""
            inputMode="numeric"
            maxLength={4}
            style={{
              width: "100%", padding: "16px 18px", fontSize: 28, fontWeight: 700,
              border: `2px solid ${COLORS.border}`, borderRadius: 12, outline: "none",
              fontFamily: "inherit", boxSizing: "border-box", background: COLORS.surfaceLight,
              textAlign: "center", letterSpacing: "8px", color: COLORS.heading,
            }}
            onFocus={(e) => (e.target.style.borderColor = COLORS.orange)}
            onBlur={(e) => (e.target.style.borderColor = COLORS.border)}
            onKeyDown={(e) => e.key === "Enter" && handleLogin()}
          />
        </div>

        {error && (
          <div style={{
            background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 10,
            padding: "12px 16px", marginBottom: 16, color: COLORS.error, fontSize: 15, fontWeight: 500,
          }}>
            {error}
          </div>
        )}

        <Btn onClick={handleLogin} disabled={loading} wide size="lg">
          {loading ? "Logging in..." : "LOGIN"}
        </Btn>
      </div>
    </div>
  );
}

// ============================================================================
// TOP BAR
// ============================================================================
function TopBar({ session, onLogout, onBack, showBack, title, onSettings }) {
  const canAccessSettings = hasRoleLevel(session.operator?.role, 2);

  return (
    <div style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 100,
      background: COLORS.surface, color: COLORS.heading, padding: "0 24px",
      height: 64, display: "flex", alignItems: "center", justifyContent: "space-between",
      borderBottom: `1px solid ${COLORS.border}`,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
        {showBack && (
          <button onClick={onBack} style={{
            background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`, color: COLORS.text,
            padding: "8px 16px", borderRadius: 8, fontSize: 15, fontWeight: 600,
            cursor: "pointer", fontFamily: "inherit",
          }}>
            ← Back
          </button>
        )}
        <span style={{ fontWeight: 700, fontSize: 18 }}>
          <span style={{ color: COLORS.heading }}>Win</span>
          <span style={{ color: COLORS.orange }}>Stash</span>
          <span style={{ color: COLORS.textMuted }}> POS</span>
        </span>
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: 20 }}>
        <DeviceStatus />
        <div style={{ textAlign: "right", lineHeight: 1.3 }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: COLORS.heading }}>
            {session.operator?.name?.split(" ")[0]} <span style={{ color: COLORS.textMuted }}>({session.operator?.role})</span>
          </div>
          <div style={{ fontSize: 12, color: COLORS.textMuted }}>{session.location?.name}</div>
        </div>
        {canAccessSettings && onSettings && (
          <button
            onClick={onSettings}
            title="Location Settings"
            style={{
              background: COLORS.surfaceLight,
              border: `1px solid ${COLORS.border}`,
              color: COLORS.text,
              padding: "8px",
              borderRadius: 8,
              fontSize: 18,
              cursor: "pointer",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              width: 40,
              height: 40,
            }}
          >
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="3"></circle>
              <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
            </svg>
          </button>
        )}
        <button onClick={onLogout} style={{
          background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`, color: COLORS.text,
          padding: "8px 16px", borderRadius: 8, fontSize: 14, fontWeight: 600,
          cursor: "pointer", fontFamily: "inherit",
        }}>
          Sign Out
        </button>
      </div>
    </div>
  );
}

// ============================================================================
// DASHBOARD SCREEN
// ============================================================================
function DashboardScreen({ onNavigate, session, onToast }) {
  const isNfcLocation = session.location?.card_reader_type === "nfc";
  const isSuperAdmin = session.operator?.role === "super_admin";
  const isManagerPlus = hasRoleLevel(session.operator?.role, 2);
  const [devResetting, setDevResetting] = useState(false);

  const actions = [
    {
      id: "register",
      icon: "👤",
      title: "Register New Player",
      desc: "ID Required",
    },
    {
      id: "load",
      icon: "💰",
      title: "Redeem Winnings",
      desc: "Load COAM Winnings onto Gift Card",
    },
    {
      id: "purchase",
      icon: "🛒",
      title: "Gift Card Purchase",
      desc: "Debit Player Card for In-Store Sale",
    },
    {
      id: "accounts",
      icon: "🔍",
      title: "Player Accounts",
      desc: "Search, View & Manage Player Accounts",
    },
  ];

  // Card Inventory — manager+ always; attendants only if owner enabled the toggle
  const attendantInventory = session.location?.allow_attendant_inventory;
  if (isManagerPlus || attendantInventory) {
    actions.push({
      id: "inventory",
      icon: "📦",
      title: "Card Inventory",
      desc: "View Card Stock & Status",
    });
  }

  // Cash Out — available to all POS roles during testing (excludes master, who uses admin portal)
  actions.push({
    id: "withdrawal",
    icon: "💵",
    title: "Cash Out Player",
    desc: "Manual Counter Withdrawal",
  });

  return (
    <div style={{ padding: 40, maxWidth: 800, margin: "0 auto" }}>
      <div style={{ marginBottom: 36 }}>
        <h2 style={{ fontSize: 30, fontWeight: 700, color: COLORS.heading, margin: "0 0 6px" }}>
          Welcome, {session.operator?.name?.split(" ")[0]}!
        </h2>
        <p style={{ fontSize: 17, color: COLORS.textMuted, margin: 0 }}>
          {session.location?.name}
        </p>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: actions.length <= 2 ? "1fr 1fr" : actions.length === 3 ? "1fr 1fr 1fr" : "1fr 1fr", gap: 24 }}>
        {actions.map((a) => (
          <button
            key={a.id}
            onClick={() => onNavigate(a.id)}
            style={{
              background: COLORS.surface,
              border: `2px solid ${COLORS.border}`,
              borderRadius: 20,
              padding: "40px 32px",
              cursor: "pointer",
              textAlign: "center",
              transition: "all .2s ease",
              fontFamily: "inherit",
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.borderColor = COLORS.orange;
              e.currentTarget.style.transform = "translateY(-2px)";
              e.currentTarget.style.boxShadow = `0 8px 32px ${COLORS.orangeGlow}`;
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.borderColor = COLORS.border;
              e.currentTarget.style.transform = "translateY(0)";
              e.currentTarget.style.boxShadow = "none";
            }}
          >
            <div style={{ fontSize: 52, marginBottom: 16 }}>{a.icon}</div>
            <div style={{ fontSize: 24, fontWeight: 700, color: COLORS.orange, marginBottom: 8 }}>
              {a.title}
            </div>
            <div style={{ fontSize: 15, color: COLORS.textMuted, lineHeight: 1.5 }}>
              {a.desc}
            </div>
          </button>
        ))}
      </div>

      {isSuperAdmin && (
        <div style={{ marginTop: 20, paddingTop: 24, borderTop: `1px dashed ${COLORS.border}` }}>
          <div style={{ fontSize: 12, color: COLORS.textMuted, fontWeight: 600, letterSpacing: ".5px", textTransform: "uppercase", marginBottom: 12 }}>
            Dev Tools
          </div>
          <button
            onClick={async () => {
              if (!window.confirm("Reset test data?\n\nThis will:\n• Delete the most recently registered player at this location\n• Restore NFC-001000xx cards to inventory\n\nCannot be undone.")) return;
              setDevResetting(true);
              try {
                const result = await api("player-manage", { action: "dev_reset_test_data", data: {} }, session.token);
                onToast(result.message || "Reset complete", "success");
              } catch (err) {
                onToast(err.error || "Reset failed", "error");
              } finally {
                setDevResetting(false);
              }
            }}
            disabled={devResetting}
            style={{
              background: "transparent",
              border: `1px solid ${COLORS.error}`,
              borderRadius: 10,
              padding: "10px 20px",
              cursor: devResetting ? "not-allowed" : "pointer",
              color: COLORS.error,
              fontSize: 13,
              fontWeight: 600,
              fontFamily: "inherit",
              opacity: devResetting ? 0.5 : 1,
            }}
          >
            {devResetting ? "Resetting..." : "Reset Test Data"}
          </button>
        </div>
      )}
    </div>
  );
}

// ============================================================================
// AGE VERIFICATION GATE
// ============================================================================
// Shown before Register, Load, Purchase, and Withdrawal screens.
// Players must be 18 or older. Each attempt is logged for compliance.
// Scanning the ID here also pre-fills the registration form so the operator
// does not have to scan the license twice.
// ============================================================================
function AgeVerificationGate({ session, onVerified, onToast }) {
  const captureCtx = useCaptureSDK();
  const { scanId, isScanning: isCameraScanning } = useIdScanner();
  const [scanning, setScanning] = useState(true);
  const [scanResult, setScanResult] = useState(null);
  const [manualMode, setManualMode] = useState(false);
  const [manualDobMM, setManualDobMM] = useState("");
  const [manualDobDD, setManualDobDD] = useState("");
  const [manualDobYYYY, setManualDobYYYY] = useState("");
  const dobDDRef = useRef(null);
  const dobYYYYRef = useRef(null);
  const [manualLicense, setManualLicense] = useState("");

  const scannerAvailable = true; // HID mode: scanner always available when paired

  function logVerification(passed, reason, parsed) {
    appendLocalLog({
      action: "id_verification",
      actor_type: "operator",
      actor_id: session?.operator?.id,
      actor_name: session?.operator?.name,
      location_id: session?.location?.id,
      details: {
        passed,
        reason,
        player_name: [parsed?.first_name, parsed?.last_name].filter(Boolean).join(" ") || null,
        license_number: parsed?.id_number || manualLicense || null,
        id_state: parsed?.id_state || null,
        age: parsed?.age ?? null,
      },
    });
  }

  // Register HID barcode callback while waiting for scan
  useEffect(() => {
    if (!captureCtx?.setOnBarcode || !scanning) return;
    captureCtx.setOnBarcode((data) => { processIdData(data); });
    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, scanning, onVerified]); // eslint-disable-line

  function handleManualSubmit() {
    const mm = parseInt(manualDobMM, 10);
    const dd = parseInt(manualDobDD, 10);
    const yyyy = parseInt(manualDobYYYY, 10);
    if (!manualDobMM || !manualDobDD || !manualDobYYYY) { onToast("Enter date of birth", "error"); return; }
    if (isNaN(mm) || mm < 1 || mm > 12) { onToast("Invalid month", "error"); return; }
    if (isNaN(dd) || dd < 1 || dd > 31) { onToast("Invalid day", "error"); return; }
    if (isNaN(yyyy) || yyyy < 1900 || yyyy > new Date().getFullYear()) { onToast("Invalid year", "error"); return; }
    const dob = new Date(yyyy, mm - 1, dd);
    if (isNaN(dob.getTime())) { onToast("Invalid date", "error"); return; }
    const today = new Date();
    let age = today.getFullYear() - dob.getFullYear();
    const m = today.getMonth() - dob.getMonth();
    if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
    const passed = age >= 18;
    logVerification(passed, passed ? "passed_manual" : "underage_manual",
      { age, id_number: manualLicense });
    if (!passed) {
      setScanResult({ passed: false, age,
        reason: `Player must be 18 or older (age: ${age}).` });
      setManualMode(false);
      return;
    }
    onVerified({ name: null, age, licenseNumber: manualLicense || null,
      verifiedAt: new Date().toISOString(), parsedLicense: null });
  }

  const handleReset = () => {
    setScanResult(null);
    setScanning(true);
    setManualMode(false);
    setManualDobMM("");
    setManualDobDD("");
    setManualDobYYYY("");
    setManualLicense("");
  };

  // Shared processor — handles barcode data from either HID scanner or camera
  function processIdData(data) {
    const parsed = parseDriversLicense(data);
    if (!parsed.last_name && !parsed.first_name) {
      setScanResult({ passed: false, reason: "Could not read ID. Please try again." });
      setScanning(false);
      return;
    }
    const name = [parsed.first_name, parsed.last_name].filter(Boolean).join(" ");
    if (parsed.isExpired) {
      logVerification(false, "expired", parsed);
      logIdVerification({ licenseNumber: parsed.id_number, operatorId: session?.operator?.id, locationId: session?.location?.id, isOver21: false, isExpired: true, wasApproved: false });
      setScanResult({ passed: false, name, reason: "ID is expired — cannot proceed." });
      setScanning(false);
      return;
    }
    if (!parsed.isOver18) {
      logVerification(false, "underage", parsed);
      logIdVerification({ licenseNumber: parsed.id_number, operatorId: session?.operator?.id, locationId: session?.location?.id, isOver21: false, isExpired: false, wasApproved: false });
      setScanResult({ passed: false, name, age: parsed.age,
        reason: `Player must be 18 or older (age on file: ${parsed.age ?? "unknown"}).` });
      setScanning(false);
      return;
    }
    logVerification(true, "passed", parsed);
    logIdVerification({ licenseNumber: parsed.id_number, operatorId: session?.operator?.id, locationId: session?.location?.id, isOver21: (parsed.age ?? 0) >= 21, isExpired: false, wasApproved: true });
    setScanResult({ passed: true, name, age: parsed.age });
    setScanning(false);
    setTimeout(() => onVerified({
      name,
      age: parsed.age,
      licenseNumber: parsed.id_number,
      verifiedAt: new Date().toISOString(),
      parsedLicense: parsed,
    }), 1200);
  }

  // ── Passed ──────────────────────────────────────────────────────────────────
  if (scanResult?.passed) {
    return (
      <div style={{ padding: 40, maxWidth: 560, margin: "0 auto", textAlign: "center" }}>
        <div style={{
          background: COLORS.successBg, border: `2px solid ${COLORS.success}`,
          borderRadius: 20, padding: "48px 32px",
        }}>
          <div style={{ fontSize: 64, marginBottom: 16 }}>✅</div>
          <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.success, marginBottom: 8 }}>
            Age Verified ✓
          </div>
          <div style={{ fontSize: 20, color: COLORS.heading, marginBottom: 6 }}>{scanResult.name}</div>
          <div style={{ fontSize: 15, color: COLORS.textMuted }}>Age {scanResult.age} — proceeding...</div>
        </div>
      </div>
    );
  }

  // ── Failed ──────────────────────────────────────────────────────────────────
  if (scanResult && !scanResult.passed) {
    return (
      <div style={{ padding: 40, maxWidth: 560, margin: "0 auto", textAlign: "center" }}>
        <div style={{
          background: COLORS.errorBg, border: `2px solid ${COLORS.error}`,
          borderRadius: 20, padding: "48px 32px", marginBottom: 24,
        }}>
          <div style={{ fontSize: 64, marginBottom: 16 }}>🚫</div>
          {scanResult.name && (
            <div style={{ fontSize: 20, fontWeight: 700, color: COLORS.heading, marginBottom: 8 }}>
              {scanResult.name}
            </div>
          )}
          <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.error, marginBottom: 12 }}>
            {scanResult.reason}
          </div>
          <div style={{ fontSize: 14, color: COLORS.textMuted }}>
            Cannot proceed. Please assist the player appropriately.
          </div>
        </div>
        <Btn onClick={handleReset} wide size="lg">Scan Again</Btn>
      </div>
    );
  }

  // ── Manual entry (scanner not available) ────────────────────────────────────
  if (manualMode) {
    return (
      <div style={{ padding: 40, maxWidth: 560, margin: "0 auto" }}>
        <h2 style={{ fontSize: 24, fontWeight: 700, color: COLORS.heading, marginBottom: 8 }}>
          Manual Age Verification
        </h2>
        <p style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 24 }}>
          Visually inspect the player's ID and enter the details below.
        </p>
        <div style={{
          background: COLORS.surface, border: `1px solid ${COLORS.border}`,
          borderRadius: 14, padding: "24px", marginBottom: 20,
        }}>
          <div style={{ marginBottom: 16 }}>
            <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
              Date of Birth <span style={{ color: COLORS.orange }}>*</span>
            </label>
            <div style={{ display: "flex", gap: 10 }}>
              <div style={{ flex: "0 0 80px" }}>
                <input
                  type="number" placeholder="MM" inputMode="numeric"
                  value={manualDobMM}
                  onChange={e => { const v = e.target.value.slice(0, 2); setManualDobMM(v); if (v.length === 2) dobDDRef.current?.focus(); }}
                  maxLength={2} min={1} max={12}
                  style={{ width: "100%", padding: "14px 12px", fontSize: 22, fontWeight: 700, textAlign: "center", border: `2px solid ${COLORS.border}`, borderRadius: 10, background: COLORS.surface, color: COLORS.heading, fontFamily: "inherit" }}
                />
                <div style={{ fontSize: 11, color: COLORS.textMuted, textAlign: "center", marginTop: 4 }}>Month</div>
              </div>
              <div style={{ flex: "0 0 80px" }}>
                <input
                  ref={dobDDRef}
                  type="number" placeholder="DD" inputMode="numeric"
                  value={manualDobDD}
                  onChange={e => { const v = e.target.value.slice(0, 2); setManualDobDD(v); if (v.length === 2) dobYYYYRef.current?.focus(); }}
                  maxLength={2} min={1} max={31}
                  style={{ width: "100%", padding: "14px 12px", fontSize: 22, fontWeight: 700, textAlign: "center", border: `2px solid ${COLORS.border}`, borderRadius: 10, background: COLORS.surface, color: COLORS.heading, fontFamily: "inherit" }}
                />
                <div style={{ fontSize: 11, color: COLORS.textMuted, textAlign: "center", marginTop: 4 }}>Day</div>
              </div>
              <div style={{ flex: 1 }}>
                <input
                  ref={dobYYYYRef}
                  type="number" placeholder="YYYY" inputMode="numeric"
                  value={manualDobYYYY}
                  onChange={e => setManualDobYYYY(e.target.value.slice(0, 4))}
                  maxLength={4} min={1900} max={new Date().getFullYear()}
                  style={{ width: "100%", padding: "14px 12px", fontSize: 22, fontWeight: 700, textAlign: "center", border: `2px solid ${COLORS.border}`, borderRadius: 10, background: COLORS.surface, color: COLORS.heading, fontFamily: "inherit" }}
                />
                <div style={{ fontSize: 11, color: COLORS.textMuted, textAlign: "center", marginTop: 4 }}>Year</div>
              </div>
            </div>
          </div>
          <Input label="License / ID Number" value={manualLicense} onChange={setManualLicense}
            placeholder="Optional — for compliance log" uppercase />
        </div>
        <div style={{ display: "flex", gap: 12 }}>
          <Btn onClick={() => setManualMode(false)} variant="secondary" wide>Back</Btn>
          <Btn onClick={handleManualSubmit} wide>Verify Age</Btn>
        </div>
      </div>
    );
  }

  // ── Waiting for scan ─────────────────────────────────────────────────────────
  return (
    <div style={{ padding: 40, maxWidth: 560, margin: "0 auto", textAlign: "center" }}>
      <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>
        Age Verification Required
      </h2>
      <p style={{ fontSize: 15, color: COLORS.textMuted, marginBottom: 32 }}>
        Players must be 18 or older to participate.
      </p>

      <IdScanOptions
        onCameraClick={() => scanId({
          onSuccess: (raw) => processIdData(raw),
          onError: (msg) => onToast(msg, 'error'),
        })}
        isCameraScanning={isCameraScanning}
      />

      <button
        onClick={() => setManualMode(true)}
        style={{
          background: "none", border: "none", color: COLORS.textMuted,
          fontSize: 13, cursor: "pointer", textDecoration: "underline", fontFamily: "inherit",
          marginTop: 8,
        }}
      >
        Enter details manually instead
      </button>
    </div>
  );
}

// ============================================================================
// REGISTER PLAYER SCREEN
// ============================================================================

// Parse AAMVA PDF417 barcode data from driver's license
function parseDriversLicense(data) {
  const result = {};

  // Regex-based extractor — finds each AAMVA field code and captures its value.
  // Data always has \n between fields (HID scanner adds \n on Enter; camera/Scanbot
  // returns raw PDF417 text which already has \n). The ([^\r\n]*) stops at the newline
  // so we get exactly the field value with no stripping needed.
  function field(code) {
    const m = data.match(new RegExp(code + '([^\\r\\n]*)'));
    if (!m) return null;
    return m[1].trim() || null;
  }

  result.last_name = field("DCS");
  result.first_name = field("DAC") || field("DCT");
  result.middle_name = field("DAD");
  result.id_number = field("DAQ");
  result.address_line1 = field("DAG");
  result.city = field("DAI");
  const st = field("DAJ");
  if (st) { result.state = st; result.id_state = st; }
  const zip = field("DAK");
  if (zip) result.zip = zip.substring(0, 5);

  const dob = field("DBB");
  if (dob && dob.length === 8) {
    result.date_of_birth = `${dob.substring(4, 8)}-${dob.substring(0, 2)}-${dob.substring(2, 4)}`;
  }
  const exp = field("DBA");
  if (exp && exp.length === 8) {
    result.id_expiration = `${exp.substring(4, 8)}-${exp.substring(0, 2)}-${exp.substring(2, 4)}`;
  }

  // Log what was extracted to help diagnose scanner output differences
  if (!result.last_name && !result.first_name) {
    console.warn('[parseDriversLicense] No name extracted. Raw preview:', data.substring(0, 200));
  }

  // Compute age and derived flags from parsed dates
  if (result.date_of_birth) {
    try {
      const dob = new Date(result.date_of_birth);
      const today = new Date();
      let age = today.getFullYear() - dob.getFullYear();
      const m = today.getMonth() - dob.getMonth();
      if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
      result.age = age;
      result.isOver18 = age >= 18;
    } catch (_) {
      result.age = null;
      result.isOver18 = false;
    }
  } else {
    result.age = null;
    result.isOver18 = false;
  }
  if (result.id_expiration) {
    try {
      result.isExpired = new Date(result.id_expiration) < new Date();
    } catch (_) {
      result.isExpired = false;
    }
  } else {
    result.isExpired = false;
  }
  // Fallback: DAQ is sometimes embedded in the AAMVA subfile header line rather than on its own line
  if (!result.id_number) {
    const m = data.match(/DAQ([^\n\r]+)/);
    if (m) result.id_number = m[1].trim();
  }

  result.raw = data;

  return result;
}

function RegisterScreen({ session, onToast, onNavigate, onCardAssigned }) {
  const captureCtx = useCaptureSDK();
  const { scanId, isScanning: isCamScanningId } = useIdScanner();
  const isNfcLocation = session.location?.card_reader_type === "nfc";
  // Age verification managed internally — only required for new WinStash accounts
  const [verifiedPlayer, setVerifiedPlayer] = useState(null);

  const initial = {
    first_name: "", last_name: "", date_of_birth: "",
    id_number: "", id_state: "GA", id_expiration: "",
    phone: "", email: "", address_line1: "", address_line2: "",
    city: "", state: "GA", zip: "", card_number: "", nfc_ndef_id: "",
  };
  const [regMode, setRegMode] = useState(null); // null = pathway selection, "new" = new account, "existing" = existing account
  const [existingMethod, setExistingMethod] = useState(null); // null = method selection, "qr" | "phone" | "id"
  const [existingPhone, setExistingPhone] = useState("");
  const [form, setForm] = useState(initial);
  const [loading, setLoading] = useState(false);
  const [scanningId, setScanningId] = useState(false);
  const [scanningCard, setScanningCard] = useState(false);
  const [existingPlayer, setExistingPlayer] = useState(null); // found via QR/phone/ID for "existing" pathway
  const [manualCardEntry, setManualCardEntry] = useState(false);
  const [manualCardSuffix, setManualCardSuffix] = useState("");
  const [smsConsent, setSmsConsent] = useState(false);

  const set = (field) => (value) => {
    if (field === "phone") value = fmtPhone(value);
    if (field === "card_number") value = fmtCard(value);
    setForm((f) => ({ ...f, [field]: value }));
  };

  // Notify parent whenever a card is assigned so it can return it to inventory on back
  useEffect(() => {
    if (onCardAssigned) onCardAssigned(form.card_number || form.nfc_ndef_id || null);
  }, [form.card_number, form.nfc_ndef_id]); // eslint-disable-line

  // Pre-fill form from the age verification scan so the operator doesn't scan the ID twice
  useEffect(() => {
    if (!verifiedPlayer?.parsedLicense || regMode !== "new") return;
    const p = verifiedPlayer.parsedLicense;
    setForm(f => ({
      ...f,
      first_name: p.first_name || f.first_name,
      last_name: p.last_name || f.last_name,
      date_of_birth: p.date_of_birth || f.date_of_birth,
      id_number: p.id_number || f.id_number,
      id_state: p.id_state || f.id_state,
      id_expiration: p.id_expiration || f.id_expiration,
      address_line1: p.address_line1 || f.address_line1,
      city: p.city || f.city,
      state: p.state || f.state,
      zip: p.zip || f.zip,
    }));
  }, [verifiedPlayer, regMode]); // eslint-disable-line

  // Register barcode callback for ID scanning (new mode), QR scanning (existing/QR), or DL scan (existing/ID)
  useEffect(() => {
    if (!captureCtx?.setOnBarcode) return;
    const needsBarcode = scanningId || (regMode === "existing" && existingMethod === "qr" && !existingPlayer);
    if (!needsBarcode) return;

    captureCtx.setOnBarcode(async (data, symbology) => {
      if (scanningId && regMode === "existing" && existingMethod === "id") {
        // DL scan in existing-account mode — look up player by ID number
        const parsed = parseDriversLicense(data);
        if (parsed.id_number) {
          setScanningId(false);
          setLoading(true);
          try {
            const res = await api("player-manage", { action: "find_by_id", data: { id_number: parsed.id_number } }, session.token);
            setExistingPlayer(res);
            setForm(f => ({
              ...f,
              first_name: res.player?.first_name || "",
              last_name: res.player?.last_name || "",
              phone: res.player?.phone ? fmtPhone(res.player.phone) : "",
              email: res.player?.email || "",
            }));
            onToast("Account found — assign a gift card to complete", "success");
          } catch (err) {
            onToast(err.error || "No account found for this ID", "error");
          } finally {
            setLoading(false);
          }
        } else {
          onToast("Could not read ID. Try again.", "warning");
          setScanningId(false);
        }
      } else if (scanningId) {
        // DL scan in new-account mode — fill form fields
        const parsed = parseDriversLicense(data);
        if (parsed.last_name || parsed.first_name) {
          setForm(f => ({
            ...f,
            first_name: parsed.first_name || f.first_name,
            last_name: parsed.last_name || f.last_name,
            date_of_birth: parsed.date_of_birth || f.date_of_birth,
            id_number: parsed.id_number || f.id_number,
            id_state: parsed.id_state || f.id_state,
            id_expiration: parsed.id_expiration || f.id_expiration,
            address_line1: parsed.address_line1 || f.address_line1,
            city: parsed.city || f.city,
            state: parsed.state || f.state,
            zip: parsed.zip || f.zip,
          }));
          onToast("ID scanned successfully!", "success");
        } else {
          onToast("Could not read ID. Try again or enter manually.", "warning");
        }
        setScanningId(false);
      } else if (regMode === "existing" && existingMethod === "qr" && !existingPlayer) {
        // QR code scan for existing account lookup
        await handleExistingQrScan(data);
      }
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, scanningId, regMode, existingMethod, existingPlayer, onToast]);

  // Claim NFC handler for entire registration flow.
  // When scanningCard is true, route taps to card assignment.
  // When scanningCard is false, silently discard taps to prevent
  // other screens (e.g. player lookup) from receiving stray scans.
  useEffect(() => {
    if (!captureCtx?.setOnNfcTag) return;

    captureCtx.setOnNfcTag((tag) => {
      if (!scanningCard) return; // silently discard — registration is active
      // tag.cardId is the plain card_number written to the chip (e.g. "NFC-4DE9AA5D")
      if (tag.cardId) {
        setForm(f => ({ ...f, card_number: tag.cardId.toUpperCase(), nfc_ndef_id: "" }));
        onToast("Gift card scanned!", "success");
      } else {
        onToast("Could not read card. Try again.", "error");
      }
      setScanningCard(false);
    });

    return () => captureCtx.setOnNfcTag(null);
  }, [captureCtx, scanningCard, onToast]);

  const handleSubmit = async () => {
    if (existingPlayer) {
      // Existing account pathway — PII already on file, only phone needed
      if (!form.phone) return onToast("Phone number missing", "error");
    } else {
      if (!form.first_name || !form.last_name || !form.date_of_birth || !form.id_number || !form.phone) {
        return onToast("Fill in all required fields", "error");
      }
    }
    if (!existingPlayer && !smsConsent) {
      return onToast("SMS consent is required to register", "error");
    }
    if (!form.card_number && !form.nfc_ndef_id) {
      return onToast("A gift card must be assigned before completing registration", "error");
    }
    const playerFirstName = form.first_name || existingPlayer?.player?.name?.split(" ")[0] || "Player";
    const payload = {
      ...form,
      phone: form.phone.replace(/\D/g, ""),
      card_number: form.card_number
        ? (/^(WSH|NFC)-/i.test(form.card_number) ? form.card_number.toUpperCase() : "WSH-" + form.card_number)
        : "",
    };
    setLoading(true);
    try {
      const result = await api("register-player", payload, session.token);
      const assignedCard = result.card_number || payload.card_number || null;
      onToast(`${playerFirstName} registered!`, "success");
      onNavigate("load", { prefillCard: assignedCard, skipNewCard: !!assignedCard });
    } catch (err) {
      if (err.status === 409 && err.action === "duplicate") {
        onToast("A player with this information is already registered. Please look up their existing account.", "error");
        handleReset();
      } else if (err.status === 409) {
        onToast("Registration conflict. Please try again or contact support.", "error");
        handleReset();
      } else if (err.status === 400 && /gift card/i.test(err.error || err.message || "")) {
        onToast("Please select a gift card before completing registration.", "error");
      } else {
        onToast(err.error || err.message || "Registration failed", "error");
        handleReset();
      }
    } finally {
      setLoading(false);
    }
  };

  const handleReset = () => {
    setForm(initial);
    setScanningId(false);
    setScanningCard(false);
    setRegMode(null);
    setExistingMethod(null);
    setExistingPhone("");
    setExistingPlayer(null);
  };

  // Phone lookup handler for "Existing WinStash Account" pathway
  const handleExistingPhoneLookup = async () => {
    const cleaned = existingPhone.replace(/\D/g, "");
    if (cleaned.length < 10) return onToast("Enter a valid 10-digit phone number", "error");
    setLoading(true);
    try {
      const res = await api("player-manage", { action: "find_by_phone", data: { phone: cleaned } }, session.token);
      setExistingPlayer(res);
      setForm(f => ({
        ...f,
        first_name: res.player?.first_name || "",
        last_name: res.player?.last_name || "",
        phone: res.player?.phone ? fmtPhone(res.player.phone) : "",
        email: res.player?.email || "",
      }));
      onToast("Account found — assign a gift card to complete", "success");
    } catch (err) {
      onToast(err.error || "No account found with that phone number", "error");
    } finally {
      setLoading(false);
    }
  };

  // QR scan handler for "Existing WinStash Account" pathway
  const handleExistingQrScan = async (qrData) => {
    setLoading(true);
    try {
      const data = await api("player-lookup-qr", { qr_data: qrData }, session.token);
      setExistingPlayer(data);
      // Pre-fill form with existing player data
      setForm(f => ({
        ...f,
        first_name: data.player?.first_name || "",
        last_name: data.player?.last_name || "",
        phone: data.player?.phone ? fmtPhone(data.player.phone) : "",
        email: data.player?.email || "",
      }));
      onToast("Existing account found — assign a gift card to complete", "success");
    } catch (err) {
      onToast(err.error || "QR code not recognized", "error");
    } finally {
      setLoading(false);
    }
  };

  // Age verification gate — new accounts only; existing accounts skip (already verified)
  if (regMode === "new" && !verifiedPlayer) {
    return (
      <AgeVerificationGate
        session={session}
        onVerified={setVerifiedPlayer}
        onToast={onToast}
      />
    );
  }

  // — Pathway selection screen
  if (!regMode) {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>
          Register Player
        </h2>
        <p style={{ fontSize: 15, color: COLORS.textMuted, marginBottom: 32 }}>
          Select the type of registration
        </p>
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <button
            onClick={() => setRegMode("new")}
            style={{
              background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
              padding: "28px 24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
              boxShadow: `0 0 20px ${COLORS.orangeGlow}`,
            }}
          >
            <div style={{ fontSize: 28, marginBottom: 8 }}>🪪</div>
            <div style={{ fontSize: 19, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>
              New WinStash Account
            </div>
            <div style={{ fontSize: 14, color: COLORS.textMuted }}>
              Scan the player's driver's license to create a brand-new account
            </div>
          </button>
          <button
            onClick={() => setRegMode("existing")}
            style={{
              background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
              padding: "28px 24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
              transition: "all .15s",
            }}
            onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
            onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
          >
            <div style={{ fontSize: 28, marginBottom: 8 }}>📱</div>
            <div style={{ fontSize: 19, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>
              Existing WinStash Account
            </div>
            <div style={{ fontSize: 14, color: COLORS.textMuted }}>
              Scan the player's QR code from their WinStash app to link their existing account
            </div>
          </button>
        </div>
      </div>
    );
  }

  // — Existing account pathway
  if (regMode === "existing") {
    const backToMethodSelect = () => { setExistingMethod(null); setScanningId(false); setExistingPlayer(null); };

    // Step: player found — show card assignment
    if (existingPlayer) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 24px" }}>Link Existing Account</h2>
          <div style={{ background: COLORS.successBg, border: `2px solid ${COLORS.success}`, borderRadius: 14, padding: "20px 24px", marginBottom: 20 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.success, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 8 }}>Account Found</div>
            <div style={{ fontSize: 20, fontWeight: 700, color: COLORS.heading }}>{existingPlayer.player?.name}</div>
            <div style={{ fontSize: 14, color: COLORS.textMuted }}>***-***-{existingPlayer.player?.phone_last4}</div>
          </div>
          <div style={{ background: COLORS.surfaceLight, border: `2px solid ${COLORS.orange}`, borderRadius: 14, padding: "20px 24px", marginBottom: 20 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>Assign Gift Card</div>
            {isNfcLocation ? (
              scanningCard ? (
                <div style={{ textAlign: "center", padding: "16px 0" }}>
                  <div style={{ fontSize: 48, marginBottom: 12 }}>📲</div>
                  <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading, marginBottom: 8 }}>Tap Gift Card on Reader</div>
                  <Btn onClick={() => setScanningCard(false)} variant="secondary" size="sm">Cancel</Btn>
                </div>
              ) : form.nfc_ndef_id || /^NFC-[A-Z0-9]{8}$/i.test(form.card_number) ? (
                <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <div style={{ flex: 1, padding: "14px 16px", fontSize: 16, fontWeight: 700, border: `2px solid ${COLORS.success}`, borderRadius: 10, background: COLORS.successBg, color: COLORS.success }}>
                    {/^NFC-[A-Z0-9]{8}$/i.test(form.card_number) ? `✓ ${form.card_number.toUpperCase()}` : "✓ NFC Card Assigned"}
                  </div>
                  <Btn onClick={() => { setForm(f => ({ ...f, nfc_ndef_id: "", card_number: "" })); setManualCardEntry(false); setManualCardSuffix(""); }} variant="secondary" size="sm">Change</Btn>
                </div>
              ) : manualCardEntry ? (
                <div>
                  <div style={{ display: "flex", alignItems: "center", background: COLORS.surface, borderRadius: 10, border: `2px solid ${COLORS.border}`, marginBottom: 8 }}>
                    <span style={{ padding: "14px 0 14px 16px", fontSize: 22, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "1px" }}>NFC-</span>
                    <input
                      value={manualCardSuffix}
                      onChange={(e) => {
                        const v = e.target.value.replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8);
                        setManualCardSuffix(v);
                        setForm(f => ({ ...f, card_number: v ? "NFC-" + v : "" }));
                      }}
                      placeholder="XXXXXXXX"
                      maxLength={8}
                      style={{ flex: 1, padding: "14px 16px 14px 0", fontSize: 22, fontWeight: 700, border: "none", outline: "none", fontFamily: "'Space Mono', monospace", background: "transparent", letterSpacing: "1px", textTransform: "uppercase", color: COLORS.heading }}
                    />
                  </div>
                  <button onClick={() => { setManualCardEntry(false); setManualCardSuffix(""); setForm(f => ({ ...f, card_number: "" })); }} style={{ background: "none", border: "none", color: COLORS.textMuted, fontSize: 13, cursor: "pointer", padding: 0 }}>← Use NFC tap instead</button>
                </div>
              ) : (
                <div>
                  <button onClick={() => setScanningCard(true)} style={{ width: "100%", padding: "18px", background: COLORS.surface, border: `2px dashed ${COLORS.border}`, borderRadius: 10, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 12, fontFamily: "inherit", marginBottom: 8 }}>
                    <span style={{ fontSize: 24 }}>📲</span>
                    <span style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading }}>Tap Gift Card to Assign</span>
                  </button>
                  <button onClick={() => setManualCardEntry(true)} style={{ background: "none", border: "none", color: COLORS.textMuted, fontSize: 13, cursor: "pointer", padding: 0 }}>Enter card number manually instead</button>
                </div>
              )
            ) : (
              <div style={{ display: "flex", alignItems: "center", background: COLORS.surface, borderRadius: 10, border: `2px solid ${COLORS.border}` }}>
                <span style={{ padding: "14px 0 14px 16px", fontSize: 22, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.textMuted }}>WSH-</span>
                <input value={form.card_number} onChange={(e) => set("card_number")(fmtGiftCardSuffix(e.target.value))} placeholder="00000" maxLength={5}
                  style={{ flex: 1, padding: "14px 16px 14px 0", fontSize: 22, fontWeight: 700, border: "none", outline: "none", fontFamily: "'Space Mono', monospace", background: "transparent", textTransform: "uppercase", color: COLORS.heading }} />
              </div>
            )}
          </div>
          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={backToMethodSelect} variant="secondary" style={{ flex: 1 }}>Back</Btn>
            <Btn onClick={handleSubmit} disabled={loading} style={{ flex: 1 }}>{loading ? "Linking..." : "Link Account to This Location"}</Btn>
          </div>
        </div>
      );
    }

    // Step: method selection
    if (!existingMethod) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>Link Existing Account</h2>
          <p style={{ fontSize: 15, color: COLORS.textMuted, marginBottom: 28 }}>How would you like to find the player?</p>
          <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
            {[
              { method: "qr", icon: "📱", label: "Scan QR Code", desc: "Player opens WinStash app and shows their QR code", recommended: true },
              { method: "phone", icon: "📞", label: "Search by Phone Number", desc: "Find account by the player's mobile number" },
              { method: "id", icon: "🪪", label: "Scan ID", desc: "Look up player by scanning the back of their ID" },
            ].map(({ method, icon, label, desc, recommended }) => (
              <button key={method} onClick={() => { setExistingMethod(method); if (method === "id") setScanningId(true); }}
                style={{ background: COLORS.surface, border: `2px solid ${recommended ? COLORS.orange : COLORS.border}`, borderRadius: 16, padding: "20px 24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit", transition: "all .15s", boxShadow: recommended ? `0 0 16px ${COLORS.orangeGlow}` : "none" }}
                onMouseEnter={(e) => { if (!recommended) e.currentTarget.style.borderColor = COLORS.orange; }}
                onMouseLeave={(e) => { if (!recommended) e.currentTarget.style.borderColor = COLORS.border; }}
              >
                <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
                  <span style={{ fontSize: 22 }}>{icon}</span>
                  {recommended && <span style={{ background: COLORS.orange, color: "#fff", fontSize: 10, padding: "3px 7px", borderRadius: 5, fontWeight: 700 }}>RECOMMENDED</span>}
                </div>
                <div style={{ fontSize: 17, fontWeight: 700, color: COLORS.heading, marginBottom: 3 }}>{label}</div>
                <div style={{ fontSize: 13, color: COLORS.textMuted }}>{desc}</div>
              </button>
            ))}
          </div>
          <div style={{ marginTop: 20 }}>
            <Btn onClick={handleReset} variant="secondary" wide>Back</Btn>
          </div>
        </div>
      );
    }

    // Step: QR scan waiting
    if (existingMethod === "qr") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 24px" }}>Link Existing Account</h2>
          <div style={{ background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16, padding: "28px 24px" }}>
            <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 8, textAlign: "center" }}>Scan Player's QR Code</div>
            <div style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 20, textAlign: "center" }}>Ask the player to open their WinStash app and show you the QR code on screen</div>
            {loading && <div style={{ fontSize: 14, color: COLORS.orange, marginBottom: 16, textAlign: "center" }}>Looking up account...</div>}
            <QrScanOptions
              onCameraClick={() => captureCtx?.scanQrWithCamera({ onError: (msg) => onToast(msg, 'error') })}
              isCameraScanning={captureCtx?.isQrCameraScanning}
              disabled={loading}
              onCancel={backToMethodSelect}
            />
          </div>
        </div>
      );
    }

    // Step: phone number lookup
    if (existingMethod === "phone") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 24px" }}>Link Existing Account</h2>
          <div style={{ background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16, padding: "28px 24px" }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>Enter Player's Phone Number</div>
            <input
              type="tel" inputMode="tel" value={existingPhone}
              onChange={(e) => setExistingPhone(fmtPhone(e.target.value))}
              placeholder="(555) 555-5555" autoFocus maxLength={14}
              onKeyDown={(e) => e.key === "Enter" && handleExistingPhoneLookup()}
              style={{ width: "100%", padding: "16px 18px", fontSize: 22, fontWeight: 700, border: `2px solid ${COLORS.orange}`, borderRadius: 12, background: COLORS.surfaceLight, color: COLORS.heading, fontFamily: "'Space Mono', monospace", boxSizing: "border-box", letterSpacing: "1px", outline: "none" }}
            />
            <div style={{ marginTop: 20, display: "flex", gap: 12 }}>
              <Btn onClick={backToMethodSelect} variant="secondary" wide size="lg">Back</Btn>
              <Btn onClick={handleExistingPhoneLookup} disabled={loading || existingPhone.replace(/\D/g, "").length < 10} wide size="lg">
                {loading ? "Looking up..." : "Find Account"}
              </Btn>
            </div>
          </div>
        </div>
      );
    }

    // Step: DL scan for existing account
    if (existingMethod === "id") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 24px" }}>Link Existing Account</h2>
          <div style={{ background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16, padding: "40px 24px", textAlign: "center" }}>
            {scanningId ? (
              <>
                <div style={{ fontSize: 48, marginBottom: 12 }}>🪪</div>
                <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.orange, marginBottom: 16 }}>Scan ID</div>
                <IdScanOptions
                  onCameraClick={() => scanId({
                    onSuccess: async (raw) => {
                      const parsed = parseDriversLicense(raw);
                      if (!parsed?.id_number) { onToast("Could not read ID. Try again.", "warning"); return; }
                      setScanningId(false);
                      setLoading(true);
                      try {
                        const res = await api("player-manage", { action: "find_by_id", data: { id_number: parsed.id_number } }, session.token);
                        setExistingPlayer(res);
                        setForm(f => ({
                          ...f,
                          first_name: res.player?.first_name || "",
                          last_name: res.player?.last_name || "",
                          phone: res.player?.phone ? fmtPhone(res.player.phone) : "",
                          email: res.player?.email || "",
                        }));
                        onToast("Account found — assign a gift card to complete", "success");
                      } catch (err) {
                        onToast(err.error || "No account found for this ID", "error");
                      } finally { setLoading(false); }
                    },
                    onError: (msg) => onToast(msg, 'error'),
                  })}
                  isCameraScanning={isCamScanningId}
                  disabled={loading}
                  onCancel={backToMethodSelect}
                />
                {loading && <div style={{ fontSize: 14, color: COLORS.orange, marginTop: 12 }}>Looking up account...</div>}
              </>
            ) : (
              <>
                <div style={{ fontSize: 48, marginBottom: 12 }}>🪪</div>
                <div style={{ fontSize: 16, color: COLORS.textMuted, marginBottom: 20 }}>Ready to scan</div>
                <div style={{ display: "flex", gap: 12 }}>
                  <Btn onClick={backToMethodSelect} variant="secondary" wide>Back</Btn>
                  <Btn onClick={() => setScanningId(true)} wide>Scan ID</Btn>
                </div>
              </>
            )}
          </div>
        </div>
      );
    }
  }

  // — Registration form (New Account)
  return (
    <div style={{ padding: "32px 40px", maxWidth: 720, margin: "0 auto" }}>
      <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 20px" }}>
        Register New Player
      </h2>

      {/* Step instructions */}
      <div style={{
        background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 14,
        padding: "16px 20px", marginBottom: 24,
        display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12,
      }}>
        {[
          { step: 1, label: "Scan ID" },
          { step: 2, label: "Input Phone Number" },
          { step: 3, label: "Assign Gift Card" },
        ].map(({ step, label }) => (
          <div key={step} style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{
              width: 28, height: 28, borderRadius: "50%", background: COLORS.orange,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 13, fontWeight: 700, color: "#fff", flexShrink: 0,
            }}>{step}</div>
            <span style={{ fontSize: 13, color: COLORS.text, fontWeight: 600 }}>{label}</span>
          </div>
        ))}
      </div>

      {/* Scan ID Button */}
      <div style={{ marginBottom: 24 }}>
        {verifiedPlayer?.parsedLicense && !scanningId ? (
          // ID was already scanned during age verification — show verified badge
          <div style={{
            background: COLORS.successBg, border: `2px solid ${COLORS.success}`, borderRadius: 14,
            padding: "18px 24px", display: "flex", alignItems: "center", gap: 12,
          }}>
            <span style={{ fontSize: 24 }}>✅</span>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 16, fontWeight: 700, color: COLORS.success }}>
                Step 1 — ID Verified &amp; Pre-filled
              </div>
              <div style={{ fontSize: 13, color: COLORS.textMuted }}>
                Age {verifiedPlayer.age} · {verifiedPlayer.name}
              </div>
            </div>
            <button
              onClick={() => setScanningId(true)}
              style={{
                background: "none", border: `1px solid ${COLORS.border}`, borderRadius: 8,
                padding: "6px 12px", fontSize: 12, color: COLORS.textMuted,
                cursor: "pointer", fontFamily: "inherit",
              }}
            >
              Re-scan
            </button>
          </div>
        ) : scanningId ? (
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 14,
            padding: "24px",
          }}>
            <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.orange, marginBottom: 16, textAlign: "center" }}>
              Scan ID Barcode
            </div>
            <IdScanOptions
              onCameraClick={() => scanId({
                onSuccess: (raw) => {
                  const parsed = parseDriversLicense(raw);
                  if (parsed?.last_name || parsed?.first_name) {
                    setForm(f => ({
                      ...f,
                      first_name: parsed.first_name || f.first_name,
                      last_name: parsed.last_name || f.last_name,
                      date_of_birth: parsed.date_of_birth || f.date_of_birth,
                      id_number: parsed.id_number || f.id_number,
                      id_state: parsed.id_state || f.id_state,
                      id_expiration: parsed.id_expiration || f.id_expiration,
                      address_line1: parsed.address_line1 || f.address_line1,
                      city: parsed.city || f.city,
                      state: parsed.state || f.state,
                      zip: parsed.zip || f.zip,
                    }));
                    setScanningId(false);
                    onToast("ID scanned — form pre-filled", "success");
                  } else {
                    onToast("Could not read ID. Try again.", "warning");
                  }
                },
                onError: (msg) => onToast(msg, 'error'),
              })}
              isCameraScanning={isCamScanningId}
              onCancel={() => setScanningId(false)}
            />
          </div>
        ) : (
          <button
            onClick={() => setScanningId(true)}
            style={{
              width: "100%", padding: "18px 24px", background: COLORS.surface,
              border: `2px solid ${COLORS.border}`, borderRadius: 14, cursor: "pointer",
              display: "flex", alignItems: "center", justifyContent: "center", gap: 12,
              fontFamily: "inherit", transition: "all .15s",
            }}
            onMouseEnter={(e) => e.currentTarget.style.borderColor = COLORS.orange}
            onMouseLeave={(e) => e.currentTarget.style.borderColor = COLORS.border}
          >
            <span style={{ fontSize: 24 }}>🪪</span>
            <span style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading }}>Step 1 — Scan ID</span>
            <span style={{ fontSize: 14, color: COLORS.textMuted }}>(auto-fill form)</span>
          </button>
        )}
      </div>

      {/* Name Row */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
        <Input label="First Name" value={form.first_name} onChange={set("first_name")} required autoFocus uppercase />
        <Input label="Last Name" value={form.last_name} onChange={set("last_name")} required uppercase />
      </div>

      {/* DOB + Phone */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
        <Input label="Date of Birth" value={form.date_of_birth} onChange={set("date_of_birth")}
          type="date" required />
        <Input label="Step 2 — Phone Number" value={form.phone} onChange={set("phone")}
          placeholder="(555) 123-4567" inputMode="tel" required />
      </div>

      {/* SMS Consent */}
      <div
        onClick={() => setSmsConsent(v => !v)}
        style={{
          display: "flex", alignItems: "flex-start", gap: 12, cursor: "pointer",
          fontSize: 12, color: COLORS.textMuted, lineHeight: 1.6,
          padding: "10px 14px", background: COLORS.surface,
          border: `1px solid ${smsConsent ? COLORS.orange : COLORS.border}`, borderRadius: 8, marginBottom: 4,
        }}
      >
        <input
          type="checkbox"
          checked={smsConsent}
          onChange={e => { e.stopPropagation(); setSmsConsent(e.target.checked); }}
          style={{ marginTop: 2, accentColor: COLORS.orange, width: 16, height: 16, flexShrink: 0, cursor: "pointer" }}
        />
        <span>
          By providing your phone number during WinStash gift card registration, you consent to receive transactional SMS messages. Message and data rates may apply. Reply STOP to opt out at any time.
          {" "}See our Terms &amp; Conditions and Privacy Policy at{" "}
          <a href="https://winstash.app/terms/" target="_blank" rel="noopener noreferrer"
            style={{ color: COLORS.orange, textDecoration: "underline" }}
            onClick={e => e.stopPropagation()}>winstash.app/terms</a>
          {" · "}
          <a href="https://winstash.app/privacy/" target="_blank" rel="noopener noreferrer"
            style={{ color: COLORS.orange, textDecoration: "underline" }}
            onClick={e => e.stopPropagation()}>winstash.app/privacy</a>
        </span>
      </div>

      {/* Email */}
      <div style={{ marginBottom: 4 }}>
        <Input label="Email Address (optional)" value={form.email} onChange={set("email")}
          placeholder="player@example.com" inputMode="email" />
      </div>

      {/* ID Section */}
      <div style={{
        background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
        border: `1px solid ${COLORS.border}`,
      }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
          Government ID
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr 1fr", gap: 16 }}>
          <Input label="ID Number" value={form.id_number} onChange={set("id_number")} required uppercase />
          <Select label="State" value={form.id_state} onChange={set("id_state")} options={US_STATES} />
          <Input label="Expiration" value={form.id_expiration} onChange={set("id_expiration")} type="date" />
        </div>
      </div>

      {/* Address Section */}
      <div style={{
        background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
        border: `1px solid ${COLORS.border}`,
      }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
          Address (Optional)
        </div>
        <Input label="Street Address" value={form.address_line1} onChange={set("address_line1")} uppercase />
        <Input label="Apt / Suite" value={form.address_line2} onChange={set("address_line2")} uppercase />
        <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr 1fr", gap: 16 }}>
          <Input label="City" value={form.city} onChange={set("city")} uppercase />
          <Select label="State" value={form.state} onChange={set("state")} options={US_STATES} />
          <Input label="ZIP" value={form.zip} onChange={set("zip")} maxLength={5} inputMode="numeric" />
        </div>
      </div>

      {/* Gift Card Assignment */}
      <div style={{
        background: COLORS.surfaceLight, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
        padding: "20px 24px", marginBottom: 28,
      }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
          Assign Gift Card
        </div>

        {isNfcLocation ? (
          // NFC Location: Tap card to assign (or enter manually)
          scanningCard ? (
            <div style={{ textAlign: "center", padding: "16px 0" }}>
              <div style={{ fontSize: 48, marginBottom: 12, animation: "pulse 1s infinite" }}>📲</div>
              <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading, marginBottom: 8 }}>
                Tap Gift Card on Reader
              </div>
              <Btn onClick={() => setScanningCard(false)} variant="secondary" size="sm">
                Cancel
              </Btn>
            </div>
          ) : form.nfc_ndef_id || /^NFC-[A-Z0-9]{8}$/i.test(form.card_number) ? (
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div style={{
                flex: 1, padding: "14px 16px", fontSize: 18, fontWeight: 700,
                border: `2px solid ${COLORS.success}`, borderRadius: 10,
                background: COLORS.successBg, color: COLORS.success,
                fontFamily: "'Space Mono', monospace", letterSpacing: "1px",
              }}>
                {/^NFC-[A-Z0-9]{8}$/i.test(form.card_number) ? `✓ ${form.card_number.toUpperCase()}` : "✓ NFC Card Assigned"}
              </div>
              <Btn onClick={() => { setForm(f => ({ ...f, nfc_ndef_id: "", card_number: "" })); setManualCardEntry(false); setManualCardSuffix(""); }} variant="secondary" size="sm">
                Change
              </Btn>
            </div>
          ) : manualCardEntry ? (
            <div>
              <div style={{ display: "flex", alignItems: "center", background: COLORS.surface, borderRadius: 10, border: `2px solid ${COLORS.border}`, marginBottom: 8 }}>
                <span style={{ padding: "14px 0 14px 16px", fontSize: 22, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "1px" }}>NFC-</span>
                <input
                  value={manualCardSuffix}
                  onChange={(e) => {
                    const v = e.target.value.replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8);
                    setManualCardSuffix(v);
                    setForm(f => ({ ...f, card_number: v ? "NFC-" + v : "" }));
                  }}
                  placeholder="XXXXXXXX"
                  maxLength={8}
                  style={{ flex: 1, padding: "14px 16px 14px 0", fontSize: 22, fontWeight: 700, border: "none", outline: "none", fontFamily: "'Space Mono', monospace", background: "transparent", letterSpacing: "1px", textTransform: "uppercase", color: COLORS.heading }}
                />
              </div>
              <button onClick={() => { setManualCardEntry(false); setManualCardSuffix(""); setForm(f => ({ ...f, card_number: "" })); }} style={{ background: "none", border: "none", color: COLORS.textMuted, fontSize: 13, cursor: "pointer", padding: 0 }}>← Use NFC tap instead</button>
            </div>
          ) : (
            <div>
              <button
                onClick={() => setScanningCard(true)}
                style={{
                  width: "100%", padding: "18px", background: COLORS.surface,
                  border: `2px dashed ${COLORS.border}`, borderRadius: 10, cursor: "pointer",
                  display: "flex", alignItems: "center", justifyContent: "center", gap: 12,
                  fontFamily: "inherit", transition: "all .15s", marginBottom: 8,
                }}
                onMouseEnter={(e) => e.currentTarget.style.borderColor = COLORS.orange}
                onMouseLeave={(e) => e.currentTarget.style.borderColor = COLORS.border}
              >
                <span style={{ fontSize: 24 }}>📲</span>
                <span style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading }}>Tap Gift Card to Assign</span>
              </button>
              <button onClick={() => setManualCardEntry(true)} style={{ background: "none", border: "none", color: COLORS.textMuted, fontSize: 13, cursor: "pointer", padding: 0 }}>Enter card number manually instead</button>
            </div>
          )
        ) : (
          // Magstripe Location: Enter card number
          <div style={{ display: "flex", alignItems: "center", background: COLORS.surface, borderRadius: 10, border: `2px solid ${COLORS.border}` }}>
            <span style={{
              padding: "14px 0 14px 16px", fontSize: 22, fontWeight: 700,
              fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "1px",
            }}>WSH-</span>
            <input
              value={form.card_number}
              onChange={(e) => set("card_number")(fmtGiftCardSuffix(e.target.value))}
              placeholder="00000"
              maxLength={5}
              style={{
                flex: 1, padding: "14px 16px 14px 0", fontSize: 22, fontWeight: 700,
                border: "none", outline: "none",
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                background: "transparent", letterSpacing: "1px", textTransform: "uppercase",
                color: COLORS.heading,
              }}
            />
          </div>
        )}
      </div>

      {!form.card_number && !form.nfc_ndef_id && (
        <div style={{ fontSize: 13, color: COLORS.warning, textAlign: "center", marginBottom: 8 }}>
          ⚠ Scroll up and assign a gift card before registering
        </div>
      )}
      <Btn onClick={handleSubmit} disabled={loading || (!form.card_number && !form.nfc_ndef_id)} wide size="xl">
        {loading ? "Registering..." : "Register Player"}
      </Btn>
    </div>
  );
}

// ============================================================================
// NFC LOOKUP VIEW — Tap card to identify player
// ============================================================================
function NfcLookupView({ session, onToast, onBack, onSuccess, loading: parentLoading, title = "Redeem Winnings" }) {
  const captureCtx = useCaptureSDK();
  const [status, setStatus] = useState("ready"); // ready, waiting, reading, error
  const [manualNfcId, setManualNfcId] = useState("");
  const [error, setError] = useState(null);

  const webNfcState = captureCtx?.webNfcState ?? 'unsupported';
  const webNfcAvailable = webNfcState !== 'unsupported';
  const webNfcScanning = webNfcState === 'scanning';

  // Register NFC tag callback — receives from both Web NFC and HID
  useEffect(() => {
    if (!captureCtx?.setOnNfcTag) return;

    captureCtx.setOnNfcTag((tag) => {
      console.log('[NfcLookupView] tag received, status:', status, 'cardId length:', tag.cardId?.length ?? 'null', 'blank:', tag.blank ?? false);
      if (status !== "ready" && status !== "waiting") {
        console.log('[NfcLookupView] tag BLOCKED — status is:', status);
        return;
      }
      setStatus("reading");
      setError(null);
      if (tag.cardId) {
        console.log('[NfcLookupView] calling onSuccess with cardId length:', tag.cardId.length);
        onSuccess(tag.cardId);
        // Keep status="reading" to block duplicate scans while parent lookup runs.
        // Back button uses disabled={parentLoading} so it still works after lookup fails.
      } else if (tag.blank) {
        setError("Card has no NFC data — it needs to be programmed first.");
        setStatus("error");
      } else {
        setError("Could not read card — tap again or enter the number manually");
        setStatus("error");
      }
    });

    return () => captureCtx.setOnNfcTag(null);
  }, [captureCtx, status, onSuccess]);

  // Stop Web NFC when leaving this view
  useEffect(() => {
    return () => captureCtx?.stopWebNfc?.();
  }, [captureCtx]);

  // Handle manual NFC ID entry (fallback)
  const handleManualLookup = () => {
    if (manualNfcId.length >= 8) onSuccess("NFC-" + manualNfcId);
  };

  const handleEnableWebNfc = async () => {
    setError(null);
    setStatus("ready");
    await captureCtx?.startWebNfc?.();
  };

  const isLoading = parentLoading || status === "reading";

  return (
    <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
      <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
        {title}
      </h2>

      <div style={{
        background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
        padding: "32px 24px", textAlign: "center",
      }}>
        {/* Primary: HID reader instructions (always shown when not reading) */}
        {status !== "reading" && !webNfcScanning && (
          <>
            <div style={{ fontSize: 72, marginBottom: 16 }}>📲</div>
            <h3 style={{ fontSize: 22, fontWeight: 700, color: COLORS.orange, marginBottom: 12 }}>
              Tap Player's Card
            </h3>
            <p style={{ fontSize: 15, color: COLORS.textMuted, marginBottom: 24 }}>
              Hold the NFC card against the card reader
            </p>
          </>
        )}

        {/* Web NFC active (tablet NFC scanning) */}
        {webNfcScanning && status !== "reading" && (
          <>
            <div style={{ fontSize: 72, marginBottom: 16 }}>📲</div>
            <h3 style={{ fontSize: 22, fontWeight: 700, color: COLORS.orange, marginBottom: 12 }}>
              NFC Active — Tap Card to Back of Tablet
            </h3>
            <p style={{ fontSize: 15, color: COLORS.textMuted, marginBottom: 24 }}>
              Hold the NFC card flat against the back of the tablet near the top
            </p>
          </>
        )}

        {status === "reading" && (
          <>
            <div style={{ fontSize: 72, marginBottom: 16, animation: "pulse 1s infinite" }}>📲</div>
            <h3 style={{ fontSize: 22, fontWeight: 700, color: COLORS.orange, marginBottom: 24 }}>
              Reading Card...
            </h3>
          </>
        )}

        {status === "error" && (
          <div style={{
            background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 10,
            padding: "12px 16px", marginBottom: 20, color: COLORS.error, fontSize: 14,
          }}>
            {error || "Card read failed. Please try again."}
          </div>
        )}

        <Btn onClick={onBack} variant="secondary" wide size="lg" disabled={parentLoading}>
          Back
        </Btn>

        {/* Manual entry fallback */}
        <div style={{ marginTop: 20, paddingTop: 20, borderTop: `1px solid ${COLORS.border}` }}>
          <p style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 12 }}>
            Or enter card number manually:
          </p>
          <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.border}`, marginBottom: 12 }}>
            <span style={{ padding: "12px 0 12px 16px", fontSize: 20, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px" }}>NFC-</span>
            <input
              value={manualNfcId}
              onChange={(e) => setManualNfcId(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8))}
              placeholder="00000000"
              maxLength={8}
              onKeyDown={(e) => e.key === "Enter" && manualNfcId.length >= 8 && handleManualLookup()}
              style={{
                flex: 1, padding: "12px 16px 12px 0", fontSize: 20, fontWeight: 700,
                border: "none", outline: "none", fontFamily: "'Space Mono', monospace",
                background: "transparent", letterSpacing: "2px",
                textTransform: "uppercase", color: COLORS.heading,
              }}
            />
          </div>
          <Btn onClick={handleManualLookup} disabled={isLoading || manualNfcId.length < 8} wide size="md">
            {isLoading ? "Looking up..." : "Find Player"}
          </Btn>
        </div>
      </div>

      <style>{`
        @keyframes pulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.5; }
        }
      `}</style>
    </div>
  );
}

// ============================================================================
// NAME SEARCH VIEW — Search for player by name or phone
// ============================================================================
function NameSearchView({ session, onToast, onBack, onSelect, title = "Redeem Winnings", searchLabel = "Search by Name or Phone", searchPlaceholder = "Enter name or phone...", inputMode = "text" }) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [searched, setSearched] = useState(false);

  const handleSearch = async () => {
    if (query.trim().length < 2) {
      onToast("Enter at least 2 characters to search", "warning");
      return;
    }

    setLoading(true);
    setSearched(true);

    try {
      const data = await api("player-search", { query: query.trim() }, session.token);
      setResults(data.players || []);
      if (data.players?.length === 0) {
        onToast("No players found", "info");
      }
    } catch (err) {
      onToast(err.error || "Search failed", "error");
      setResults([]);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
      <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
        {title}
      </h2>

      <div style={{
        background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
        padding: "28px 24px",
      }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
          {searchLabel}
        </div>

        <div style={{ display: "flex", gap: 12, marginBottom: 20 }}>
          <input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder={searchPlaceholder}
            autoFocus
            inputMode={inputMode}
            onKeyDown={(e) => e.key === "Enter" && query.length >= 2 && handleSearch()}
            style={{
              flex: 1, padding: "14px 16px", fontSize: 18, fontWeight: 600,
              border: `2px solid ${COLORS.border}`, borderRadius: 10, outline: "none",
              background: COLORS.surfaceLight, color: COLORS.heading,
              fontFamily: "inherit",
            }}
          />
          <Btn onClick={handleSearch} disabled={loading || query.length < 2} size="lg">
            {loading ? "..." : "Search"}
          </Btn>
        </div>

        {/* Search Results */}
        {results.length > 0 && (
          <div style={{ marginBottom: 20 }}>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 12, fontWeight: 600 }}>
              {results.length} player{results.length !== 1 ? "s" : ""} found
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {results.map((r, i) => (
                <button
                  key={i}
                  onClick={() => onSelect(r)}
                  style={{
                    background: COLORS.surfaceLight, border: `2px solid ${COLORS.border}`,
                    borderRadius: 12, padding: "16px 20px", cursor: "pointer",
                    textAlign: "left", fontFamily: "inherit", transition: "all .15s",
                    display: "flex", justifyContent: "space-between", alignItems: "center",
                  }}
                  onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
                  onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
                >
                  <div>
                    <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading }}>
                      {r.player.name}
                    </div>
                    <div style={{ fontSize: 14, color: COLORS.textMuted }}>
                      Phone: ***-***-{r.player.phone_last4}
                    </div>
                  </div>
                  <div style={{ textAlign: "right" }}>
                    <div style={{ fontSize: 20, fontWeight: 700, color: COLORS.success }}>
                      {fmtCurrency(r.balance)}
                    </div>
                  </div>
                </button>
              ))}
            </div>
          </div>
        )}

        {/* No results message */}
        {searched && results.length === 0 && !loading && (
          <div style={{
            background: COLORS.surfaceLight, borderRadius: 12, padding: "24px",
            textAlign: "center", marginBottom: 20,
          }}>
            <div style={{ fontSize: 32, marginBottom: 8 }}>🔍</div>
            <div style={{ fontSize: 16, color: COLORS.textMuted }}>
              No players found matching "{query}"
            </div>
          </div>
        )}

        <Btn onClick={onBack} variant="secondary" wide size="lg">
          Back
        </Btn>
      </div>
    </div>
  );
}

// ============================================================================
// LOAD FUNDS SCREEN — Two-Factor Authorization Flow
// ============================================================================
function LoadScreen({ session, onToast, prefillCard, skipNewCard, onPrefillConsumed, onLoadComplete, isOnline, operatorPinHash }) {
  const captureCtx = useCaptureSDK();
  const { scanId, isScanning: isCameraScanning } = useIdScanner();

  // Steps: lookup → [new_card if needed] → amount → authorize → done
  const [step, setStep] = useState("lookup");
  const [lookupMethod, setLookupMethod] = useState(null); // "card", "nfc", "qr", "id"

  // Determine if location uses NFC
  const isNfcLocation = session.location?.card_reader_type === "nfc";
  const [cardNumber, setCardNumber] = useState("");
  const [idNumber, setIdNumber] = useState("");
  const [player, setPlayer] = useState(null);
  const [wallet, setWallet] = useState(null);
  const [card, setCard] = useState(null); // { id, number, status, type, is_reloadable, load_count, needs_new_card }
  const [limits, setLimits] = useState(null);
  const [amount, setAmount] = useState("");
  const [ticketRef, setTicketRef] = useState("");
  const [operatorCardSuffix, setOperatorCardSuffix] = useState("");
  const [operatorPin, setOperatorPin] = useState("");
  const [loading, setLoading] = useState(false);
  const [txn, setTxn] = useState(null);
  const [fraudAlert, setFraudAlert] = useState(null);
  const [newCardNumber, setNewCardNumber] = useState(""); // For new card issuance step
  const [pendingCardFee, setPendingCardFee] = useState(0); // Card fee deferred until post-load
  const [pendingCardId, setPendingCardId] = useState(null); // Card ID for deferred fee (Load More flow)
  const [registrationCardId, setRegistrationCardId] = useState(null); // Card ID assigned at registration
  const [offlineCacheWarning, setOfflineCacheWarning] = useState(false);

  const pinRef = useRef(null);

  // Quick-amount presets
  const presets = [5, 10, 20, 25, 50, 100];

  // ── STEP 1a: Card Lookup ──────────────────────────────────────────────
  const handleCardLookup = async () => {
    if (!cardNumber || cardNumber.length < 5) return onToast("Enter a 5-digit card number", "error");
    setLoading(true);
    const fullCardNumber = "WSH-" + cardNumber;
    try {
      const data = await api("player-lookup", { card_number: fullCardNumber }, session.token);
      setPlayer(data.player);
      setWallet(data.wallet);
      setCard(data.card);
      setLimits(data.limits);
      cachePlayerData(fullCardNumber, { player: data.player, wallet: data.wallet, card: data.card, limits: data.limits });

      if (data.card?.needs_new_card || new Date() < new Date("2026-07-01")) {
        setStep("new_card");
      } else {
        setStep("amount");
      }
    } catch (err) {
      if (!isOnline) {
        const cached = getCachedPlayer(fullCardNumber);
        if (cached) {
          setPlayer(cached.player); setWallet(cached.wallet);
          setCard(cached.card); setLimits(cached.limits);
          setOfflineCacheWarning(true);
          if (cached.card?.needs_new_card || new Date() < new Date("2026-07-01")) {
            setStep("new_card");
          } else {
            setStep("amount");
          }
        } else {
          onToast("Player not found in offline cache — connect to look up new players", "error");
        }
      } else {
        onToast(err.error || "Card not found at this location", "error");
      }
    } finally {
      setLoading(false);
    }
  };

  // ── STEP 1b: ID Lookup ────────────────────────────────────────────────
  // idOverride: pass a license number directly (from camera scan) instead of reading state
  const handleIdLookup = async (idOverride) => {
    const id = idOverride || idNumber;
    if (!id || id.length < 4) return onToast("Enter a valid ID number", "error");
    setLoading(true);
    try {
      const data = await api("player-lookup-id", { id_number: id }, session.token);
      setPlayer(data.player);
      setWallet(data.wallet);
      setCard(data.card);
      setLimits(data.limits);

      // Save card number if available
      if (data.card?.number) {
        setCardNumber(data.card.number);
        cachePlayerData(data.card.number, { player: data.player, wallet: data.wallet, card: data.card, limits: data.limits });
      }

      onToast(`Player found: ${data.player.name}`, "success");
      if (data.card?.needs_new_card || new Date() < new Date("2026-07-01")) {
        setStep("new_card");
      } else {
        setStep("amount");
      }
    } catch (err) {
      onToast(err.error || "ID lookup failed", "error");
    } finally {
      setLoading(false);
    }
  };

  // ── STEP 1d: QR Code Lookup ──────────────────────────────────────────
  const handleQrLookup = async (qrData) => {
    setLoading(true);
    try {
      const data = await api("player-lookup-qr", { qr_data: qrData }, session.token);
      setPlayer(data.player);
      setWallet(data.wallet);
      setCard(data.card);
      setLimits(data.limits);

      if (data.card?.number) {
        setCardNumber(data.card.number);
        cachePlayerData(data.card.number, { player: data.player, wallet: data.wallet, card: data.card, limits: data.limits });
      }

      if (data.card?.needs_new_card || new Date() < new Date("2026-07-01")) {
        setStep("new_card");
      } else {
        setStep("amount");
      }
    } catch (err) {
      onToast(err.error || "Invalid QR code", "error");
    } finally {
      setLoading(false);
    }
  };

  // ── STEP 1e: NFC Card Lookup ────────────────────────────────────────
  const handleNfcLookup = async (nfcNdefId, skipNewCardStep = false) => {
    setLoading(true);
    try {
      // NFC tap always sends the card_number (e.g. "NFC-4DE9AA5D") written as plain NDEF Text.
      const data = await api("player-lookup", { card_number: nfcNdefId.toUpperCase() }, session.token);
      setPlayer(data.player);
      setWallet(data.wallet);
      setCard(data.card);
      setLimits(data.limits);
      cachePlayerData(nfcNdefId.toUpperCase ? nfcNdefId.toUpperCase() : nfcNdefId, { player: data.player, wallet: data.wallet, card: data.card, limits: data.limits });

      if (data.card?.number) {
        setCardNumber(data.card.number);
      }

      // Skip new_card step if card was just assigned during registration.
      // Store the card id so we can charge the fee after the load completes.
      if (skipNewCardStep && data.card?.id) {
        setRegistrationCardId(data.card.id);
      }

      if (!skipNewCardStep && (data.card?.needs_new_card || new Date() < new Date("2026-07-01"))) {
        setStep("new_card");
      } else {
        setStep("amount");
      }
    } catch (err) {
      if (!isOnline) {
        const cacheKey = (nfcNdefId.toUpperCase ? nfcNdefId.toUpperCase() : nfcNdefId);
        const cached = getCachedPlayer(cacheKey);
        if (cached) {
          setPlayer(cached.player); setWallet(cached.wallet);
          setCard(cached.card); setLimits(cached.limits);
          if (cached.card?.number) setCardNumber(cached.card.number);
          setOfflineCacheWarning(true);
          if (!skipNewCardStep && (cached.card?.needs_new_card || new Date() < new Date("2026-07-01"))) {
            setStep("new_card");
          } else {
            setStep("amount");
          }
        } else {
          onToast("Player not found in offline cache — connect to look up new players", "error");
        }
      } else {
        onToast(err.error || "NFC card not found at this location", "error");
      }
    } finally {
      setLoading(false);
    }
  };

  // Auto-lookup when arriving from registration with a pre-assigned card.
  // IMPORTANT: Do NOT call onPrefillConsumed() until lookup completes.
  // Calling it early clears the prefillCard prop, which causes LoadScreen to
  // re-render with prefillCard=null + step="lookup" → flashes "Redeem Winnings".
  useEffect(() => {
    if (prefillCard && step === "lookup") {
      // skipNewCard=true: card was just issued at registration, go straight to amount.
      // handleNfcLookup handles both NFC-XXXXXXXX (card_number lookup) and real NDEF IDs.
      // For WSH- magstripe cards, use card_number lookup directly.
      if (/^WSH-/i.test(prefillCard)) {
        // Magstripe prefill — look up by card_number, set registrationCardId for fee deferral
        const doMagstripePrefill = async () => {
          try {
            const data = await api("player-lookup", { card_number: prefillCard.toUpperCase() }, session.token);
            setPlayer(data.player);
            setWallet(data.wallet);
            setCard(data.card);
            setLimits(data.limits);
            if (data.card?.number) setCardNumber(data.card.number);
            if (data.card?.id) setRegistrationCardId(data.card.id);
            cachePlayerData(prefillCard.toUpperCase(), { player: data.player, wallet: data.wallet, card: data.card, limits: data.limits });
            setStep("amount");
          } catch (err) {
            onToast(err.error || "Card lookup failed", "error");
          } finally {
            onPrefillConsumed?.();
          }
        };
        doMagstripePrefill();
      } else {
        // NFC card (NFC-XXXXXXXX) or real NDEF ID — handleNfcLookup handles both.
        // handleNfcLookup is async and will set step on completion.
        handleNfcLookup(prefillCard, skipNewCard).finally(() => {
          onPrefillConsumed?.();
        });
      }
    }
  }, []);

  // Handle unified card identification (from any source)
  const handleCardIdentified = async (cardId, source) => {
    // source: 'nfc' | 'magstripe' | 'manual' | 'qr' | 'id'
    if (source === "nfc") {
      await handleNfcLookup(cardId);
    } else if (source === "qr") {
      await handleQrLookup(cardId);
    } else if (source === "id") {
      setIdNumber(cardId);
      await handleIdLookup(cardId);
    } else {
      setCardNumber(cardId);
      await handleCardLookup();
    }
  };

  // Legacy handler for card lookup
  const handleLookup = handleCardLookup;

  // ── STEP 2: Proceed to Authorization ──────────────────────────────────
  const handleProceedToAuth = () => {
    const loadAmt = parseFloat(amount);
    if (!loadAmt || loadAmt <= 0) return onToast("Enter a valid amount", "error");
    if (!ticketRef.trim()) return onToast("Enter a ticket reference", "error");

    if (limits && loadAmt > limits.max_single_load) {
      return onToast(`Exceeds single load limit of ${fmtCurrency(limits.max_single_load)}`, "error");
    }
    if (limits && loadAmt > limits.daily_remaining) {
      return onToast(`Exceeds daily remaining limit of ${fmtCurrency(limits.daily_remaining)}`, "error");
    }

    setStep("authorize");
  };

  // ── STEP 3: Two-Factor Authorization + Execute Load ───────────────────
  const handleAuthorizeAndLoad = async () => {
    if (!operatorCardSuffix || operatorCardSuffix.length < 5) {
      return onToast("Enter your 5-digit operator card number", "error");
    }
    if (!operatorPin || operatorPin.length < 4) {
      return onToast("Enter your PIN", "error");
    }

    // ── OFFLINE: verify locally, queue the transaction ────────────────────
    if (!isOnline) {
      const sessionSuffix = (session.operator?.card_number || '').replace(/\D/g, '').slice(-5);
      const enteredHash = await hashPin(operatorPin);
      if (operatorCardSuffix !== sessionSuffix || enteredHash !== operatorPinHash) {
        return onToast("Incorrect card or PIN", "error");
      }
      const loadAmtNum = parseFloat(amount);
      addToOfflineQueue({
        type: 'load',
        operator_id: session.operator.id,
        location_id: session.location.id,
        card_number: card?.number || ("WSH-" + cardNumber),
        wallet_id: wallet.id,
        amount: loadAmtNum,
        ticket_reference: ticketRef.trim(),
        player_name: player?.name || '',
      });
      // Notify kiosk of pending load so it can adjust its local adjusted_balance
      const lastQueued = getOfflineQueue().slice(-1)[0];
      if (lastQueued) notifyKioskOfPendingLoad(lastQueued, session.location.id);
      appendLocalLog({ action: 'load_queued', actor_type: 'operator', actor_id: session?.operator?.id, actor_name: session?.operator?.name, location_id: session?.location?.id, details: { amount: loadAmtNum, card_number: cardNumber } });
      const pendingAdj = pendingBalanceFor(wallet.id);
      setTxn({ queued: true, amount: loadAmtNum, new_balance: (parseFloat(wallet.balance) || 0) + pendingAdj + loadAmtNum });
      onLoadComplete?.();
      setStep("done");
      return;
    }

    setLoading(true);
    try {
      const fullOperatorCard = "WSO-" + operatorCardSuffix;
      // Step A: Get authorization token
      const authData = await api("operator-authorize", {
        operator_card_number: fullOperatorCard,
        pin: operatorPin,
        wallet_id: wallet.id,
        amount: parseFloat(amount),
        ticket_reference: ticketRef.trim(),
        card_id: card?.id,
      }, session.token);

      // Step B: Execute load with auth token
      const loadData = await api("load-funds", {
        auth_token: authData.auth_token,
      }, session.token);

      setTxn(loadData.transaction);

      // Update wallet balance and daily limits with post-load values
      const loadAmtNum = parseFloat(amount);
      setWallet(prev => ({ ...prev, balance: loadData.transaction.new_balance }));
      setLimits(prev => prev ? {
        ...prev,
        daily_loaded: (prev.daily_loaded || 0) + loadAmtNum,
        daily_remaining: Math.max(0, (prev.daily_remaining || 0) - loadAmtNum),
      } : prev);

      // Charge deferred card fee now that load has succeeded.
      // Two cases:
      //   1. New card issued during Load More flow (pendingCardId set in handleIssueNewCard)
      //   2. Registration card — card assigned at registration, fee never charged (registrationCardId)
      const cardIdToCharge = pendingCardId || registrationCardId || null;
      if (cardIdToCharge && wallet?.id) {
        try {
          const feeData = await api("player-manage", {
            action: "charge_card_fee",
            data: { wallet_id: wallet.id, card_id: cardIdToCharge },
          }, session.token);
          const charged = feeData.card_fee || 0;
          // Update txn so done screen shows post-fee balance
          setTxn(prev => prev ? { ...prev, new_balance: Math.max(0, prev.new_balance - charged) } : prev);
          // Update wallet balance display
          setWallet(prev => prev ? { ...prev, balance: Math.max(0, prev.balance - charged) } : prev);
          setPendingCardFee(0);
          setPendingCardId(null);
          setRegistrationCardId(null);

          if (loadData.fraud?.withdrawal_blocked) {
            const code = alertCode(loadData.fraud?.alerts?.[0]?.id);
            onToast(`Load complete. Supervisor review required (Code: ${code}). ${fmtCurrency(charged)} card fee charged.`, "warning");
          } else if (loadData.fraud) {
            onToast(`Load complete. Review in progress. ${fmtCurrency(charged)} card fee charged.`, "warning");
          } else {
            onToast(`${fmtCurrency(loadAmtNum)} loaded. ${fmtCurrency(charged)} card fee charged.`, "success");
          }
        } catch (feeErr) {
          console.error("Card fee charge failed:", feeErr);
          onToast(`${fmtCurrency(loadAmtNum)} loaded. Card fee charge failed — contact manager.`, "warning");
        }
      } else {
        // No card fee to charge
        if (loadData.fraud) {
          if (loadData.fraud.withdrawal_blocked) {
            const code = alertCode(loadData.fraud?.alerts?.[0]?.id);
            onToast(`Load complete. Supervisor review required (Code: ${code}).`, "warning");
          } else {
            onToast("Load complete. Review in progress.", "warning");
          }
        } else {
          onToast(`${fmtCurrency(loadAmtNum)} loaded successfully!`, "success");
        }
      }

      if (loadData.fraud) setFraudAlert(loadData.fraud);

      appendLocalLog({ action: "pos_load", actor_type: "operator", actor_id: session?.operator?.id, actor_name: session?.operator?.name, location_id: session?.location?.id, details: { amount: loadAmtNum, player_id: wallet?.player_id } });
      onLoadComplete?.();
      setStep("done");
    } catch (err) {
      if (err.status === 410) {
        onToast("Authorization expired. Please try again.", "error");
        setOperatorCardSuffix("");
        setOperatorPin("");
      } else if (err.status === 409) {
        onToast("Authorization already used. Please try again.", "error");
        setStep("amount");
        setOperatorCardSuffix("");
        setOperatorPin("");
      } else {
        onToast(err.error || "Load failed", "error");
      }
    } finally {
      setLoading(false);
    }
  };

  // ── ID barcode listener — active while lookupMethod === "id" ────────
  // The S370 HID scanner dumps raw PDF417 data as keystrokes; we intercept
  // it here, parse the license, and run the lookup — same as camera scan.
  useEffect(() => {
    if (lookupMethod !== "id" || step !== "lookup") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode((data) => {
      const parsed = parseDriversLicense(data);
      if (parsed.id_number) {
        handleIdLookup(parsed.id_number);
      } else {
        onToast("Could not read ID barcode — try again or enter manually", "error");
      }
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, lookupMethod, step]);

  // Restore HID focus when entering ID scan mode (scanner must have focus to receive keystrokes)
  useEffect(() => {
    if (lookupMethod === "id" && step === "lookup") {
      captureCtx?.refocus?.();
    }
  }, [captureCtx, lookupMethod, step]);

  // ── QR barcode listener — active while lookupMethod === "qr" ─────────
  useEffect(() => {
    if (lookupMethod !== "qr" || step !== "lookup") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode((data) => {
      handleQrLookup(data);
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, lookupMethod, step]);

  // ── Reset ─────────────────────────────────────────────────────────────
  const handleReset = () => {
    setStep("lookup");
    setLookupMethod(null);
    setCardNumber("");
    setIdNumber("");
    setPlayer(null);
    setWallet(null);
    setCard(null);
    setLimits(null);
    setAmount("");
    setTicketRef("");
    setOperatorCardSuffix("");
    setOperatorPin("");
    setTxn(null);
    setFraudAlert(null);
    setNewCardNumber("");
    setPendingCardFee(0);
    setPendingCardId(null);
    setRegistrationCardId(null);
    setOfflineCacheWarning(false);
  };

  const handleLoadMore = () => {
    setAmount("");
    setTicketRef("");
    setOperatorCardSuffix("");
    setOperatorPin("");
    setTxn(null);
    setFraudAlert(null);
    setNewCardNumber("");
    setPendingCardFee(0);
    setPendingCardId(null);
    setRegistrationCardId(null);
    // Single-use cards need a new card for each load; before July 1 2026, all redemptions require a new card
    if (!card?.is_reloadable || new Date() < new Date("2026-07-01")) {
      setStep("new_card");
    } else {
      setStep("amount");
    }
  };

  // ── Issue New Card (single-use enforcement) ──────────────────────────
  const handleIssueNewCard = async () => {
    if (!newCardNumber || newCardNumber.length < 5) {
      return onToast("Enter a 5-digit card number", "error");
    }
    setLoading(true);
    try {
      const fullNewCard = (isNfcLocation ? "NFC-" : "WSH-") + newCardNumber;
      const data = await api("player-manage", {
        action: "issue_new_card",
        data: { wallet_id: wallet.id, card_number: fullNewCard, defer_fee: true },
      }, session.token);

      // Update card state with the newly issued card
      setCard({
        id: data.card.id,
        number: data.card.number,
        status: "issued",
        type: isNfcLocation ? "nfc" : "magstripe",
        is_reloadable: data.card.is_reloadable,
        load_count: 0,
        needs_new_card: false,
      });
      setCardNumber(data.card.number);

      // Store fee to be charged after load (not charged yet)
      setPendingCardFee(data.card.card_fee);
      setPendingCardId(data.card.id);

      onToast("New card issued. Card fee will be charged after load.", "success");
      setStep("amount");
    } catch (err) {
      onToast(err.error || "Failed to issue new card", "error");
    } finally {
      setLoading(false);
    }
  };

  // — Player info bar
  const PlayerBar = () => (
    <div style={{
      background: COLORS.successBg, border: `2px solid ${COLORS.success}`, borderRadius: 16,
      padding: "20px 24px", marginBottom: 28,
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <div>
        <div style={{ fontSize: 13, color: COLORS.success, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
          Player
        </div>
        <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading }}>
          {player?.name}
        </div>
        <div style={{ fontSize: 14, color: COLORS.textMuted, fontFamily: "'Space Mono', monospace" }}>
          {player?.id ? "WS-" + player.id.split("-")[0].toUpperCase() : cardNumber}
        </div>
      </div>
      <div style={{ textAlign: "right" }}>
        <div style={{ fontSize: 13, color: COLORS.success, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
          Balance
        </div>
        <div style={{ fontSize: 32, fontWeight: 700, color: COLORS.heading }}>
          {fmtCurrency(wallet?.balance || 0)}
        </div>
        {limits && (
          <div style={{ fontSize: 12, color: COLORS.textMuted }}>
            Daily remaining: {fmtCurrency(limits.daily_remaining)}
          </div>
        )}
      </div>
    </div>
  );

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Player Lookup - Method Selection or Input
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "lookup") {
    // If we have a prefill card, skip the lookup UI entirely — the useEffect will handle it.
    if (prefillCard) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto", textAlign: "center" }}>
          <div style={{ fontSize: 14, color: COLORS.textMuted, marginTop: 60 }}>Loading player…</div>
        </div>
      );
    }

    // Show method selection if no method chosen
    if (!lookupMethod) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Redeem Winnings
          </h2>
          <p style={{ fontSize: 16, color: COLORS.textMuted, marginBottom: 28 }}>
            How would you like to find the player?
          </p>

          {/* Lookup by identity only — card/NFC removed so the system always prompts a new card after finding the player */}
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <button
              onClick={() => setLookupMethod("qr")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s", boxShadow: `0 0 20px ${COLORS.orangeGlow}`,
              }}
            >
              <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8 }}>
                <div style={{ fontSize: 24 }}>📱</div>
                <div style={{
                  background: COLORS.orange, color: COLORS.heading, fontSize: 11,
                  padding: "4px 8px", borderRadius: 6, fontWeight: 700,
                }}>RECOMMENDED</div>
              </div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Scan QR Code</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Scan QR code from player's WinStash app</div>
            </button>

            <button
              onClick={() => setLookupMethod("id")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>🪪</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Scan ID</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Look up player by scanning the back of their ID</div>
            </button>

            <button
              onClick={() => setLookupMethod("name")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>🔍</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Search by Name</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Find player by name or phone number</div>
            </button>
          </div>
        </div>
      );
    }

    // Card lookup input
    if (lookupMethod === "card") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Redeem Winnings
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px", textAlign: "center",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
              {isNfcLocation ? "Enter Gift Card Number" : "Swipe Gift Card"}
            </div>
            <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.orange}` }}>
              <span style={{
                padding: "16px 0 16px 18px", fontSize: 22, fontWeight: 700,
                fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px",
              }}>WSH-</span>
              <input
                value={cardNumber}
                onChange={(e) => setCardNumber(fmtGiftCardSuffix(e.target.value))}
                placeholder="00000"
                autoFocus
                maxLength={5}
                onKeyDown={(e) => e.key === "Enter" && cardNumber.length >= 5 && handleLookup()}
                style={{
                  flex: 1, padding: "16px 18px 16px 0", fontSize: 22, fontWeight: 700,
                  border: "none", outline: "none",
                  fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                  background: "transparent", letterSpacing: "2px",
                  textTransform: "uppercase", color: COLORS.heading,
                }}
              />
            </div>
            <p style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 10, marginBottom: 0 }}>
              Enter the 5-digit code from the gift card
            </p>
            <div style={{ marginTop: 20, display: "flex", gap: 12 }}>
              <Btn onClick={() => setLookupMethod(null)} variant="secondary" wide size="lg">
                Back
              </Btn>
              <Btn onClick={handleLookup} disabled={loading || cardNumber.length < 5} wide size="lg">
                {loading ? "Looking up..." : "Find Player"}
              </Btn>
            </div>
          </div>
        </div>
      );
    }

    // QR Code lookup
    if (lookupMethod === "qr") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Redeem Winnings
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12, textAlign: "center" }}>
              Scan Player's QR Code
            </div>
            <p style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 20, textAlign: "center" }}>
              Ask the player to open their WinStash app and show you the QR code on screen
            </p>
            <QrScanOptions
              onCameraClick={() => captureCtx?.scanQrWithCamera({ onError: (msg) => onToast(msg, 'error') })}
              isCameraScanning={captureCtx?.isQrCameraScanning}
              disabled={loading}
              onCancel={() => setLookupMethod(null)}
            />
          </div>
        </div>
      );
    }

    // ID lookup
    if (lookupMethod === "id") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Redeem Winnings
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16, textAlign: "center" }}>
              Scan Driver's License
            </div>

            <IdScanOptions
              onCameraClick={() => scanId({
                onSuccess: (raw) => {
                  const parsed = parseDriversLicense(raw);
                  if (parsed.id_number) {
                    handleIdLookup(parsed.id_number);
                  } else {
                    onToast("Could not read ID number — try scanning again or enter manually", "error");
                  }
                },
                onError: (msg) => onToast(msg, 'error'),
              })}
              isCameraScanning={isCameraScanning}
              disabled={loading}
            />

            {/* Divider */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, margin: '20px 0' }}>
              <div style={{ flex: 1, height: 1, background: COLORS.border }} />
              <span style={{ fontSize: 12, color: COLORS.textMuted }}>or enter manually</span>
              <div style={{ flex: 1, height: 1, background: COLORS.border }} />
            </div>

            {/* Manual entry */}
            <input
              value={idNumber}
              onChange={(e) => setIdNumber(e.target.value.toUpperCase())}
              placeholder="Driver's License #"
              onKeyDown={(e) => e.key === "Enter" && idNumber.length >= 4 && handleIdLookup()}
              style={{
                width: "100%", padding: "18px 20px", fontSize: 24, fontWeight: 700,
                border: `2px solid ${COLORS.border}`, borderRadius: 12, outline: "none",
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                background: "#1E1E1E", letterSpacing: "1px", textAlign: "center",
                textTransform: "uppercase", color: COLORS.heading,
              }}
            />
            <div style={{ marginTop: 20, display: "flex", gap: 12 }}>
              <Btn onClick={() => setLookupMethod(null)} variant="secondary" wide size="lg">
                Back
              </Btn>
              <Btn onClick={() => handleIdLookup()} disabled={loading || idNumber.length < 4} wide size="lg">
                {loading ? "Looking up..." : "Find Player"}
              </Btn>
            </div>
          </div>
        </div>
      );
    }

    // Name search lookup
    if (lookupMethod === "name") {
      return (
        <NameSearchView
          session={session}
          onToast={onToast}
          onBack={() => setLookupMethod(null)}
          onSelect={(result) => {
            setPlayer({
              id: result.player.id,
              name: result.player.name,
              first_name: result.player.first_name,
              last_name: result.player.last_name,
            });
            setWallet({
              id: result.wallet_id,
              external_id: result.wallet_external_id,
              balance: result.balance,
            });
            if (result.card) {
              setCard(result.card);
              if (result.card.number) setCardNumber(result.card.number);
            }
            if (result.card?.needs_new_card || new Date() < new Date("2026-07-01")) {
              setStep("new_card");
            } else {
              setStep("amount");
            }
          }}
        />
      );
    }

    // NFC card tap lookup
    if (lookupMethod === "nfc") {
      return (
        <NfcLookupView
          session={session}
          onToast={onToast}
          onBack={() => setLookupMethod(null)}
          onSuccess={(nfcNdefId) => handleNfcLookup(nfcNdefId)}
          loading={loading}
        />
      );
    }

  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: New Card Required (single-use enforcement)
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "new_card") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
          Redeem Winnings
        </h2>
        <PlayerBar />

        <div style={{
          background: COLORS.warningBg, border: `2px solid ${COLORS.warning}`, borderRadius: 16,
          padding: "24px", marginBottom: 28,
        }}>
          <div style={{ fontSize: 16, fontWeight: 700, color: COLORS.warning, marginBottom: 8 }}>
            Step 2: Assign New Gift Card
          </div>
          <div style={{ fontSize: 14, color: COLORS.text, lineHeight: 1.5 }}>
            Georgia law requires a new physical gift card for each COAM redemption before July 1, 2026.
          </div>
          <div style={{ fontSize: 14, color: COLORS.text, marginTop: 8, fontWeight: 600 }}>
            A {fmtCurrency(session.location?.physical_card_fee || 2)} card fee will be charged.
          </div>
        </div>

        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
          padding: "28px 24px", textAlign: "center",
        }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
            {isNfcLocation ? "Tap or Enter New Card Number" : "Swipe New Gift Card"}
          </div>
          <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.orange}` }}>
            <span style={{
              padding: "16px 0 16px 18px", fontSize: 22, fontWeight: 700,
              fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px",
            }}>{isNfcLocation ? "NFC-" : "WSH-"}</span>
            <input
              value={newCardNumber}
              onChange={(e) => setNewCardNumber(isNfcLocation ? e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8) : fmtGiftCardSuffix(e.target.value))}
              placeholder={isNfcLocation ? "********" : "00000"}
              autoFocus
              maxLength={isNfcLocation ? 8 : 5}
              onKeyDown={(e) => e.key === "Enter" && newCardNumber.length >= 5 && handleIssueNewCard()}
              style={{
                flex: 1, padding: "16px 18px 16px 0", fontSize: 22, fontWeight: 700,
                border: "none", outline: "none",
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                background: "transparent", letterSpacing: "2px",
                textTransform: "uppercase", color: COLORS.heading,
              }}
            />
          </div>
          <p style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 10, marginBottom: 0 }}>
            Scan or enter the new card from inventory
          </p>
        </div>

        <div style={{ marginTop: 20, display: "flex", gap: 12 }}>
          <Btn onClick={handleReset} variant="secondary" wide size="lg">
            Cancel
          </Btn>
          <Btn onClick={handleIssueNewCard} disabled={loading || newCardNumber.length < 5} wide size="lg">
            {loading ? "Issuing..." : "Issue New Card"}
          </Btn>
        </div>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Enter Amount + Ticket Reference
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "amount") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
          Redeem Winnings
        </h2>
        <PlayerBar />

        {offlineCacheWarning && (
          <div style={{
            background: COLORS.warningBg, border: `1px solid ${COLORS.warning}`, borderRadius: 10,
            padding: '12px 16px', marginBottom: 20, fontSize: 13, color: COLORS.warning,
          }}>
            Offline — showing cached player data. Balance may not be current. Load will be queued and processed when connection returns.
          </div>
        )}

        {/* Amount Input */}
        <div style={{ marginBottom: 20 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 8 }}>
            Step 2: Amount to Load
          </label>
          <div style={{ position: "relative" }}>
            <span style={{
              position: "absolute", left: 18, top: "50%", transform: "translateY(-50%)",
              fontSize: 28, fontWeight: 700, color: COLORS.textMuted,
            }}>$</span>
            <input
              value={amount}
              onChange={(e) => setAmount(e.target.value.replace(/[^\d.]/g, ""))}
              placeholder="0.00"
              inputMode="decimal"
              autoFocus
              style={{
                width: "100%", padding: "18px 20px 18px 42px", fontSize: 32, fontWeight: 700,
                border: `2px solid ${COLORS.border}`, borderRadius: 12, outline: "none",
                fontFamily: "inherit", boxSizing: "border-box", background: COLORS.surface,
                color: COLORS.heading,
              }}
            />
          </div>
        </div>

        {/* Quick-amount buttons */}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 20 }}>
          {presets.map((p) => (
            <button
              key={p}
              onClick={() => setAmount(p.toString())}
              style={{
                padding: "14px 0", fontSize: 20, fontWeight: 700,
                border: amount === p.toString() ? `2px solid ${COLORS.orange}` : `2px solid ${COLORS.border}`,
                borderRadius: 12, cursor: "pointer", fontFamily: "inherit",
                background: amount === p.toString() ? COLORS.orange : COLORS.surface,
                color: COLORS.heading,
                transition: "all .15s",
              }}
            >
              ${p}
            </button>
          ))}
        </div>

        {/* Ticket Reference */}
        <div style={{ marginBottom: 28 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 8 }}>
            Ticket / Voucher Reference <span style={{ color: COLORS.orange }}>*</span>
          </label>
          <input
            value={ticketRef}
            onChange={(e) => setTicketRef(e.target.value)}
            placeholder="Enter ticket or voucher number"
            onKeyDown={(e) => e.key === "Enter" && handleProceedToAuth()}
            style={{
              width: "100%", padding: "14px 16px", fontSize: 18,
              border: `2px solid ${COLORS.border}`, borderRadius: 10, outline: "none",
              fontFamily: "'Space Mono', monospace", boxSizing: "border-box", background: COLORS.surface,
              color: COLORS.heading,
            }}
          />
        </div>

        <Btn onClick={handleProceedToAuth} disabled={!amount || !ticketRef.trim()} wide size="xl">
          Continue to Authorization
        </Btn>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Operator Two-Factor Authorization
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "authorize") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
          Redeem Winnings
        </h2>
        <PlayerBar />

        {/* Transaction Summary */}
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 14,
          padding: "20px 24px", marginBottom: 28,
        }}>
          <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 12 }}>
            <span style={{ fontSize: 14, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Load Amount</span>
            <span style={{ fontSize: 24, fontWeight: 700, color: COLORS.success }}>{fmtCurrency(parseFloat(amount) || 0)}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between" }}>
            <span style={{ fontSize: 14, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Ticket Ref</span>
            <span style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading, fontFamily: "'Space Mono', monospace" }}>{ticketRef}</span>
          </div>
        </div>

        {/* Operator Authorization */}
        <div style={{
          background: COLORS.surfaceLight, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
          padding: "24px", marginBottom: 28,
        }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16, textAlign: "center" }}>
            Step 3: Operator Authorization
          </div>

          {/* Operator Card */}
          <div style={{ marginBottom: 16 }}>
            <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase" }}>
              Operator Card
            </label>
            <div style={{ display: "flex", alignItems: "center", background: COLORS.surface, borderRadius: 10, border: `2px solid ${COLORS.border}` }}>
              <span style={{
                padding: "14px 0 14px 16px", fontSize: 20, fontWeight: 700,
                fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "1px",
              }}>WSO-</span>
              <input
                inputMode="numeric"
                value={operatorCardSuffix}
                onChange={(e) => {
                  const formatted = fmtOperatorCardSuffix(e.target.value);
                  setOperatorCardSuffix(formatted);
                  if (formatted.length >= 5 && pinRef.current) pinRef.current.focus();
                }}
                placeholder="00000"
                autoFocus
                maxLength={5}
                style={{
                  flex: 1, padding: "14px 16px 14px 0", fontSize: 20, fontWeight: 700,
                  border: "none", outline: "none",
                  fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                  background: "transparent", letterSpacing: "1px", textTransform: "uppercase",
                  color: COLORS.heading,
                }}
              />
            </div>
          </div>

          {/* Operator PIN */}
          <div>
            <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase" }}>
              PIN
            </label>
            <input
              ref={pinRef}
              type="password"
              value={operatorPin}
              onChange={(e) => setOperatorPin(e.target.value.replace(/\D/g, "").slice(0, 4))}
              placeholder=""
              inputMode="numeric"
              maxLength={4}
              onKeyDown={(e) => e.key === "Enter" && handleAuthorizeAndLoad()}
              style={{
                width: "100%", padding: "14px 16px", fontSize: 24, fontWeight: 700,
                border: `2px solid ${COLORS.border}`, borderRadius: 10, outline: "none",
                fontFamily: "inherit", boxSizing: "border-box", background: COLORS.surface,
                textAlign: "center", letterSpacing: "6px", color: COLORS.heading,
              }}
            />
          </div>
        </div>

        <Btn onClick={handleAuthorizeAndLoad} disabled={loading || operatorCardSuffix.length < 5 || operatorPin.length < 4} wide size="xl">
          {loading ? "Processing..." : `Authorize & Load ${fmtCurrency(parseFloat(amount) || 0)}`}
        </Btn>

        <div style={{ marginTop: 12 }}>
          <Btn onClick={() => setStep("amount")} wide size="md" variant="secondary">
            Back to Edit Amount
          </Btn>
        </div>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Done / Receipt
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "done" && txn?.queued) {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <div style={{
          background: COLORS.warningBg, border: `2px solid ${COLORS.warning}`,
          borderRadius: 20, padding: "44px 36px", textAlign: "center",
        }}>
          <div style={{ fontSize: 48, marginBottom: 16 }}>🟡</div>
          <h3 style={{ fontSize: 26, fontWeight: 700, margin: "0 0 8px", color: COLORS.warning }}>
            Load Queued (Offline)
          </h3>
          <p style={{ fontSize: 16, color: COLORS.textMuted, margin: "0 0 24px" }}>
            Will be processed when connection returns
          </p>
          <div style={{
            background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
            border: `1px solid ${COLORS.border}`, display: "flex", justifyContent: "space-between",
          }}>
            <div>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Queued Amount</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.warning }}>
                +{fmtCurrency(txn?.amount || 0)}
              </div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Est. Balance</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.heading }}>
                {fmtCurrency(txn?.new_balance || 0)}
              </div>
            </div>
          </div>
          <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 24 }}>
            Estimated balance includes this queued load. Actual balance confirmed after sync.
          </div>
          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={handleReset} wide size="lg" variant="secondary">
              New Card Lookup
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  if (step === "done") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <div style={{
          background: fraudAlert?.withdrawal_blocked ? COLORS.errorBg : COLORS.successBg,
          border: `2px solid ${fraudAlert?.withdrawal_blocked ? COLORS.error : COLORS.success}`,
          borderRadius: 20, padding: "44px 36px", textAlign: "center",
        }}>
          <div style={{ fontSize: 56, marginBottom: 16 }}>
            {fraudAlert?.withdrawal_blocked ? "⚠️" : "✅"}
          </div>
          <h3 style={{
            fontSize: 26, fontWeight: 700, margin: "0 0 8px",
            color: fraudAlert?.withdrawal_blocked ? COLORS.error : COLORS.success,
          }}>
            {fraudAlert?.withdrawal_blocked ? "Funds Loaded — Supervisor Verification Required" : "Funds Loaded"}
          </h3>
          <p style={{ fontSize: 18, color: COLORS.text, margin: "0 0 28px" }}>
            {player?.name}
          </p>

          {/* Transaction Details */}
          <div style={{
            background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 16,
            border: `1px solid ${COLORS.border}`, display: "flex", justifyContent: "space-between",
          }}>
            <div>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Loaded</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.success }}>
                +{fmtCurrency(txn?.amount || 0)}
              </div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>New Balance</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.heading }}>
                {fmtCurrency(txn?.new_balance || 0)}
              </div>
            </div>
          </div>

          {txn?.ticket_reference && (
            <div style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 16 }}>
              Ticket: <span style={{ fontFamily: "'Space Mono', monospace", fontWeight: 600 }}>{txn.ticket_reference}</span>
            </div>
          )}

          {/* Supervisor Review Notice */}
          {fraudAlert && (
            <div style={{
              background: fraudAlert.withdrawal_blocked ? COLORS.errorBg : COLORS.warningBg,
              border: `1px solid ${fraudAlert.withdrawal_blocked ? COLORS.error : COLORS.warning}`,
              borderRadius: 10, padding: "16px 20px", marginBottom: 24, textAlign: "left",
            }}>
              <div style={{
                fontSize: 14, fontWeight: 700, marginBottom: 8,
                color: fraudAlert.withdrawal_blocked ? COLORS.error : COLORS.warning,
              }}>
                {fraudAlert.withdrawal_blocked
                  ? `⚠ Supervisor verification required — Code: ${alertCode(fraudAlert.alerts?.[0]?.id)}`
                  : "⚠ Transaction review in progress"}
              </div>
              <div style={{ fontSize: 13, color: COLORS.text, lineHeight: 1.5 }}>
                {fraudAlert.withdrawal_blocked
                  ? "Please notify a supervisor before completing this transaction."
                  : "No action required. Transaction has been recorded."}
              </div>
            </div>
          )}

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={handleLoadMore} wide size="lg">
              Load More for {player?.name?.split(" ")[0]}
            </Btn>
          </div>
          <div style={{ marginTop: 12 }}>
            <Btn onClick={handleReset} wide size="md" variant="secondary">
              New Card Lookup
            </Btn>
          </div>
        </div>
      </div>
    );
  }
}

// ============================================================================
// TECHNICIAN LOGIN SCREEN
// ============================================================================
function TechnicianLoginScreen({ onCancel, onSuccess, onToast }) {
  const [codeSuffix, setCodeSuffix] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [sendingCode, setSendingCode] = useState(false);
  const [codeSent, setCodeSent] = useState(false);

  const handleCodeChange = (v) => {
    // Allow alphanumeric, max 6 chars
    const formatted = v.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 6);
    setCodeSuffix(formatted);
  };

  const handleSendCode = async () => {
    setSendingCode(true);
    setError(null);
    try {
      // When live, this will trigger a 2FA SMS to the manager/owner on file.
      // For now, display a note that this feature is coming soon.
      await new Promise(r => setTimeout(r, 800)); // simulate network
      setCodeSent(true);
      onToast("2FA SMS feature coming soon — contact your administrator for the setup code", "info");
    } finally {
      setSendingCode(false);
    }
  };

  const handleSubmit = async () => {
    if (codeSuffix.length < 6) {
      return setError("Enter the full 6-character code");
    }
    setLoading(true);
    setError(null);
    try {
      const fullCode = "TECH-" + codeSuffix;
      const res = await fetch(`${SUPABASE_URL}/functions/v1/technician-validate`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
        },
        body: JSON.stringify({ code: fullCode }),
      });
      const data = await res.json();
      if (!res.ok) {
        throw { error: data.error || "Validation failed" };
      }
      onToast("Settings access granted", "success");
      onSuccess({
        token: data.technician_token,
        location: data.location,
        technicianName: data.technician_name,
        sessionExpiresAt: new Date(data.session_expires_at),
      });
    } catch (err) {
      console.error("Tech login error:", err);
      setError(err.error || "Invalid setup code");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{
      minHeight: "100vh",
      background: COLORS.bg,
      display: "flex", alignItems: "center", justifyContent: "center",
      fontFamily: "'DM Sans', -apple-system, sans-serif",
    }}>
      <div style={{
        background: COLORS.surface, borderRadius: 24, padding: "48px 44px", width: 420,
        boxShadow: "0 24px 80px rgba(0,0,0,.5)", border: `1px solid ${COLORS.border}`,
      }}>
        <div style={{ textAlign: "center", marginBottom: 36 }}>
          <div style={{ fontSize: 48, marginBottom: 16 }}>
            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke={COLORS.orange} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="3"></circle>
              <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
            </svg>
          </div>
          <h1 style={{ fontSize: 24, fontWeight: 700, margin: "0 0 4px", color: COLORS.heading }}>
            Location Settings
          </h1>
          <p style={{ fontSize: 15, color: COLORS.textMuted, margin: 0 }}>
            Enter your technician setup code
          </p>
        </div>

        {/* SMS 2FA button — will send a one-time code when live */}
        <div style={{ marginBottom: 20 }}>
          <button
            onClick={handleSendCode}
            disabled={sendingCode || codeSent}
            style={{
              width: "100%", padding: "14px 20px", borderRadius: 12, cursor: sendingCode || codeSent ? "default" : "pointer",
              background: codeSent ? COLORS.successBg : COLORS.surfaceLight,
              border: `2px solid ${codeSent ? COLORS.success : COLORS.border}`,
              display: "flex", alignItems: "center", justifyContent: "center", gap: 10,
              fontFamily: "inherit", transition: "all .2s",
              opacity: sendingCode ? 0.7 : 1,
            }}
          >
            <span style={{ fontSize: 20 }}>{codeSent ? "✅" : "💬"}</span>
            <div style={{ textAlign: "left" }}>
              <div style={{ fontSize: 14, fontWeight: 700, color: codeSent ? COLORS.success : COLORS.heading }}>
                {sendingCode ? "Sending..." : codeSent ? "Code Sent" : "Send 2FA Code via SMS"}
              </div>
              <div style={{ fontSize: 12, color: COLORS.textMuted }}>
                {codeSent ? "Check your manager's phone for the code" : "Sends a one-time code to the manager on file"}
              </div>
            </div>
          </button>
        </div>

        <div style={{ marginBottom: 20 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
            Setup Code <span style={{ color: COLORS.orange }}>*</span>
          </label>
          <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.orange}` }}>
            <span style={{ paddingLeft: 18, fontSize: 22, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "4px", userSelect: "none" }}>TECH-</span>
            <input
              type="text"
              value={codeSuffix}
              onChange={(e) => handleCodeChange(e.target.value)}
              placeholder="A7F3B2"
              autoFocus
              maxLength={6}
              style={{
                flex: 1, padding: "16px 18px 16px 4px", fontSize: 22, fontWeight: 700,
                border: "none", outline: "none", textAlign: "center",
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                background: "transparent", letterSpacing: "4px",
                textTransform: "uppercase", color: COLORS.heading,
              }}
              onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
            />
          </div>
          <p style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 6, textAlign: "center" }}>
            Enter the 6-character code shown in the admin portal
          </p>
        </div>

        {error && (
          <div style={{
            background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 10,
            padding: "12px 16px", marginBottom: 16, color: COLORS.error, fontSize: 15, fontWeight: 500,
          }}>
            {error}
          </div>
        )}

        <Btn onClick={handleSubmit} disabled={loading || codeSuffix.length < 6} wide size="lg">
          {loading ? "Validating..." : "ACCESS SETTINGS"}
        </Btn>

        <div style={{ marginTop: 12 }}>
          <Btn onClick={onCancel} wide size="md" variant="secondary">
            Cancel
          </Btn>
        </div>
      </div>
    </div>
  );
}

// ============================================================================
// LOCATION SETTINGS FORM
// ============================================================================
// Timezones list for dropdown
const TIMEZONES = [
  { value: "America/New_York", label: "Eastern Time" },
  { value: "America/Chicago", label: "Central Time" },
  { value: "America/Denver", label: "Mountain Time" },
  { value: "America/Los_Angeles", label: "Pacific Time" },
  { value: "America/Anchorage", label: "Alaska Time" },
  { value: "Pacific/Honolulu", label: "Hawaii Time" },
];

const LOCATION_STATUS = [
  { value: "active", label: "Active" },
  { value: "suspended", label: "Suspended" },
  { value: "closed", label: "Closed" },
];

const CARD_READER_TYPES = [
  { value: "nfc", label: "NFC (Socket S370)" },
  { value: "magstripe", label: "Magnetic Stripe (Legacy)" },
];

const DAYS_OF_WEEK = [
  { id: "monday", label: "Monday" },
  { id: "tuesday", label: "Tuesday" },
  { id: "wednesday", label: "Wednesday" },
  { id: "thursday", label: "Thursday" },
  { id: "friday", label: "Friday" },
  { id: "saturday", label: "Saturday" },
  { id: "sunday", label: "Sunday" },
];

// ── GEOFENCE MAP COMPONENT ──────────────────────────────────────────────────
// Uses Leaflet.js with OpenStreetMap tiles (free, no API key required)
function GeofenceMap({ latitude, longitude, radius, onLocationChange }) {
  const mapRef = useRef(null);
  const mapInstanceRef = useRef(null);
  const markerRef = useRef(null);
  const circleRef = useRef(null);
  const [loading, setLoading] = useState(false);
  const [mapReady, setMapReady] = useState(false);

  // Default to Atlanta if no coordinates
  const lat = parseFloat(latitude) || 33.749;
  const lng = parseFloat(longitude) || -84.388;
  const radiusMeters = parseInt(radius) || 300;

  // Initialize map
  useEffect(() => {
    // Check if Leaflet is loaded
    if (typeof L === "undefined") {
      console.warn("Leaflet not loaded - map unavailable");
      return;
    }

    // Only initialize once
    if (mapInstanceRef.current) return;

    const map = L.map(mapRef.current).setView([lat, lng], 15);
    mapInstanceRef.current = map;

    // Add OpenStreetMap tiles
    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
      maxZoom: 19,
    }).addTo(map);

    const readOnly = !onLocationChange;

    // Add marker (draggable only when editable)
    const marker = L.marker([lat, lng], { draggable: !readOnly }).addTo(map);
    markerRef.current = marker;

    // Add geofence circle
    const circle = L.circle([lat, lng], {
      radius: radiusMeters,
      color: COLORS.orange,
      fillColor: COLORS.orange,
      fillOpacity: 0.2,
      weight: 2,
    }).addTo(map);
    circleRef.current = circle;

    if (!readOnly) {
      // Handle marker drag
      marker.on("dragend", (e) => {
        const pos = e.target.getLatLng();
        circle.setLatLng(pos);
        onLocationChange(pos.lat.toFixed(6), pos.lng.toFixed(6));
      });

      // Handle map click
      map.on("click", (e) => {
        marker.setLatLng(e.latlng);
        circle.setLatLng(e.latlng);
        onLocationChange(e.latlng.lat.toFixed(6), e.latlng.lng.toFixed(6));
      });
    }

    setMapReady(true);

    return () => {
      map.remove();
      mapInstanceRef.current = null;
    };
  }, []);

  // Update marker and circle when props change
  useEffect(() => {
    if (!mapReady || !markerRef.current || !circleRef.current) return;

    const newLat = parseFloat(latitude) || 33.749;
    const newLng = parseFloat(longitude) || -84.388;
    const newRadius = parseInt(radius) || 300;

    markerRef.current.setLatLng([newLat, newLng]);
    circleRef.current.setLatLng([newLat, newLng]);
    circleRef.current.setRadius(newRadius);

    // Pan to new location if significantly different
    const map = mapInstanceRef.current;
    if (map) {
      const currentCenter = map.getCenter();
      const distance = Math.sqrt(
        Math.pow(currentCenter.lat - newLat, 2) + Math.pow(currentCenter.lng - newLng, 2)
      );
      if (distance > 0.001) {
        map.setView([newLat, newLng], map.getZoom());
      }
    }
  }, [latitude, longitude, radius, mapReady]);

  // Find My Location handler
  const handleFindMyLocation = () => {
    if (!navigator.geolocation) {
      alert("Geolocation is not supported by your browser");
      return;
    }

    setLoading(true);
    navigator.geolocation.getCurrentPosition(
      (position) => {
        const { latitude: newLat, longitude: newLng } = position.coords;
        if (onLocationChange) onLocationChange(newLat.toFixed(6), newLng.toFixed(6));

        if (mapInstanceRef.current) {
          mapInstanceRef.current.setView([newLat, newLng], 16);
        }
        setLoading(false);
      },
      (error) => {
        console.error("Geolocation error:", error);
        alert("Could not get your location. Please check permissions.");
        setLoading(false);
      },
      { enableHighAccuracy: true, timeout: 10000 }
    );
  };

  // Check if Leaflet is available
  if (typeof L === "undefined") {
    return (
      <div style={{
        background: COLORS.surfaceLight,
        borderRadius: 12,
        padding: 32,
        textAlign: "center",
        border: `1px dashed ${COLORS.border}`,
      }}>
        <p style={{ color: COLORS.textMuted, margin: 0 }}>
          Map unavailable. Add Leaflet CSS/JS to enable interactive map.
        </p>
        <p style={{ color: COLORS.textMuted, fontSize: 12, marginTop: 8 }}>
          Include leaflet.css and leaflet.js from CDN
        </p>
      </div>
    );
  }

  const mapReadOnly = !onLocationChange;

  return (
    <div>
      {!mapReadOnly && (
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 12 }}>
          <button
            onClick={handleFindMyLocation}
            disabled={loading}
            style={{
              padding: "10px 16px",
              fontSize: 14,
              fontWeight: 600,
              border: `1px solid ${COLORS.orange}`,
              borderRadius: 8,
              cursor: loading ? "not-allowed" : "pointer",
              background: "transparent",
              color: COLORS.orange,
              display: "flex",
              alignItems: "center",
              gap: 8,
            }}
          >
            {loading ? "Locating..." : "Find My Location"}
          </button>
        </div>
      )}
      <div
        ref={mapRef}
        style={{
          height: 300,
          borderRadius: 12,
          overflow: "hidden",
          border: `1px solid ${COLORS.border}`,
        }}
      />
      <p style={{ color: COLORS.textMuted, fontSize: 12, marginTop: 8, textAlign: "center" }}>
        {mapReadOnly
          ? "Map is read-only. Use the admin portal to update coordinates."
          : "Click on the map or drag the marker to set location. The orange circle shows the geofence radius."}
      </p>
    </div>
  );
}

// ── OPERATING HOURS EDITOR ──────────────────────────────────────────────────
function OperatingHoursEditor({ hours, onChange, isDirty }) {
  // Check if hours are disabled (null or has disabled flag)
  const hoursNotSet = !hours || hours === null || hours.disabled === true;

  const [localHours, setLocalHours] = useState(() => {
    if (hours && typeof hours === "object" && !hours.disabled) return hours;
    // Default hours
    return DAYS_OF_WEEK.reduce((acc, day) => {
      acc[day.id] = { open: "09:00", close: "21:00", closed: false };
      return acc;
    }, {});
  });

  const [showHours, setShowHours] = useState(!hoursNotSet);

  const handleToggleHoursNotSet = () => {
    if (showHours) {
      // Switching to "Hours Not Set" - store null/disabled
      setShowHours(false);
      onChange(null);
    } else {
      // Switching to show hours - restore local hours
      setShowHours(true);
      onChange(localHours);
    }
  };

  const handleDayChange = (dayId, field, value) => {
    const updated = {
      ...localHours,
      [dayId]: { ...localHours[dayId], [field]: value },
    };
    setLocalHours(updated);
    onChange(updated);
  };

  const handleClosedToggle = (dayId) => {
    const updated = {
      ...localHours,
      [dayId]: { ...localHours[dayId], closed: !localHours[dayId]?.closed },
    };
    setLocalHours(updated);
    onChange(updated);
  };

  return (
    <div>
      {/* Hours Not Set Toggle */}
      <div style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        padding: "16px 20px",
        background: COLORS.surfaceLight,
        borderRadius: 12,
        border: `1px solid ${COLORS.border}`,
        marginBottom: 24,
      }}>
        <div>
          <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading }}>
            {showHours ? "Show Operating Hours" : "Hours Not Set"}
          </div>
          <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 4 }}>
            {showHours
              ? "Operating hours are displayed in the app"
              : "Hours will not be shown to players in the app"}
          </div>
        </div>
        <button
          onClick={handleToggleHoursNotSet}
          style={{
            width: 56, height: 32, borderRadius: 16, border: "none", cursor: "pointer",
            background: showHours ? COLORS.success : COLORS.border,
            position: "relative", transition: "background .2s",
          }}
        >
          <span style={{
            position: "absolute", top: 4, left: showHours ? 28 : 4,
            width: 24, height: 24, borderRadius: 12, background: COLORS.heading,
            transition: "left .2s",
          }} />
        </button>
      </div>

      {showHours && (
        <>
          <div style={{ marginBottom: 20 }}>
            <p style={{ fontSize: 14, color: COLORS.textMuted, lineHeight: 1.6 }}>
              Set operating hours for each day. Leave times empty for 24-hour operation.
              {isDirty && <span style={{ color: COLORS.orange, marginLeft: 8 }}>* Modified</span>}
            </p>
          </div>

          {DAYS_OF_WEEK.map((day) => {
            const dayData = localHours[day.id] || { open: "", close: "", closed: false };
            const isClosed = dayData.closed;

            return (
              <div
                key={day.id}
                style={{
                  display: "grid",
                  gridTemplateColumns: "100px 1fr 1fr 80px",
                  gap: 12,
                  alignItems: "center",
                  marginBottom: 12,
                  padding: "12px 16px",
                  background: COLORS.surfaceLight,
                  borderRadius: 10,
                  opacity: isClosed ? 0.6 : 1,
                }}
              >
                <span style={{ fontSize: 15, fontWeight: 600, color: COLORS.heading }}>
                  {day.label}
                </span>

                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={{ fontSize: 13, color: COLORS.textMuted }}>Open</span>
                  <input
                    type="time"
                    value={dayData.open || ""}
                    onChange={(e) => handleDayChange(day.id, "open", e.target.value)}
                    disabled={isClosed}
                    style={{
                      padding: "8px 12px",
                      fontSize: 15,
                      border: `1px solid ${COLORS.border}`,
                      borderRadius: 6,
                      background: isClosed ? COLORS.surface : "#1E1E1E",
                      color: COLORS.heading,
                      fontFamily: "inherit",
                    }}
                  />
                </div>

                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={{ fontSize: 13, color: COLORS.textMuted }}>Close</span>
                  <input
                    type="time"
                    value={dayData.close || ""}
                    onChange={(e) => handleDayChange(day.id, "close", e.target.value)}
                    disabled={isClosed}
                    style={{
                      padding: "8px 12px",
                      fontSize: 15,
                      border: `1px solid ${COLORS.border}`,
                      borderRadius: 6,
                      background: isClosed ? COLORS.surface : "#1E1E1E",
                      color: COLORS.heading,
                      fontFamily: "inherit",
                    }}
                  />
                </div>

                <button
                  onClick={() => handleClosedToggle(day.id)}
                  style={{
                    padding: "8px 12px",
                    fontSize: 13,
                    fontWeight: 600,
                    border: "none",
                    borderRadius: 6,
                    cursor: "pointer",
                    background: isClosed ? COLORS.error : COLORS.successBg,
                    color: isClosed ? "#fff" : COLORS.success,
                  }}
                >
                  {isClosed ? "Closed" : "Open"}
                </button>
              </div>
            );
          })}
        </>
      )}
    </div>
  );
}

// ── TEAM MANAGER ─────────────────────────────────────────────────────────────
function TeamManager({ locationId, operatorRole, token, onToast }) {
  const [operators, setOperators] = useState([]);
  const [loading, setLoading] = useState(true);
  const [showAddForm, setShowAddForm] = useState(false);
  const [newOperator, setNewOperator] = useState({
    first_name: "", last_name: "", email: "", phone: "", role: "attendant", pin: "",
  });
  const [editingOp, setEditingOp] = useState(null);
  const [editForm, setEditForm] = useState({ first_name: "", last_name: "", email: "", phone: "", role: "", pin: "" });

  const roleLevel = ROLE_LEVELS[operatorRole] || 0;

  const fetchOperators = async () => {
    setLoading(true);
    try {
      const res = await fetch(`${SUPABASE_URL}/functions/v1/operator-manage`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
          "x-operator-token": token,
        },
        body: JSON.stringify({ action: "list" }),
      });
      const data = await res.json();
      if (data.operators) {
        setOperators(data.operators);
      } else if (data.error) {
        onToast(data.error, "error");
      }
    } catch (err) {
      console.error("Fetch operators error:", err);
      onToast("Failed to load team members", "error");
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchOperators();
  }, []);

  const handleCreateOperator = async () => {
    if (!newOperator.first_name || !newOperator.last_name || !newOperator.pin) {
      onToast("First name, last name, and PIN are required", "error");
      return;
    }

    try {
      const res = await fetch(`${SUPABASE_URL}/functions/v1/operator-manage`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
          "x-operator-token": token,
        },
        body: JSON.stringify({ action: "create", data: newOperator }),
      });
      const data = await res.json();
      if (data.success) {
        onToast(`Created ${newOperator.first_name} ${newOperator.last_name}. Card: ${data.operator.card_number}`, "success");
        setShowAddForm(false);
        setNewOperator({ first_name: "", last_name: "", email: "", phone: "", role: "attendant", pin: "" });
        fetchOperators();
      } else {
        onToast(data.error || "Failed to create operator", "error");
      }
    } catch (err) {
      console.error("Create operator error:", err);
      onToast("Failed to create operator", "error");
    }
  };

  const handleUpdateStatus = async (opId, newStatus) => {
    try {
      const res = await fetch(`${SUPABASE_URL}/functions/v1/operator-manage`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
          "x-operator-token": token,
        },
        body: JSON.stringify({ action: "update", data: { operator_id: opId, status: newStatus } }),
      });
      const data = await res.json();
      if (data.success) {
        onToast(`Operator ${newStatus}`, "success");
        fetchOperators();
      } else {
        onToast(data.error || "Failed to update operator", "error");
      }
    } catch (err) {
      console.error("Update operator error:", err);
      onToast("Failed to update operator", "error");
    }
  };

  const openEdit = (op) => {
    setEditingOp(op);
    setEditForm({ first_name: op.first_name, last_name: op.last_name, email: op.email || "", phone: op.phone || "", role: op.role, pin: "" });
    setShowAddForm(false);
  };

  const handleEditOperator = async () => {
    if (!editForm.first_name || !editForm.last_name) {
      onToast("First and last name are required", "error");
      return;
    }
    try {
      const res = await fetch(`${SUPABASE_URL}/functions/v1/operator-manage`, {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: `Bearer ${ANON_KEY}`, "x-operator-token": token },
        body: JSON.stringify({
          action: "update",
          data: {
            operator_id: editingOp.id,
            first_name: editForm.first_name,
            last_name: editForm.last_name,
            email: editForm.email,
            phone: editForm.phone,
            role: editForm.role !== editingOp.role ? editForm.role : undefined,
            pin: editForm.pin || undefined,
          },
        }),
      });
      const data = await res.json();
      if (data.success) {
        onToast(`${editForm.first_name} ${editForm.last_name} updated`, "success");
        setEditingOp(null);
        fetchOperators();
      } else {
        onToast(data.error || "Failed to update operator", "error");
      }
    } catch (err) {
      onToast("Failed to update operator", "error");
    }
  };

  // Available roles for creation (can only create roles below own level)
  // super_admin is never available in the dropdown (cannot be created)
  // admin is only available to super_admin
  const availableRoles = [
    { value: "attendant", label: "Attendant" },
    ...(roleLevel >= 3 ? [{ value: "manager", label: "Manager" }] : []),
    ...(roleLevel >= 4 ? [{ value: "owner", label: "Owner" }] : []),
    ...(roleLevel >= 5 ? [{ value: "admin", label: "Admin" }] : []),
    // Note: super_admin never appears - cannot be created via UI
  ];

  if (loading) {
    return (
      <div style={{ textAlign: "center", padding: 40, color: COLORS.textMuted }}>
        Loading team...
      </div>
    );
  }

  return (
    <div>
      {/* Edit Operator Modal */}
      {editingOp && (
        <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.7)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div style={{ background: COLORS.surface, borderRadius: 16, padding: 28, width: 480, maxWidth: "90vw", border: `1px solid ${COLORS.border}` }}>
            <h3 style={{ color: COLORS.heading, margin: "0 0 20px", fontSize: 18 }}>Edit Team Member</h3>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              <Input label="First Name" value={editForm.first_name} onChange={(v) => setEditForm({ ...editForm, first_name: v })} required />
              <Input label="Last Name" value={editForm.last_name} onChange={(v) => setEditForm({ ...editForm, last_name: v })} required />
              <Input label="Email" value={editForm.email} onChange={(v) => setEditForm({ ...editForm, email: v })} type="email" />
              <Input label="Phone" value={editForm.phone} onChange={(v) => setEditForm({ ...editForm, phone: fmtPhone(v) })} />
              <Select label="Role" value={editForm.role} onChange={(v) => setEditForm({ ...editForm, role: v })} options={availableRoles} />
              <Input label="New PIN (leave blank to keep)" value={editForm.pin} onChange={(v) => setEditForm({ ...editForm, pin: v.replace(/\D/g, "").slice(0, 6) })} type="password" />
            </div>
            <div style={{ display: "flex", gap: 10, marginTop: 20, justifyContent: "flex-end" }}>
              <Btn onClick={() => setEditingOp(null)} size="sm" variant="ghost">Cancel</Btn>
              <Btn onClick={handleEditOperator} size="sm">Save Changes</Btn>
            </div>
          </div>
        </div>
      )}

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 20 }}>
        <p style={{ fontSize: 14, color: COLORS.textMuted, margin: 0 }}>
          Manage operators at this location. You can add or suspend team members based on your role.
        </p>
        <Btn onClick={() => { setShowAddForm(!showAddForm); setEditingOp(null); }} size="sm">
          {showAddForm ? "Cancel" : "+ Add Team Member"}
        </Btn>
      </div>

      {showAddForm && (
        <div style={{
          background: COLORS.surfaceLight,
          borderRadius: 12,
          padding: 20,
          marginBottom: 20,
          border: `1px solid ${COLORS.border}`,
        }}>
          <h4 style={{ color: COLORS.heading, margin: "0 0 16px", fontSize: 16 }}>New Team Member</h4>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            <Input label="First Name" value={newOperator.first_name} onChange={(v) => setNewOperator({ ...newOperator, first_name: v })} required />
            <Input label="Last Name" value={newOperator.last_name} onChange={(v) => setNewOperator({ ...newOperator, last_name: v })} required />
            <Input label="Email" value={newOperator.email} onChange={(v) => setNewOperator({ ...newOperator, email: v })} type="email" />
            <Input label="Phone" value={newOperator.phone} onChange={(v) => setNewOperator({ ...newOperator, phone: fmtPhone(v) })} />
            <Select label="Role" value={newOperator.role} onChange={(v) => setNewOperator({ ...newOperator, role: v })} options={availableRoles} />
            <Input label="PIN (4+ digits)" value={newOperator.pin} onChange={(v) => setNewOperator({ ...newOperator, pin: v.replace(/\D/g, "").slice(0, 6) })} type="password" required />
          </div>
          <div style={{ marginTop: 16, textAlign: "right" }}>
            <Btn onClick={handleCreateOperator} size="sm">Create Operator</Btn>
          </div>
        </div>
      )}

      <div style={{ border: `1px solid ${COLORS.border}`, borderRadius: 12, overflow: "hidden" }}>
        <table style={{ width: "100%", borderCollapse: "collapse" }}>
          <thead>
            <tr style={{ background: COLORS.surfaceLight }}>
              <th style={{ padding: "12px 16px", textAlign: "left", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, textTransform: "uppercase" }}>Name</th>
              <th style={{ padding: "12px 16px", textAlign: "left", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, textTransform: "uppercase" }}>Role</th>
              <th style={{ padding: "12px 16px", textAlign: "left", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, textTransform: "uppercase" }}>Card</th>
              <th style={{ padding: "12px 16px", textAlign: "left", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, textTransform: "uppercase" }}>Status</th>
              <th style={{ padding: "12px 16px", textAlign: "right", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, textTransform: "uppercase" }}>Actions</th>
            </tr>
          </thead>
          <tbody>
            {operators.map((op) => (
              <tr key={op.id} style={{ borderTop: `1px solid ${COLORS.border}` }}>
                <td style={{ padding: "12px 16px" }}>
                  <div style={{ fontWeight: 600, color: COLORS.heading }}>{op.first_name} {op.last_name}</div>
                  {op.email && <div style={{ fontSize: 12, color: COLORS.textMuted }}>{op.email}</div>}
                </td>
                <td style={{ padding: "12px 16px" }}>
                  <span style={{
                    padding: "4px 10px",
                    borderRadius: 6,
                    fontSize: 12,
                    fontWeight: 600,
                    background: op.role === "owner" ? COLORS.orangeGlow : op.role === "manager" ? COLORS.blueBg : COLORS.surfaceLight,
                    color: op.role === "owner" ? COLORS.orange : op.role === "manager" ? COLORS.blue : COLORS.text,
                  }}>
                    {op.role}
                  </span>
                </td>
                <td style={{ padding: "12px 16px", fontFamily: "'Space Mono', monospace", fontSize: 13, color: COLORS.text }}>
                  {op.card_number || "-"}
                </td>
                <td style={{ padding: "12px 16px" }}>
                  <span style={{
                    padding: "4px 10px",
                    borderRadius: 6,
                    fontSize: 12,
                    fontWeight: 600,
                    background: op.status === "active" ? COLORS.successBg : op.status === "suspended" ? COLORS.warningBg : COLORS.errorBg,
                    color: op.status === "active" ? COLORS.success : op.status === "suspended" ? COLORS.warning : COLORS.error,
                  }}>
                    {op.status}
                  </span>
                </td>
                <td style={{ padding: "12px 16px", textAlign: "right" }}>
                  {op.can_manage && (
                    <>
                      <button
                        onClick={() => openEdit(op)}
                        style={{
                          padding: "6px 12px", fontSize: 12, fontWeight: 600,
                          border: `1px solid ${COLORS.border}`, borderRadius: 6,
                          cursor: "pointer", background: "transparent", color: COLORS.text,
                        }}
                      >
                        Edit
                      </button>
                      {op.status === "active" && (
                        <button
                          onClick={() => handleUpdateStatus(op.id, "suspended")}
                          style={{
                            padding: "6px 12px",
                            fontSize: 12,
                            fontWeight: 600,
                            border: `1px solid ${COLORS.warning}`,
                            borderRadius: 6,
                            cursor: "pointer",
                            background: "transparent",
                            color: COLORS.warning,
                            marginLeft: 8,
                          }}
                        >
                          Suspend
                        </button>
                      )}
                      {op.status === "suspended" && (
                        <button
                          onClick={() => handleUpdateStatus(op.id, "active")}
                          style={{
                            padding: "6px 12px",
                            fontSize: 12,
                            fontWeight: 600,
                            border: `1px solid ${COLORS.success}`,
                            borderRadius: 6,
                            cursor: "pointer",
                            background: "transparent",
                            color: COLORS.success,
                            marginLeft: 8,
                          }}
                        >
                          Reactivate
                        </button>
                      )}
                    </>
                  )}
                </td>
              </tr>
            ))}
            {operators.length === 0 && (
              <tr>
                <td colSpan={5} style={{ padding: 24, textAlign: "center", color: COLORS.textMuted }}>
                  No team members found
                </td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ── PROMOS MANAGER ───────────────────────────────────────────────────────────
function PromosManager({ locationId, promosEnabled, onTogglePromos, token, onToast }) {
  const [promos, setPromos] = useState([]);
  const [loading, setLoading] = useState(false);
  const [budget, setBudget] = useState(null);
  const [showIssueForm, setShowIssueForm] = useState(false);
  const [lookup, setLookup] = useState("");
  const [lookupResult, setLookupResult] = useState(null);
  const [lookupLoading, setLookupLoading] = useState(false);
  const [promoType, setPromoType] = useState("free_withdrawal");
  const [expiresInDays, setExpiresInDays] = useState("30");
  const [promoDescription, setPromoDescription] = useState("");
  const [issuing, setIssuing] = useState(false);

  const isEnabled = promosEnabled === true || promosEnabled === "true";

  const promoApi = async (action, method = "GET", body = null) => {
    const url = `${SUPABASE_URL}/functions/v1/location-promos?action=${action}`;
    const res = await fetch(url, {
      method,
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${ANON_KEY}`,
        "x-technician-token": token,
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const data = await res.json();
    if (!res.ok) throw data;
    return data;
  };

  const loadPromos = async () => {
    setLoading(true);
    try {
      const [listRes, budgetRes] = await Promise.all([
        promoApi("list"),
        promoApi("budget"),
      ]);
      setPromos(listRes.promos ?? []);
      setBudget(budgetRes);
    } catch (e) {
      onToast(e?.error || "Failed to load promos", "error");
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (isEnabled && !showIssueForm) loadPromos();
  }, [isEnabled, showIssueForm]);

  const handleLookup = async () => {
    if (!lookup.trim()) return;
    setLookupLoading(true);
    setLookupResult(null);
    try {
      const isPhone = /\d{7,}/.test(lookup.replace(/\D/g, ""));
      const body = isPhone ? { phone: lookup } : { card_number: lookup.trim().toUpperCase() };
      const res = await promoApi("lookup-player", "POST", body);
      setLookupResult(res);
    } catch (e) {
      onToast(e?.error || "Player not found", "error");
    } finally {
      setLookupLoading(false);
    }
  };

  const handleIssue = async () => {
    if (!lookupResult?.player_id) return;
    setIssuing(true);
    try {
      await promoApi("issue", "POST", {
        player_id: lookupResult.player_id,
        type: promoType,
        expires_in_days: parseInt(expiresInDays) || 30,
        description: promoDescription.trim() || undefined,
      });
      onToast(`Promo issued to ${lookupResult.player_name}`, "success");
      setShowIssueForm(false);
      setLookup("");
      setLookupResult(null);
      setPromoDescription("");
    } catch (e) {
      if (e?.error?.includes("budget")) {
        onToast(`Budget exceeded — $${(e.budget_remaining ?? 0).toFixed(2)} remaining this month`, "error");
      } else {
        onToast(e?.error || "Failed to issue promo", "error");
      }
    } finally {
      setIssuing(false);
    }
  };

  const handleCancelPromo = async (promoId) => {
    try {
      await promoApi(`cancel&id=${promoId}`, "POST");
      onToast("Promo cancelled", "success");
      loadPromos();
    } catch (e) {
      onToast(e?.error || "Failed to cancel promo", "error");
    }
  };

  const statusColor = (s) => s === "available" ? COLORS.success : s === "used" ? COLORS.textMuted : COLORS.error;
  const typeLabel = (t) => ({ free_withdrawal: "Free Withdrawal", fee_discount_pct: "% Discount", fee_discount_flat: "$ Discount" }[t] || t);

  return (
    <div>
      {/* Enable/disable toggle */}
      <div style={{ marginBottom: 24 }}>
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          padding: "16px 20px", background: COLORS.surfaceLight,
          borderRadius: 12, border: `1px solid ${COLORS.border}`,
        }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading }}>Enable Promotions at This Location</div>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 4 }}>When enabled, players can redeem promotional offers here</div>
          </div>
          <button
            onClick={() => onTogglePromos(!isEnabled)}
            style={{
              width: 56, height: 32, borderRadius: 16, border: "none", cursor: "pointer",
              background: isEnabled ? COLORS.success : COLORS.border,
              position: "relative", transition: "background .2s",
            }}
          >
            <span style={{
              position: "absolute", top: 4, left: isEnabled ? 28 : 4,
              width: 24, height: 24, borderRadius: 12, background: COLORS.heading,
              transition: "left .2s",
            }} />
          </button>
        </div>
      </div>

      {isEnabled && !showIssueForm && (
        <>
          {/* Budget summary */}
          {budget && (
            <div style={{
              padding: "14px 18px", background: COLORS.surfaceLight,
              border: `1px solid ${COLORS.border}`, borderRadius: 10, marginBottom: 20,
              display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12,
            }}>
              {[
                { label: "Monthly Budget", val: budget.monthly_budget, color: COLORS.heading },
                { label: "Used This Month", val: budget.used_budget, color: COLORS.warning },
                { label: "Remaining", val: budget.remaining_budget, color: parseFloat(budget.remaining_budget) > 0 ? COLORS.success : COLORS.error },
              ].map(({ label, val, color }) => (
                <div key={label}>
                  <div style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".3px" }}>{label}</div>
                  <div style={{ fontSize: 18, fontWeight: 700, color, marginTop: 2 }}>${parseFloat(val || 0).toFixed(2)}</div>
                </div>
              ))}
            </div>
          )}

          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
            <h4 style={{ color: COLORS.heading, margin: 0, fontSize: 16 }}>Location Promos Issued</h4>
            <Btn size="sm" onClick={() => setShowIssueForm(true)}>+ Issue Promo</Btn>
          </div>

          {loading ? (
            <div style={{ textAlign: "center", padding: 32, color: COLORS.textMuted }}>Loading...</div>
          ) : promos.length === 0 ? (
            <div style={{ border: `1px solid ${COLORS.border}`, borderRadius: 12, padding: 32, textAlign: "center", color: COLORS.textMuted }}>
              <div style={{ fontSize: 13 }}>No promos issued yet. Use "+ Issue Promo" to give a player a reward.</div>
            </div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {promos.map((p) => (
                <div key={p.id} style={{
                  padding: "12px 16px", background: COLORS.surfaceLight,
                  border: `1px solid ${COLORS.border}`, borderRadius: 10,
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 3 }}>
                      <span style={{ fontSize: 13, fontWeight: 600, color: COLORS.heading }}>{p.player_name}</span>
                      {p.player_phone_last4 && <span style={{ fontSize: 11, color: COLORS.textMuted }}>···{p.player_phone_last4}</span>}
                      <span style={{ fontSize: 11, padding: "2px 7px", borderRadius: 10, background: `${statusColor(p.status)}22`, color: statusColor(p.status), fontWeight: 600, textTransform: "uppercase" }}>
                        {p.status}
                      </span>
                    </div>
                    <div style={{ fontSize: 12, color: COLORS.textMuted }}>
                      {typeLabel(p.type)} · {p.description || "—"}
                      {p.expires_at && <> · Expires {new Date(p.expires_at).toLocaleDateString()}</>}
                    </div>
                  </div>
                  {p.status === "available" && (
                    <button
                      onClick={() => handleCancelPromo(p.id)}
                      style={{ fontSize: 11, color: COLORS.error, background: "none", border: "none", cursor: "pointer", marginLeft: 12 }}
                    >
                      Cancel
                    </button>
                  )}
                </div>
              ))}
            </div>
          )}
        </>
      )}

      {/* Issue promo form */}
      {isEnabled && showIssueForm && (
        <div>
          <div style={{ display: "flex", alignItems: "center", marginBottom: 20, gap: 12 }}>
            <button onClick={() => { setShowIssueForm(false); setLookupResult(null); setLookup(""); }}
              style={{ background: "none", border: "none", color: COLORS.textMuted, cursor: "pointer", fontSize: 18, padding: 0 }}>
              ←
            </button>
            <h4 style={{ color: COLORS.heading, margin: 0, fontSize: 16 }}>Issue Promotional Credit</h4>
          </div>

          <div style={{ marginBottom: 16 }}>
            <label style={{ display: "block", fontSize: 13, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>
              Player Phone or Card Number
            </label>
            <div style={{ display: "flex", gap: 8 }}>
              <input
                value={lookup}
                onChange={(e) => { setLookup(e.target.value); setLookupResult(null); }}
                placeholder="(555) 555-5555 or WSH-XXXXX"
                style={{
                  flex: 1, padding: "12px 14px", fontSize: 16, borderRadius: 8, outline: "none",
                  border: `2px solid ${COLORS.border}`, background: COLORS.inputBg, color: COLORS.heading, fontFamily: "inherit",
                }}
                onKeyDown={(e) => e.key === "Enter" && handleLookup()}
              />
              <Btn onClick={handleLookup} disabled={!lookup.trim() || lookupLoading} size="md">
                {lookupLoading ? "..." : "Find"}
              </Btn>
            </div>
          </div>

          {lookupResult && (
            <>
              <div style={{ marginBottom: 20, padding: "14px 16px", background: `${COLORS.success}11`, border: `1px solid ${COLORS.success}44`, borderRadius: 10 }}>
                <div style={{ fontSize: 15, fontWeight: 600, color: COLORS.success }}>{lookupResult.player_name}</div>
                <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 2 }}>···{lookupResult.player_phone_last4}</div>
                {lookupResult.existing_promo_count > 0 && (
                  <div style={{ fontSize: 12, color: COLORS.warning, marginTop: 6 }}>
                    This player already has {lookupResult.existing_promo_count} active promo(s) at this location.
                  </div>
                )}
              </div>

              <div style={{ marginBottom: 16 }}>
                <label style={{ display: "block", fontSize: 13, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>Promo Type</label>
                <select value={promoType} onChange={(e) => setPromoType(e.target.value)}
                  style={{ width: "100%", padding: "12px 14px", fontSize: 16, borderRadius: 8, border: `2px solid ${COLORS.border}`, background: COLORS.inputBg, color: COLORS.heading, fontFamily: "inherit" }}>
                  <option value="free_withdrawal">Free Withdrawal (full fee waived)</option>
                  <option value="fee_discount_pct">% Fee Discount</option>
                  <option value="fee_discount_flat">$ Flat Fee Discount</option>
                </select>
              </div>

              <div style={{ marginBottom: 16 }}>
                <label style={{ display: "block", fontSize: 13, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>Expires In</label>
                <select value={expiresInDays} onChange={(e) => setExpiresInDays(e.target.value)}
                  style={{ width: "100%", padding: "12px 14px", fontSize: 16, borderRadius: 8, border: `2px solid ${COLORS.border}`, background: COLORS.inputBg, color: COLORS.heading, fontFamily: "inherit" }}>
                  <option value="7">7 days</option>
                  <option value="14">14 days</option>
                  <option value="30">30 days</option>
                  <option value="60">60 days</option>
                  <option value="90">90 days</option>
                </select>
              </div>

              <div style={{ marginBottom: 20 }}>
                <label style={{ display: "block", fontSize: 13, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>Description (optional)</label>
                <input
                  value={promoDescription}
                  onChange={(e) => setPromoDescription(e.target.value)}
                  placeholder="e.g., VIP player appreciation"
                  maxLength={120}
                  style={{
                    width: "100%", padding: "12px 14px", fontSize: 16, borderRadius: 8,
                    border: `2px solid ${COLORS.border}`, background: COLORS.inputBg, color: COLORS.heading,
                    fontFamily: "inherit", boxSizing: "border-box",
                  }}
                />
              </div>

              {budget && budget.monthly_budget > 0 && (
                <div style={{ marginBottom: 16, fontSize: 13, color: COLORS.textMuted }}>
                  Budget remaining: <strong style={{ color: parseFloat(budget.remaining_budget) > 0 ? COLORS.success : COLORS.error }}>
                    ${parseFloat(budget.remaining_budget || 0).toFixed(2)}
                  </strong>
                  {budget.platform_amount_per_promo > 0 && ` · WinStash cost: $${parseFloat(budget.platform_amount_per_promo).toFixed(2)} per free withdrawal`}
                </div>
              )}

              <div style={{ display: "flex", gap: 12 }}>
                <Btn variant="secondary" size="md" style={{ flex: 1 }} onClick={() => { setLookupResult(null); setLookup(""); }}>Back</Btn>
                <Btn size="md" style={{ flex: 2 }} onClick={handleIssue} disabled={issuing}>
                  {issuing ? "Issuing..." : `Issue Promo to ${lookupResult.player_name.split(" ")[0]}`}
                </Btn>
              </div>
            </>
          )}
        </div>
      )}
    </div>
  );
}

const formatPhoneNumber = (value) => {
  if (!value) return "";
  const digits = String(value).replace(/\D/g, "");
  if (digits.length === 0) return "";
  if (digits.length <= 3) return `(${digits}`;
  if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
  return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
};

// ============================================================================
// CASH OUT PLAYER SCREEN (Manager+)
// ============================================================================
// Multi-step POS-initiated cash withdrawal:
//   search → amount → confirm → pin → result
// ============================================================================
function WithdrawalScreen({ session, onBack, onToast }) {
  const locationId = session.location?.id;
  const [step, setStep] = useState("search");
  const [searchName, setSearchName] = useState("");
  const [searching, setSearching] = useState(false);
  const [players, setPlayers] = useState([]);
  const [locationInfo, setLocationInfo] = useState(null); // { withdrawal_fee, max_single_withdrawal }
  const [selectedPlayer, setSelectedPlayer] = useState(null);
  const [amount, setAmount] = useState("");
  const [pin, setPin] = useState("");
  const [executing, setExecuting] = useState(false);
  const [txResult, setTxResult] = useState(null); // success result or { error }

  const withdrawalFee = locationInfo?.withdrawal_fee ?? 0;
  const amtNum = parseFloat(amount) || 0;
  const totalDeducted = amtNum + withdrawalFee;
  const newBalance = (selectedPlayer?.balance ?? 0) - totalDeducted;
  const maxSingle = locationInfo?.max_single_withdrawal;
  const amountValid = amtNum >= 1
    && newBalance >= 0
    && (!maxSingle || amtNum <= maxSingle);

  const posApi = async (path, body) => {
    const res = await fetch(`${SUPABASE_URL}/functions/v1/pos-withdrawal/${path}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${ANON_KEY}`,
        "x-operator-token": session.token,
      },
      body: JSON.stringify(body),
    });
    return res.json();
  };

  const handleSearch = async () => {
    if (searchName.trim().length < 2) return onToast("Enter at least 2 characters", "error");
    setSearching(true);
    setPlayers([]);
    try {
      const data = await posApi("search", { name: searchName.trim() });
      if (data.error) { onToast(data.error, "error"); return; }
      setPlayers(data.players ?? []);
      setLocationInfo(data.location);
      if ((data.players ?? []).length === 0) onToast("No players found at this location", "info");
    } catch { onToast("Network error", "error"); }
    finally { setSearching(false); }
  };

  const handleSelectPlayer = (p) => {
    setSelectedPlayer(p);
    setAmount("");
    setStep("amount");
  };

  const handleExecute = async () => {
    if (pin.length < 4) return;
    setExecuting(true);
    try {
      const data = await posApi("execute", {
        wallet_id: selectedPlayer.wallet_id,
        player_id: selectedPlayer.player_id,
        amount: amtNum,
        pin,
      });
      if (data.error === "pin_incorrect") {
        setPin("");
        onToast("Incorrect PIN — please try again", "error");
        return;
      }
      if (data.error) {
        const detail = [data.detail, data.hint].filter(Boolean).join(" | ");
        onToast(detail ? `${data.error}: ${detail}` : data.error, "error");
        setStep("amount");
        return;
      }
      setTxResult(data);
      setStep("result");
    } catch { onToast("Network error", "error"); }
    finally { setExecuting(false); }
  };

  const handlePinKey = (key) => {
    if (executing) return;
    if (key === "back") { setPin((p) => p.slice(0, -1)); return; }
    if (key === "submit") { handleExecute(); return; }
    if (pin.length < 4) setPin((p) => p + key);
  };

  const reset = () => {
    setStep("search"); setSearchName(""); setPlayers([]);
    setSelectedPlayer(null); setAmount(""); setPin(""); setTxResult(null);
  };

  const fmtD = (n) => `$${Number(n || 0).toFixed(2)}`;
  const card = (children, style = {}) => (
    <div style={{ background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 16, padding: 24, marginBottom: 20, ...style }}>
      {children}
    </div>
  );

  return (
    <div style={{ padding: "32px 24px", maxWidth: 560, margin: "0 auto" }}>

      {/* ── STEP: search ─────────────────────────────────────────── */}
      {step === "search" && (
        <>
          <h2 style={{ fontSize: 24, fontWeight: 700, color: COLORS.heading, marginBottom: 6 }}>Cash Out Player</h2>
          <p style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 28 }}>Search by first or last name</p>

          {card(
            <>
              <label style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", display: "block", marginBottom: 6 }}>
                Player Name
              </label>
              <div style={{ display: "flex", gap: 10 }}>
                <input
                  type="text"
                  value={searchName}
                  onChange={(e) => setSearchName(e.target.value)}
                  onKeyDown={(e) => e.key === "Enter" && handleSearch()}
                  placeholder="First or last name..."
                  autoFocus
                  style={{ flex: 1, background: COLORS.inputBg, border: `1px solid ${COLORS.border}`, borderRadius: 10, padding: "10px 14px", fontSize: 16, color: COLORS.text, outline: "none" }}
                />
                <Btn onClick={handleSearch} disabled={searching} size="md">
                  {searching ? "…" : "Search"}
                </Btn>
              </div>
            </>
          )}

          {players.length > 0 && (
            <div>
              <div style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 10 }}>
                {players.length} result{players.length !== 1 ? "s" : ""}
              </div>
              {players.map((p) => {
                const noWallet = p.wallet_id === null;
                const blocked = p.withdrawal_blocked;
                const inactive = !noWallet && p.wallet_status !== "active";
                const disabled = noWallet || blocked || inactive;
                return (
                  <button
                    key={p.player_id}
                    onClick={() => !disabled && handleSelectPlayer(p)}
                    disabled={disabled}
                    style={{
                      width: "100%", textAlign: "left", background: COLORS.surface,
                      border: `1px solid ${COLORS.border}`, borderRadius: 12, padding: "14px 18px",
                      cursor: disabled ? "not-allowed" : "pointer",
                      marginBottom: 10, fontFamily: "inherit",
                      opacity: disabled ? 0.5 : 1,
                      display: "flex", alignItems: "center", justifyContent: "space-between",
                    }}
                    onMouseEnter={(e) => { if (!disabled) e.currentTarget.style.borderColor = COLORS.orange; }}
                    onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
                  >
                    <div>
                      <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading, marginBottom: 2 }}>{p.name}</div>
                      <div style={{ fontSize: 13, color: COLORS.textMuted }}>{p.phone}</div>
                      {noWallet && (
                        <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 3 }}>No account at this location</div>
                      )}
                      {blocked && (
                        <div style={{ fontSize: 12, color: COLORS.error, marginTop: 3 }}>
                          ⚠ Supervisor hold active
                        </div>
                      )}
                      {inactive && !blocked && (
                        <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 3 }}>Wallet {p.wallet_status}</div>
                      )}
                    </div>
                    <div style={{ textAlign: "right" }}>
                      {noWallet ? (
                        <div style={{ fontSize: 13, color: COLORS.textMuted }}>—</div>
                      ) : (
                        <>
                          <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.orange }}>{fmtD(p.balance)}</div>
                          <div style={{ fontSize: 11, color: COLORS.textMuted }}>balance</div>
                        </>
                      )}
                    </div>
                  </button>
                );
              })}
            </div>
          )}
        </>
      )}

      {/* ── STEP: amount ─────────────────────────────────────────── */}
      {step === "amount" && selectedPlayer && (
        <>
          <button onClick={() => setStep("search")} style={{ background: "none", border: "none", color: COLORS.orange, cursor: "pointer", fontSize: 13, padding: 0, marginBottom: 20, fontFamily: "inherit" }}>
            ← Back to search
          </button>

          {card(
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
              <div>
                <div style={{ fontSize: 20, fontWeight: 700, color: COLORS.heading }}>{selectedPlayer.name}</div>
                <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 2 }}>{selectedPlayer.phone}</div>
              </div>
              <div style={{ textAlign: "right" }}>
                <div style={{ fontSize: 32, fontWeight: 800, color: COLORS.orange }}>{fmtD(selectedPlayer.balance)}</div>
                <div style={{ fontSize: 12, color: COLORS.textMuted }}>available balance</div>
              </div>
            </div>
          )}

          {card(
            <>
              <label style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", display: "block", marginBottom: 6 }}>
                Cash Amount
              </label>
              <div style={{ position: "relative" }}>
                <span style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", fontSize: 20, color: COLORS.textMuted, fontWeight: 600 }}>$</span>
                <input
                  type="number"
                  value={amount}
                  onChange={(e) => setAmount(e.target.value)}
                  placeholder="0.00"
                  min="1"
                  step="0.01"
                  autoFocus
                  style={{ width: "100%", background: COLORS.inputBg, border: `1px solid ${COLORS.border}`, borderRadius: 12, padding: "14px 14px 14px 32px", fontSize: 24, fontWeight: 700, color: COLORS.heading, outline: "none", boxSizing: "border-box", WebkitAppearance: "none", MozAppearance: "textfield" }}
                />
              </div>

              {amtNum > 0 && (
                <div style={{ marginTop: 16, padding: "12px 16px", background: COLORS.surfaceLight, borderRadius: 10 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, color: COLORS.textMuted, marginBottom: 4 }}>
                    <span>Withdrawal fee</span><span>{fmtD(withdrawalFee)}</span>
                  </div>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, color: COLORS.textMuted, marginBottom: 4 }}>
                    <span>Total deducted</span><span>{fmtD(totalDeducted)}</span>
                  </div>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 15, fontWeight: 700, color: newBalance < 0 ? COLORS.error : COLORS.text, borderTop: `1px solid ${COLORS.border}`, paddingTop: 8, marginTop: 4 }}>
                    <span>Remaining balance</span><span>{fmtD(Math.max(0, newBalance))}</span>
                  </div>
                  {newBalance < 0 && (
                    <div style={{ marginTop: 8, fontSize: 12, color: COLORS.error }}>Insufficient balance</div>
                  )}
                  {maxSingle && amtNum > maxSingle && (
                    <div style={{ marginTop: 4, fontSize: 12, color: COLORS.error }}>Exceeds single withdrawal limit of {fmtD(maxSingle)}</div>
                  )}
                </div>
              )}
            </>
          )}

          <Btn
            onClick={() => setStep("confirm")}
            disabled={!amountValid}
            wide
            size="md"
          >
            Continue → Confirm
          </Btn>
        </>
      )}

      {/* ── STEP: confirm ────────────────────────────────────────── */}
      {step === "confirm" && selectedPlayer && (
        <>
          <button onClick={() => setStep("amount")} style={{ background: "none", border: "none", color: COLORS.orange, cursor: "pointer", fontSize: 13, padding: 0, marginBottom: 20, fontFamily: "inherit" }}>
            ← Edit amount
          </button>
          <h2 style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading, marginBottom: 20 }}>Confirm Withdrawal</h2>

          {card(
            <>
              <div style={{ fontSize: 16, fontWeight: 600, color: COLORS.heading, marginBottom: 16 }}>{selectedPlayer.name}</div>
              {[
                ["Cash to player", fmtD(amtNum), COLORS.text],
                ["Withdrawal fee", fmtD(withdrawalFee), COLORS.textMuted],
                ["Total deducted", fmtD(totalDeducted), COLORS.text],
                ["Current balance", fmtD(selectedPlayer.balance), COLORS.textMuted],
                ["Balance after", fmtD(newBalance), newBalance < 10 ? COLORS.warning : COLORS.success],
              ].map(([label, value, color]) => (
                <div key={label} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 0", borderBottom: `1px solid ${COLORS.border}` }}>
                  <span style={{ fontSize: 14, color: COLORS.textMuted }}>{label}</span>
                  <span style={{ fontSize: 16, fontWeight: 600, color }}>{value}</span>
                </div>
              ))}
            </>
          )}

          <div style={{ background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`, borderRadius: 12, padding: "12px 16px", marginBottom: 20, fontSize: 13, color: COLORS.textMuted }}>
            After confirming, hand device to player to enter their PIN.
          </div>

          <Btn onClick={() => setStep("pin")} wide size="md">
            Confirm — {fmtD(amtNum)}
          </Btn>
          <Btn variant="secondary" onClick={() => setStep("search")} wide size="md" style={{ marginTop: 10 }}>
            Cancel
          </Btn>
        </>
      )}

      {/* ── STEP: pin ────────────────────────────────────────────── */}
      {step === "pin" && (
        <>
          <div style={{ textAlign: "center", marginBottom: 32 }}>
            <div style={{ fontSize: 18, fontWeight: 600, color: COLORS.heading, marginBottom: 6 }}>
              Player — Enter Your PIN
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted }}>Withdrawing {fmtD(amtNum)} for {selectedPlayer?.name?.split(" ")[0]}</div>
          </div>

          {/* PIN dots */}
          <div style={{ display: "flex", justifyContent: "center", gap: 20, marginBottom: 40 }}>
            {[0, 1, 2, 3].map((i) => (
              <div key={i} style={{
                width: 20, height: 20, borderRadius: "50%",
                background: i < pin.length ? COLORS.orange : "transparent",
                border: `2px solid ${i < pin.length ? COLORS.orange : COLORS.border}`,
                transition: "all .15s",
              }} />
            ))}
          </div>

          {/* PIN pad */}
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, maxWidth: 320, margin: "0 auto 24px" }}>
            {["1","2","3","4","5","6","7","8","9","back","0","submit"].map((key) => {
              const isAction = key === "back" || key === "submit";
              const isSubmit = key === "submit";
              const disabled = executing || (isSubmit && pin.length < 4);
              return (
                <button
                  key={key}
                  onClick={() => handlePinKey(key)}
                  disabled={disabled}
                  style={{
                    height: 72, borderRadius: 14, fontSize: isAction ? 18 : 24, fontWeight: 600,
                    fontFamily: "inherit",
                    background: isSubmit ? COLORS.orange : COLORS.surfaceLight,
                    border: `2px solid ${isSubmit ? COLORS.orange : COLORS.border}`,
                    color: isSubmit ? "#fff" : COLORS.heading,
                    cursor: disabled ? "not-allowed" : "pointer",
                    opacity: disabled ? 0.4 : 1,
                    transition: "all .1s",
                  }}
                  onMouseDown={(e) => { if (!disabled) e.currentTarget.style.transform = "scale(.95)"; }}
                  onMouseUp={(e) => { e.currentTarget.style.transform = ""; }}
                >
                  {key === "back" ? "⌫" : key === "submit" ? (executing ? "…" : "✓") : key}
                </button>
              );
            })}
          </div>

          <Btn variant="secondary" onClick={() => { setPin(""); setStep("confirm"); }} wide size="md">
            Cancel
          </Btn>
        </>
      )}

      {/* ── STEP: result ─────────────────────────────────────────── */}
      {step === "result" && txResult && (
        <>
          <div style={{ textAlign: "center", marginBottom: 28 }}>
            <div style={{ fontSize: 64, marginBottom: 12 }}>✓</div>
            <h2 style={{ fontSize: 26, fontWeight: 800, color: COLORS.success, marginBottom: 6 }}>Cashout Complete</h2>
            <div style={{ fontSize: 14, color: COLORS.textMuted }}>Player receipt sent via SMS</div>
          </div>

          {card(
            <>
              {[
                ["Cash paid out", fmtD(txResult.amount_withdrawn)],
                ["Fee charged", fmtD(txResult.fee_charged)],
                ["Total deducted", fmtD(txResult.total_deducted)],
                ["New balance", fmtD(txResult.new_balance)],
              ].map(([label, value]) => (
                <div key={label} style={{ display: "flex", justifyContent: "space-between", padding: "8px 0", borderBottom: `1px solid ${COLORS.border}` }}>
                  <span style={{ fontSize: 14, color: COLORS.textMuted }}>{label}</span>
                  <span style={{ fontSize: 15, fontWeight: 600, color: COLORS.text }}>{value}</span>
                </div>
              ))}
              <div style={{ marginTop: 14, textAlign: "center" }}>
                <div style={{ fontSize: 11, color: COLORS.textMuted, letterSpacing: ".05em", textTransform: "uppercase", marginBottom: 4 }}>Confirmation</div>
                <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading, letterSpacing: ".15em" }}>{txResult.confirmation_code}</div>
              </div>
            </>
          )}

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={reset} variant="secondary" size="md" style={{ flex: 1 }}>
              New Cashout
            </Btn>
            <Btn onClick={onBack} size="md" style={{ flex: 1 }}>
              Done
            </Btn>
          </div>
        </>
      )}
    </div>
  );
}

// ── (stub kept so nothing else breaks) ───────────────────────────────────────
function WithdrawalSimulatorPanel({ session, locationId, onToast }) {
  const fmtD = (n) => `$${Number(n || 0).toFixed(2)}`;

  return (
    <div style={{ padding: "0 4px" }}>
      {/* Warning banner */}
      <div style={{ background: COLORS.warningBg, border: `1px solid ${COLORS.warning}`, borderRadius: 8, padding: "8px 12px", marginBottom: 16, fontSize: 12, color: COLORS.warning }}>
        ⚠ Temporary testing tool — will be removed before production kiosk launch
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 }}>
        <div>
          <label style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", display: "block", marginBottom: 4 }}>Player Phone</label>
          <input
            type="tel"
            value={phone}
            onChange={(e) => setPhone(e.target.value)}
            placeholder="(404) 555-1234"
            style={{ width: "100%", background: COLORS.inputBg, border: `1px solid ${COLORS.border}`, borderRadius: 8, padding: "8px 12px", fontSize: 14, color: COLORS.text, outline: "none", boxSizing: "border-box" }}
          />
        </div>
        <div>
          <label style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", display: "block", marginBottom: 4 }}>Amount ($)</label>
          <input
            type="number"
            value={amount}
            onChange={(e) => setAmount(e.target.value)}
            placeholder="100"
            min="1"
            style={{ width: "100%", background: COLORS.inputBg, border: `1px solid ${COLORS.border}`, borderRadius: 8, padding: "8px 12px", fontSize: 14, color: COLORS.text, outline: "none", boxSizing: "border-box", WebkitAppearance: "none", MozAppearance: "textfield" }}
          />
        </div>
      </div>

      <button
        onClick={handleSimulate}
        disabled={loading}
        style={{ width: "100%", padding: "10px 0", background: COLORS.orange, color: "#fff", border: "none", borderRadius: 8, fontSize: 14, fontWeight: 600, cursor: loading ? "not-allowed" : "pointer", opacity: loading ? 0.6 : 1, marginBottom: 20 }}
      >
        {loading ? "Running simulation…" : "Run Simulation"}
      </button>

      {result && (
        <div>
          {/* PASS / FAIL banner */}
          <div style={{ background: result.would_succeed ? COLORS.successBg : COLORS.errorBg, border: `1px solid ${result.would_succeed ? COLORS.success : COLORS.error}`, borderRadius: 8, padding: "12px 16px", marginBottom: 16, display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{ fontSize: 28 }}>{result.would_succeed ? "✓" : "✗"}</span>
            <div>
              <div style={{ fontSize: 16, fontWeight: 700, color: result.would_succeed ? COLORS.success : COLORS.error }}>
                {result.would_succeed ? "WOULD SUCCEED" : "WOULD FAIL"}
              </div>
              {result.failure_reason && <div style={{ fontSize: 12, color: COLORS.error, marginTop: 2 }}>{result.failure_reason}</div>}
            </div>
          </div>

          {/* Player info */}
          <div style={{ background: COLORS.surfaceLight, borderRadius: 8, padding: "10px 14px", marginBottom: 12, fontSize: 13 }}>
            <span style={{ color: COLORS.textMuted }}>Player: </span>
            <span style={{ color: COLORS.text, fontWeight: 600 }}>{result.player?.name}</span>
            <span style={{ color: COLORS.textMuted, marginLeft: 12 }}>Balance: </span>
            <span style={{ color: COLORS.orange, fontWeight: 600 }}>{fmtD(result.player?.balance)}</span>
          </div>

          {/* Fee breakdown */}
          <div style={{ background: COLORS.surfaceLight, borderRadius: 8, padding: "10px 14px", marginBottom: 12 }}>
            <div style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>Fee Breakdown</div>
            {[
              ["Requested", fmtD(parseFloat(amount))],
              ["Withdrawal Fee", fmtD(result.fee_breakdown?.withdrawal_fee)],
              ["Total Deducted", fmtD(result.fee_breakdown?.total_deducted)],
              ["WinStash Share", fmtD(result.fee_breakdown?.platform_share)],
              ["Master Share", fmtD(result.fee_breakdown?.master_share)],
              ["Location Share", fmtD(result.fee_breakdown?.location_share)],
            ].map(([label, val]) => (
              <div key={label} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 4 }}>
                <span style={{ color: COLORS.textMuted }}>{label}</span>
                <span style={{ color: COLORS.text, fontWeight: 500 }}>{val}</span>
              </div>
            ))}
          </div>

          {/* Daily status */}
          <div style={{ background: COLORS.surfaceLight, borderRadius: 8, padding: "10px 14px", marginBottom: 12 }}>
            <div style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>Daily Status</div>
            {[
              ["Daily Total So Far", fmtD(result.daily_status?.daily_total_so_far)],
              ["Daily Limit", fmtD(result.daily_status?.max_daily_withdrawal)],
              ["Remaining Before", fmtD(result.daily_status?.daily_remaining_before)],
              ["Remaining After", fmtD(result.daily_status?.daily_remaining_after)],
            ].map(([label, val]) => (
              <div key={label} style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 4 }}>
                <span style={{ color: COLORS.textMuted }}>{label}</span>
                <span style={{ color: COLORS.text, fontWeight: 500 }}>{val}</span>
              </div>
            ))}
          </div>

          {/* Validation checks */}
          <div style={{ background: COLORS.surfaceLight, borderRadius: 8, padding: "10px 14px" }}>
            <div style={{ fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>Checks</div>
            {(result.checks ?? []).map((check, i) => (
              <div key={i} style={{ display: "flex", alignItems: "flex-start", gap: 8, marginBottom: 6 }}>
                <span style={{ fontSize: 14, color: check.pass ? COLORS.success : COLORS.error, flexShrink: 0, marginTop: 1 }}>{check.pass ? "✓" : "✗"}</span>
                <div>
                  <div style={{ fontSize: 13, color: COLORS.text }}>{check.label}</div>
                  {check.detail && <div style={{ fontSize: 11, color: COLORS.textMuted }}>{check.detail}</div>}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function LocationSettingsForm({ techSession, onExit, onToast }) {
  const [location, setLocation] = useState(techSession.location);
  const [activeTab, setActiveTab] = useState("basic");
  const [saving, setSaving] = useState(false);
  const [dirty, setDirty] = useState({});
  const [errors, setErrors] = useState({});
  const [timeRemaining, setTimeRemaining] = useState(0);
  const [hardware, setHardware] = useState([]);
  const [posLog, setPosLog] = useState(null);
  const [posLogLoading, setPosLogLoading] = useState(false);
  const [posLogError, setPosLogError] = useState(null);
  const [posLogExpanded, setPosLogExpanded] = useState(null);

  // Fee split state — dollar amounts per transaction
  const initFeeSplit = techSession.location?.fee_split;
  const initWFee = parseFloat(techSession.location?.withdrawal_fee) || 0;
  const initCFee = parseFloat(techSession.location?.physical_card_fee) || 0;
  const fmt2 = (v) => (v && Number(v) > 0) ? Number(v).toFixed(2) : "";

  // Compute initial split values once (shared by feeSplit + inputValues states)
  const _iPA = initFeeSplit?.platform_amount != null
    ? parseFloat(initFeeSplit.platform_amount)
    : (initFeeSplit ? Math.round(initWFee * parseFloat(initFeeSplit.platform_pct) / 100 * 100) / 100 : 2.50);
  const _iMA = initFeeSplit?.master_amount != null ? parseFloat(initFeeSplit.master_amount) : 0;
  const _iLA = initFeeSplit?.platform_amount != null
    ? Math.max(0, Math.round((initWFee - _iPA - _iMA) * 100) / 100)
    : (initFeeSplit ? Math.round(initWFee * parseFloat(initFeeSplit.location_pct) / 100 * 100) / 100 : Math.max(0, initWFee - 2.50));
  const _iCPA = initFeeSplit?.card_platform_amount != null ? parseFloat(initFeeSplit.card_platform_amount) : 0;
  const _iCMA = initFeeSplit?.card_master_amount != null ? parseFloat(initFeeSplit.card_master_amount) : 0;
  const _iCLA = initFeeSplit?.card_platform_amount != null
    ? Math.max(0, Math.round((initCFee - _iCPA - _iCMA) * 100) / 100)
    : initCFee;

  // POS purchase fee split initialization
  const initPPFee = parseFloat(techSession.location?.pos_purchase_fee) || 0;
  const _iPPFA = initFeeSplit?.pos_purchase_platform_amount != null
    ? parseFloat(initFeeSplit.pos_purchase_platform_amount) : 0;
  const _iPPMA = initFeeSplit?.pos_purchase_master_amount != null
    ? parseFloat(initFeeSplit.pos_purchase_master_amount) : 0;
  const _iPPLA = (_iPPFA > 0 || _iPPMA > 0)
    ? Math.max(0, Math.round((initPPFee - _iPPFA - _iPPMA) * 100) / 100)
    : Math.max(0, initPPFee);

  const [feeSplit, setFeeSplit] = useState({
    platform_amount: _iPA, master_amount: _iMA, location_amount: _iLA,
    has_master: initFeeSplit ? (initFeeSplit.master_org_id != null) : false,
    card_platform_amount: _iCPA, card_master_amount: _iCMA, card_location_amount: _iCLA,
    card_has_master: initFeeSplit ? (initFeeSplit.master_org_id != null && _iCMA > 0) : false,
    pos_purchase_platform_amount: _iPPFA, pos_purchase_master_amount: _iPPMA, pos_purchase_location_amount: _iPPLA,
    pos_purchase_has_master: initFeeSplit ? (initFeeSplit.master_org_id != null && _iPPMA > 0) : false,
  });
  const [inputValues, setInputValues] = useState({
    platform_amount: fmt2(_iPA), master_amount: fmt2(_iMA), location_amount: fmt2(_iLA),
    card_platform_amount: fmt2(_iCPA), card_master_amount: fmt2(_iCMA), card_location_amount: fmt2(_iCLA),
    pos_purchase_platform_amount: fmt2(_iPPFA), pos_purchase_master_amount: fmt2(_iPPMA), pos_purchase_location_amount: fmt2(_iPPLA),
  });
  const [feeSplitDirty, setFeeSplitDirty] = useState(false);

  const handleFeeSplitChange = (key, val) => {
    setFeeSplit((prev) => ({ ...prev, [key]: val }));
    setFeeSplitDirty(true);
  };

  // Geofencing master switch — initialized from techSession (technician-validate includes it),
  // then confirmed/refreshed by GET to technician-update-location.
  // Defaults to FALSE (locked) so the toggle is always blocked until master status is confirmed.
  const [geofencingMasterEnabled, setGeofencingMasterEnabled] = useState(
    techSession.geofencing_master_enabled === true
  );

  // Tier config state (location status thresholds — owner editable from POS)
  const [tierConfig, setTierConfig] = useState({
    plus_threshold: String(techSession.location?.tier_config?.plus_threshold ?? 10),
    premium_threshold: String(techSession.location?.tier_config?.premium_threshold ?? 25),
  });
  const [tierConfigDirty, setTierConfigDirty] = useState(false);

  const handleTierConfigChange = (field, val) => {
    setTierConfig((prev) => ({ ...prev, [field]: val }));
    setTierConfigDirty(true);
  };

  // Timer countdown
  useEffect(() => {
    const updateTimer = () => {
      const remaining = Math.max(0, Math.floor((techSession.sessionExpiresAt - Date.now()) / 1000));
      setTimeRemaining(remaining);
      if (remaining === 0) {
        onToast("Session expired", "error");
        onExit();
      }
    };
    updateTimer();
    const interval = setInterval(updateTimer, 1000);
    return () => clearInterval(interval);
  }, [techSession.sessionExpiresAt, onExit, onToast]);

  // Re-fetch fresh location data from DB on mount so admin changes are visible
  useEffect(() => {
    fetch(`${SUPABASE_URL}/functions/v1/technician-update-location`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${ANON_KEY}`,
        "x-technician-token": techSession.token,
      },
    })
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (!data?.location) return;
        setLocation(data.location);
        if (data.geofencing_master_enabled !== undefined) {
          setGeofencingMasterEnabled(data.geofencing_master_enabled);
        }
        if (data.hardware) setHardware(data.hardware);
        if (data.tier_config) {
          setTierConfig({
            plus_threshold: String(data.tier_config.plus_threshold ?? 10),
            premium_threshold: String(data.tier_config.premium_threshold ?? 25),
          });
        }
        if (data.location.fee_split) {
          const fs = data.location.fee_split;
          const newWFee = parseFloat(data.location.withdrawal_fee) || 0;
          const newCFee = parseFloat(data.location.physical_card_fee) || 0;
          const pa = fs.platform_amount != null ? parseFloat(fs.platform_amount) : 0;
          const ma = fs.master_amount != null ? parseFloat(fs.master_amount) : 0;
          const cpa = fs.card_platform_amount != null ? parseFloat(fs.card_platform_amount) : 0;
          const cma = fs.card_master_amount != null ? parseFloat(fs.card_master_amount) : 0;
          const newLA = Math.max(0, Math.round((newWFee - pa - ma) * 100) / 100);
          const newCLA = Math.max(0, Math.round((newCFee - cpa - cma) * 100) / 100);
          setFeeSplit({
            platform_amount: pa, master_amount: ma, location_amount: newLA,
            has_master: fs.master_org_id != null,
            card_platform_amount: cpa, card_master_amount: cma, card_location_amount: newCLA,
            card_has_master: fs.master_org_id != null && cma > 0,
          });
          setInputValues({
            platform_amount: fmt2(pa), master_amount: fmt2(ma), location_amount: fmt2(newLA),
            card_platform_amount: fmt2(cpa), card_master_amount: fmt2(cma), card_location_amount: fmt2(newCLA),
          });
        }
      })
      .catch(() => { /* silently ignore refresh failure — stale snapshot still works */ });
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const formatTime = (seconds) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins}:${secs.toString().padStart(2, "0")}`;
  };

  const handleChange = (field, value) => {
    setLocation((prev) => ({ ...prev, [field]: value }));
    setDirty((prev) => ({ ...prev, [field]: true }));
    setErrors((prev) => {
      const next = { ...prev };
      delete next[field];
      return next;
    });
  };

  const handleSave = async () => {
    const dirtyFields = Object.keys(dirty).filter((f) => dirty[f]);
    if (dirtyFields.length === 0 && !feeSplitDirty && !tierConfigDirty) {
      onToast("No changes to save", "info");
      return;
    }

    setSaving(true);
    setErrors({});

    // Validate limit caps before sending
    const LIMIT_CAP_MAP = {
      max_single_load:        { cap: "admin_cap_single_load",        label: "Max Single Load" },
      max_daily_load:         { cap: "admin_cap_daily_load",         label: "Max Daily Load" },
      max_single_withdrawal:  { cap: "admin_cap_single_withdrawal",  label: "Max Single Withdrawal" },
      max_daily_withdrawal:   { cap: "admin_cap_daily_withdrawal",   label: "Max Daily Withdrawal" },
      max_pos_purchase:       { cap: "admin_cap_pos_purchase",       label: "Max Single Gift Card Purchase" },
      max_daily_pos_purchase: { cap: "admin_cap_daily_pos_purchase", label: "Max Daily Gift Card Purchase" },
    };
    const limitErrors = {};
    for (const field of dirtyFields) {
      const info = LIMIT_CAP_MAP[field];
      if (!info) continue;
      const cap = parseFloat(location[info.cap]) || 0;
      if (cap <= 0) continue;
      const val = parseFloat(location[field]);
      if (isNaN(val)) continue;
      // val=0 on gift card fields means "no limit" → effectively exceeds any positive cap
      const giftCardField = field === "max_pos_purchase" || field === "max_daily_pos_purchase";
      if (giftCardField && val === 0) {
        limitErrors[field] = `Cannot set "no limit" — admin cap is $${cap.toLocaleString()}`;
      } else if (val > cap) {
        limitErrors[field] = `Exceeds admin-set cap of $${cap.toLocaleString()}`;
      }
    }
    if (Object.keys(limitErrors).length > 0) {
      setErrors(limitErrors);
      onToast("One or more limits exceed admin-set caps", "error");
      setSaving(false);
      return;
    }

    // Validate fee caps before sending
    if (feeSplitDirty) {
      const maxWFee = parseFloat(location.max_withdrawal_fee) || 0;
      const maxCFee = parseFloat(location.max_physical_card_fee) || 0;
      if (maxWFee > 0) {
        const wT = Math.round(((parseFloat(feeSplit.platform_amount) || 0) + (feeSplit.has_master ? (parseFloat(feeSplit.master_amount) || 0) : 0) + (parseFloat(feeSplit.location_amount) || 0)) * 100) / 100;
        if (wT > maxWFee + 0.001) {
          onToast(`Withdrawal fee total ($${wT.toFixed(2)}) exceeds max allowed ($${maxWFee.toFixed(2)})`, "error");
          setSaving(false);
          return;
        }
      }
      if (maxCFee > 0) {
        const cT = Math.round(((parseFloat(feeSplit.card_platform_amount) || 0) + (feeSplit.card_has_master ? (parseFloat(feeSplit.card_master_amount) || 0) : 0) + (parseFloat(feeSplit.card_location_amount) || 0)) * 100) / 100;
        if (cT > maxCFee + 0.001) {
          onToast(`Card fee total ($${cT.toFixed(2)}) exceeds max allowed ($${maxCFee.toFixed(2)})`, "error");
          setSaving(false);
          return;
        }
      }
    }

    try {
      const updates = {};
      for (const field of dirtyFields) {
        updates[field] = location[field];
      }

      // Block enabling geofencing when the platform master switch is off
      if (!geofencingMasterEnabled && (updates.geofence_enabled === true || updates.geofence_enabled === "true")) {
        // Revert the local toggle state back to OFF since master is disabled
        setLocation((prev) => ({ ...prev, geofence_enabled: false }));
        setDirty((prev) => { const d = { ...prev }; delete d.geofence_enabled; return d; });
        onToast("Geofencing is disabled platform-wide. Enable it in System Settings first.", "error");
        setSaving(false);
        return;
      }

      const body = { updates };
      if (tierConfigDirty) {
        body.tier_config = {
          plus_threshold: parseInt(tierConfig.plus_threshold) || 10,
          premium_threshold: parseInt(tierConfig.premium_threshold) || 25,
        };
      }
      if (feeSplitDirty) {
        // Compute totals from split amounts — these become the stored fees
        const wTotal = Math.round(((parseFloat(feeSplit.platform_amount) || 0)
          + (feeSplit.has_master ? (parseFloat(feeSplit.master_amount) || 0) : 0)
          + (parseFloat(feeSplit.location_amount) || 0)) * 100) / 100;
        const cTotal = Math.round(((parseFloat(feeSplit.card_platform_amount) || 0)
          + (feeSplit.card_has_master ? (parseFloat(feeSplit.card_master_amount) || 0) : 0)
          + (parseFloat(feeSplit.card_location_amount) || 0)) * 100) / 100;

        // Include computed fee totals in location updates
        if (wTotal > 0) updates.withdrawal_fee = wTotal;
        if (cTotal > 0) updates.physical_card_fee = cTotal;

        body.fee_split = {
          platform_amount: parseFloat(feeSplit.platform_amount) || 0,
          master_amount: feeSplit.has_master ? (parseFloat(feeSplit.master_amount) || 0) : 0,
          withdrawal_fee: wTotal,
        };
        body.card_fee_split = {
          card_platform_amount: parseFloat(feeSplit.card_platform_amount) || 0,
          card_master_amount: feeSplit.card_has_master ? (parseFloat(feeSplit.card_master_amount) || 0) : 0,
        };
        const ppTotal = Math.round(((parseFloat(feeSplit.pos_purchase_platform_amount) || 0)
          + (feeSplit.pos_purchase_has_master ? (parseFloat(feeSplit.pos_purchase_master_amount) || 0) : 0)
          + (parseFloat(feeSplit.pos_purchase_location_amount) || 0)) * 100) / 100;
        updates.pos_purchase_fee = ppTotal;
        body.pos_purchase_fee_split = {
          pos_purchase_platform_amount: parseFloat(feeSplit.pos_purchase_platform_amount) || 0,
          pos_purchase_master_amount: feeSplit.pos_purchase_has_master ? (parseFloat(feeSplit.pos_purchase_master_amount) || 0) : 0,
        };
      }

      const res = await fetch(`${SUPABASE_URL}/functions/v1/technician-update-location`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${ANON_KEY}`,
          "x-technician-token": techSession.token,
        },
        body: JSON.stringify(body),
      });

      const data = await res.json();
      if (!res.ok) {
        if (data.errors) {
          setErrors(data.errors);
          onToast("Some fields failed validation", "error");
        } else {
          throw { error: data.error || "Update failed" };
        }
        return;
      }

      // Update local state with server response
      if (data.location) {
        setLocation(data.location);
        if (data.location.fee_split) {
          const fs = data.location.fee_split;
          const newWFee = parseFloat(data.location.withdrawal_fee) || 0;
          const newCFee = parseFloat(data.location.physical_card_fee) || 0;
          const pa = fs.platform_amount != null ? parseFloat(fs.platform_amount) : 0;
          const ma = fs.master_amount != null ? parseFloat(fs.master_amount) : 0;
          const cpa = fs.card_platform_amount != null ? parseFloat(fs.card_platform_amount) : 0;
          const cma = fs.card_master_amount != null ? parseFloat(fs.card_master_amount) : 0;
          const newLA = Math.max(0, Math.round((newWFee - pa - ma) * 100) / 100);
          const newCLA = Math.max(0, Math.round((newCFee - cpa - cma) * 100) / 100);
          setFeeSplit({
            platform_amount: pa, master_amount: ma, location_amount: newLA,
            has_master: fs.master_org_id != null,
            card_platform_amount: cpa, card_master_amount: cma, card_location_amount: newCLA,
            card_has_master: fs.master_org_id != null && cma > 0,
          });
          setInputValues({
            platform_amount: fmt2(pa), master_amount: fmt2(ma), location_amount: fmt2(newLA),
            card_platform_amount: fmt2(cpa), card_master_amount: fmt2(cma), card_location_amount: fmt2(newCLA),
          });
        }
      }
      setDirty({});
      setFeeSplitDirty(false);
      setTierConfigDirty(false);
      if (data.tier_config) {
        setTierConfig({
          plus_threshold: String(data.tier_config.plus_threshold ?? 10),
          premium_threshold: String(data.tier_config.premium_threshold ?? 25),
        });
      }
      onToast(`Saved ${data.updated_fields?.length || 0} setting(s)`, "success");
      appendLocalLog({ action: "settings_save", actor_type: "operator", actor_id: null, actor_name: techSession.operatorRole, location_id: techSession.location?.id, details: { updated_fields: data.updated_fields, location: techSession.location?.name } });

      if (data.errors) {
        setErrors(data.errors);
      }
    } catch (err) {
      console.error("Save error:", err);
      onToast(err.error || "Failed to save changes", "error");
    } finally {
      setSaving(false);
    }
  };

  // Get operator role level for permission checks
  const operatorRole = techSession.operatorRole || "manager";
  const roleLevel = ROLE_LEVELS[operatorRole] || 2;
  const isOwnerPlus = roleLevel >= 3;
  const isAdminPlus = roleLevel >= 4;
  const isManagerPlus = roleLevel >= 2;

  // Build tabs based on role permissions
  const tabs = [
    { id: "basic", label: "Basic Info" },
    { id: "hours", label: "Hours" },
    ...(isOwnerPlus ? [{ id: "fees", label: "Fees" }] : []),
    ...(isOwnerPlus ? [{ id: "limits", label: "Limits" }] : []),
    ...(isOwnerPlus ? [{ id: "geofencing", label: "Geofencing" }] : []),
    ...(isAdminPlus ? [{ id: "hardware", label: "Hardware" }] : []),
    ...(isManagerPlus ? [{ id: "team", label: "Team" }] : []),
    ...(isOwnerPlus ? [{ id: "tiers", label: "Rewards Tiers" }] : []),
    ...(isOwnerPlus ? [{ id: "log", label: "Log" }] : []),
  ];

  const renderField = (field, label, type = "text", options = {}) => {
    const hasError = !!errors[field];
    const isDirty = !!dirty[field];
    const locked = !!options.locked;

    // Out-of-range validation for number fields
    const rawVal = location[field];
    const numVal = parseFloat(rawVal);
    const outOfRange = type === "number" && isDirty && !isNaN(numVal) &&
      ((options.min !== undefined && numVal < options.min) || (options.max !== undefined && numVal > options.max));
    const borderColor = hasError || outOfRange ? COLORS.error : COLORS.border;

    if (type === "select" && options.options) {
      return (
        <div style={{ marginBottom: 16 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
            {label} {isDirty && !locked && <span style={{ color: COLORS.orange }}>*</span>}
            {locked && <span style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 400, marginLeft: 6, textTransform: "none", letterSpacing: 0 }}>Admin only</span>}
          </label>
          <select
            value={location[field] ?? ""}
            disabled={locked}
            onChange={(e) => !locked && handleChange(field, e.target.value)}
            style={{
              width: "100%", padding: "14px 16px", fontSize: 18,
              border: `2px solid ${borderColor}`, borderRadius: 10, outline: "none",
              fontFamily: "inherit", boxSizing: "border-box",
              background: locked ? "#2A2A2A" : "#1E1E1E",
              color: locked ? COLORS.textMuted : COLORS.heading,
              appearance: "auto", cursor: locked ? "default" : "auto",
            }}
          >
            {options.options.map((o) => (
              <option key={o.value} value={o.value}>{o.label}</option>
            ))}
          </select>
          {options.hint && <div style={{ color: COLORS.textMuted, fontSize: 12, marginTop: 4 }}>{options.hint}</div>}
          {hasError && <div style={{ color: COLORS.error, fontSize: 12, marginTop: 4 }}>{errors[field]}</div>}
          {outOfRange && <div style={{ color: COLORS.error, fontSize: 12, marginTop: 4 }}>Value out of allowed range ({options.min !== undefined ? `$${options.min}` : ""}–{options.max !== undefined ? `$${options.max}` : ""})</div>}
        </div>
      );
    }

    if (type === "toggle") {
      const isEnabled = location[field] === true || location[field] === "true";
      return (
        <div style={{ marginBottom: 16, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
          <label style={{ fontSize: 16, fontWeight: 600, color: locked ? COLORS.textMuted : COLORS.heading }}>
            {label}
            {locked && <span style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 400, marginLeft: 8 }}>Admin only</span>}
          </label>
          <button
            onClick={() => !locked && handleChange(field, !isEnabled)}
            style={{
              width: 56, height: 32, borderRadius: 16, border: "none",
              cursor: locked ? "default" : "pointer",
              background: locked ? COLORS.border : (isEnabled ? COLORS.success : COLORS.border),
              position: "relative", transition: "background .2s", opacity: locked ? 0.5 : 1,
            }}
          >
            <span style={{
              position: "absolute", top: 4, left: isEnabled ? 28 : 4,
              width: 24, height: 24, borderRadius: 12, background: COLORS.heading,
              transition: "left .2s",
            }} />
          </button>
        </div>
      );
    }

    if (type === "textarea") {
      return (
        <div style={{ marginBottom: 16 }}>
          <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
            {label} {isDirty && !locked && <span style={{ color: COLORS.orange }}>*</span>}
            {locked && <span style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 400, marginLeft: 6, textTransform: "none", letterSpacing: 0 }}>Admin only</span>}
          </label>
          <textarea
            value={location[field] ?? ""}
            disabled={locked}
            onChange={(e) => !locked && handleChange(field, e.target.value)}
            placeholder={options.placeholder || ""}
            maxLength={options.maxLength}
            style={{
              width: "100%", padding: "14px 16px", fontSize: 16,
              border: `2px solid ${borderColor}`, borderRadius: 10, outline: "none",
              fontFamily: "inherit", boxSizing: "border-box",
              background: locked ? "#2A2A2A" : "#1E1E1E",
              color: locked ? COLORS.textMuted : COLORS.heading,
              minHeight: 80, resize: locked ? "none" : "vertical",
              cursor: locked ? "default" : "auto",
            }}
          />
          {options.hint && <div style={{ color: COLORS.textMuted, fontSize: 12, marginTop: 4 }}>{options.hint}</div>}
          {hasError && <div style={{ color: COLORS.error, fontSize: 12, marginTop: 4 }}>{errors[field]}</div>}
        </div>
      );
    }

    return (
      <div style={{ marginBottom: 16 }}>
        <label style={{ display: "block", fontSize: 14, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, letterSpacing: ".3px", textTransform: "uppercase" }}>
          {label} {isDirty && !locked && <span style={{ color: COLORS.orange }}>*</span>}
          {locked && <span style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 400, marginLeft: 6, textTransform: "none", letterSpacing: 0 }}>Admin only</span>}
        </label>
        <div style={{ position: "relative" }}>
          {options.prefix && (
            <span style={{
              position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)",
              color: COLORS.textMuted, fontSize: 18, fontWeight: 600,
            }}>{options.prefix}</span>
          )}
          <input
            type={type}
            disabled={locked}
            value={type === "tel" ? formatPhoneNumber(location[field]) : (location[field] ?? "")}
            onChange={(e) => !locked && handleChange(field, type === "tel" ? formatPhoneNumber(e.target.value) : e.target.value)}
            onWheel={type === "number" ? (e) => e.target.blur() : undefined}
            onBlur={() => {
              if (locked || type !== "number" || !options.prefix) return;
              const v = parseFloat(location[field]) || 0;
              handleChange(field, v.toFixed(2));
            }}
            placeholder={options.placeholder || ""}
            inputMode={options.inputMode || "text"}
            min={options.min !== undefined ? options.min : undefined}
            max={options.max !== undefined ? options.max : undefined}
            style={{
              width: "100%",
              padding: options.prefix ? "14px 16px 14px 32px" : "14px 16px",
              fontSize: 18,
              border: `2px solid ${borderColor}`,
              borderRadius: 10, outline: "none", fontFamily: "inherit", boxSizing: "border-box",
              background: locked ? "#2A2A2A" : "#1E1E1E",
              color: locked ? COLORS.textMuted : COLORS.heading,
              cursor: locked ? "default" : "auto",
            }}
          />
        </div>
        {options.hint && <div style={{ color: outOfRange ? COLORS.error : COLORS.textMuted, fontSize: 12, marginTop: 4 }}>{options.hint}</div>}
        {hasError && <div style={{ color: COLORS.error, fontSize: 12, marginTop: 4 }}>{errors[field]}</div>}
      </div>
    );
  };

  return (
    <div style={{ padding: 32, maxWidth: 800, margin: "0 auto" }}>
      {/* Header */}
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        marginBottom: 32, paddingBottom: 20, borderBottom: `1px solid ${COLORS.border}`,
      }}>
        <div>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 4px" }}>
            Location Settings
          </h2>
          <p style={{ fontSize: 13, color: COLORS.textMuted, margin: 0 }}>
            Setup code authenticated by: {techSession.technicianName}
          </p>
        </div>
        <div style={{ textAlign: "right" }}>
          <div style={{
            fontSize: 28, fontWeight: 700, fontFamily: "'Space Mono', monospace",
            color: timeRemaining < 300 ? COLORS.warning : COLORS.text,
          }}>
            {formatTime(timeRemaining)}
          </div>
          <div style={{ fontSize: 12, color: COLORS.textMuted }}>remaining</div>
        </div>
      </div>

      {/* Tabs */}
      <div style={{
        display: "flex", gap: 8, marginBottom: 28, overflowX: "auto",
        paddingBottom: 4,
      }}>
        {tabs.map((tab) => (
          <button
            key={tab.id}
            onClick={() => setActiveTab(tab.id)}
            style={{
              padding: "10px 20px",
              borderRadius: 10,
              border: `2px solid ${activeTab === tab.id ? COLORS.orange : COLORS.border}`,
              background: activeTab === tab.id ? COLORS.orangeGlow : COLORS.surfaceLight,
              color: activeTab === tab.id ? COLORS.orange : COLORS.text,
              fontSize: 15,
              fontWeight: 600,
              cursor: "pointer",
              fontFamily: "inherit",
              whiteSpace: "nowrap",
            }}
          >
            {tab.label}
          </button>
        ))}
      </div>

      {/* Tab Content */}
      <div style={{
        background: COLORS.surface, borderRadius: 16, padding: "28px 32px",
        border: `1px solid ${COLORS.border}`,
      }}>
        {activeTab === "basic" && (
          <>
            {!isAdminPlus && (
              <div style={{ marginBottom: 20, padding: "10px 14px", background: COLORS.surfaceLight, borderRadius: 8, border: `1px solid ${COLORS.border}` }}>
                <p style={{ fontSize: 13, color: COLORS.textMuted, margin: 0 }}>Basic info is view-only. Contact a WinStash admin to make changes.</p>
              </div>
            )}
            {/* Location Identity */}
            {renderField("legal_name", "Location Legal Name", "text", { locked: !isAdminPlus })}
            {renderField("name", "Location D/B/A", "text", { locked: !isAdminPlus })}
            {renderField("license_number", "GLC COAM Location License #", "text", { locked: !isAdminPlus })}
            {renderField("master_company_name", "Master Company Name", "text", { locked: true })}
            {renderField("master_license_number", "Master License #", "text", { locked: true })}

            {/* Address */}
            {renderField("address_line1", "Address Line 1", "text", { locked: !isAdminPlus })}
            {renderField("address_line2", "Address Line 2", "text", { locked: !isAdminPlus })}
            <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr 1fr", gap: 12 }}>
              {renderField("city", "City", "text", { locked: !isAdminPlus })}
              {renderField("state", "State", "text", { placeholder: "GA", locked: !isAdminPlus })}
              {renderField("zip", "ZIP Code", "text", { locked: !isAdminPlus })}
            </div>

            {/* Contact */}
            {renderField("phone", "Location Phone", "tel", { locked: !isAdminPlus })}
            {renderField("contact_email", "Contact Email", "email", {
              hint: "Displayed in App for Player Inquiries",
              locked: !isAdminPlus,
            })}

            {/* Owner's Info Section */}
            <div style={{
              borderTop: `1px solid ${COLORS.border}`,
              marginTop: 24, paddingTop: 20, marginBottom: 16,
            }}>
              <h4 style={{ color: COLORS.heading, margin: "0 0 4px", fontSize: 16, fontWeight: 600 }}>Location Owner's Info</h4>
              <p style={{ color: COLORS.textMuted, fontSize: 13, margin: 0 }}>Contact Information for the Location Owner</p>
            </div>
            {renderField("owner_name", "Owner's Name", "text", { locked: !isAdminPlus })}
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              {renderField("owner_cell", "Owner's Cell", "tel", { locked: !isAdminPlus })}
              {renderField("owner_email", "Owner's Email", "email", { locked: !isAdminPlus })}
            </div>

            {/* Location Details Section */}
            <div style={{
              borderTop: `1px solid ${COLORS.border}`,
              marginTop: 24, paddingTop: 20, marginBottom: 16,
            }}>
              <h4 style={{ color: COLORS.heading, margin: "0 0 4px", fontSize: 16, fontWeight: 600 }}>Location Details</h4>
              <p style={{ color: COLORS.textMuted, fontSize: 13, margin: 0 }}>Information Displayed in the Player App</p>
            </div>
            {renderField("machine_count", "Machine Count", "select", {
              options: Array.from({ length: 10 }, (_, i) => ({ value: String(i), label: String(i) })),
              hint: "Number of COAMs at this Location",
              locked: !isAdminPlus,
            })}
            {renderField("location_description", "Location Description", "textarea", {
              hint: "Brief Description for App Listing (e.g., Bar/Restaurant, Convenience Store, etc.)",
              maxLength: 500, locked: !isAdminPlus,
            })}
            {renderField("location_image_url", "Image URL", "url", {
              hint: "Photo URL for App Listing", locked: !isAdminPlus,
            })}
            {renderField("timezone", "Timezone", "select", { options: TIMEZONES, locked: !isAdminPlus })}
            {renderField("status", "Status", "select", { options: LOCATION_STATUS, locked: !isAdminPlus })}
          </>
        )}

        {activeTab === "hours" && (
          <>
            <OperatingHoursEditor
              hours={location.operating_hours}
              onChange={(hours) => handleChange("operating_hours", hours)}
              isDirty={!!dirty.operating_hours}
            />

            {isOwnerPlus && location.operating_hours && (
              <div style={{ marginTop: 20, background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 10, overflow: "hidden" }}>
                <div style={{ padding: "12px 18px", borderBottom: `1px solid ${COLORS.border}` }}>
                  <div style={{ fontSize: 11, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "1px", fontFamily: "'Space Mono', monospace" }}>Hours Enforcement</div>
                </div>
                {[
                  { key: "enforce_pos_hours", label: "Enforce Hours — POS", desc: "Block operator login outside operating hours" },
                  { key: "enforce_kiosk_hours", label: "Enforce Hours — Kiosk", desc: "Block kiosk loads and withdrawals outside operating hours" },
                ].map(({ key, label, desc }, i) => {
                  const isOn = location[key] === true || location[key] === "true";
                  return (
                    <div key={key} style={{ display: "flex", alignItems: "center", gap: 16, padding: "14px 18px", borderTop: i > 0 ? `1px solid ${COLORS.border}` : "none" }}>
                      <div style={{ flex: 1 }}>
                        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.heading }}>{label}</div>
                        <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 2 }}>{desc}</div>
                      </div>
                      <button
                        onClick={() => handleChange(key, !isOn)}
                        style={{ width: 56, height: 32, borderRadius: 16, border: "none", cursor: "pointer", background: isOn ? COLORS.success : COLORS.border, position: "relative", transition: "background .2s" }}
                      >
                        <span style={{ position: "absolute", top: 4, left: isOn ? 28 : 4, width: 24, height: 24, borderRadius: 12, background: COLORS.heading, transition: "left .2s" }} />
                      </button>
                    </div>
                  );
                })}
              </div>
            )}

            {/* Attendant Access */}
            {isOwnerPlus && (
              <div style={{ marginTop: 20, background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 10, overflow: "hidden" }}>
                <div style={{ padding: "12px 18px", borderBottom: `1px solid ${COLORS.border}` }}>
                  <div style={{ fontSize: 11, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "1px", fontFamily: "'Space Mono', monospace" }}>Attendant Access</div>
                </div>
                {(() => {
                  const isOn = location.allow_attendant_inventory === true || location.allow_attendant_inventory === "true";
                  return (
                    <div style={{ display: "flex", alignItems: "center", gap: 16, padding: "14px 18px" }}>
                      <div style={{ flex: 1 }}>
                        <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.heading }}>Card Inventory Access</div>
                        <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 2 }}>Allow attendants to receive cards into inventory</div>
                      </div>
                      <button
                        onClick={() => handleChange("allow_attendant_inventory", !isOn)}
                        style={{ width: 56, height: 32, borderRadius: 16, border: "none", cursor: "pointer", background: isOn ? COLORS.success : COLORS.border, position: "relative", transition: "background .2s" }}
                      >
                        <span style={{ position: "absolute", top: 4, left: isOn ? 28 : 4, width: 24, height: 24, borderRadius: 12, background: COLORS.heading, transition: "left .2s" }} />
                      </button>
                    </div>
                  );
                })()}
              </div>
            )}
          </>
        )}

        {activeTab === "fees" && (() => {
          // amtInput is called as a plain function (not a component) so focus is never lost on re-render
          const amtInput = (key, lockRow, maxVal) => {
            const isOver = maxVal !== undefined && (parseFloat(inputValues[key]) || 0) > maxVal;
            return (
              <div>
                <div style={{ position: "relative" }}>
                  <span style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", color: COLORS.textMuted, fontSize: 15, pointerEvents: "none" }}>$</span>
                  <input
                    type="text" inputMode="decimal" placeholder="0.00"
                    disabled={lockRow}
                    value={inputValues[key]}
                    onChange={(e) => {
                      if (lockRow) return;
                      const raw = e.target.value;
                      setInputValues((prev) => ({ ...prev, [key]: raw }));
                      handleFeeSplitChange(key, parseFloat(raw) || 0);
                    }}
                    onBlur={(e) => {
                      if (lockRow) return;
                      const v = parseFloat(e.target.value) || 0;
                      setInputValues((prev) => ({ ...prev, [key]: v > 0 ? v.toFixed(2) : "" }));
                    }}
                    style={{ width: "100%", padding: "10px 12px 10px 22px", background: lockRow ? "#2A2A2A" : COLORS.surface, border: `1px solid ${isOver ? COLORS.error : COLORS.border}`, borderRadius: 8, color: lockRow ? COLORS.textMuted : COLORS.text, fontSize: 16, fontFamily: "'Space Mono', monospace", cursor: lockRow ? "default" : "auto" }}
                  />
                </div>
                {!lockRow && maxVal !== undefined && (
                  <div style={{ fontSize: 11, marginTop: 3, color: isOver ? COLORS.error : COLORS.textMuted }}>
                    {isOver ? `Exceeds max ($${maxVal.toFixed(2)})` : `Max: $${maxVal.toFixed(2)}`}
                  </div>
                )}
              </div>
            );
          };

          // SplitPanel is also called as a plain function to avoid component remounting on state changes
          const SplitPanel = ({ title, keys, hasMasterKey, maxFeeKey }) => {
            const p = parseFloat(feeSplit[keys.platform]) || 0;
            const m = feeSplit[hasMasterKey] ? (parseFloat(feeSplit[keys.master]) || 0) : 0;
            const l = parseFloat(feeSplit[keys.location]) || 0;
            const total = Math.round((p + m + l) * 100) / 100;
            const maxFee = parseFloat(location[maxFeeKey]) || 0;
            const locMax = maxFee > 0 ? Math.max(0, Math.round((maxFee - p - m) * 100) / 100) : undefined;
            return (
              <div style={{ marginBottom: 24 }}>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: COLORS.textMuted, marginBottom: 10, letterSpacing: ".3px", textTransform: "uppercase" }}>
                  {title} {feeSplitDirty && <span style={{ color: COLORS.orange }}>*</span>}
                </label>
                <div style={{ background: COLORS.surfaceLight, borderRadius: 10, padding: 16, border: `1px solid ${COLORS.border}` }}>
                  <div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 14 }}>
                    <div style={{ display: "grid", gridTemplateColumns: "160px 1fr", gap: 12, alignItems: "center" }}>
                      <span style={{ fontSize: 14, fontWeight: 600, color: COLORS.textMuted }}>WinStash <span style={{ fontSize: 10, fontWeight: 400 }}>Admin only</span></span>
                      {amtInput(keys.platform, true)}
                    </div>
                    <div style={{ display: "grid", gridTemplateColumns: "160px 1fr", gap: 12, alignItems: "start" }}>
                      <span style={{ fontSize: 14, fontWeight: 600, color: isOwnerPlus ? COLORS.text : COLORS.textMuted, paddingTop: 10 }}>
                        Location{!isOwnerPlus && <span style={{ fontSize: 10, fontWeight: 400 }}> Owner only</span>}
                      </span>
                      {amtInput(keys.location, !isOwnerPlus, locMax)}
                    </div>
                    {feeSplit[hasMasterKey] && (
                      <div style={{ display: "grid", gridTemplateColumns: "160px 1fr", gap: 12, alignItems: "center" }}>
                        <span style={{ fontSize: 14, fontWeight: 600, color: COLORS.textMuted }}>Master Licensee <span style={{ fontSize: 10, fontWeight: 400 }}>Admin only</span></span>
                        {amtInput(keys.master, true)}
                      </div>
                    )}
                  </div>
                  <div style={{ borderTop: `2px solid ${COLORS.border}`, paddingTop: 10 }}>
                    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                      <span style={{ fontSize: 14, fontWeight: 700, color: COLORS.heading }}>Total Fee</span>
                      <span style={{ fontFamily: "'Space Mono', monospace", fontSize: 16, fontWeight: 700, color: maxFee > 0 && total > maxFee + 0.001 ? COLORS.error : COLORS.orange }}>{fmtCurrency(total)}</span>
                    </div>
                    {maxFee > 0 && total > maxFee + 0.001 && (
                      <div style={{ fontSize: 12, color: COLORS.error, marginTop: 4, textAlign: "right" }}>
                        Exceeds max allowed fee of {fmtCurrency(maxFee)}
                      </div>
                    )}
                  </div>
                  {!feeSplit[hasMasterKey] && isAdminPlus && (
                    <button onClick={() => { setFeeSplit((prev) => ({ ...prev, [hasMasterKey]: true })); setFeeSplitDirty(true); }}
                      style={{ marginTop: 10, fontSize: 12, color: COLORS.textMuted, background: "none", border: "none", cursor: "pointer", textDecoration: "underline" }}>
                      + Add Master Licensee
                    </button>
                  )}
                  {feeSplit[hasMasterKey] && isAdminPlus && (
                    <button onClick={() => { setFeeSplit((prev) => ({ ...prev, [hasMasterKey]: false, [keys.master]: 0 })); setFeeSplitDirty(true); setInputValues((prev) => ({ ...prev, [keys.master]: "" })); }}
                      style={{ marginTop: 10, fontSize: 12, color: COLORS.error, background: "none", border: "none", cursor: "pointer", textDecoration: "underline" }}>
                      Remove Master Licensee
                    </button>
                  )}
                </div>
              </div>
            );
          };

          return (
            <>
              <div style={{ marginBottom: 20 }}>
                <p style={{ fontSize: 14, color: COLORS.textMuted, lineHeight: 1.6 }}>
                  Enter each party's fixed dollar share per transaction. The total shown is automatically charged to the player.
                </p>
              </div>

              {SplitPanel({
                title: "Withdrawal Fee Split",
                hasMasterKey: "has_master",
                maxFeeKey: "max_withdrawal_fee",
                keys: { platform: "platform_amount", master: "master_amount", location: "location_amount" },
              })}

              {SplitPanel({
                title: "Physical Card Fee Split",
                hasMasterKey: "card_has_master",
                maxFeeKey: "max_physical_card_fee",
                keys: { platform: "card_platform_amount", master: "card_master_amount", location: "card_location_amount" },
              })}

              {SplitPanel({
                title: "POS Purchase Fee Split",
                hasMasterKey: "pos_purchase_has_master",
                maxFeeKey: "max_pos_purchase_fee",
                keys: { platform: "pos_purchase_platform_amount", master: "pos_purchase_master_amount", location: "pos_purchase_location_amount" },
              })}

              <div style={{ borderTop: `1px solid ${COLORS.border}`, marginTop: 24, paddingTop: 20 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: COLORS.text, marginBottom: 12 }}>Player App</div>
                {renderField("show_fees_on_app", "Show Fees on Player App", "toggle", {
                  locked: !isOwnerPlus,
                  hint: "When enabled, players see withdrawal and card fees on the Locations page",
                })}
                {renderField("show_hours_on_app", "Show Hours on Player App", "toggle", {
                  locked: !isOwnerPlus,
                  hint: "When enabled, players see operating hours on the Locations page",
                })}
              </div>
            </>
          );
        })()}

        {activeTab === "limits" && (
          <>
            <div style={{ marginBottom: 20 }}>
              <p style={{ fontSize: 14, color: COLORS.textMuted, lineHeight: 1.6 }}>
                Set transaction limits for player protection and compliance.
              </p>
            </div>
            {renderField("max_single_load", "Max Single Load", "number", {
              prefix: "$",
              min: 250,
              max: Number(location.admin_cap_single_load) || 5000,
              hint: `Suggested: $250–$${(Number(location.admin_cap_single_load) || 5000).toLocaleString()}`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_daily_load", "Max Daily Load", "number", {
              prefix: "$",
              min: 250,
              max: Number(location.admin_cap_daily_load) || 10000,
              hint: `Suggested: $250–$${(Number(location.admin_cap_daily_load) || 10000).toLocaleString()}`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_single_withdrawal", "Max Single Withdrawal", "number", {
              prefix: "$",
              min: 250,
              max: Number(location.admin_cap_single_withdrawal) || 2000,
              hint: `Suggested: $250–$${(Number(location.admin_cap_single_withdrawal) || 2000).toLocaleString()}`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_daily_withdrawal", "Max Daily Withdrawal", "number", {
              prefix: "$",
              min: 500,
              max: Number(location.admin_cap_daily_withdrawal) || 4000,
              hint: `Suggested: $500–$${(Number(location.admin_cap_daily_withdrawal) || 4000).toLocaleString()}`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_pos_purchase", "Max Single Gift Card Purchase", "number", {
              prefix: "$",
              min: 0,
              max: Number(location.admin_cap_pos_purchase) || 500,
              hint: `Suggested: $0–$${(Number(location.admin_cap_pos_purchase) || 500).toLocaleString()} · $0 = no limit`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_daily_pos_purchase", "Max Daily Gift Card Purchase", "number", {
              prefix: "$",
              min: 0,
              max: Number(location.admin_cap_daily_pos_purchase) || 2500,
              hint: `Suggested: $0–$${(Number(location.admin_cap_daily_pos_purchase) || 2500).toLocaleString()} · $0 = no limit`,
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("owner_free_gc_purchases", "Free GC Purchases (Owner)", "number", {
              min: 0,
              max: 1000,
              hint: "Number of fee-free GC purchases per player per month the location will fund. 0 = no free purchases from the location.",
              inputMode: "numeric",
              locked: !isOwnerPlus,
            })}
            {renderField("hold_timeout_minutes", "Kiosk Hold Timeout", "number", {
              hint: "Minutes before a pending kiosk withdrawal is automatically reversed and funds returned to the player's account (1-30)",
              inputMode: "numeric",
              locked: !isAdminPlus,
            })}
            {renderField("load_confirm_timeout_minutes", "Load Confirm Timeout", "number", {
              hint: "Minutes for player to confirm a load (1-30)",
              inputMode: "numeric",
              locked: !isAdminPlus,
            })}

            {/* Offline Withdrawal Limits */}
            <div style={{ marginTop: 24, marginBottom: 12, paddingTop: 20, borderTop: `1px solid ${COLORS.border}` }}>
              <p style={{ fontSize: 13, fontWeight: 600, color: COLORS.text, margin: "0 0 4px" }}>Offline Withdrawal Limits</p>
              <p style={{ fontSize: 12, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 }}>
                Maximum allowed while the kiosk is operating offline. Cannot exceed the Admin Max set by WinStash.
              </p>
            </div>
            {renderField("max_offline_single_withdrawal", "Max Offline Single Withdrawal", "number", {
              prefix: "$",
              min: 0,
              max: 500,
              hint: "Per-transaction cap while kiosk is offline (Admin Max: $500)",
              inputMode: "decimal",
              locked: !isOwnerPlus,
            })}
            {renderField("max_offline_withdrawal_count", "Max Offline Withdrawal Count", "number", {
              min: 0,
              max: 10,
              hint: "Max number of kiosk withdrawals per offline session (Admin Max: 10)",
              inputMode: "numeric",
              locked: !isOwnerPlus,
            })}
          </>
        )}

        {activeTab === "geofencing" && (
          <>
            {/* Master switch banner */}
            {!geofencingMasterEnabled && (
              <div style={{
                background: "#2D1F00", border: `1px solid #D97706`,
                borderRadius: 8, padding: "12px 14px", marginBottom: 20,
                display: "flex", alignItems: "flex-start", gap: 10,
              }}>
                <span style={{ fontSize: 16, lineHeight: 1, marginTop: 1 }}>⚠</span>
                <p style={{ fontSize: 13, color: "#FCD34D", margin: 0, lineHeight: 1.5 }}>
                  Geofencing is currently disabled platform-wide. Contact WinStash support or enable it in the Admin Portal under System Settings to configure geofencing for this location.
                </p>
              </div>
            )}

            <div style={{ opacity: geofencingMasterEnabled ? 1 : 0.4, pointerEvents: geofencingMasterEnabled ? "auto" : "none" }}>
              <div style={{ marginBottom: 20 }}>
                <p style={{ fontSize: 14, color: COLORS.textMuted, lineHeight: 1.6 }}>
                  Configure geofencing for push notifications when players approach this location.
                </p>
              </div>
              {renderField("geofence_enabled", "Enable Geofencing", "toggle", { locked: !isOwnerPlus || !geofencingMasterEnabled })}

              {/* Interactive Map */}
              <div style={{ marginTop: 24, marginBottom: 24 }}>
                <GeofenceMap
                  latitude={location.latitude}
                  longitude={location.longitude}
                  radius={location.geofence_radius_meters}
                  onLocationChange={null}
                />
              </div>

              {/* Manual coordinate inputs — always locked at POS (use admin portal) */}
              {(!location.latitude || !location.longitude) && (
                <div style={{ background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`, borderRadius: 8, padding: "10px 14px", marginBottom: 12 }}>
                  <p style={{ fontSize: 13, color: COLORS.textMuted, margin: 0 }}>
                    ⚠ Coordinates not set — configure latitude &amp; longitude in the Admin Portal to enable geofencing.
                  </p>
                </div>
              )}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
                {renderField("latitude", "Latitude", "number", {
                  hint: location.latitude ? "-90 to 90" : "Not set — Admin Portal",
                  inputMode: "decimal",
                  locked: true,
                })}
                {renderField("longitude", "Longitude", "number", {
                  hint: location.longitude ? "-180 to 180" : "Not set — Admin Portal",
                  inputMode: "decimal",
                  locked: true,
                })}
              </div>
              {renderField("geofence_radius_meters", "Geofence Radius (yards)", "number", {
                hint: "Distance in yards to trigger notification (109-1094)",
                inputMode: "numeric",
                locked: !isOwnerPlus || !geofencingMasterEnabled,
              })}
            </div>
          </>
        )}

        {activeTab === "hardware" && (
          <>
            {/* Hardware Inventory — read-only list */}
            <div style={{ marginBottom: 20 }}>
              <p style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.08em", fontWeight: 600, marginBottom: 12 }}>
                Installed Hardware
              </p>
              {hardware.length === 0 ? (
                <p style={{ fontSize: 14, color: COLORS.textMuted, fontStyle: "italic" }}>
                  No hardware assigned to this location.
                </p>
              ) : (
                <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
                  <thead>
                    <tr style={{ borderBottom: `1px solid ${COLORS.border}` }}>
                      {["Item", "Asset Tag #", "Serial Number", "Condition"].map((col) => (
                        <th key={col} style={{
                          textAlign: "left", fontSize: 11, color: COLORS.textMuted,
                          textTransform: "uppercase", letterSpacing: "0.06em",
                          fontWeight: 600, paddingBottom: 8, paddingRight: 16,
                        }}>{col}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {hardware.map((h, i) => (
                      <tr key={h.id} style={{ borderBottom: i < hardware.length - 1 ? `1px solid ${COLORS.border}` : "none" }}>
                        <td style={{ padding: "10px 16px 10px 0", color: COLORS.text, fontWeight: 500 }}>
                          {h.hardware_type?.name ?? "Unknown"}
                        </td>
                        <td style={{ padding: "10px 16px 10px 0", color: COLORS.textMuted, fontFamily: "monospace", fontSize: 12 }}>
                          {h.asset_tag ? `#${h.asset_tag}` : "—"}
                        </td>
                        <td style={{ padding: "10px 16px 10px 0", color: COLORS.textMuted, fontFamily: "monospace", fontSize: 12 }}>
                          {h.serial_number ?? "—"}
                        </td>
                        <td style={{ padding: "10px 0", color: COLORS.textMuted, fontSize: 12, textTransform: "capitalize" }}>
                          {h.condition}
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              )}
            </div>

            {/* Card Reader Type — admin-only setting */}
            {isAdminPlus && (
              <div style={{ borderTop: `1px solid ${COLORS.border}`, paddingTop: 20, marginTop: 8 }}>
                <p style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.08em", fontWeight: 600, marginBottom: 12 }}>
                  Admin Settings
                </p>
                {renderField("card_reader_type", "Card Reader Type", "select", {
                  options: CARD_READER_TYPES,
                })}
              </div>
            )}
          </>
        )}

        {activeTab === "team" && (
          <TeamManager
            locationId={location.id}
            operatorRole={operatorRole}
            token={techSession.operatorToken}
            onToast={onToast}
          />
        )}

        {activeTab === "log" && (() => {
          // Fetch logs on first render of this tab
          if (posLog === null && !posLogLoading && !posLogError) {
            setPosLogLoading(true);
            fetch(`${SUPABASE_URL}/functions/v1/admin-activity/pos-log`, {
              headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${ANON_KEY}`,
                "apikey": ANON_KEY,
                "x-operator-token": techSession.operatorToken,
              },
            })
              .then((r) => r.json())
              .then((d) => {
                if (d.error) setPosLogError(d.error);
                else setPosLog(d.logs ?? []);
              })
              .catch(() => setPosLogError("Network error"))
              .finally(() => setPosLogLoading(false));
          }

          const fmtTime = (ts) => {
            const d = new Date(ts);
            return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
          };

          return (
            <div>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
                <div style={{ fontSize: 13, color: COLORS.textMuted }}>Last 200 entries for this location</div>
                <button
                  onClick={() => { setPosLog(null); setPosLogError(null); setPosLogExpanded(null); }}
                  style={{ background: "none", border: `1px solid ${COLORS.border}`, color: COLORS.textMuted, borderRadius: 8, padding: "6px 14px", fontSize: 13, cursor: "pointer", fontFamily: "inherit" }}
                >
                  Refresh
                </button>
              </div>

              {posLogLoading && (
                <div style={{ textAlign: "center", padding: 40, color: COLORS.textMuted, fontSize: 14 }}>Loading log...</div>
              )}
              {posLogError && (
                <div style={{ padding: 16, background: "#2a1a1a", border: `1px solid ${COLORS.error}`, borderRadius: 10, color: COLORS.error, fontSize: 13 }}>
                  {posLogError}
                </div>
              )}
              {posLog && posLog.length === 0 && (
                <div style={{ textAlign: "center", padding: 40, color: COLORS.textMuted, fontSize: 14 }}>No log entries yet.</div>
              )}
              {posLog && posLog.length > 0 && (
                <div style={{ overflowX: "auto" }}>
                  <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
                    <thead>
                      <tr style={{ borderBottom: `1px solid ${COLORS.border}` }}>
                        {["Time", "Action", "Actor", "Endpoint", "Status"].map((h) => (
                          <th key={h} style={{ padding: "6px 12px", textAlign: "left", fontSize: 11, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 600 }}>{h}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {posLog.map((entry) => {
                        const isErr = entry.response_status >= 400;
                        const statusColor = isErr ? COLORS.error : COLORS.success;
                        return (
                          <>
                            <tr
                              key={entry.id}
                              onClick={() => setPosLogExpanded(posLogExpanded === entry.id ? null : entry.id)}
                              style={{ borderBottom: `1px solid ${COLORS.border}`, cursor: "pointer", background: posLogExpanded === entry.id ? COLORS.surfaceLight : "transparent" }}
                            >
                              <td style={{ padding: "8px 12px", color: COLORS.textMuted, whiteSpace: "nowrap" }}>{fmtTime(entry.created_at)}</td>
                              <td style={{ padding: "8px 12px", color: COLORS.text, fontWeight: 500 }}>{entry.action ?? "—"}</td>
                              <td style={{ padding: "8px 12px", color: COLORS.textMuted }}>{entry.actor_name ?? "—"}</td>
                              <td style={{ padding: "8px 12px", color: COLORS.textMuted, fontFamily: "monospace" }}>{entry.endpoint}</td>
                              <td style={{ padding: "8px 12px" }}>
                                <span style={{ color: statusColor, fontWeight: 600 }}>{entry.response_status ?? "—"}</span>
                                {entry.duration_ms != null && <span style={{ color: COLORS.textMuted, fontSize: 11, marginLeft: 6 }}>{entry.duration_ms}ms</span>}
                              </td>
                            </tr>
                            {posLogExpanded === entry.id && (
                              <tr key={`${entry.id}-d`} style={{ background: COLORS.surfaceLight }}>
                                <td colSpan={5} style={{ padding: "10px 12px" }}>
                                  {entry.error_message && (
                                    <div style={{ marginBottom: 8, padding: "6px 10px", background: "#2a1a1a", border: `1px solid ${COLORS.error}`, borderRadius: 6, color: COLORS.error, fontSize: 12 }}>
                                      {entry.error_message}
                                    </div>
                                  )}
                                  <pre style={{ margin: 0, fontSize: 11, color: COLORS.text, background: COLORS.surface, borderRadius: 6, padding: "8px 10px", overflowX: "auto", maxHeight: 120 }}>
                                    {JSON.stringify(entry.request_summary, null, 2)}
                                  </pre>
                                </td>
                              </tr>
                            )}
                          </>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              )}
            </div>
          );
        })()}

        {activeTab === "tiers" && (
          <div>
            <div style={{ marginBottom: 20 }}>
              <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 16 }}>
                Set how many withdrawals at this location a player needs to reach Plus or Premium status.
                These thresholds affect which rewards and promos players qualify for.
              </div>

              {/* Location Status Tier diagram */}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, marginBottom: 24 }}>
                {[
                  { label: "Preferred", desc: "Default status", color: COLORS.textMuted, icon: "⭐" },
                  { label: "Plus", desc: `${parseInt(tierConfig.plus_threshold) || 10}+ withdrawals`, color: COLORS.orange, icon: "⭐⭐" },
                  { label: "Premium", desc: `${parseInt(tierConfig.premium_threshold) || 25}+ withdrawals`, color: "#a855f7", icon: "⭐⭐⭐" },
                ].map((tier) => (
                  <div key={tier.label} style={{ padding: "14px 12px", background: COLORS.surfaceLight, borderRadius: 8, border: `1px solid ${COLORS.border}`, textAlign: "center" }}>
                    <div style={{ fontSize: 18, marginBottom: 4 }}>{tier.icon}</div>
                    <div style={{ fontSize: 13, fontWeight: 700, color: tier.color }}>{tier.label}</div>
                    <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 2 }}>{tier.desc}</div>
                  </div>
                ))}
              </div>

              {/* Threshold inputs */}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
                <div>
                  <label style={{ display: "block", fontSize: 12, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>
                    Plus Threshold (withdrawals)
                    {tierConfigDirty && <span style={{ color: COLORS.orange, marginLeft: 6 }}>*</span>}
                  </label>
                  <input
                    type="number"
                    min="1"
                    max="999"
                    value={tierConfig.plus_threshold}
                    onChange={(e) => handleTierConfigChange("plus_threshold", e.target.value)}
                    onWheel={(e) => e.target.blur()}
                    style={{ width: "100%", boxSizing: "border-box", background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 6, padding: "8px 12px", color: COLORS.text, fontSize: 14, outline: "none" }}
                  />
                  <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 4 }}>Players become Plus after this many withdrawals at this location</div>
                </div>
                <div>
                  <label style={{ display: "block", fontSize: 12, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase", letterSpacing: ".3px" }}>
                    Premium Threshold (withdrawals)
                    {tierConfigDirty && <span style={{ color: COLORS.orange, marginLeft: 6 }}>*</span>}
                  </label>
                  <input
                    type="number"
                    min="1"
                    max="9999"
                    value={tierConfig.premium_threshold}
                    onChange={(e) => handleTierConfigChange("premium_threshold", e.target.value)}
                    onWheel={(e) => e.target.blur()}
                    style={{ width: "100%", boxSizing: "border-box", background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 6, padding: "8px 12px", color: COLORS.text, fontSize: 14, outline: "none" }}
                  />
                  <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 4 }}>Players become Premium after this many withdrawals at this location</div>
                </div>
              </div>
            </div>

            <div style={{ marginTop: 16, padding: "12px 14px", background: COLORS.surfaceLight, borderRadius: 8, border: `1px solid ${COLORS.border}` }}>
              <div style={{ fontSize: 12, fontWeight: 600, color: COLORS.textMuted, marginBottom: 6, textTransform: "uppercase" }}>WinStash Global Tiers</div>
              <div style={{ fontSize: 12, color: COLORS.textMuted }}>
                WinStash Silver / Gold / Platinum / Diamond tiers are based on a player's <strong style={{ color: COLORS.text }}>lifetime withdrawal count</strong> across all locations — not location-specific.
                Tier withdrawal limit overrides are configured in the Admin Portal.
              </div>
            </div>
          </div>
        )}

      </div>

      {/* Action Buttons */}
      <div style={{ display: "flex", gap: 12, marginTop: 24 }}>
        <Btn onClick={onExit} variant="secondary" size="md" style={{ flex: 1 }}>
          Exit Settings
        </Btn>
        <Btn
          onClick={handleSave}
          disabled={saving || (Object.keys(dirty).filter(k => dirty[k]).length === 0 && !feeSplitDirty && !tierConfigDirty)}
          size="md"
          style={{ flex: 1 }}
        >
          {saving ? "Saving..." : "Save Changes"}
        </Btn>
      </div>
    </div>
  );
}

// ============================================================================
// GIFT CARD PURCHASE SCREEN
// ============================================================================
// Flow: card_and_pin → amount → confirm → done
function PurchaseScreen({ session, onToast, isOnline }) {
  const captureCtx = useCaptureSDK();
  const isNfcLocation = session.location?.card_reader_type === "nfc";

  const [step, setStep] = useState("card_and_pin");
  const [lookupMethod, setLookupMethod] = useState(null); // null, "card", "nfc"
  const [cardNumber, setCardNumber] = useState("");
  const [nfcId, setNfcId] = useState(null); // stored NFC NDEF ID after card tap
  const [pin, setPin] = useState("");
  const [player, setPlayer] = useState(null);
  const [wallet, setWallet] = useState(null);
  const [amount, setAmount] = useState("");
  const [description, setDescription] = useState("");
  const [loading, setLoading] = useState(false);
  const [txn, setTxn] = useState(null);
  const [error, setError] = useState(null);
  const [feeData, setFeeData] = useState(null); // from verify response: { total_fee, fee_to_charge, fee_waived, purchase_number }
  // PIN set flow (when player has no PIN)
  const [pinNotSetPlayerId, setPinNotSetPlayerId] = useState(null);
  const [pinNotSetPlayerName, setPinNotSetPlayerName] = useState(null);
  const [newPin, setNewPin] = useState("");
  const [confirmPin, setConfirmPin] = useState("");
  const [pinSetStep, setPinSetStep] = useState("new"); // "new" or "confirm"

  const verifyingRef = useRef(false); // prevent double-fire from useEffect re-run

  // ── PIN PAD ───────────────────────────────────────────────────────────
  const handlePinDigit = (digit) => {
    if (pin.length < 4) setPin(pin + digit);
  };
  const handlePinDelete = () => {
    setPin(pin.slice(0, -1));
  };

  // ── STEP 1: Verify card + PIN ─────────────────────────────────────────
  const handleVerify = async (cardNum) => {
    const resolvedCard = cardNum || (isNfcLocation ? "NFC-" + cardNumber : "WSH-" + cardNumber);
    if (!resolvedCard || resolvedCard === "NFC-" || resolvedCard === "WSH-") return onToast("Enter a card number", "error");
    if (pin.length < 4) return onToast("Enter your 4-digit PIN", "error");

    setLoading(true);
    setError(null);
    try {
      const payload = { pin };
      if (resolvedCard.startsWith("NFC-") || resolvedCard.startsWith("nfc-")) {
        payload.card_number = resolvedCard;
      } else {
        payload.card_number = resolvedCard;
      }

      const data = await api("pos-purchase/verify", payload, session.token);

      if (data.error === "pin_not_set") {
        setPinNotSetPlayerId(data.player_id);
        setPinNotSetPlayerName(data.player_name);
        setPin("");
        setStep("set_pin");
        return;
      }

      setPlayer(data.player);
      setWallet(data.wallet);
      if (data.fee) setFeeData(data.fee);
      // Cache for offline use
      cachePlayerData(resolvedCard, { player: data.player, wallet: data.wallet, card: { number: resolvedCard }, limits: null });
      setStep("amount");
    } catch (err) {
      if (!isOnline) {
        const cached = getCachedPlayer(resolvedCard);
        if (cached) {
          setPlayer(cached.player);
          setWallet(cached.wallet);
          setStep("amount");
          onToast("Offline — using cached player data. PIN not verified.", "warning");
        } else {
          setError("Player not in offline cache. Internet required for new players.");
        }
      } else {
        const code = err.error || "";
        if (code === "wrong_pin") {
          setError("Incorrect PIN. Please try again.");
          setPin("");
        } else if (code === "card_not_found") {
          setError("Card not found at this location.");
        } else if (code === "account_locked") {
          setError("Account is locked due to too many failed PIN attempts. Try again in 15 minutes.");
        } else if (code === "player_suspended") {
          setError("This player's account is suspended.");
        } else if (code === "wallet_frozen") {
          setError("This wallet has been frozen.");
        } else {
          setError(err.error || err.message || "Verification failed");
        }
      }
    } finally {
      setLoading(false);
    }
  };

  // Handle NFC card tap — just store the NFC ID (card identification is step 1)
  const handleNfcTap = async (nfcNdefId) => {
    setNfcId(nfcNdefId);
    setError(null);
  };

  // Submit NFC + PIN verification to API
  const handleNfcVerify = async (ndefId, pinCode) => {
    setLoading(true);
    setError(null);
    try {
      const data = await api("pos-purchase/verify", {
        card_number: ndefId.toUpperCase(),
        pin: pinCode,
      }, session.token);

      if (data.error === "pin_not_set") {
        setPinNotSetPlayerId(data.player_id);
        setPinNotSetPlayerName(data.player_name);
        setPin("");
        setStep("set_pin");
        setLoading(false);
        return;
      }

      setPlayer(data.player);
      setWallet(data.wallet);
      if (data.fee) setFeeData(data.fee);
      setStep("amount");
    } catch (err) {
      const code = err.error || "";
      const msg = code === "wrong_pin" ? "Incorrect PIN. Please try again."
                : err.error || err.message || "Verification failed";
      onToast(msg, "error");
      setPin("");
    } finally {
      setLoading(false);
    }
  };

  // ── NFC listener for card tap (step 1) ──────────────────────────────
  useEffect(() => {
    if (lookupMethod !== "nfc" || step !== "card_and_pin") {
      captureCtx?.setOnNfcTag?.(null);
      return;
    }
    if (!captureCtx?.setOnNfcTag) return;

    captureCtx.setOnNfcTag((tag) => {
      if (tag.cardId) {
        handleNfcTap(tag.cardId);
      } else if (tag.blank) {
        setError("Card has no NFC data — it needs to be programmed first.");
      } else {
        setError("Could not read NFC card — tap again");
      }
    });

    return () => {
      captureCtx.setOnNfcTag(null);
      captureCtx.stopWebNfc?.();
    };
  }, [captureCtx, lookupMethod, step]);

  // ── Auto-submit when both NFC ID and 4-digit PIN are collected ──────
  // Use a ref guard so an API failure doesn't re-trigger via loading state change.
  useEffect(() => {
    if (lookupMethod === "nfc" && nfcId && pin.length === 4 && !verifyingRef.current) {
      verifyingRef.current = true;
      handleNfcVerify(nfcId, pin).finally(() => { verifyingRef.current = false; });
    }
  }, [nfcId, pin, lookupMethod]); // intentionally omit `loading`

  // ── QR barcode listener for QR purchase method ───────────────────────
  useEffect(() => {
    if (lookupMethod !== "qr" || step !== "card_and_pin") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode(async (data) => {
      setLoading(true);
      setError(null);
      try {
        // QR code contains player wallet info — look up and go straight to PIN entry
        const result = await api("player-lookup-qr", { qr_data: data }, session.token);
        if (result.card?.number) setCardNumber(result.card.number.replace(/^(NFC-|WSH-)/, ""));
        setPlayer(result.player);
        setWallet(result.wallet);
        // Switch to card entry mode to collect PIN, pre-filled card number
        setLookupMethod("card");
      } catch (err) {
        setError(err.error || "QR code not recognized");
      } finally {
        setLoading(false);
      }
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, lookupMethod, step]);

  // ── STEP 3: Execute purchase ──────────────────────────────────────────
  const handleExecute = async () => {
    const purchaseAmt = parseFloat(amount);

    // ── OFFLINE: queue the purchase ───────────────────────────────────────
    if (!isOnline) {
      if (parseFloat(wallet?.balance || 0) < purchaseAmt) {
        return onToast("Insufficient balance", "error");
      }
      const cardKey = isNfcLocation ? ("NFC-" + cardNumber) : ("WSH-" + cardNumber);
      addToOfflineQueue({
        type: 'purchase',
        operator_id: session.operator.id,
        location_id: session.location.id,
        card_number: cardKey,
        wallet_id: wallet.id,
        amount: purchaseAmt,
        description: description.trim() || "In-store purchase",
        player_name: player?.name || '',
      });
      appendLocalLog({ action: 'purchase_queued', actor_type: 'operator', actor_id: session?.operator?.id, actor_name: session?.operator?.name, location_id: session?.location?.id, details: { amount: purchaseAmt, wallet_id: wallet?.id } });
      const pendingAdj = pendingBalanceFor(wallet.id);
      setTxn({ queued: true, amount: purchaseAmt, new_balance: (parseFloat(wallet.balance) || 0) + pendingAdj - purchaseAmt });
      setStep("done");
      return;
    }

    setLoading(true);
    try {
      const data = await api("pos-purchase/execute", {
        wallet_id: wallet.id,
        amount: purchaseAmt,
        description: description.trim() || "In-store purchase",
      }, session.token);

      setTxn(data.transaction);
      setStep("done");
      const feeMsg = data.transaction.fee_charged > 0 ? ` + ${fmtCurrency(data.transaction.fee_charged)} fee` : data.transaction.fee_absorbed ? " (fee-free)" : "";
      onToast(`${fmtCurrency(purchaseAmt)} purchase completed${feeMsg}!`, "success");
      appendLocalLog({ action: "pos_purchase", actor_type: "operator", actor_id: session?.operator?.id, actor_name: session?.operator?.name, location_id: session?.location?.id, details: { amount: purchaseAmt, wallet_id: wallet?.id } });
    } catch (err) {
      if (err.error?.includes("Insufficient")) {
        onToast("Insufficient balance for this purchase", "error");
      } else {
        onToast(err.error || "Purchase failed", "error");
      }
    } finally {
      setLoading(false);
    }
  };

  // ── Reset ─────────────────────────────────────────────────────────────
  const handleReset = () => {
    setStep("card_and_pin");
    setLookupMethod(null);
    setCardNumber("");
    setNfcId(null);
    setPin("");
    setPlayer(null);
    setWallet(null);
    setAmount("");
    setDescription("");
    setTxn(null);
    setError(null);
  };

  // — Player info bar (purchase variant)
  const PurchasePlayerBar = () => (
    <div style={{
      background: COLORS.blueBg, border: `2px solid ${COLORS.blue}`, borderRadius: 16,
      padding: "20px 24px", marginBottom: 28,
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <div>
        <div style={{ fontSize: 13, color: COLORS.blue, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
          Player
        </div>
        <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading }}>
          {player?.name}
        </div>
      </div>
      <div style={{ textAlign: "right" }}>
        <div style={{ fontSize: 13, color: COLORS.blue, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
          Available Balance
        </div>
        <div style={{ fontSize: 32, fontWeight: 700, color: COLORS.heading }}>
          {fmtCurrency(wallet?.balance || 0)}
        </div>
      </div>
    </div>
  );

  // ═══════════════════════════════════════════════════════════════════════
  // HANDLER: Set PIN for player
  // ═══════════════════════════════════════════════════════════════════════
  const handleSetPin = async () => {
    if (newPin.length < 4) return onToast("Enter a 4-digit PIN", "error");
    if (pinSetStep === "new") {
      setPinSetStep("confirm");
      setConfirmPin("");
      return;
    }
    if (confirmPin !== newPin) {
      onToast("PINs do not match. Try again.", "error");
      setNewPin("");
      setConfirmPin("");
      setPinSetStep("new");
      return;
    }
    setLoading(true);
    try {
      await api("player-manage", {
        action: "set_pin",
        data: { player_id: pinNotSetPlayerId, new_pin: newPin },
      }, session.token);
      onToast("PIN set successfully! Player can now make purchases.", "success");
      // Reset to card_and_pin so they can re-verify with new PIN
      setStep("card_and_pin");
      setPin("");
      setNewPin("");
      setConfirmPin("");
      setPinSetStep("new");
      setPinNotSetPlayerId(null);
      setPinNotSetPlayerName(null);
    } catch (err) {
      onToast(err.error || "Failed to set PIN", "error");
    } finally {
      setLoading(false);
    }
  };

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Set PIN (when player has no PIN)
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "set_pin") {
    const activePin = pinSetStep === "new" ? newPin : confirmPin;
    const setActivePin = pinSetStep === "new" ? setNewPin : setConfirmPin;

    return (
      <div style={{ padding: 40, maxWidth: 480, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>
          Set Player PIN
        </h2>
        <p style={{ fontSize: 16, color: COLORS.textMuted, marginBottom: 24 }}>
          {pinNotSetPlayerName} does not have a PIN set.
        </p>

        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
          padding: "28px 24px", textAlign: "center",
        }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
            {pinSetStep === "new" ? "Enter New 4-Digit PIN" : "Confirm PIN"}
          </div>

          {/* PIN display */}
          <div style={{ display: "flex", justifyContent: "center", gap: 12, marginBottom: 24 }}>
            {[0, 1, 2, 3].map((i) => (
              <div key={i} style={{
                width: 48, height: 56, borderRadius: 10,
                border: `2px solid ${activePin.length > i ? COLORS.orange : COLORS.border}`,
                background: COLORS.surfaceLight,
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 28, fontWeight: 700, color: COLORS.heading,
              }}>
                {activePin.length > i ? "●" : ""}
              </div>
            ))}
          </div>

          {/* PIN pad */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, maxWidth: 280, margin: "0 auto" }}>
            {[1,2,3,4,5,6,7,8,9].map((d) => (
              <button key={d} onClick={() => activePin.length < 4 && setActivePin(activePin + d)}
                style={{
                  padding: "16px", fontSize: 24, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                  borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.heading,
                  cursor: "pointer", fontFamily: "'Space Mono', monospace",
                }}>{d}</button>
            ))}
            <button onClick={() => { setStep("card_and_pin"); setNewPin(""); setConfirmPin(""); setPinSetStep("new"); }}
              style={{
                padding: "16px", fontSize: 14, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.textMuted,
                cursor: "pointer", fontFamily: "inherit",
              }}>Cancel</button>
            <button onClick={() => activePin.length < 4 && setActivePin(activePin + "0")}
              style={{
                padding: "16px", fontSize: 24, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.heading,
                cursor: "pointer", fontFamily: "'Space Mono', monospace",
              }}>0</button>
            <button onClick={() => setActivePin(activePin.slice(0, -1))}
              style={{
                padding: "16px", fontSize: 18, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.textMuted,
                cursor: "pointer",
              }}>⌫</button>
          </div>

          <div style={{ marginTop: 20 }}>
            <Btn onClick={handleSetPin} disabled={loading || activePin.length < 4} wide size="lg">
              {loading ? "Setting PIN..." : pinSetStep === "new" ? "Next — Confirm PIN" : "Set PIN"}
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Card + PIN Entry
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "card_and_pin") {
    // Method selection
    if (!lookupMethod) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>
            Gift Card Purchase
          </h2>

          {/* Step instructions */}
          <div style={{
            background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
            padding: "14px 16px", marginBottom: 24,
          }}>
            {[
              { step: 1, label: "Select an Option Below" },
              { step: 2, label: "Player Enters PIN (Turn Screen)" },
              { step: 3, label: "Attendant Enters Sale Total" },
              { step: 4, label: "Confirm Amount & Description" },
            ].map(({ step: s, label }) => (
              <div key={s} style={{ display: "flex", alignItems: "center", gap: 10, padding: "4px 0" }}>
                <div style={{
                  width: 22, height: 22, borderRadius: "50%", background: COLORS.orange,
                  display: "flex", alignItems: "center", justifyContent: "center",
                  fontSize: 11, fontWeight: 700, color: "#fff", flexShrink: 0,
                }}>{s}</div>
                <span style={{ fontSize: 13, color: COLORS.text }}>{label}</span>
              </div>
            ))}
          </div>

          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            {isNfcLocation && (
              <button
                onClick={() => setLookupMethod("nfc")}
                style={{
                  background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
                  padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                  transition: "all .15s", boxShadow: `0 0 20px ${COLORS.orangeGlow}`,
                }}
              >
                <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <div style={{ fontSize: 24 }}>📲</div>
                  <div style={{
                    background: COLORS.orange, color: COLORS.heading, fontSize: 11,
                    padding: "4px 8px", borderRadius: 6, fontWeight: 700,
                  }}>RECOMMENDED</div>
                </div>
                <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4, marginTop: 8 }}>Tap Gift Card + PIN</div>
                <div style={{ fontSize: 14, color: COLORS.textMuted }}>Tap the player's gift card, then enter PIN</div>
              </button>
            )}

            <button
              onClick={() => setLookupMethod("card")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>{isNfcLocation ? "⌨️" : "💳"}</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>
                {isNfcLocation ? "Enter Card Number + PIN" : "Swipe Gift Card + PIN"}
              </div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>
                {isNfcLocation ? "Type card number and enter PIN" : "Swipe card and enter PIN"}
              </div>
            </button>

            <button
              onClick={() => setLookupMethod("qr")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>📱</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>
                Scan QR Code
              </div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>
                Scan QR code from player's WinStash app
              </div>
            </button>
          </div>
        </div>
      );
    }

    // Card number + PIN input
    if (lookupMethod === "card") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Gift Card Purchase
          </h2>

          {/* Player info box — shown after QR scan pre-populates the player */}
          {player && (
            <div style={{
              background: COLORS.successBg, border: `2px solid ${COLORS.success}`, borderRadius: 16,
              padding: "20px 24px", marginBottom: 20,
              display: "flex", justifyContent: "space-between", alignItems: "center",
            }}>
              <div>
                <div style={{ fontSize: 13, color: COLORS.success, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
                  Player Identified
                </div>
                <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading }}>
                  {player.name}
                </div>
                <div style={{ fontSize: 14, color: COLORS.textMuted, fontFamily: "'Space Mono', monospace" }}>
                  {player.id ? "WS-" + player.id.split("-")[0].toUpperCase() : ""}
                </div>
              </div>
              <div style={{ textAlign: "right" }}>
                <div style={{ fontSize: 13, color: COLORS.success, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
                  Balance
                </div>
                <div style={{ fontSize: 32, fontWeight: 700, color: COLORS.heading }}>
                  {fmtCurrency(wallet?.balance || 0)}
                </div>
              </div>
            </div>
          )}

          {/* Card Number Input — hidden when player was identified via QR */}
          {!player && <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
            padding: "24px", marginBottom: 20,
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12, textAlign: "center" }}>
              {isNfcLocation ? "Enter Gift Card Number" : "Swipe Gift Card"}
            </div>
            <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.border}` }}>
              <span style={{
                padding: "16px 0 16px 18px", fontSize: 22, fontWeight: 700,
                fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px",
              }}>{isNfcLocation ? "NFC-" : "WSH-"}</span>
              <input
                value={cardNumber}
                onChange={(e) => setCardNumber(isNfcLocation ? e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8) : fmtGiftCardSuffix(e.target.value))}
                placeholder={isNfcLocation ? "********" : "00000"}
                autoFocus
                maxLength={isNfcLocation ? 8 : 5}
                style={{
                  flex: 1, padding: "16px 18px 16px 0", fontSize: 22, fontWeight: 700,
                  border: "none", outline: "none",
                  fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                  background: "transparent", letterSpacing: "2px",
                  textTransform: "uppercase", color: COLORS.heading,
                }}
              />
            </div>
          </div>}

          {/* PIN Entry */}
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
            padding: "24px", marginBottom: 20,
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12, textAlign: "center" }}>
              Player's 4-Digit PIN
            </div>

            {/* PIN dots */}
            <div style={{ display: "flex", justifyContent: "center", gap: 16, marginBottom: 20 }}>
              {[0, 1, 2, 3].map((i) => (
                <div key={i} style={{
                  width: 20, height: 20, borderRadius: "50%",
                  background: i < pin.length ? COLORS.orange : COLORS.border,
                  transition: "background .15s",
                }} />
              ))}
            </div>

            {/* Number pad */}
            <div style={{
              display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10,
              maxWidth: 280, margin: "0 auto",
            }}>
              {[1, 2, 3, 4, 5, 6, 7, 8, 9, null, 0, "del"].map((key) => {
                if (key === null) return <div key="empty" />;
                return (
                  <button
                    key={key}
                    onClick={() => key === "del" ? handlePinDelete() : handlePinDigit(String(key))}
                    style={{
                      padding: "18px", fontSize: 24, fontWeight: 700,
                      background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`,
                      borderRadius: 12, color: key === "del" ? COLORS.textMuted : COLORS.heading,
                      cursor: "pointer", fontFamily: "inherit",
                      transition: "all .1s",
                    }}
                    onMouseEnter={(e) => { e.currentTarget.style.background = COLORS.border; }}
                    onMouseLeave={(e) => { e.currentTarget.style.background = COLORS.surfaceLight; }}
                  >
                    {key === "del" ? "⌫" : key}
                  </button>
                );
              })}
            </div>
          </div>

          {error && (
            <div style={{
              background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 12,
              padding: "14px 18px", marginBottom: 16, fontSize: 14, color: COLORS.error,
            }}>
              {error}
            </div>
          )}

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={() => { setLookupMethod(null); setPin(""); setCardNumber(""); setPlayer(null); setWallet(null); setError(null); }} variant="secondary" wide size="lg">
              Back
            </Btn>
            <Btn
              onClick={() => handleVerify()}
              disabled={loading || (!player && cardNumber.length < (isNfcLocation ? 8 : 5)) || pin.length < 4}
              wide size="lg"
            >
              {loading ? "Verifying..." : "Verify"}
            </Btn>
          </div>
        </div>
      );
    }

    // QR scan from app
    if (lookupMethod === "qr") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Gift Card Purchase
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px",
          }}>
            <h3 style={{ fontSize: 22, fontWeight: 700, color: COLORS.orange, marginBottom: 12, textAlign: "center" }}>
              Scan Player's QR Code
            </h3>
            <p style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 20, textAlign: "center" }}>
              Ask the player to open their WinStash app and show you the QR code on screen
            </p>
            {error && (
              <div style={{ background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 10, padding: "12px 16px", marginBottom: 20, color: COLORS.error, fontSize: 14 }}>
                {error}
              </div>
            )}
            <QrScanOptions
              onCameraClick={() => captureCtx?.scanQrWithCamera({ onError: (msg) => setError(msg) })}
              isCameraScanning={captureCtx?.isQrCameraScanning}
              disabled={loading}
              onCancel={() => { setLookupMethod(null); setError(null); }}
            />
          </div>
        </div>
      );
    }

    // NFC tap + PIN (card first, then PIN)
    if (lookupMethod === "nfc") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 16px" }}>
            Gift Card Purchase
          </h2>

          {loading && (
            <div style={{ textAlign: "center", padding: "10px 0 18px", fontSize: 16, color: COLORS.orange, fontWeight: 600 }}>
              Verifying...
            </div>
          )}

          {/* Step 1: NFC Tap area */}
          <div style={{
            background: COLORS.surface, border: `2px solid ${nfcId ? COLORS.success : COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px", textAlign: "center", marginBottom: 20,
            boxShadow: !nfcId ? `0 0 20px ${COLORS.orangeGlow}` : "none",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: nfcId ? COLORS.success : COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
              Step 1: Tap Gift Card
            </div>
            <div style={{ fontSize: 48, marginBottom: 12 }}>{nfcId ? "✅" : "📲"}</div>
            <p style={{ fontSize: 16, color: nfcId ? COLORS.success : COLORS.text, margin: 0 }}>
              {nfcId
                ? "Card scanned — now enter PIN below"
                : captureCtx?.webNfcState === "scanning"
                  ? "Hold the card flat against the back of the tablet"
                  : "Tap the player's gift card on the reader"}
            </p>
          </div>

          {/* Step 2: PIN Entry */}
          <div style={{
            background: COLORS.surface, border: `2px solid ${nfcId && pin.length === 4 ? COLORS.success : COLORS.border}`, borderRadius: 16,
            padding: "24px", marginBottom: 20,
            opacity: nfcId ? 1 : 0.5,
            pointerEvents: nfcId ? "auto" : "none",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12, textAlign: "center" }}>
              Step 2: Enter Player's 4-Digit PIN
            </div>

            <div style={{ display: "flex", justifyContent: "center", gap: 16, marginBottom: 20 }}>
              {[0, 1, 2, 3].map((i) => (
                <div key={i} style={{
                  width: 20, height: 20, borderRadius: "50%",
                  background: i < pin.length ? COLORS.orange : COLORS.border,
                  transition: "background .15s",
                }} />
              ))}
            </div>

            <div style={{
              display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10,
              maxWidth: 280, margin: "0 auto",
            }}>
              {[1, 2, 3, 4, 5, 6, 7, 8, 9, null, 0, "del"].map((key) => {
                if (key === null) return <div key="empty" />;
                return (
                  <button
                    key={key}
                    onClick={() => key === "del" ? handlePinDelete() : handlePinDigit(String(key))}
                    style={{
                      padding: "18px", fontSize: 24, fontWeight: 700,
                      background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`,
                      borderRadius: 12, color: key === "del" ? COLORS.textMuted : COLORS.heading,
                      cursor: "pointer", fontFamily: "inherit",
                    }}
                  >
                    {key === "del" ? "⌫" : key}
                  </button>
                );
              })}
            </div>
          </div>

          <Btn onClick={() => { setLookupMethod(null); setPin(""); setNfcId(null); setError(null); }} variant="secondary" wide size="lg">
            Back
          </Btn>
        </div>
      );
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Enter Purchase Amount
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "amount") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
          Gift Card Purchase
        </h2>
        <PurchasePlayerBar />

        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
          padding: "28px 24px",
        }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16, textAlign: "center" }}>
            Sale Total
          </div>
          <div style={{
            display: "flex", alignItems: "center", background: COLORS.surfaceLight,
            borderRadius: 12, border: `2px solid ${COLORS.orange}`, marginBottom: 20,
          }}>
            <span style={{
              padding: "20px 0 20px 20px", fontSize: 32, fontWeight: 700, color: COLORS.textMuted,
            }}>$</span>
            <input
              value={amount}
              onChange={(e) => {
                const val = e.target.value.replace(/[^0-9.]/g, "");
                // Allow only one decimal point and max 2 decimal places
                const parts = val.split(".");
                if (parts.length > 2) return;
                if (parts[1] && parts[1].length > 2) return;
                setAmount(val);
              }}
              placeholder="0.00"
              autoFocus
              inputMode="decimal"
              style={{
                flex: 1, padding: "20px 20px 20px 4px", fontSize: 32, fontWeight: 700,
                border: "none", outline: "none", fontFamily: "'Space Mono', monospace",
                boxSizing: "border-box", background: "transparent", color: COLORS.heading,
              }}
            />
          </div>

          {/* Quick amount presets */}
          <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 10, marginBottom: 20 }}>
            {[5, 10, 20, 25, 50, 75, 100, 200].map((preset) => (
              <button
                key={preset}
                onClick={() => setAmount(String(preset))}
                disabled={preset > (wallet?.balance || 0)}
                style={{
                  padding: "14px 8px", fontSize: 16, fontWeight: 700,
                  background: amount === String(preset) ? COLORS.orange : COLORS.surfaceLight,
                  color: preset > (wallet?.balance || 0) ? COLORS.textMuted : amount === String(preset) ? "#000" : COLORS.heading,
                  border: `1px solid ${amount === String(preset) ? COLORS.orange : COLORS.border}`,
                  borderRadius: 10, cursor: preset > (wallet?.balance || 0) ? "not-allowed" : "pointer",
                  fontFamily: "inherit", opacity: preset > (wallet?.balance || 0) ? 0.4 : 1,
                }}
              >
                ${preset}
              </button>
            ))}
          </div>

          {parseFloat(amount) > (wallet?.balance || 0) && (
            <div style={{
              background: COLORS.errorBg, border: `1px solid ${COLORS.error}`, borderRadius: 10,
              padding: "12px 16px", marginBottom: 16, fontSize: 14, color: COLORS.error,
            }}>
              Amount exceeds available balance of {fmtCurrency(wallet?.balance || 0)}
            </div>
          )}

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={handleReset} variant="secondary" wide size="lg">
              Cancel
            </Btn>
            <Btn
              onClick={() => {
                const amt = parseFloat(amount);
                if (!amt || amt <= 0) return onToast("Enter a valid amount", "error");
                if (amt > (wallet?.balance || 0)) return onToast("Insufficient balance", "error");
                setStep("confirm");
              }}
              disabled={!amount || parseFloat(amount) <= 0 || parseFloat(amount) > (wallet?.balance || 0)}
              wide size="lg"
            >
              Continue
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Confirm Purchase
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "confirm") {
    const purchaseAmount = parseFloat(amount);
    const feeCharged = feeData?.fee_to_charge || 0;
    const feeWaived = feeData?.fee_waived || 0;
    const totalDeducted = purchaseAmount + feeCharged;
    const newBalance = (wallet?.balance || 0) - totalDeducted;

    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
          Confirm Purchase
        </h2>

        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 20,
          padding: "32px 28px",
        }}>
          {/* Player */}
          <div style={{ textAlign: "center", marginBottom: 24 }}>
            <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Player</div>
            <div style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading }}>{player?.name}</div>
          </div>

          {/* Amount breakdown */}
          <div style={{
            background: COLORS.surfaceLight, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
            border: `1px solid ${COLORS.border}`,
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 12 }}>
              <span style={{ fontSize: 15, color: COLORS.textMuted }}>Current Balance</span>
              <span style={{ fontSize: 15, fontWeight: 700, color: COLORS.heading }}>{fmtCurrency(wallet?.balance || 0)}</span>
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
              <span style={{ fontSize: 15, color: COLORS.error }}>Purchase Amount</span>
              <span style={{ fontSize: 15, fontWeight: 700, color: COLORS.error }}>-{fmtCurrency(purchaseAmount)}</span>
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
              <span style={{ fontSize: 15, color: COLORS.error }}>GC Purchase Fee</span>
              <span style={{ fontSize: 15, fontWeight: 700, color: COLORS.error }}>-{fmtCurrency(feeData?.total_fee || 0)}</span>
            </div>
            {feeWaived > 0 && (
              <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
                <span style={{ fontSize: 15, color: COLORS.success }}>Fees Waived</span>
                <span style={{ fontSize: 15, fontWeight: 700, color: COLORS.success }}>+{fmtCurrency(feeWaived)}</span>
              </div>
            )}
            <div style={{ borderTop: `1px solid ${COLORS.border}`, paddingTop: 12, marginTop: 8, display: "flex", justifyContent: "space-between" }}>
              <span style={{ fontSize: 15, fontWeight: 700, color: COLORS.textMuted }}>Remaining Balance</span>
              <span style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading }}>{fmtCurrency(newBalance)}</span>
            </div>
          </div>

          {/* Optional description */}
          <div style={{ marginBottom: 24 }}>
            <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, marginBottom: 8 }}>
              Description (optional)
            </div>
            <input
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              placeholder="e.g., Fuel purchase, Store merchandise"
              style={{
                width: "100%", padding: "14px 16px", fontSize: 15,
                border: `1px solid ${COLORS.border}`, borderRadius: 10, outline: "none",
                background: COLORS.surfaceLight, color: COLORS.heading, boxSizing: "border-box",
                fontFamily: "inherit",
              }}
            />
          </div>

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={() => setStep("amount")} variant="secondary" wide size="lg">
              Back
            </Btn>
            <Btn onClick={handleExecute} disabled={loading} wide size="lg">
              {loading ? "Processing..." : `Confirm ${fmtCurrency(totalDeducted)}`}
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Purchase Complete
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "done" && txn?.queued) {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <div style={{
          background: COLORS.warningBg, border: `2px solid ${COLORS.warning}`,
          borderRadius: 20, padding: "44px 36px", textAlign: "center",
        }}>
          <div style={{ fontSize: 48, marginBottom: 16 }}>🟡</div>
          <h3 style={{ fontSize: 26, fontWeight: 700, margin: "0 0 8px", color: COLORS.warning }}>
            Purchase Queued (Offline)
          </h3>
          <p style={{ fontSize: 16, color: COLORS.textMuted, margin: "0 0 24px" }}>
            Will be processed when connection returns
          </p>
          <div style={{
            background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
            border: `1px solid ${COLORS.border}`, display: "flex", justifyContent: "space-between",
          }}>
            <div>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Queued Amount</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.warning }}>
                -{fmtCurrency(txn?.amount || 0)}
              </div>
            </div>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Est. Balance</div>
              <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.heading }}>
                {fmtCurrency(txn?.new_balance || 0)}
              </div>
            </div>
          </div>
          <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 24 }}>
            Estimated balance includes pending queue adjustments. Confirmed after sync.
          </div>
          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={handleReset} wide size="lg" variant="secondary">
              New Purchase
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  if (step === "done") {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
        <div style={{
          background: COLORS.successBg, border: `2px solid ${COLORS.success}`,
          borderRadius: 20, padding: "44px 36px", textAlign: "center",
        }}>
          <div style={{ fontSize: 56, marginBottom: 16 }}>✅</div>
          <h3 style={{ fontSize: 26, fontWeight: 700, margin: "0 0 8px", color: COLORS.success }}>
            Purchase Complete
          </h3>
          <p style={{ fontSize: 18, color: COLORS.text, margin: "0 0 24px" }}>
            {player?.name}
          </p>

          {/* Transaction Details */}
          <div style={{
            background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 16,
            border: `1px solid ${COLORS.border}`,
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 12 }}>
              <div>
                <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>Total Deducted</div>
                <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.error }}>
                  -{fmtCurrency(txn?.total_deducted || txn?.amount || 0)}
                </div>
              </div>
              <div style={{ textAlign: "right" }}>
                <div style={{ fontSize: 13, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase" }}>New Balance</div>
                <div style={{ fontSize: 28, fontWeight: 700, color: COLORS.heading }}>
                  {fmtCurrency(txn?.new_balance || 0)}
                </div>
              </div>
            </div>
            {/* Fee breakdown */}
            <div style={{ borderTop: `1px solid ${COLORS.border}`, paddingTop: 10 }}>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 4 }}>
                <span style={{ color: COLORS.textMuted }}>Purchase Amount</span>
                <span style={{ color: COLORS.text, fontWeight: 500 }}>{fmtCurrency(txn?.amount || 0)}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 4 }}>
                <span style={{ color: COLORS.error }}>GC Purchase Fee</span>
                <span style={{ color: COLORS.error, fontWeight: 500 }}>-{fmtCurrency(feeData?.total_fee || 0)}</span>
              </div>
              {(txn?.fee_absorbed || feeData?.fee_waived > 0) && (
                <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 4 }}>
                  <span style={{ color: COLORS.success, fontWeight: 600 }}>Fees Waived</span>
                  <span style={{ color: COLORS.success, fontWeight: 600 }}>+{fmtCurrency(txn?.fee_absorbed ? feeData?.total_fee || 0 : feeData?.fee_waived || 0)}</span>
                </div>
              )}
            </div>
          </div>

          {/* Confirmation Code */}
          <div style={{
            background: COLORS.surface, borderRadius: 14, padding: "20px 24px", marginBottom: 20,
            border: `2px solid ${COLORS.orange}`,
          }}>
            <div style={{ fontSize: 13, color: COLORS.orange, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 8 }}>
              Confirmation Code
            </div>
            <div style={{
              fontSize: 36, fontWeight: 700, color: COLORS.orange,
              fontFamily: "'Space Mono', monospace", letterSpacing: "4px",
            }}>
              {txn?.confirmation_code}
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 8 }}>
              Use this code for store POS reconciliation
            </div>
          </div>

          <div style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 24 }}>
            SMS receipt sent to player
          </div>

          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={handleReset} wide size="lg">
              New Purchase
            </Btn>
          </div>
          <div style={{ marginTop: 12 }}>
            <Btn onClick={handleReset} wide size="md" variant="secondary">
              Done
            </Btn>
          </div>
        </div>
      </div>
    );
  }
}

// ============================================================================
// CARD INVENTORY SCREEN
// ============================================================================
function CardInventoryScreen({ session, onToast }) {
  const captureCtx = useCaptureSDK();
  const [loading, setLoading] = useState(true);
  const [inventory, setInventory] = useState(null);
  const [error, setError] = useState(null);
  // Remove cards
  const [showRemove, setShowRemove] = useState(false);
  const [removeReason, setRemoveReason] = useState("lost_damaged");
  const [removeNotes, setRemoveNotes] = useState("");
  const [removing, setRemoving] = useState(false);
  const [selectedCardIds, setSelectedCardIds] = useState([]);
  // Provisioned cards list
  const [cardListTab, setCardListTab] = useState("assigned"); // "assigned" | "unassigned"
  const [cardList, setCardList] = useState(null); // { assigned: [], unassigned: [] }
  const [loadingCardList, setLoadingCardList] = useState(false);
  const [confirming, setConfirming] = useState(false);
  // Scan to Verify
  const [verifyInput, setVerifyInput] = useState("");
  const [verifyResult, setVerifyResult] = useState(null); // null | { found, card, wallet, player, error }
  const [verifyLoading, setVerifyLoading] = useState(false);
  const [verifyListening, setVerifyListening] = useState(false);

  const fetchInventory = async () => {
    setLoading(true);
    setError(null);
    try {
      const data = await api("card-inventory", {
        location_id: session.location?.id,
      }, session.token);
      setInventory(data);
    } catch (err) {
      setError(err.error || "Failed to load inventory");
      onToast(err.error || "Failed to load inventory", "error");
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { fetchInventory(); }, []);

  const fetchCardList = async () => {
    setLoadingCardList(true);
    try {
      const data = await api("card-inventory/inventory-cards", {}, session.token);
      setCardList(data);
    } catch (err) {
      onToast(err.error || "Failed to load card list", "error");
    } finally {
      setLoadingCardList(false);
    }
  };

  const handleRemoveCards = async () => {
    if (selectedCardIds.length === 0) {
      onToast("Select at least one card to retire", "error");
      return;
    }
    setRemoving(true);
    try {
      const result = await api("card-inventory/remove-cards", {
        type: "provisioned",
        card_ids: selectedCardIds,
        reason: removeReason,
        notes: removeNotes.trim() || null,
      }, session.token);
      onToast(`${result.quantity_removed} card${result.quantity_removed !== 1 ? "s" : ""} retired`, "success");
      setSelectedCardIds([]);
      setRemoveNotes("");
      setShowRemove(false);
      fetchInventory();
    } catch (err) {
      onToast(err.error || "Failed to retire cards", "error");
    } finally {
      setRemoving(false);
    }
  };

  const handleConfirmReceipt = async () => {
    setConfirming(true);
    try {
      const result = await api("card-inventory/confirm-receipt", {}, session.token);
      onToast(`${result.confirmed} card${result.confirmed !== 1 ? "s" : ""} confirmed received`, "success");
      fetchInventory();
    } catch (err) {
      onToast(err.error || "Failed to confirm receipt", "error");
    } finally {
      setConfirming(false);
    }
  };

  const verifyInputRef = React.useRef(null);

  const handleVerifyCard = async (input, isNfc = false) => {
    const trimmed = input?.trim();
    if (!trimmed) return;
    setVerifyLoading(true);
    setVerifyResult(null);

    // NFC taps now always write the card_number as plain NDEF Text (e.g. "NFC-4DE9AA5D").
    const payload = { card_number: trimmed.toUpperCase() };

    try {
      const data = await api("card-verify", payload, session.token);
      setVerifyResult(data);
    } catch (err) {
      setVerifyResult({ found: false, error: err.error || "Lookup failed" });
    } finally {
      setVerifyLoading(false);
    }
  };

  // NFC tap listener for verify
  React.useEffect(() => {
    if (!captureCtx?.setOnNfcTag || !verifyListening) return;
    captureCtx.setOnNfcTag((tag) => {
      setVerifyListening(false);
      handleVerifyCard(tag.cardId, true);
    });
    return () => captureCtx.setOnNfcTag(null);
  }, [captureCtx, verifyListening]);

  if (loading) {
    return (
      <div style={{ padding: 40, textAlign: "center" }}>
        <div style={{ fontSize: 48, marginBottom: 16 }}>📦</div>
        <div style={{ fontSize: 18, color: COLORS.textMuted }}>Loading inventory...</div>
      </div>
    );
  }

  if (error) {
    return (
      <div style={{ padding: 40, maxWidth: 720, margin: "0 auto", textAlign: "center" }}>
        <div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
        <div style={{ fontSize: 18, color: COLORS.error, marginBottom: 16 }}>{error}</div>
        <Btn onClick={fetchInventory} wide size="lg">Retry</Btn>
      </div>
    );
  }

  const { summary } = inventory || {};
  const provisionedAvailable = inventory?.provisioned_available || summary?.inventory || 0;
  const totalIssued = (summary?.issued || 0) + (summary?.active || 0);
  const totalFrozen = summary?.frozen || 0;
  const inTransit = summary?.in_transit || 0;

  return (
    <div style={{ padding: 40, maxWidth: 900, margin: "0 auto" }}>
      <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 8px" }}>
        Card Inventory
      </h2>
      <p style={{ fontSize: 16, color: COLORS.textMuted, marginBottom: 28 }}>
        {session.location?.name}
      </p>

      {/* ── Scan to Verify ── */}
      <div style={{
        background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
        padding: "20px 24px", marginBottom: 24,
      }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 14 }}>
          Verify Card
        </div>

        {/* Input row */}
        <div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
          <input
            ref={verifyInputRef}
            type="text"
            value={verifyInput}
            onChange={(e) => setVerifyInput(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") { e.target.blur(); handleVerifyCard(verifyInput); } }}
            placeholder="Enter card number or tap card above"
            style={{
              flex: 1, padding: "12px 14px", fontSize: 15, fontWeight: 600,
              background: COLORS.inputBg, color: COLORS.heading,
              border: `1px solid ${COLORS.border}`, borderRadius: 8,
              outline: "none", fontFamily: "'Space Mono', monospace",
            }}
          />
          <Btn
            onClick={() => { verifyInputRef.current?.blur(); handleVerifyCard(verifyInput); }}
            disabled={verifyLoading || !verifyInput.trim()}
            size="lg"
          >
            {verifyLoading ? "..." : "Look Up"}
          </Btn>
        </div>

        {/* NFC tap button — shown for all locations */}
        <Btn
          onClick={() => {
            // Blur ALL inputs so the HID scanner global listener can receive keystrokes
            verifyInputRef.current?.blur();
            document.activeElement?.blur();
            setVerifyResult(null);
            setVerifyListening(true);
          }}
          disabled={verifyLoading || verifyListening}
          variant="secondary"
          wide
          size="lg"
          style={{ marginBottom: verifyListening ? 12 : 0 }}
        >
          {verifyListening ? "🔵 Tap card on scanner now..." : "Tap Card to Verify (NFC Scanner)"}
        </Btn>

        {/* Result */}
        {verifyResult && (
          <div style={{
            marginTop: 14, padding: "16px 18px", borderRadius: 10,
            background: verifyResult.found ? COLORS.surfaceLight : "#1a0a0a",
            border: `2px solid ${verifyResult.found
              ? (verifyResult.card?.status === "active" ? COLORS.success
                : verifyResult.card?.status === "frozen" ? COLORS.error
                : verifyResult.card?.status === "inventory" ? (COLORS.blue || "#4a9eff")
                : COLORS.border)
              : COLORS.error}`,
          }}>
            {!verifyResult.found ? (
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <span style={{ fontSize: 22 }}>❌</span>
                <div>
                  <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.error }}>Card Not Found</div>
                  <div style={{ fontSize: 13, color: COLORS.textMuted }}>
                    {verifyResult.error || "This card is not in this location's inventory."}
                  </div>
                </div>
              </div>
            ) : (
              <>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 10 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    <span style={{ fontSize: 22 }}>
                      {verifyResult.card.status === "active" ? "✅"
                        : verifyResult.card.status === "frozen" ? "🔒"
                        : verifyResult.card.status === "inventory" ? "📦"
                        : "⚠️"}
                    </span>
                    <div>
                      <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.heading, fontFamily: "'Space Mono', monospace" }}>
                        {verifyResult.card.number}
                      </div>
                      <div style={{ fontSize: 12, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px" }}>
                        {verifyResult.card.type} · {verifyResult.card.status}
                      </div>
                    </div>
                  </div>
                  {verifyResult.wallet && (
                    <div style={{ textAlign: "right" }}>
                      <div style={{ fontSize: 20, fontWeight: 700, color: COLORS.heading }}>
                        {fmtCurrency(verifyResult.wallet.balance)}
                      </div>
                      <div style={{ fontSize: 11, color: COLORS.textMuted }}>balance</div>
                    </div>
                  )}
                </div>

                {verifyResult.player ? (
                  <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", background: COLORS.surface, borderRadius: 8 }}>
                    <span style={{ fontSize: 16 }}>👤</span>
                    <div>
                      <div style={{ fontSize: 14, fontWeight: 600, color: COLORS.heading }}>{verifyResult.player.name}</div>
                      <div style={{ fontSize: 12, color: COLORS.textMuted }}>
                        ***-{verifyResult.player.phone_last4}
                        {verifyResult.player.status !== "active" && (
                          <span style={{ color: COLORS.error, marginLeft: 8 }}>· {verifyResult.player.status}</span>
                        )}
                      </div>
                    </div>
                  </div>
                ) : (
                  <div style={{ fontSize: 13, color: COLORS.textMuted, fontStyle: "italic" }}>
                    Unassigned — not yet issued to a player
                  </div>
                )}
              </>
            )}
          </div>
        )}
      </div>

      {/* In-transit banner */}
      {inTransit > 0 && (
        <div style={{
          background: COLORS.blueBg || "#0d1a2d", border: `2px solid ${COLORS.blue || "#4a9eff"}`, borderRadius: 12,
          padding: "14px 20px", marginBottom: 20,
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12,
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <span style={{ fontSize: 22 }}>📬</span>
            <div>
              <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.blue || "#4a9eff" }}>
                {inTransit} Card{inTransit !== 1 ? "s" : ""} In Transit from HQ
              </div>
              <div style={{ fontSize: 13, color: COLORS.text }}>
                Confirm receipt to add to inventory
              </div>
            </div>
          </div>
          <Btn onClick={handleConfirmReceipt} disabled={confirming} size="sm">
            {confirming ? "Confirming..." : "Confirm Receipt"}
          </Btn>
        </div>
      )}

      {/* Provisioned Cards summary */}
      <div style={{ marginBottom: 16 }}>
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.blue || "#4a9eff"}`, borderRadius: 16,
          padding: "24px", textAlign: "center",
        }}>
          <div style={{ fontSize: 42, fontWeight: 700, color: COLORS.blue || "#4a9eff" }}>{provisionedAvailable}</div>
          <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.blue || "#4a9eff", textTransform: "uppercase", letterSpacing: ".5px" }}>Provisioned</div>
          <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 4 }}>Programmed, ready to issue</div>
        </div>
      </div>

      {/* Card status summary */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16, marginBottom: 24 }}>
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.success}`, borderRadius: 16,
          padding: "20px", textAlign: "center",
        }}>
          <div style={{ fontSize: 32, fontWeight: 700, color: COLORS.success }}>{totalIssued}</div>
          <div style={{ fontSize: 12, fontWeight: 700, color: COLORS.success, textTransform: "uppercase", letterSpacing: ".5px" }}>In Use</div>
        </div>
        <div style={{
          background: COLORS.surface, border: `2px solid ${totalFrozen > 0 ? COLORS.error : COLORS.border}`, borderRadius: 16,
          padding: "20px", textAlign: "center",
        }}>
          <div style={{ fontSize: 32, fontWeight: 700, color: totalFrozen > 0 ? COLORS.error : COLORS.textMuted }}>{totalFrozen}</div>
          <div style={{ fontSize: 12, fontWeight: 700, color: totalFrozen > 0 ? COLORS.error : COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px" }}>Frozen</div>
        </div>
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
          padding: "20px", textAlign: "center",
        }}>
          <div style={{ fontSize: 32, fontWeight: 700, color: COLORS.heading }}>{inventory?.total_cards || 0}</div>
          <div style={{ fontSize: 12, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase", letterSpacing: ".5px" }}>Total</div>
        </div>
      </div>

      {/* Remove Cards button / form */}
      {!showRemove ? (
        <Btn onClick={() => { setShowRemove(true); }} wide size="lg" variant="secondary" style={{ marginBottom: 20 }}>
          Remove Cards (Lost / Damaged)
        </Btn>
      ) : (
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.error}`, borderRadius: 12,
          padding: "24px", marginBottom: 20,
        }}>
          <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.error, marginBottom: 16 }}>
            Remove Cards — Lost / Damaged
          </div>

          {/* Card ID input */}
          <div style={{ marginBottom: 12 }}>
            <label style={{ fontSize: 13, color: COLORS.textMuted, display: "block", marginBottom: 4 }}>
              Card IDs to Retire (comma-separated)
            </label>
            <input
              type="text"
              value={selectedCardIds.join(", ")}
              onChange={(e) => {
                const ids = e.target.value.split(",").map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0);
                setSelectedCardIds(ids);
              }}
              placeholder="e.g. 1, 2, 3"
              style={{
                width: "100%", padding: "12px 16px", fontSize: 16, fontWeight: 700,
                background: COLORS.inputBg, color: COLORS.heading,
                border: `1px solid ${COLORS.border}`, borderRadius: 8,
                outline: "none", boxSizing: "border-box",
              }}
              autoFocus
            />
            <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 4 }}>
              Enter the internal card IDs of inventory-status cards to retire
            </div>
          </div>

          {/* Reason dropdown */}
          <div style={{ marginBottom: 12 }}>
            <label style={{ fontSize: 13, color: COLORS.textMuted, display: "block", marginBottom: 4 }}>
              Reason
            </label>
            <select
              value={removeReason}
              onChange={(e) => setRemoveReason(e.target.value)}
              style={{
                width: "100%", padding: "10px 16px", fontSize: 14,
                background: COLORS.inputBg, color: COLORS.text,
                border: `1px solid ${COLORS.border}`, borderRadius: 8,
                outline: "none", boxSizing: "border-box",
              }}
            >
              <option value="lost_damaged">Lost or Damaged</option>
              <option value="defective">Defective</option>
              <option value="expired">Expired</option>
              <option value="other">Other</option>
            </select>
          </div>

          {/* Notes */}
          <div style={{ marginBottom: 16 }}>
            <label style={{ fontSize: 13, color: COLORS.textMuted, display: "block", marginBottom: 4 }}>
              Notes (optional)
            </label>
            <input
              type="text"
              value={removeNotes}
              onChange={(e) => setRemoveNotes(e.target.value)}
              placeholder="Additional details..."
              style={{
                width: "100%", padding: "10px 16px", fontSize: 14,
                background: COLORS.inputBg, color: COLORS.text,
                border: `1px solid ${COLORS.border}`, borderRadius: 8,
                outline: "none", boxSizing: "border-box",
              }}
            />
          </div>

          <div style={{ display: "flex", gap: 12 }}>
            <Btn
              onClick={handleRemoveCards}
              disabled={removing || (removeType === "blank" ? !removeQty : selectedCardIds.length === 0)}
              style={{ flex: 1, background: COLORS.error }}
            >
              {removing ? "Retiring..." : `Retire ${selectedCardIds.length} Card${selectedCardIds.length !== 1 ? "s" : ""}`}
            </Btn>
            <Btn onClick={() => { setShowRemove(false); setRemoveNotes(""); setSelectedCardIds([]); }} variant="secondary" style={{ flex: 1 }}>
              Cancel
            </Btn>
          </div>
        </div>
      )}

      {/* Detailed breakdown */}
      <div style={{
        background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
        padding: "20px 24px", marginBottom: 20,
      }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
          Breakdown by Status
        </div>
        {Object.entries(summary || {}).map(([status, count]) => (
          <div key={status} style={{
            display: "flex", justifyContent: "space-between", alignItems: "center",
            padding: "8px 0", borderBottom: `1px solid ${COLORS.border}`,
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{
                display: "inline-block", width: 8, height: 8, borderRadius: "50%",
                background: status === "active" ? COLORS.success : status === "inventory" ? COLORS.blue || "#4a9eff" : status === "frozen" ? COLORS.error : status === "issued" ? COLORS.orange : COLORS.textMuted,
              }} />
              <span style={{ fontSize: 14, color: COLORS.text, textTransform: "capitalize" }}>{status}</span>
            </div>
            <span style={{ fontSize: 16, fontWeight: 700, color: COLORS.heading }}>{count}</span>
          </div>
        ))}
      </div>

      {/* Type breakdown if available */}
      {inventory?.by_type && (
        <div style={{
          background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
          padding: "20px 24px", marginBottom: 20,
        }}>
          <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
            By Card Type
          </div>
          {inventory.by_type.map((t) => (
            <div key={t.type} style={{
              display: "flex", justifyContent: "space-between", alignItems: "center",
              padding: "8px 0", borderBottom: `1px solid ${COLORS.border}`,
            }}>
              <span style={{ fontSize: 14, color: COLORS.text }}>{t.type === "nfc" ? "NFC" : "Magstripe"}</span>
              <span style={{ fontSize: 16, fontWeight: 700, color: COLORS.heading }}>{t.total} total ({t.available} available)</span>
            </div>
          ))}
        </div>
      )}

      {/* Provisioned Cards List */}
      <div style={{
        background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
        padding: "20px 24px", marginBottom: 20,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
          <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px" }}>
            Provisioned Cards
          </div>
          {!cardList ? (
            <Btn onClick={fetchCardList} size="sm" variant="secondary" disabled={loadingCardList}>
              {loadingCardList ? "Loading..." : "Load List"}
            </Btn>
          ) : (
            <Btn onClick={fetchCardList} size="sm" variant="secondary" disabled={loadingCardList}>
              {loadingCardList ? "..." : "Refresh"}
            </Btn>
          )}
        </div>

        {cardList && (
          <>
            {/* Tab toggle */}
            <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
              {[
                { key: "assigned", label: `Assigned (${cardList.assigned?.length || 0})` },
                { key: "unassigned", label: `Unassigned (${cardList.unassigned?.length || 0})` },
              ].map(({ key, label }) => (
                <button key={key} onClick={() => setCardListTab(key)} style={{
                  flex: 1, padding: "10px", borderRadius: 8, cursor: "pointer", fontWeight: 700, fontSize: 13,
                  background: cardListTab === key ? COLORS.orange : COLORS.surfaceLight,
                  color: cardListTab === key ? "#fff" : COLORS.text,
                  border: `2px solid ${cardListTab === key ? COLORS.orange : COLORS.border}`,
                }}>
                  {label}
                </button>
              ))}
            </div>

            {/* Card rows */}
            {cardListTab === "assigned" && (
              cardList.assigned?.length === 0 ? (
                <div style={{ textAlign: "center", color: COLORS.textMuted, fontSize: 14, padding: "16px 0" }}>No assigned cards</div>
              ) : (
                <div style={{ display: "flex", flexDirection: "column", gap: 8, maxHeight: 320, overflowY: "auto" }}>
                  {cardList.assigned?.map((c) => (
                    <div key={c.id} style={{
                      display: "flex", justifyContent: "space-between", alignItems: "center",
                      padding: "10px 12px", background: COLORS.surfaceLight, borderRadius: 8,
                      border: `1px solid ${COLORS.border}`,
                    }}>
                      <div>
                        <div
                          onClick={() => { copyToClipboard(c.card_number.replace(/^[A-Z]+-/i, "")); onToast("Copied!", "success"); }}
                          style={{ fontSize: 14, fontWeight: 700, color: COLORS.heading, fontFamily: "'Space Mono', monospace", cursor: "pointer" }}
                          title="Click to copy (copies suffix only)"
                        >
                          {c.card_number}
                        </div>
                        {c.wallet?.player && (
                          <div style={{ fontSize: 12, color: COLORS.textMuted }}>
                            {c.wallet.player.name} · ***-{c.wallet.player.phone_last4}
                          </div>
                        )}
                      </div>
                      <div style={{ textAlign: "right" }}>
                        <div style={{
                          fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px",
                          color: c.status === "active" ? COLORS.success : c.status === "frozen" ? COLORS.error : COLORS.orange,
                        }}>{c.status}</div>
                        {c.wallet?.balance != null && (
                          <div style={{ fontSize: 13, color: COLORS.text }}>{fmtCurrency(c.wallet.balance)}</div>
                        )}
                      </div>
                    </div>
                  ))}
                </div>
              )
            )}

            {cardListTab === "unassigned" && (
              cardList.unassigned?.length === 0 ? (
                <div style={{ textAlign: "center", color: COLORS.textMuted, fontSize: 14, padding: "16px 0" }}>No unassigned provisioned cards</div>
              ) : (
                <div style={{ display: "flex", flexDirection: "column", gap: 8, maxHeight: 320, overflowY: "auto" }}>
                  {cardList.unassigned?.map((c) => (
                    <div key={c.id} style={{
                      display: "flex", justifyContent: "space-between", alignItems: "center",
                      padding: "10px 12px", background: COLORS.surfaceLight, borderRadius: 8,
                      border: `1px solid ${COLORS.border}`,
                    }}>
                      <div
                        onClick={() => { copyToClipboard(c.card_number.replace(/^[A-Z]+-/i, "")); onToast("Copied!", "success"); }}
                        style={{ fontSize: 14, fontWeight: 700, color: COLORS.heading, fontFamily: "'Space Mono', monospace", cursor: "pointer" }}
                        title="Click to copy (copies suffix only)"
                      >
                        {c.card_number}
                      </div>
                      <div style={{ fontSize: 11, fontWeight: 700, color: COLORS.textMuted, textTransform: "uppercase" }}>
                        {c.card_type} · unassigned
                      </div>
                    </div>
                  ))}
                </div>
              )
            )}
          </>
        )}

        {!cardList && !loadingCardList && (
          <div style={{ textAlign: "center", color: COLORS.textMuted, fontSize: 13, padding: "8px 0" }}>
            Click "Load List" to view individual card records
          </div>
        )}
      </div>

      <Btn onClick={fetchInventory} variant="secondary" wide size="lg" disabled={loading}>
        {loading ? "Refreshing..." : "Refresh"}
      </Btn>
    </div>
  );
}

// ============================================================================
// Defined at module scope so React sees a stable component identity across re-renders.
// If defined inside PlayerSearchScreen, every state change creates a new function reference,
// causing React to unmount+remount the input on each keystroke (losing focus after 1 char).
function EditableField({ label, field, value, editMode, editFields, setEditFields }) {
  if (editMode) {
    return (
      <div style={{ marginBottom: 12 }}>
        <div style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 4 }}>
          {label}
        </div>
        <input
          value={editFields[field] !== undefined ? editFields[field] : (value || "")}
          onChange={(e) => setEditFields({ ...editFields, [field]: e.target.value })}
          style={{
            width: "100%", padding: "8px 10px", fontSize: 14,
            border: `1px solid ${COLORS.border}`, borderRadius: 6,
            background: COLORS.surfaceLight, color: COLORS.heading,
            fontFamily: "inherit", boxSizing: "border-box",
          }}
        />
      </div>
    );
  }
  return (
    <div style={{ marginBottom: 8 }}>
      <span style={{ fontSize: 12, color: COLORS.textMuted }}>{label}:</span>{" "}
      <span style={{ fontSize: 14, color: COLORS.text }}>{value || "—"}</span>
    </div>
  );
}

// ============================================================================
// PLAYER ACCOUNT SEARCH SCREEN
// ============================================================================
function PlayerSearchScreen({ session, onToast, onNavigate }) {
  const captureCtx = useCaptureSDK();
  const { scanId: scanDlId, isScanning: isCamScanningDl } = useIdScanner();
  const isNfcLocation = session.location?.card_reader_type === "nfc";
  const roleLevel = ROLE_LEVELS[session.operator?.role] || 0;

  // Steps: lookup → profile
  const [step, setStep] = useState("lookup");
  const [lookupMethod, setLookupMethod] = useState(null);
  const [loading, setLoading] = useState(false);

  // Player details from get_details
  const [details, setDetails] = useState(null);
  const [transactions, setTransactions] = useState([]);
  const [txPage, setTxPage] = useState(1);
  const [txPagination, setTxPagination] = useState(null);
  const [showTransactions, setShowTransactions] = useState(false);

  // Confirmation modal
  const [confirmAction, setConfirmAction] = useState(null); // { type, title, message, card_id }
  // Card management
  const [selectedCardId, setSelectedCardId] = useState(null); // expanded card for actions
  const [cardTab, setCardTab] = useState("active"); // "active" | "inactive"

  // Edit mode (Owner+)
  const [editMode, setEditMode] = useState(false);
  const [editFields, setEditFields] = useState({});

  // Set PIN
  const [showSetPin, setShowSetPin] = useState(false);
  const [setPinValue, setSetPinValue] = useState("");
  const [setPinConfirm, setSetPinConfirm] = useState("");
  const [setPinStep, setSetPinStep] = useState("new"); // "new" or "confirm"

  // Phone change request (Owner+)
  const [phoneChangeModal, setPhoneChangeModal] = useState(null); // null | "enter" | "scan_id" | "done"
  const [phoneChangeNewPhone, setPhoneChangeNewPhone] = useState("");
  const [phoneChangeReason, setPhoneChangeReason] = useState("");
  const [phoneChangeDlData, setPhoneChangeDlData] = useState(null);
  const [phoneChangeDlVerified, setPhoneChangeDlVerified] = useState(false);
  const [phoneChangeScanMsg, setPhoneChangeScanMsg] = useState("");
  const [phoneChangeError, setPhoneChangeError] = useState("");
  const [phoneChangeDoneMsg, setPhoneChangeDoneMsg] = useState("");

  // Lookup state
  const [cardNumber, setCardNumber] = useState("");
  const [idNumber, setIdNumber] = useState("");

  // ── Fetch full player details ────────────────────────────────────────
  const fetchDetails = async (params) => {
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "get_details",
        data: params,
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
        return;
      }

      setDetails(data);
      setStep("profile");
    } catch (err) {
      onToast(err.error || "Failed to load player details", "error");
    } finally {
      setLoading(false);
    }
  };

  // ── Lookup handlers ───────────────────────────────────────────────────
  const handleCardLookup = async () => {
    if (isNfcLocation) {
      // NFC location: card number format is NFC-XXXXXXXX (8 hex chars)
      if (!cardNumber || cardNumber.length < 8) return onToast("Enter the 8-character NFC card number", "error");
      setLoading(true);
      try {
        const data = await api("player-lookup", { card_number: "NFC-" + cardNumber.toUpperCase() }, session.token);
        await fetchDetails({ card_id: data.card.id });
      } catch (err) {
        onToast(err.error || "Card not found at this location", "error");
        setLoading(false);
      }
    } else {
      // Magstripe location: card number format is WSH-XXXXX (5 digits)
      if (!cardNumber || cardNumber.length < 5) return onToast("Enter a 5-digit card number", "error");
      setLoading(true);
      try {
        const data = await api("player-lookup", { card_number: "WSH-" + cardNumber }, session.token);
        await fetchDetails({ card_id: data.card.id });
      } catch (err) {
        onToast(err.error || "Card not found at this location", "error");
        setLoading(false);
      }
    }
  };

  const handleIdLookup = async (idOverride) => {
    const id = idOverride || idNumber;
    if (!id || id.length < 4) return onToast("Enter a valid ID number", "error");
    setLoading(true);
    try {
      const data = await api("player-lookup-id", { id_number: id }, session.token);
      await fetchDetails({ card_id: data.card.id });
    } catch (err) {
      onToast(err.error || "ID lookup failed", "error");
      setLoading(false);
    }
  };

  const handleNfcLookup = async (nfcNdefId) => {
    setLoading(true);
    try {
      // NFC tap sends the card_number (plain NDEF Text, e.g. "NFC-4DE9AA5D").
      const data = await api("player-lookup", { card_number: nfcNdefId.toUpperCase() }, session.token);
      await fetchDetails({ card_id: data.card.id });
    } catch (err) {
      onToast(err.error || "NFC card not found", "error");
      setLoading(false);
    }
  };

  const handleQrLookup = async (qrData) => {
    setLoading(true);
    try {
      const data = await api("player-lookup-qr", { qr_data: qrData }, session.token);
      await fetchDetails({ card_id: data.card.id });
    } catch (err) {
      onToast(err.error || "Invalid QR code", "error");
      setLoading(false);
    }
  };

  const handleNameSelect = async (result) => {
    await fetchDetails({ wallet_id: result.wallet_id });
  };

  // ── QR barcode listener — active while lookupMethod === "qr" ─────────
  useEffect(() => {
    if (lookupMethod !== "qr" || step !== "lookup") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode((data) => {
      handleQrLookup(data);
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, lookupMethod, step]);

  // ── ID barcode listener — active while lookupMethod === "id" ─────────
  useEffect(() => {
    if (lookupMethod !== "id" || step !== "lookup") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode((data) => {
      const parsed = parseDriversLicense(data);
      if (parsed.id_number) {
        handleIdLookup(parsed.id_number.toUpperCase());
      } else {
        onToast("Could not read ID barcode — try again or enter manually", "error");
      }
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, lookupMethod, step]); // eslint-disable-line

  // Restore HID focus when entering ID scan mode
  useEffect(() => {
    if (lookupMethod === "id" && step === "lookup") {
      captureCtx?.refocus?.();
    }
  }, [captureCtx, lookupMethod, step]); // eslint-disable-line

  // ── DL scan listener for phone change identity verification ──────────
  useEffect(() => {
    if (phoneChangeModal !== "scan_id") return;
    if (!captureCtx?.setOnBarcode) return;

    captureCtx.setOnBarcode((data) => {
      const parsed = parseDriversLicense(data);
      if (!parsed.id_number) {
        setPhoneChangeScanMsg("Could not read ID barcode. Please try again.");
        return;
      }
      const playerIdNum = details?.player?.id_number;
      if (playerIdNum && parsed.id_number.trim().toLowerCase() === playerIdNum.trim().toLowerCase()) {
        setPhoneChangeDlData(parsed);
        setPhoneChangeDlVerified(true);
        setPhoneChangeScanMsg("ID verified — matches player record.");
      } else if (!playerIdNum) {
        setPhoneChangeDlData(parsed);
        setPhoneChangeDlVerified(false);
        setPhoneChangeScanMsg("ID scanned. Player has no ID on file — admin will review.");
      } else {
        setPhoneChangeDlData(parsed);
        setPhoneChangeDlVerified(false);
        setPhoneChangeScanMsg("ID does not match player's record. Stored — admin will review.");
      }
    });

    return () => captureCtx.setOnBarcode(null);
  }, [captureCtx, phoneChangeModal, details?.player?.id_number]);

  // ── Phone change request submit ───────────────────────────────────────
  const handlePhoneChangeSubmit = async () => {
    setPhoneChangeError("");
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "request_phone_change",
        data: {
          player_id: details.player.id,
          new_phone: phoneChangeNewPhone.replace(/\D/g, ""),
          reason: phoneChangeReason.trim(),
          dl_scan_data: phoneChangeDlData,
        },
      }, session.token);

      if (data.error) {
        setPhoneChangeError(data.error);
      } else {
        setPhoneChangeDoneMsg(data.message || "Phone change request submitted.");
        setPhoneChangeModal("done");
        onToast("Phone change request submitted for admin review", "success");
      }
    } catch (err) {
      setPhoneChangeError(err.error || "Failed to submit request");
    } finally {
      setLoading(false);
    }
  };

  // ── Load transactions ────────────────────────────────────────────────
  const loadTransactions = async (page = 1) => {
    if (!details?.wallet?.id) return;
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "get_transactions",
        data: { wallet_id: details.wallet.id, page },
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
      } else {
        setTransactions(data.transactions || []);
        setTxPagination(data.pagination);
        setTxPage(page);
        setShowTransactions(true);
      }
    } catch (err) {
      onToast(err.error || "Failed to load transactions", "error");
    } finally {
      setLoading(false);
    }
  };

  // ── Actions ──────────────────────────────────────────────────────────
  const handleFreezeCard = async () => {
    const targetCardId = confirmAction?.card_id || details.card.id;
    const reason = confirmAction?.reason || "Frozen by operator";
    setConfirmAction(null);
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "freeze_card",
        data: { card_id: targetCardId, reason },
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast("Card frozen successfully", "success");
        setSelectedCardId(null);
        await fetchDetails({ wallet_id: details.wallet.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to freeze card", "error");
    } finally {
      setLoading(false);
    }
  };

  const handleUnfreezeCard = async () => {
    const targetCardId = confirmAction?.card_id || details.card.id;
    setConfirmAction(null);
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "unfreeze_card",
        data: { card_id: targetCardId, reason: "Unfrozen by operator" },
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast("Card unfrozen successfully", "success");
        setSelectedCardId(null);
        await fetchDetails({ wallet_id: details.wallet.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to unfreeze card", "error");
    } finally {
      setLoading(false);
    }
  };

  const handleFlagFraud = async () => {
    setConfirmAction(null);
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "flag_suspected_fraud",
        data: { wallet_id: details.wallet.id, reason: "Supervisor hold placed by operator" },
      }, session.token);
      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast("Supervisor hold placed", "warning");
        await fetchDetails({ wallet_id: details.wallet.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to flag account", "error");
    } finally {
      setLoading(false);
    }
  };

  const handlePinReset = async () => {
    setConfirmAction(null);
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "initiate_pin_reset",
        data: { player_id: details.player.id },
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast(`PIN reset initiated. SMS sent to ${data.phone_masked}`, "success");
        await fetchDetails({ card_id: details.card.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to reset PIN", "error");
    } finally {
      setLoading(false);
    }
  };

  const handleSaveEdit = async () => {
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "update_player_info",
        data: { player_id: details.player.id, ...editFields },
      }, session.token);

      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast("Player info updated", "success");
        setEditMode(false);
        setEditFields({});
        await fetchDetails({ card_id: details.card.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to update player info", "error");
    } finally {
      setLoading(false);
    }
  };

  const handleSetPlayerPin = async () => {
    if (setPinValue.length < 4) return onToast("Enter a 4-digit PIN", "error");
    if (setPinStep === "new") {
      setSetPinStep("confirm");
      setSetPinConfirm("");
      return;
    }
    if (setPinConfirm !== setPinValue) {
      onToast("PINs do not match. Try again.", "error");
      setSetPinValue("");
      setSetPinConfirm("");
      setSetPinStep("new");
      return;
    }
    setLoading(true);
    try {
      const data = await api("player-manage", {
        action: "set_pin",
        data: { player_id: details.player.id, new_pin: setPinValue },
      }, session.token);
      if (data.error) {
        onToast(data.error, "error");
      } else {
        onToast("PIN set successfully", "success");
        setShowSetPin(false);
        setSetPinValue("");
        setSetPinConfirm("");
        setSetPinStep("new");
        await fetchDetails({ wallet_id: details.wallet.id });
      }
    } catch (err) {
      onToast(err.error || "Failed to set PIN", "error");
    } finally {
      setLoading(false);
    }
  };

  const handleReset = () => {
    setStep("lookup");
    setLookupMethod(null);
    setDetails(null);
    setTransactions([]);
    setTxPage(1);
    setTxPagination(null);
    setShowTransactions(false);
    setEditMode(false);
    setEditFields({});
    setSelectedCardId(null);
    setShowSetPin(false);
    setSetPinValue("");
    setSetPinConfirm("");
    setSetPinStep("new");
    setCardNumber("");
    setIdNumber("");
    setConfirmAction(null);
  };

  // ═══════════════════════════════════════════════════════════════════════
  // CONFIRMATION MODAL
  // ═══════════════════════════════════════════════════════════════════════
  const ConfirmModal = () => {
    if (!confirmAction) return null;

    const handlers = {
      freeze: handleFreezeCard,
      unfreeze: handleUnfreezeCard,
      pin_reset: handlePinReset,
      flag_fraud: handleFlagFraud,
    };

    return (
      <div style={{
        position: "fixed", top: 0, left: 0, right: 0, bottom: 0,
        background: "rgba(0,0,0,0.7)", display: "flex", alignItems: "center",
        justifyContent: "center", zIndex: 1000,
      }}>
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.orange}`,
          borderRadius: 20, padding: "32px", maxWidth: 420, width: "90%",
        }}>
          <h3 style={{ fontSize: 22, fontWeight: 700, color: COLORS.heading, marginBottom: 16 }}>
            {confirmAction.title}
          </h3>
          <p style={{ fontSize: 16, color: COLORS.text, lineHeight: 1.6, marginBottom: 28 }}>
            {confirmAction.message}
          </p>
          <div style={{ display: "flex", gap: 12 }}>
            <Btn onClick={() => setConfirmAction(null)} variant="secondary" wide size="lg">
              Cancel
            </Btn>
            <Btn
              onClick={handlers[confirmAction.type]}
              color={confirmAction.type === "unfreeze" ? COLORS.success : confirmAction.type === "flag_fraud" ? COLORS.warning : COLORS.error}
              wide
              size="lg"
            >
              {confirmAction.type === "freeze" ? "Freeze Card" :
               confirmAction.type === "unfreeze" ? "Unfreeze Card" :
               confirmAction.type === "flag_fraud" ? "Place Hold" :
               "Reset PIN"}
            </Btn>
          </div>
        </div>
      </div>
    );
  };

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Player Lookup - Method Selection
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "lookup") {
    if (!lookupMethod) {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Player Accounts
          </h2>
          <p style={{ fontSize: 16, color: COLORS.textMuted, marginBottom: 28 }}>
            How would you like to find the player?
          </p>

          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            {isNfcLocation && (
              <button
                onClick={() => setLookupMethod("nfc")}
                style={{
                  background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
                  padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                  transition: "all .15s", boxShadow: `0 0 20px ${COLORS.orangeGlow}`,
                }}
              >
                <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <div style={{ fontSize: 24 }}>📲</div>
                  <div style={{
                    background: COLORS.orange, color: COLORS.heading, fontSize: 11,
                    padding: "4px 8px", borderRadius: 6, fontWeight: 700,
                  }}>RECOMMENDED</div>
                </div>
                <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4, marginTop: 8 }}>Tap Gift Card</div>
                <div style={{ fontSize: 14, color: COLORS.textMuted }}>Tap the player's gift card on the reader</div>
              </button>
            )}

            {!isNfcLocation && (
              <button
                onClick={() => setLookupMethod("card")}
                style={{
                  background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                  padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                  transition: "all .15s",
                }}
                onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
                onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
              >
                <div style={{ fontSize: 24, marginBottom: 8 }}>💳</div>
                <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Swipe Gift Card</div>
                <div style={{ fontSize: 14, color: COLORS.textMuted }}>Swipe the player's WinStash gift card</div>
              </button>
            )}

            <button
              onClick={() => setLookupMethod("qr")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>📱</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Scan QR Code</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Scan QR code from player's mobile app</div>
            </button>

            <button
              onClick={() => setLookupMethod("id")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>🪪</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Scan ID</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Look up player by their government ID number</div>
            </button>

            <button
              onClick={() => setLookupMethod("name")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>🔍</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Search by Name</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Find player by first or last name</div>
            </button>

            <button
              onClick={() => setLookupMethod("phone")}
              style={{
                background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
                padding: "24px", cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                transition: "all .15s",
              }}
              onMouseEnter={(e) => { e.currentTarget.style.borderColor = COLORS.orange; }}
              onMouseLeave={(e) => { e.currentTarget.style.borderColor = COLORS.border; }}
            >
              <div style={{ fontSize: 24, marginBottom: 8 }}>📞</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: COLORS.heading, marginBottom: 4 }}>Search by Phone Number</div>
              <div style={{ fontSize: 14, color: COLORS.textMuted }}>Find player by their mobile phone number</div>
            </button>
          </div>
        </div>
      );
    }

    // Card lookup input
    if (lookupMethod === "card") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Player Accounts
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px", textAlign: "center",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
              {isNfcLocation ? "Enter NFC Card Number" : "Swipe or Enter Card Number"}
            </div>
            <div style={{ display: "flex", alignItems: "center", background: COLORS.surfaceLight, borderRadius: 12, border: `2px solid ${COLORS.orange}` }}>
              <span style={{
                padding: "16px 0 16px 18px", fontSize: 22, fontWeight: 700,
                fontFamily: "'Space Mono', monospace", color: COLORS.textMuted, letterSpacing: "2px",
              }}>{isNfcLocation ? "NFC-" : "WSH-"}</span>
              <input
                value={cardNumber}
                onChange={(e) => setCardNumber(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, isNfcLocation ? 8 : 5))}
                placeholder={isNfcLocation ? "A1B2C3D4" : "00000"}
                autoFocus
                maxLength={isNfcLocation ? 8 : 5}
                onKeyDown={(e) => {
                  const minLen = isNfcLocation ? 8 : 5;
                  if (e.key === "Enter" && cardNumber.length >= minLen) handleCardLookup();
                }}
                style={{
                  flex: 1, padding: "16px 18px 16px 0", fontSize: 22, fontWeight: 700,
                  border: "none", outline: "none",
                  fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                  background: "transparent", letterSpacing: "2px",
                  textTransform: "uppercase", color: COLORS.heading,
                }}
              />
            </div>
            <p style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 10, marginBottom: 0 }}>
              {isNfcLocation ? "Enter the 8-character code printed on the NFC card (NFC-XXXXXXXX)" : "Swipe the gift card or enter the 5-digit code"}
            </p>
            <div style={{ marginTop: 20, display: "flex", gap: 12 }}>
              <Btn onClick={() => setLookupMethod(null)} variant="secondary" wide size="lg">
                Back
              </Btn>
              <Btn onClick={handleCardLookup} disabled={loading || cardNumber.length < (isNfcLocation ? 8 : 5)} wide size="lg">
                {loading ? "Looking up..." : "Find Player"}
              </Btn>
            </div>
          </div>
        </div>
      );
    }

    // NFC lookup
    if (lookupMethod === "nfc") {
      return (
        <NfcLookupView
          session={session}
          onToast={onToast}
          onBack={() => setLookupMethod(null)}
          onSuccess={handleNfcLookup}
          loading={loading}
          title="Player Accounts"
        />
      );
    }

    // QR Code lookup
    if (lookupMethod === "qr") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Player Accounts
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12, textAlign: "center" }}>
              Scan Player's QR Code
            </div>
            <p style={{ fontSize: 14, color: COLORS.textMuted, marginBottom: 20, textAlign: "center" }}>
              Ask the player to open their WinStash app and show you the QR code on screen
            </p>
            <QrScanOptions
              onCameraClick={() => captureCtx?.scanQrWithCamera({ onError: (msg) => onToast(msg, 'error') })}
              isCameraScanning={captureCtx?.isQrCameraScanning}
              disabled={loading}
              onCancel={() => setLookupMethod(null)}
            />
          </div>
        </div>
      );
    }

    // ID lookup
    if (lookupMethod === "id") {
      return (
        <div style={{ padding: 40, maxWidth: 720, margin: "0 auto" }}>
          <h2 style={{ fontSize: 26, fontWeight: 700, color: COLORS.heading, margin: "0 0 28px" }}>
            Player Accounts
          </h2>
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 16,
            padding: "28px 24px", textAlign: "center",
          }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
              Scan or Enter ID Number
            </div>
            <IdScanOptions
              onCameraClick={() => scanDlId({
                onSuccess: (raw) => {
                  const parsed = parseDriversLicense(raw);
                  if (parsed?.id_number) {
                    setIdNumber(parsed.id_number.toUpperCase());
                    handleIdLookup(parsed.id_number.toUpperCase());
                  } else {
                    onToast("Could not read ID number. Enter manually below.", "warning");
                  }
                },
                onError: (msg) => onToast(msg, 'error'),
              })}
              isCameraScanning={isCamScanningDl}
              disabled={loading}
            />
            <div style={{ display: "flex", alignItems: "center", gap: 12, margin: "20px 0 16px" }}>
              <div style={{ flex: 1, height: 1, background: COLORS.border }} />
              <span style={{ fontSize: 12, color: COLORS.textMuted }}>or enter manually</span>
              <div style={{ flex: 1, height: 1, background: COLORS.border }} />
            </div>
            <input
              value={idNumber}
              onChange={(e) => setIdNumber(e.target.value)}
              placeholder="Enter government ID number"
              onKeyDown={(e) => e.key === "Enter" && idNumber.length >= 4 && handleIdLookup()}
              style={{
                width: "100%", padding: "16px", fontSize: 18, fontWeight: 600,
                border: `2px solid ${COLORS.orange}`, borderRadius: 10, outline: "none",
                background: COLORS.surfaceLight, color: COLORS.heading,
                fontFamily: "'Space Mono', monospace", boxSizing: "border-box",
                marginBottom: 16,
              }}
            />
            <div style={{ display: "flex", gap: 12 }}>
              <Btn onClick={() => setLookupMethod(null)} variant="secondary" wide size="lg">
                Back
              </Btn>
              <Btn onClick={() => handleIdLookup()} disabled={loading || idNumber.length < 4} wide size="lg">
                {loading ? "Looking up..." : "Find Player"}
              </Btn>
            </div>
          </div>
        </div>
      );
    }

    // Name search
    if (lookupMethod === "name") {
      return (
        <NameSearchView
          session={session}
          onToast={onToast}
          onBack={() => setLookupMethod(null)}
          onSelect={handleNameSelect}
          title="Player Accounts"
        />
      );
    }

    if (lookupMethod === "phone") {
      return (
        <NameSearchView
          session={session}
          onToast={onToast}
          onBack={() => setLookupMethod(null)}
          onSelect={handleNameSelect}
          title="Player Accounts"
          searchLabel="Search by Phone Number"
          searchPlaceholder="Enter phone number..."
          inputMode="tel"
        />
      );
    }

  }

  // ═══════════════════════════════════════════════════════════════════════
  // STEP: Player Profile View
  // ═══════════════════════════════════════════════════════════════════════
  if (step === "profile" && details) {
    const { player, wallet, card, fraud } = details;

    // Type label map
    const txTypeLabels = {
      load: "LOAD",
      card_fee: "CARD FEE",
      withdrawal_hold: "WITHDRAWAL",
      withdrawal_complete: "WITHDRAWAL",
      withdrawal_void: "VOID",
      withdrawal_fee: "WITHDRAWAL FEE",
      adjustment_credit: "CREDIT ADJ",
      adjustment_debit: "DEBIT ADJ",
      promo_credit: "PROMO",
    };

    const txTypeColors = {
      load: COLORS.success,
      promo_credit: COLORS.success,
      adjustment_credit: COLORS.success,
      card_fee: COLORS.error,
      withdrawal_hold: COLORS.warning,
      withdrawal_complete: COLORS.error,
      withdrawal_void: COLORS.textMuted,
      withdrawal_fee: COLORS.error,
      adjustment_debit: COLORS.error,
    };

    return (
      <div style={{ padding: "24px 32px", maxWidth: 900, margin: "0 auto" }}>
        <ConfirmModal />

        {/* Player header */}
        <div style={{
          background: COLORS.surface, border: `2px solid ${COLORS.border}`, borderRadius: 16,
          padding: "20px 24px", marginBottom: 16,
          display: "flex", justifyContent: "space-between", alignItems: "center",
        }}>
          <div>
            <div style={{ fontSize: 24, fontWeight: 700, color: COLORS.heading }}>
              {player.name}
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 4 }}>
              <span style={{ fontSize: 14, color: COLORS.textMuted }}>
                Phone: {player.phone_masked}
              </span>
              <span style={{ fontSize: 14, color: COLORS.textMuted }}>
                DOB: {player.dob_year || "—"}
              </span>
              <span style={{ fontSize: 14, color: COLORS.textMuted }}>
                ID: {player.id_state || "—"}
              </span>
            </div>
            <div style={{
              display: "inline-block", marginTop: 8, padding: "3px 10px",
              borderRadius: 6, fontSize: 12, fontWeight: 700, textTransform: "uppercase",
              background: player.status === "active" ? COLORS.successBg : COLORS.errorBg,
              color: player.status === "active" ? COLORS.success : COLORS.error,
              border: `1px solid ${player.status === "active" ? COLORS.success : COLORS.error}`,
            }}>
              {player.status}
            </div>
            {player.has_review_hold && (
              <div style={{
                display: "inline-block", marginLeft: 8, marginTop: 8, padding: "3px 10px",
                borderRadius: 6, fontSize: 12, fontWeight: 700,
                background: COLORS.warningBg,
                color: COLORS.warning,
                border: `1px solid ${COLORS.warning}`,
              }}>
                ⚠ Account review in progress
              </div>
            )}
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontSize: 13, color: COLORS.success, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".5px" }}>
              Balance
            </div>
            <div style={{ fontSize: 36, fontWeight: 700, color: COLORS.heading }}>
              {fmtCurrency(wallet.balance)}
            </div>
          </div>
        </div>

        {/* Supervisor hold banners */}
        {fraud.withdrawal_blocked && (
          <div style={{
            background: COLORS.errorBg, border: `2px solid ${COLORS.error}`, borderRadius: 12,
            padding: "14px 20px", marginBottom: 12,
            display: "flex", alignItems: "center", gap: 12,
          }}>
            <span style={{ fontSize: 22 }}>⚠</span>
            <div>
              <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.error }}>
                Supervisor hold active — please see supervisor
              </div>
              <div style={{ fontSize: 13, color: COLORS.text }}>
                Loads can still be processed. Contact supervisor for withdrawal assistance.
              </div>
            </div>
          </div>
        )}
        {fraud.has_active_alerts && !fraud.withdrawal_blocked && (
          <div style={{
            background: COLORS.warningBg, border: `2px solid ${COLORS.warning}`, borderRadius: 12,
            padding: "14px 20px", marginBottom: 12,
            display: "flex", alignItems: "center", gap: 12,
          }}>
            <span style={{ fontSize: 22 }}>⚠</span>
            <div>
              <div style={{ fontSize: 15, fontWeight: 700, color: COLORS.warning }}>
                Account review in progress
              </div>
              <div style={{ fontSize: 13, color: COLORS.text }}>
                No restrictions applied. Loads and purchases proceed normally.
              </div>
            </div>
          </div>
        )}

        {/* Info cards row */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 }}>
          {/* Wallet Stats */}
          <div style={{
            background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
            padding: "16px 20px",
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
              Wallet Stats
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 4 }}>
              Total Loaded: <span style={{ color: COLORS.success, fontWeight: 600 }}>{fmtCurrency(wallet.total_loaded)}</span>
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 4 }}>
              Total Withdrawn: <span style={{ color: COLORS.text, fontWeight: 600 }}>{fmtCurrency(wallet.total_withdrawn)}</span>
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 4 }}>
              Total Fees: <span style={{ color: COLORS.text, fontWeight: 600 }}>{fmtCurrency(wallet.total_fees_paid)}</span>
            </div>
            <div style={{ fontSize: 13, color: COLORS.textMuted }}>
              Wallet Status: <span style={{
                color: wallet.status === "active" ? COLORS.success : COLORS.error,
                fontWeight: 600,
              }}>{wallet.status}</span>
            </div>
          </div>

          {/* Card Info — Active / Inactive tabs */}
          <div style={{
            background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
            padding: "16px 20px",
          }}>
            {(() => {
              const allCards = details.all_cards || [card];
              const activeCards = allCards.filter(c => ["active", "issued"].includes(c.status));
              const inactiveCards = allCards.filter(c => !["active", "issued"].includes(c.status));
              const shownCards = cardTab === "active" ? activeCards : inactiveCards;
              return (
                <>
                  {/* Tab header */}
                  <div style={{ display: "flex", gap: 6, marginBottom: 12 }}>
                    {[
                      { key: "active", label: `Active (${activeCards.length})` },
                      { key: "inactive", label: `Inactive (${inactiveCards.length})` },
                    ].map(tab => (
                      <button key={tab.key} onClick={() => { setCardTab(tab.key); setSelectedCardId(null); }}
                        style={{
                          padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 700, cursor: "pointer",
                          background: cardTab === tab.key ? COLORS.orange : COLORS.surfaceLight,
                          color: cardTab === tab.key ? "#fff" : COLORS.textMuted,
                          border: "none", fontFamily: "inherit",
                        }}
                      >{tab.label}</button>
                    ))}
                  </div>
                  {shownCards.length === 0 ? (
                    <div style={{ fontSize: 13, color: COLORS.textMuted, fontStyle: "italic", padding: "8px 0" }}>
                      No {cardTab} cards
                    </div>
                  ) : shownCards.map((c, idx) => {
                    const isExpanded = selectedCardId === c.id;
                    const canManage = roleLevel >= 2 && (c.status === "active" || c.status === "issued" || c.status === "frozen");
                    return (
                      <div key={c.id} style={{ marginBottom: idx < shownCards.length - 1 ? 6 : 0 }}>
                        <div
                          onClick={() => canManage ? setSelectedCardId(isExpanded ? null : c.id) : null}
                          style={{
                            padding: "8px 10px", borderRadius: isExpanded ? "8px 8px 0 0" : 8,
                            background: isExpanded ? COLORS.surfaceLight : c.id === card.id ? COLORS.surfaceLight : "transparent",
                            border: isExpanded ? `1px solid ${COLORS.orange}` : c.id === card.id ? `1px solid ${COLORS.border}` : "1px solid transparent",
                            borderBottom: isExpanded ? "none" : undefined,
                            cursor: canManage ? "pointer" : "default",
                          }}
                        >
                          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                            <span style={{ fontSize: 14, fontWeight: 700, fontFamily: "'Space Mono', monospace", color: COLORS.heading, letterSpacing: "1px" }}>{c.number}</span>
                            <span style={{
                              fontSize: 11, fontWeight: 700, textTransform: "uppercase", padding: "2px 8px", borderRadius: 4,
                              background: c.status === "active" ? COLORS.successBg : c.status === "frozen" ? COLORS.errorBg : COLORS.warningBg,
                              color: c.status === "active" ? COLORS.success : c.status === "frozen" ? COLORS.error : COLORS.warning,
                              border: `1px solid ${c.status === "active" ? COLORS.success : c.status === "frozen" ? COLORS.error : COLORS.warning}`,
                            }}>{c.status}</span>
                          </div>
                          <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 2 }}>
                            {c.type === "nfc" ? "NFC" : "Magstripe"}{canManage && !isExpanded ? " — tap to manage" : ""}
                          </div>
                        </div>
                        {/* Expanded card actions — Freeze only (Suspect Fraud is account-level) */}
                        {isExpanded && (
                          <div style={{ padding: "10px", background: COLORS.surfaceLight, border: `1px solid ${COLORS.orange}`, borderTop: "none", borderRadius: "0 0 8px 8px" }}>
                            <Btn
                              onClick={() => setConfirmAction(c.status === "frozen" ? {
                                type: "unfreeze", card_id: c.id,
                                title: "Unfreeze Card?",
                                message: `Only this card (${c.number}) will be re-enabled. Other cards and the player's balance are unaffected.`,
                              } : {
                                type: "freeze", card_id: c.id, reason: "Frozen by operator",
                                title: "Freeze This Card?",
                                message: `Card ${c.number} will be disabled. Only this card is affected — the player's balance and other cards remain active.`,
                              })}
                              color={c.status === "frozen" ? COLORS.success : COLORS.error} wide size="md"
                            >{c.status === "frozen" ? "Unfreeze" : "Freeze Card"}</Btn>
                          </div>
                        )}
                      </div>
                    );
                  })}
                  <div style={{ fontSize: 13, color: COLORS.textMuted, marginTop: 10 }}>
                    PIN Set: <span style={{ color: COLORS.text, fontWeight: 600 }}>{player.has_pin ? "Yes" : "No"}</span>
                  </div>
                </>
              );
            })()}
          </div>
        </div>

        {/* Quick action buttons — Redeem & Purchase */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
          <Btn
            onClick={() => {
              const cardNum = card?.number || null;
              if (onNavigate) onNavigate("load", { prefillCard: cardNum, skipNewCard: true });
            }}
            variant="secondary"
            wide
            size="lg"
          >
            Redeem Winnings
          </Btn>
          <Btn
            onClick={() => {
              if (onNavigate) onNavigate("purchase");
            }}
            variant="secondary"
            wide
            size="lg"
          >
            Gift Card Purchase
          </Btn>
        </div>

        {/* Action buttons — Transaction History & Search Another */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 }}>
          <Btn
            onClick={() => {
              if (showTransactions) {
                setShowTransactions(false);
              } else {
                setEditMode(false); setEditFields({});
                loadTransactions(1);
              }
            }}
            disabled={loading}
            variant={showTransactions ? "primary" : "secondary"}
            wide
            size="lg"
          >
            {loading && showTransactions ? "Loading..." : "Transaction History"}
          </Btn>
          <Btn onClick={handleReset} variant="secondary" wide size="lg">
            Search Another Player
          </Btn>
        </div>

        {/* Management buttons */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 16 }}>
          {!player.has_pin ? (
            <Btn
              onClick={() => {
                setShowTransactions(false);
                setEditMode(false); setEditFields({});
                setShowSetPin(!showSetPin);
                setSetPinValue(""); setSetPinConfirm(""); setSetPinStep("new");
              }}
              variant={showSetPin ? "primary" : "secondary"}
              wide
              size="lg"
            >
              Set PIN
            </Btn>
          ) : roleLevel >= 2 ? (
            <Btn
              onClick={() => setConfirmAction({
                type: "pin_reset",
                title: "Reset Player PIN?",
                message: `A notification will be sent to ${player.phone_masked}. The player's PIN will be cleared. They must set a new PIN at the kiosk or have an operator set one.`,
              })}
              variant="secondary"
              wide
              size="lg"
            >
              Reset PIN
            </Btn>
          ) : null}

          {roleLevel >= 3 && (
            <Btn
              onClick={() => {
                if (editMode) {
                  setEditMode(false); setEditFields({});
                } else {
                  setShowTransactions(false);
                  setEditMode(true); setEditFields({});
                }
              }}
              variant={editMode ? "primary" : "secondary"}
              wide
              size="lg"
            >
              Edit Info
            </Btn>
          )}
        </div>

        {/* Supervisor hold — account-level, manager+ only */}
        {roleLevel >= 2 && !fraud?.withdrawal_blocked && (
          <div style={{ marginBottom: 12 }}>
            <Btn
              onClick={() => setConfirmAction({
                type: "flag_fraud",
                title: "Place Supervisor Hold?",
                message: `This will block all withdrawals and in-store purchases for ${player.name}'s account. The player can still load winnings. Use this for suspected unauthorized use — not for a lost/stolen card (use Freeze Card instead).`,
              })}
              color={COLORS.warning} wide size="md"
            >Place Supervisor Hold (Account-Level)</Btn>
          </div>
        )}
        {fraud?.withdrawal_blocked && (
          <div style={{
            background: COLORS.warningBg, border: `1px solid ${COLORS.warning}`, borderRadius: 10,
            padding: "10px 14px", marginBottom: 12, fontSize: 13, color: COLORS.warning, fontWeight: 600,
          }}>
            ⚠ Supervisor hold active
          </div>
        )}

        {/* Set PIN panel */}
        {showSetPin && (
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 12,
            padding: "24px", marginBottom: 16, textAlign: "center",
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
              {setPinStep === "new" ? "Enter New 4-Digit PIN" : "Confirm PIN"}
            </div>

            {/* PIN display */}
            <div style={{ display: "flex", justifyContent: "center", gap: 12, marginBottom: 20 }}>
              {[0, 1, 2, 3].map((i) => {
                const activeVal = setPinStep === "new" ? setPinValue : setPinConfirm;
                return (
                  <div key={i} style={{
                    width: 44, height: 52, borderRadius: 10,
                    border: `2px solid ${activeVal.length > i ? COLORS.orange : COLORS.border}`,
                    background: COLORS.surfaceLight,
                    display: "flex", alignItems: "center", justifyContent: "center",
                    fontSize: 24, fontWeight: 700, color: COLORS.heading,
                  }}>
                    {activeVal.length > i ? "●" : ""}
                  </div>
                );
              })}
            </div>

            {/* PIN pad */}
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8, maxWidth: 240, margin: "0 auto 16px" }}>
              {[1,2,3,4,5,6,7,8,9].map((d) => {
                const activeVal = setPinStep === "new" ? setPinValue : setPinConfirm;
                const setActive = setPinStep === "new" ? setSetPinValue : setSetPinConfirm;
                return (
                  <button key={d} onClick={() => activeVal.length < 4 && setActive(activeVal + d)}
                    style={{
                      padding: "14px", fontSize: 22, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                      borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.heading,
                      cursor: "pointer", fontFamily: "'Space Mono', monospace",
                    }}>{d}</button>
                );
              })}
              <div />
              <button onClick={() => {
                const activeVal = setPinStep === "new" ? setPinValue : setPinConfirm;
                const setActive = setPinStep === "new" ? setSetPinValue : setSetPinConfirm;
                activeVal.length < 4 && setActive(activeVal + "0");
              }}
                style={{
                  padding: "14px", fontSize: 22, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                  borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.heading,
                  cursor: "pointer", fontFamily: "'Space Mono', monospace",
                }}>0</button>
              <button onClick={() => {
                const setActive = setPinStep === "new" ? setSetPinValue : setSetPinConfirm;
                const activeVal = setPinStep === "new" ? setPinValue : setPinConfirm;
                setActive(activeVal.slice(0, -1));
              }}
                style={{
                  padding: "14px", fontSize: 16, fontWeight: 700, border: `1px solid ${COLORS.border}`,
                  borderRadius: 10, background: COLORS.surfaceLight, color: COLORS.textMuted,
                  cursor: "pointer",
                }}>⌫</button>
            </div>

            <div style={{ display: "flex", gap: 12 }}>
              <Btn onClick={() => { setShowSetPin(false); setSetPinValue(""); setSetPinConfirm(""); setSetPinStep("new"); }} variant="secondary" wide size="lg">
                Cancel
              </Btn>
              <Btn onClick={handleSetPlayerPin} disabled={loading || (setPinStep === "new" ? setPinValue : setPinConfirm).length < 4} wide size="lg">
                {loading ? "Setting..." : setPinStep === "new" ? "Next" : "Set PIN"}
              </Btn>
            </div>
          </div>
        )}

        {/* Edit mode */}
        {editMode && roleLevel >= 3 && (
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 12,
            padding: "20px", marginBottom: 16,
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
              Edit Player Info
            </div>

            {/* Phone — read-only in edit mode; changes require a separate request + admin approval */}
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
              <div>
                <div style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 2 }}>
                  Phone
                </div>
                <div style={{ fontSize: 14, color: COLORS.text }}>{fmtPhone(player.phone || "")}</div>
              </div>
              <button
                onClick={() => {
                  setPhoneChangeModal("enter");
                  setPhoneChangeNewPhone("");
                  setPhoneChangeReason("");
                  setPhoneChangeDlData(null);
                  setPhoneChangeDlVerified(false);
                  setPhoneChangeScanMsg("");
                  setPhoneChangeError("");
                  setPhoneChangeDoneMsg("");
                }}
                style={{
                  fontSize: 12, color: COLORS.orange, background: "transparent",
                  border: `1px solid ${COLORS.orange}`, borderRadius: 6,
                  padding: "4px 10px", cursor: "pointer", fontFamily: "inherit",
                }}
              >
                Request Change
              </button>
            </div>
            <EditableField label="Email" field="email" value={player.email} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />
            <EditableField label="Address Line 1" field="address_line1" value={player.address_line1} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />
            <EditableField label="Address Line 2" field="address_line2" value={player.address_line2} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />
            <EditableField label="City" field="city" value={player.city} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />
            <EditableField label="State" field="state" value={player.state} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />
            <EditableField label="ZIP" field="zip" value={player.zip} editMode={editMode} editFields={editFields} setEditFields={setEditFields} />

            <div style={{ display: "flex", gap: 12, marginTop: 16 }}>
              <Btn onClick={() => { setEditMode(false); setEditFields({}); }} variant="secondary" wide size="lg">
                Cancel
              </Btn>
              <Btn
                onClick={handleSaveEdit}
                disabled={loading || Object.keys(editFields).length === 0}
                wide
                size="lg"
              >
                {loading ? "Saving..." : "Save Changes"}
              </Btn>
            </div>
          </div>
        )}

        {/* Phone Change Request Modal */}
        {phoneChangeModal && roleLevel >= 3 && (
          <div style={{
            background: COLORS.surface, border: `2px solid ${COLORS.orange}`, borderRadius: 12,
            padding: "20px", marginBottom: 16,
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 16 }}>
              Request Phone Number Change
            </div>

            {phoneChangeModal === "enter" && (
              <div>
                <div style={{ marginBottom: 12 }}>
                  <div style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 4 }}>Current Phone</div>
                  <div style={{ fontSize: 14, color: COLORS.textMuted }}>{fmtPhone(details.player.phone || "")}</div>
                </div>
                <div style={{ marginBottom: 12 }}>
                  <div style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 4 }}>New Phone Number *</div>
                  <input
                    value={fmtPhone(phoneChangeNewPhone)}
                    onChange={(e) => setPhoneChangeNewPhone(e.target.value.replace(/\D/g, "").slice(0, 10))}
                    placeholder="(555) 123-4567"
                    style={{
                      width: "100%", padding: "8px 10px", fontSize: 14,
                      border: `1px solid ${COLORS.border}`, borderRadius: 6,
                      background: COLORS.surfaceLight, color: COLORS.heading,
                      fontFamily: "inherit", boxSizing: "border-box",
                    }}
                  />
                </div>
                <div style={{ marginBottom: 12 }}>
                  <div style={{ fontSize: 11, color: COLORS.textMuted, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 4 }}>Reason for Change *</div>
                  <textarea
                    value={phoneChangeReason}
                    onChange={(e) => setPhoneChangeReason(e.target.value)}
                    placeholder="e.g. Lost phone, new number, typo at registration..."
                    rows={2}
                    style={{
                      width: "100%", padding: "8px 10px", fontSize: 14,
                      border: `1px solid ${COLORS.border}`, borderRadius: 6,
                      background: COLORS.surfaceLight, color: COLORS.heading,
                      fontFamily: "inherit", boxSizing: "border-box", resize: "none",
                    }}
                  />
                </div>
                {phoneChangeError && (
                  <div style={{ color: COLORS.error, fontSize: 12, marginBottom: 12 }}>{phoneChangeError}</div>
                )}
                <div style={{ display: "flex", gap: 12 }}>
                  <Btn onClick={() => setPhoneChangeModal(null)} variant="secondary" wide size="lg">Cancel</Btn>
                  <Btn
                    onClick={() => { setPhoneChangeError(""); setPhoneChangeModal("scan_id"); }}
                    disabled={phoneChangeNewPhone.replace(/\D/g, "").length < 10 || !phoneChangeReason.trim()}
                    wide size="lg"
                  >
                    Next: Scan ID
                  </Btn>
                </div>
              </div>
            )}

            {phoneChangeModal === "scan_id" && (
              <div>
                <div style={{
                  background: COLORS.surfaceLight, border: `1px solid ${COLORS.border}`,
                  borderRadius: 8, padding: "20px", textAlign: "center", marginBottom: 16,
                }}>
                  <div style={{ fontSize: 32, marginBottom: 8 }}>🪪</div>
                  <div style={{ fontSize: 15, color: COLORS.text, fontWeight: 600, marginBottom: 4 }}>
                    Scan Player's Driver's License
                  </div>
                  <div style={{ fontSize: 13, color: COLORS.textMuted }}>
                    Hold the barcode up to the scanner to verify identity
                  </div>
                </div>

                {phoneChangeScanMsg && (
                  <div style={{
                    padding: "10px 14px", borderRadius: 8, marginBottom: 12, fontSize: 13,
                    background: phoneChangeDlVerified ? "#1a3a1a" : "#2a2010",
                    color: phoneChangeDlVerified ? COLORS.success : COLORS.warning,
                    border: `1px solid ${phoneChangeDlVerified ? COLORS.success : COLORS.warning}`,
                  }}>
                    {phoneChangeDlVerified ? "✓ " : "⚠ "}{phoneChangeScanMsg}
                  </div>
                )}

                {phoneChangeDlData && (
                  <div style={{ fontSize: 12, color: COLORS.textMuted, marginBottom: 12 }}>
                    Scanned: {phoneChangeDlData.first_name} {phoneChangeDlData.last_name}
                    {phoneChangeDlData.state ? ` · ${phoneChangeDlData.state} ID` : ""}
                    {phoneChangeDlData.id_number ? ` · #${phoneChangeDlData.id_number}` : ""}
                  </div>
                )}

                <div style={{ display: "flex", gap: 12 }}>
                  <Btn onClick={() => setPhoneChangeModal("enter")} variant="secondary" size="lg">Back</Btn>
                  <Btn
                    onClick={handlePhoneChangeSubmit}
                    disabled={loading}
                    wide size="lg"
                  >
                    {loading ? "Submitting..." : phoneChangeDlData ? "Submit Request" : "Submit Without Scan"}
                  </Btn>
                </div>
                {!phoneChangeDlData && (
                  <div style={{ fontSize: 11, color: COLORS.textMuted, marginTop: 8 }}>
                    Submitting without a scan will require additional admin review
                  </div>
                )}
              </div>
            )}

            {phoneChangeModal === "done" && (
              <div style={{ textAlign: "center", padding: "8px 0" }}>
                <div style={{ fontSize: 32, marginBottom: 8 }}>✓</div>
                <div style={{ fontSize: 16, color: COLORS.success, fontWeight: 700, marginBottom: 8 }}>
                  Request Submitted
                </div>
                <div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 20 }}>
                  {phoneChangeDoneMsg}
                </div>
                <Btn onClick={() => setPhoneChangeModal(null)} size="lg">Close</Btn>
              </div>
            )}
          </div>
        )}

        {/* Transaction history */}
        {showTransactions && (
          <div style={{
            background: COLORS.surface, border: `1px solid ${COLORS.border}`, borderRadius: 12,
            padding: "16px 20px", marginBottom: 16,
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: COLORS.orange, textTransform: "uppercase", letterSpacing: ".5px", marginBottom: 12 }}>
              Transaction History
              {txPagination && (
                <span style={{ color: COLORS.textMuted, fontWeight: 400, textTransform: "none", fontSize: 12, marginLeft: 8 }}>
                  ({txPagination.total} total)
                </span>
              )}
            </div>

            {transactions.length === 0 ? (
              <div style={{ padding: "20px", textAlign: "center", color: COLORS.textMuted }}>
                No transactions found
              </div>
            ) : (
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {transactions.map((tx, i) => (
                  <div
                    key={i}
                    style={{
                      display: "flex", justifyContent: "space-between", alignItems: "center",
                      padding: "10px 12px", background: COLORS.surfaceLight, borderRadius: 8,
                    }}
                  >
                    <div>
                      <span style={{
                        fontSize: 11, fontWeight: 700, color: txTypeColors[tx.type] || COLORS.text,
                        textTransform: "uppercase", letterSpacing: ".5px",
                      }}>
                        {txTypeLabels[tx.type] || tx.type}
                      </span>
                      <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 2 }}>
                        {new Date(tx.created_at).toLocaleString()}
                      </div>
                    </div>
                    <div style={{ textAlign: "right" }}>
                      <div style={{
                        fontSize: 15, fontWeight: 700,
                        color: tx.amount >= 0 ? COLORS.success : COLORS.error,
                      }}>
                        {tx.amount >= 0 ? "+" : ""}{fmtCurrency(Math.abs(tx.amount))}
                      </div>
                      <div style={{ fontSize: 11, color: COLORS.textMuted }}>
                        Bal: {fmtCurrency(tx.balance_after)}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}

            {/* Pagination */}
            {txPagination && txPagination.total > txPagination.page_size && (
              <div style={{ display: "flex", justifyContent: "center", gap: 12, marginTop: 12 }}>
                <Btn
                  onClick={() => loadTransactions(txPage - 1)}
                  disabled={txPage <= 1 || loading}
                  variant="secondary"
                  size="md"
                >
                  Previous
                </Btn>
                <span style={{ padding: "8px 12px", color: COLORS.textMuted, fontSize: 13, display: "flex", alignItems: "center" }}>
                  Page {txPage} of {Math.ceil(txPagination.total / txPagination.page_size)}
                </span>
                <Btn
                  onClick={() => loadTransactions(txPage + 1)}
                  disabled={!txPagination.has_more || loading}
                  variant="secondary"
                  size="md"
                >
                  Next
                </Btn>
              </div>
            )}
          </div>
        )}

      </div>
    );
  }

  return null;
}

// ============================================================================
// MAIN APP
// ============================================================================
export default function App() {
  const [session, setSession] = useState(null);
  const [screen, setScreen] = useState("dashboard");
  const [toast, setToast] = useState(null);
  const [techSession, setTechSession] = useState(null);
  const [loadPrefillCard, setLoadPrefillCard] = useState(null);
  const [loadSkipNewCard, setLoadSkipNewCard] = useState(false);
  const [loadRegistrationCardNum, setLoadRegistrationCardNum] = useState(null);
  const [loadCompleted, setLoadCompleted] = useState(false);
  const [registerAssignedCard, setRegisterAssignedCard] = useState(null);
  // Offline mode state
  const [operatorPinHash, setOperatorPinHash] = useState(null);
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  const [syncErrors, setSyncErrors] = useState([]);
  const [isSyncing, setIsSyncing] = useState(false);
  const [logoutReason, setLogoutReason] = useState(null);
  // Device provisioning state: null = not checked yet, true = needs provisioning, false = OK
  const [needsProvisioning, setNeedsProvisioning] = useState(null);

  // navigate(screenName) or navigate(screenName, { prefillCard: "NFC-XXXXXXXX", skipNewCard: true })
  const handleNavigate = useCallback((dest, data = {}) => {
    if (data.prefillCard !== undefined) setLoadPrefillCard(data.prefillCard || null);
    if (data.skipNewCard !== undefined) setLoadSkipNewCard(!!data.skipNewCard);
    if (dest === "load" && data.prefillCard) {
      setLoadRegistrationCardNum(data.prefillCard);
      setLoadCompleted(false);
      setRegisterAssignedCard(null); // registration completed — load screen takes over tracking
    }
    window.scrollTo(0, 0);
    setScreen(dest);
  }, []);

  // Scroll to top on every screen change (handles dashboard's direct setScreen calls too)
  useEffect(() => { window.scrollTo(0, 0); }, [screen]);

  const showToast = useCallback((message, type = "info") => {
    setToast({ message, type, key: Date.now() });
  }, []);

  // ── Session restore on mount (page refresh while offline) ──────────────
  useEffect(() => {
    try {
      const raw = localStorage.getItem(SESSION_CACHE_KEY);
      if (!raw) return;
      const cached = JSON.parse(raw);
      if (Date.now() - cached.cached_at < SESSION_CACHE_TTL) {
        setSession({ token: cached.token, operator: cached.operator, location: cached.location });
        if (cached.pinHash) setOperatorPinHash(cached.pinHash);
      }
    } catch (_) {}
  }, []);

  // ── Device provisioning check on mount ────────────────────────────────
  useEffect(() => {
    // If device is already provisioned, skip
    if (localStorage.getItem(DEVICE_TOKEN_ID_KEY)) {
      setNeedsProvisioning(false);
      return;
    }
    // Check if provisioning is required (any location or global)
    // We query system_settings via the REST API
    (async () => {
      try {
        const res = await fetch(
          `${SUPABASE_URL}/rest/v1/system_settings?key=eq.pos_device_provisioning_required&select=value`,
          { headers: { apikey: ANON_KEY, Authorization: `Bearer ${ANON_KEY}` } }
        );
        const rows = await res.json();
        if (rows?.[0]?.value === 'true') {
          setNeedsProvisioning(true);
          return;
        }
        // Global is off — check if ANY location requires it
        const locRes = await fetch(
          `${SUPABASE_URL}/rest/v1/locations?pos_device_provisioning_required=eq.true&select=id&limit=1`,
          { headers: { apikey: ANON_KEY, Authorization: `Bearer ${ANON_KEY}` } }
        );
        const locs = await locRes.json();
        // If at least one location requires it, show provisioning
        // (we can't know which location the operator is at yet)
        setNeedsProvisioning(locs?.length > 0);
      } catch (_) {
        // Network error — assume not required so existing behavior is preserved
        setNeedsProvisioning(false);
      }
    })();
  }, []);

  // ── Online / offline listeners ─────────────────────────────────────────
  useEffect(() => {
    const goOnline  = () => setIsOnline(true);
    const goOffline = () => setIsOnline(false);
    window.addEventListener('online',  goOnline);
    window.addEventListener('offline', goOffline);
    return () => {
      window.removeEventListener('online',  goOnline);
      window.removeEventListener('offline', goOffline);
    };
  }, []);

  // ── Auto-sync when coming back online ──────────────────────────────────
  useEffect(() => {
    if (isOnline && session) {
      const pending = getOfflineQueue().filter(t => t.status === 'pending');
      if (pending.length > 0) syncQueue(session.token);
    }
  }, [isOnline]); // eslint-disable-line

  // ── Periodic wallet snapshot — disabled until pos-sync-snapshot is deployed ──

  // ── Kiosk LAN discovery (runs on session, retries every 5 min) ─────────
  // Kiosk LAN discovery disabled — HTTP requests blocked by Mixed Content on HTTPS
  // Re-enable once kiosk is deployed with HTTPS support
  // useEffect(() => {
  //   if (!session?.location?.id) return;
  //   discoverKiosk(session.location.id);
  //   const id = setInterval(() => discoverKiosk(session.location.id), 5 * 60 * 1000);
  //   return () => clearInterval(id);
  // }, [session?.location?.id]);

  // ── Inactivity timeout — auto-logout after 10 min of no interaction ────
  useEffect(() => {
    if (!session) return;
    let timer = setTimeout(doInactivityLogout, INACTIVITY_TIMEOUT_MS);
    function resetTimer() {
      clearTimeout(timer);
      timer = setTimeout(doInactivityLogout, INACTIVITY_TIMEOUT_MS);
    }
    function doInactivityLogout() {
      appendLocalLog({ action: "inactivity_logout", actor_type: "system", location_id: session?.location?.id, details: { reason: "inactivity_timeout" } });
      setSession(null);
      setOperatorPinHash(null);
      setSyncErrors([]);
      setScreen("dashboard");
      setTechSession(null);
      setLogoutReason("inactivity");
      try { localStorage.removeItem(SESSION_CACHE_KEY); } catch (_) {}
    }
    for (const ev of INACTIVITY_EVENTS) window.addEventListener(ev, resetTimer, { passive: true });
    return () => {
      clearTimeout(timer);
      for (const ev of INACTIVITY_EVENTS) window.removeEventListener(ev, resetTimer);
    };
  }, [session?.token]); // eslint-disable-line

  const syncQueue = async (token) => {
    const pending = getOfflineQueue().filter(t => t.status === 'pending');
    if (!pending.length || isSyncing) return;
    setIsSyncing(true);
    showToast(`Syncing ${pending.length} queued transaction${pending.length > 1 ? 's' : ''}…`, 'info');
    try {
      const result = await api('pos-sync', { transactions: pending }, token);
      updateOfflineQueueResults(result.results);
      const ok   = result.results.filter(r =>  r.success).length;
      const fail = result.results.filter(r => !r.success).length;
      if (fail > 0) {
        setSyncErrors(result.results.filter(r => !r.success));
        showToast(`Sync: ${ok} succeeded, ${fail} failed — see details below`, 'warning');
      } else {
        setSyncErrors([]);
        showToast(`${ok} transaction${ok > 1 ? 's' : ''} synced successfully`, 'success');
      }
    } catch {
      showToast('Sync failed — will retry when connection stabilises', 'error');
    } finally {
      setIsSyncing(false);
    }
  };

  // Back button handler — unassigns the registration card if clerk cancels before loading funds
  const handleBack = useCallback(() => {
    if (screen === "load" && loadRegistrationCardNum && !loadCompleted) {
      const cardNum = loadRegistrationCardNum;
      setLoadRegistrationCardNum(null);
      showToast(`${cardNum} returned to inventory`, "info");
      api("player-manage", {
        action: "unassign_card",
        data: { card_number: cardNum },
      }, session?.token).catch(() => {});
    } else if (screen === "register" && registerAssignedCard) {
      const cardNum = registerAssignedCard;
      setRegisterAssignedCard(null);
      showToast(`${cardNum} returned to inventory`, "info");
      api("player-manage", {
        action: "unassign_card",
        data: { card_number: cardNum },
      }, session?.token).catch(() => {});
    }
    setScreen("dashboard");
  }, [screen, loadRegistrationCardNum, loadCompleted, registerAssignedCard, session, showToast]);

  const handleLogout = () => {
    appendLocalLog({ action: "operator_logout", actor_type: "operator", actor_id: session?.operator?.id, actor_name: session?.operator?.name, location_id: session?.location?.id, details: { location: session?.location?.name } });
    setSession(null);
    setOperatorPinHash(null);
    setSyncErrors([]);
    setScreen("dashboard");
    setTechSession(null);
    try { localStorage.removeItem(SESSION_CACHE_KEY); } catch (_) {}
  };

  const handleSettingsClick = () => {
    setScreen("techLogin");
  };

  const handleTechLoginSuccess = (techData) => {
    // Include operator role and token from current session for permission checks and team management
    setTechSession({
      ...techData,
      operatorRole: session.operator?.role,
      operatorToken: session.token,
    });
    setScreen("settings");
  };

  const handleSettingsExit = () => {
    setTechSession(null);
    setScreen("dashboard");
  };

  const globalStyles = `
    @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=Space+Mono:wght@400;700&display=swap');
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'DM Sans', -apple-system, sans-serif; background: ${COLORS.bg};
      /* Prevent rubber-band scroll and text selection on POS touch screens */
      overscroll-behavior: none; -webkit-user-select: none; user-select: none; }
    input, textarea { -webkit-user-select: text; user-select: text; }
    @keyframes slideDown { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } }
    @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
    /* Landscape layout: wider content area for tablets */
    .pos-content { max-width: 860px; margin: 0 auto; }
    @media (min-width: 1024px) { .pos-content { max-width: 1080px; } }
    @media (min-width: 1280px) { .pos-content { max-width: 1200px; } }
  `;

  // — Device provisioning required but not yet provisioned
  if (needsProvisioning === true) {
    return (
      <>
        <style>{globalStyles}</style>
        <ProvisioningScreen onProvisioned={() => setNeedsProvisioning(false)} />
        {toast && <Toast {...toast} onClose={() => setToast(null)} />}
      </>
    );
  }

  // — Not logged in
  if (!session) {
    return (
      <>
        <style>{globalStyles}</style>
        <LoginScreen logoutReason={logoutReason} onClearReason={() => setLogoutReason(null)} onLogin={(s) => {
          const sessionData = { token: s.token, operator: s.operator, location: s.location };
          setSession(sessionData);
          if (s.pinHash) setOperatorPinHash(s.pinHash);
          try {
            localStorage.setItem(SESSION_CACHE_KEY, JSON.stringify({ ...sessionData, pinHash: s.pinHash, cached_at: Date.now() }));
          } catch (_) {}
          setScreen("dashboard");
          setLogoutReason(null);
          appendLocalLog({ action: "operator_login", actor_type: "operator", actor_id: s.operator?.id, actor_name: s.operator?.name, location_id: s.location?.id, details: { location: s.location?.name, role: s.operator?.role } });
          // syncLocalLog(s.token); // disabled until admin-activity/sync is deployed
        }} />
        {toast && <Toast {...toast} onClose={() => setToast(null)} />}
      </>
    );
  }

  // — Technician login screen (no top bar)
  if (screen === "techLogin") {
    return (
      <>
        <style>{globalStyles}</style>
        <TechnicianLoginScreen
          onCancel={() => setScreen("dashboard")}
          onSuccess={handleTechLoginSuccess}
          onToast={showToast}
        />
        {toast && <Toast {...toast} onClose={() => setToast(null)} />}
      </>
    );
  }

  // — Settings form (with technician session)
  if (screen === "settings" && techSession) {
    return (
      <HidScannerProvider>
        <style>{globalStyles}</style>
        <div style={{ minHeight: "100vh", background: COLORS.bg }}>
          <TopBar
            session={session}
            onLogout={handleLogout}
            onBack={handleSettingsExit}
            showBack={true}
            title="Location Settings"
          />
          <div style={{ height: 64 }} />
          <LocationSettingsForm
            techSession={techSession}
            onExit={handleSettingsExit}
            onToast={showToast}
          />
        </div>
        {toast && <Toast {...toast} onClose={() => setToast(null)} />}
      </HidScannerProvider>
    );
  }

  // — Logged in (normal screens)
  const showBack = screen !== "dashboard";
  const titles = {
    dashboard: "WinStash POS",
    register: "Register Player",
    load: "Redeem Winnings",
    purchase: "Gift Card Purchase",
    inventory: "Card Inventory",
    accounts: "Player Accounts",
    withdrawal: "Cash Out Player",
  };

  const pendingCount = getOfflineQueue().filter(t => t.status === 'pending').length;

  return (
    <HidScannerProvider>
      <style>{globalStyles}</style>

      <div style={{ minHeight: "100vh", background: COLORS.bg }}>
        <TopBar
          session={session}
          onLogout={handleLogout}
          onBack={handleBack}
          onSettings={handleSettingsClick}
          showBack={showBack}
          title={titles[screen]}
        />
        <div style={{ height: 64 }} />

        {/* Offline status banner */}
        {!isOnline && (
          <div style={{
            background: COLORS.warning, color: '#000',
            padding: '7px 20px', fontSize: 13, fontWeight: 700, textAlign: 'center',
          }}>
            Offline Mode — transactions are saved locally and will sync when connection returns
            {pendingCount > 0 && ` • ${pendingCount} pending`}
          </div>
        )}
        {isSyncing && (
          <div style={{
            background: COLORS.blue, color: '#fff',
            padding: '7px 20px', fontSize: 13, fontWeight: 600, textAlign: 'center',
          }}>
            Syncing queued transactions…
          </div>
        )}
        {syncErrors.length > 0 && (
          <div style={{
            background: COLORS.errorBg, border: `1px solid ${COLORS.error}`,
            padding: '12px 20px', margin: '8px 16px', borderRadius: 8, fontSize: 13,
          }}>
            <div style={{ color: COLORS.error, fontWeight: 700, marginBottom: 6 }}>
              Some transactions failed to sync — review required:
            </div>
            {syncErrors.map((e, i) => (
              <div key={i} style={{ color: COLORS.text, marginBottom: 3 }}>
                • {e.local_id?.slice(0, 8)} — {e.error}
              </div>
            ))}
            <button onClick={() => setSyncErrors([])} style={{
              marginTop: 8, color: COLORS.textMuted, fontSize: 12,
              background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
            }}>
              Dismiss
            </button>
          </div>
        )}

        {screen === "dashboard" && (
          <DashboardScreen session={session} onNavigate={setScreen} onToast={showToast} />
        )}
        {screen === "register" && (
          <RegisterScreen session={session} onToast={showToast} onNavigate={handleNavigate} onCardAssigned={setRegisterAssignedCard} />
        )}
        {screen === "load" && (
          <LoadScreen session={session} onToast={showToast} prefillCard={loadPrefillCard} skipNewCard={loadSkipNewCard} onPrefillConsumed={() => { setLoadPrefillCard(null); setLoadSkipNewCard(false); }} onLoadComplete={() => { setLoadCompleted(true); setLoadRegistrationCardNum(null); }} isOnline={isOnline} operatorPinHash={operatorPinHash} />
        )}
        {screen === "purchase" && (
          <PurchaseScreen session={session} onToast={showToast} isOnline={isOnline} />
        )}
        {screen === "inventory" && (
          <CardInventoryScreen session={session} onToast={showToast} />
        )}
        {screen === "accounts" && (
          <PlayerSearchScreen session={session} onToast={showToast} onNavigate={handleNavigate} />
        )}
        {screen === "withdrawal" && (
          <WithdrawalScreen session={session} onBack={() => setScreen("dashboard")} onToast={showToast} />
        )}
      </div>

      {toast && <Toast {...toast} onClose={() => setToast(null)} />}
    </HidScannerProvider>
  );
}


// Auto-select field content on focus throughout the POS
document.addEventListener("focusin", (e) => {
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
    e.target.select();
  }
});

// Mount the app
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
