'use client';

import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/context/AuthContext';
import {
  eventsApi,
  doorApi,
  type Event,
  type DoorAttendee,
  type DoorAttendeesResponse,
  type DoorEntryMethod,
  type DoorPaymentMethod,
  type DoorSummary,
} from '@/lib/api';
import {
  QrCodeIcon,
  MagnifyingGlassIcon,
  ClockIcon,
  ArrowLeftIcon,
  XMarkIcon,
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
import clsx from 'clsx';
import { formatCurrency, parseDate, EVENT_TIMEZONE } from '@/lib/utils';

import { buildIndex, searchAttendees, hasExactMatch } from './_lib/search';
import { newIdempotencyKey, submitDoorAction, undoDoorAction } from './_lib/doorActions';
import { playSuccessSound, playErrorSound, vibrate } from './_lib/feedback';
import { QRScannerOverlay } from './_components/QRScannerOverlay';
import { AttendeeRow } from './_components/AttendeeRow';
import { WalkInRow, emptyWalkIn, type WalkInDraft } from './_components/WalkInRow';
import { SessionSheet, type SessionEntry } from './_components/SessionSheet';

// ═══════════════════════════════════════════════════════════════
// Door check-in screen
//
// One screen, no tabs. Manual name search is the primary interaction because
// most people arrive without their QR open; the camera is one tap away, and a
// walk-in can be created and charged without leaving the screen.
//
// Everything created here is born confirmed, paid (or comp) and checked in, in
// a single atomic call. No confirm dialogs anywhere — a 10-second Undo replaces
// them, which is what keeps the queue moving.
// ═══════════════════════════════════════════════════════════════

const REFRESH_INTERVAL_MS = 30_000;
const FLASH_MS = 900;
const UNDO_WINDOW_MS = 10_000;

const clockTime = (value: Date | string) =>
  (typeof value === 'string' ? parseDate(value) : value).toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit',
    timeZone: EVENT_TIMEZONE,
  });

const METHOD_PAST_TENSE: Record<DoorPaymentMethod, string> = {
  cash: 'paid cash',
  bitcoin: 'paid bitcoin',
  transfer: 'paid by transfer',
  guest: 'in as guest',
};

export default function AdminDoorPage() {
  const router = useRouter();
  const { user } = useAuth();
  const backHref = user?.role === 'staff' ? '/admin/events' : '/admin';

  // ── Events ──
  const [events, setEvents] = useState<Event[]>([]);
  const [selectedEventId, setSelectedEventId] = useState('');
  const [loadingEvents, setLoadingEvents] = useState(true);

  // ── Attendee list (preloaded once, searched in memory) ──
  const [data, setData] = useState<DoorAttendeesResponse | null>(null);
  const [listError, setListError] = useState<string | null>(null);
  const [loadingList, setLoadingList] = useState(false);

  // ── Screen state ──
  const [query, setQuery] = useState('');
  const [expandedId, setExpandedId] = useState<string | null>(null);
  const [flashId, setFlashId] = useState<string | null>(null);
  const [busyId, setBusyId] = useState<string | null>(null);
  const [walkInOpen, setWalkInOpen] = useState(false);
  const [walkInDraft, setWalkInDraft] = useState<WalkInDraft>(emptyWalkIn());
  const [scannerOpen, setScannerOpen] = useState(false);
  const [sessionOpen, setSessionOpen] = useState(false);

  // ── Session bookkeeping ──
  const [sessionEntries, setSessionEntries] = useState<SessionEntry[]>([]);
  const [doorCount, setDoorCount] = useState(0);
  const [checkedInCount, setCheckedInCount] = useState(0);
  const [summary, setSummary] = useState<DoorSummary | null>(null);
  const [summaryLoading, setSummaryLoading] = useState(false);

  const inputRef = useRef<HTMLInputElement>(null);
  const capacityWarnedRef = useRef(false);
  const lastScannedRef = useRef('');
  const selectedEventIdRef = useRef('');

  useEffect(() => {
    selectedEventIdRef.current = selectedEventId;
  }, [selectedEventId]);

  const eventMeta = data?.event;
  const price = eventMeta?.price ?? 0;
  const currency = eventMeta?.currency ?? 'PYG';
  const capacity = eventMeta?.capacity ?? 0;

  // ─── Load events ─────────────────────────────────────────────
  useEffect(() => {
    eventsApi
      .getAll()
      .then((res) => {
        const bookable = res.events.filter((e) => e.status === 'published' || e.status === 'unlisted');
        setEvents(bookable);

        // The door is always working one event: default to the one about to
        // start, so staff can pick up the phone and start scanning.
        const now = Date.now();
        const upcoming = bookable
          .filter((e) => new Date(e.startDatetime).getTime() >= now)
          .sort((a, b) => +new Date(a.startDatetime) - +new Date(b.startDatetime));
        const fallback = bookable
          .slice()
          .sort((a, b) => +new Date(b.startDatetime) - +new Date(a.startDatetime));
        const chosen = upcoming[0] || fallback[0];
        if (chosen) setSelectedEventId(chosen.id);
      })
      .catch(console.error)
      .finally(() => setLoadingEvents(false));
  }, []);

  // ─── Preload / refresh the attendee list ─────────────────────
  const loadAttendees = useCallback(
    async (eventId: string, opts?: { silent?: boolean }) => {
      if (!eventId) return;
      if (!opts?.silent) setLoadingList(true);
      try {
        const res = await doorApi.attendees(eventId);
        setData(res);
        setCheckedInCount(res.stats.checkedIn);
        setListError(null);
      } catch (error: any) {
        // A failed background refresh must never wipe a list staff are working from.
        if (!opts?.silent) setListError(error?.message || 'Failed to load attendees');
      } finally {
        if (!opts?.silent) setLoadingList(false);
      }
    },
    [],
  );

  useEffect(() => {
    if (!selectedEventId) {
      setData(null);
      return;
    }
    setQuery('');
    setExpandedId(null);
    setWalkInOpen(false);
    capacityWarnedRef.current = false;
    loadAttendees(selectedEventId);
  }, [selectedEventId, loadAttendees]);

  // Keep the list fresh without ever costing a keystroke.
  useEffect(() => {
    if (!selectedEventId) return;
    const interval = setInterval(() => {
      loadAttendees(selectedEventIdRef.current, { silent: true });
    }, REFRESH_INTERVAL_MS);
    const onVisible = () => {
      if (document.visibilityState === 'visible') {
        loadAttendees(selectedEventIdRef.current, { silent: true });
      }
    };
    document.addEventListener('visibilitychange', onVisible);
    return () => {
      clearInterval(interval);
      document.removeEventListener('visibilitychange', onVisible);
    };
  }, [selectedEventId, loadAttendees]);

  // ─── Search (in memory, no network per keystroke) ────────────
  const indexed = useMemo(
    () => (data?.attendees || []).map((attendee) => ({ attendee, index: buildIndex(attendee) })),
    [data?.attendees],
  );
  const results = useMemo(() => searchAttendees(indexed, query), [indexed, query]);

  const trimmedQuery = query.trim();
  const showWalkInRow =
    !!selectedEventId && (walkInOpen || (trimmedQuery.length >= 2 && !hasExactMatch(results, trimmedQuery)));

  const focusInput = useCallback(() => {
    // A 0ms defer lets the row unmount before focus moves, which stops mobile
    // browsers from scrolling the list back to the top.
    setTimeout(() => inputRef.current?.focus(), 0);
  }, []);

  // ─── Local list mutation ─────────────────────────────────────
  const upsertAttendee = useCallback((attendee: DoorAttendee) => {
    setData((prev) => {
      if (!prev) return prev;
      const existing = prev.attendees.findIndex((a) => a.ticketId === attendee.ticketId);
      const attendees =
        existing >= 0
          ? prev.attendees.map((a, i) => (i === existing ? attendee : a))
          : [...prev.attendees, attendee].sort((a, b) =>
              a.fullName.localeCompare(b.fullName, undefined, { sensitivity: 'base' }),
            );
      return { ...prev, attendees };
    });
  }, []);

  const patchAttendee = useCallback((ticketId: string, patch: Partial<DoorAttendee>) => {
    setData((prev) =>
      prev
        ? {
            ...prev,
            attendees: prev.attendees.map((a) => (a.ticketId === ticketId ? { ...a, ...patch } : a)),
          }
        : prev,
    );
  }, []);

  // ─── Undo ────────────────────────────────────────────────────
  const handleUndo = useCallback(
    async (entry: SessionEntry) => {
      const eventId = selectedEventIdRef.current;
      setSessionEntries((prev) =>
        prev.map((e) => (e.idempotencyKey === entry.idempotencyKey ? { ...e, undone: true } : e)),
      );
      setCheckedInCount((c) => Math.max(0, c - 1));
      if (entry.entry === 'walkin') setDoorCount((c) => Math.max(0, c - 1));

      try {
        await undoDoorAction(eventId, entry.idempotencyKey);
        toast.success(`Undone — ${entry.name}`, { position: 'bottom-center' });
      } catch (error: any) {
        toast.error(`Could not undo ${entry.name}: ${error?.message || 'request failed'}`, {
          position: 'bottom-center',
          duration: 8000,
        });
      } finally {
        // Whatever happened, the server is the truth about who is inside.
        loadAttendees(eventId, { silent: true });
      }
    },
    [loadAttendees],
  );

  const showUndoToast = useCallback(
    (message: string, entry: SessionEntry) => {
      toast.custom(
        (t) => (
          <div
            className={clsx(
              'flex items-center gap-3 bg-emerald-600 text-white rounded-2xl px-4 py-3 shadow-lg max-w-[92vw]',
              t.visible ? 'animate-in fade-in' : 'opacity-0',
            )}
          >
            <span className="font-semibold truncate">{message}</span>
            <button
              onClick={() => {
                toast.dismiss(t.id);
                handleUndo(entry);
              }}
              className="flex-shrink-0 min-h-[40px] px-3 font-bold underline underline-offset-2 active:scale-95 transition-transform"
            >
              Undo
            </button>
            <button
              onClick={() => toast.dismiss(t.id)}
              className="flex-shrink-0 min-h-[40px] w-8 flex items-center justify-center opacity-70"
              aria-label="Dismiss"
            >
              <XMarkIcon className="w-5 h-5" />
            </button>
          </div>
        ),
        { duration: UNDO_WINDOW_MS, position: 'bottom-center' },
      );
    },
    [handleUndo],
  );

  // ─── The one write path ──────────────────────────────────────
  // Every completed action on this screen — scan, tap, collect, walk-in — flows
  // through here so the flash, the counter, the session feed and the undo all
  // behave identically.
  const runAction = useCallback(
    async (opts: {
      ticketId?: string;
      attendee?: { firstName: string; lastName?: string; phone?: string; email?: string; ruc?: string };
      payment?: { method: DoorPaymentMethod; amount: number };
      entry: DoorEntryMethod;
      displayName: string;
      /** Ticket row to flash and optimistically mark as in. */
      optimisticTicketId?: string;
      /** Row (or 'walk-in') to keep dimmed until the write settles. */
      busyKey: string;
    }) => {
      const eventId = selectedEventIdRef.current;
      if (!eventId) return;
      setBusyId(opts.busyKey);

      const idempotencyKey = newIdempotencyKey();
      const now = new Date();

      const sessionEntry: SessionEntry = {
        idempotencyKey,
        ticketId: opts.ticketId || '',
        name: opts.displayName,
        at: clockTime(now),
        entry: opts.entry,
        method: opts.payment?.method ?? null,
        amount: opts.payment?.amount ?? 0,
        undone: false,
        failed: false,
      };

      // ── Optimistic: the person walks in now, not when the request lands ──
      setSessionEntries((prev) => [sessionEntry, ...prev].slice(0, 200));
      setCheckedInCount((c) => c + 1);
      if (opts.entry === 'walkin') setDoorCount((c) => c + 1);

      const previousRow = opts.optimisticTicketId
        ? data?.attendees.find((a) => a.ticketId === opts.optimisticTicketId)
        : undefined;

      if (opts.optimisticTicketId) {
        patchAttendee(opts.optimisticTicketId, {
          status: 'checked_in',
          checkedIn: true,
          checkinAt: now.toISOString(),
          ...(opts.payment
            ? {
                paymentStatus: opts.payment.method === 'guest' ? 'comp' : 'paid',
                amountDue: 0,
                doorMethod: opts.payment.method,
              }
            : {}),
        });
        setFlashId(opts.optimisticTicketId);
        setTimeout(() => setFlashId((id) => (id === opts.optimisticTicketId ? null : id)), FLASH_MS);
      }

      vibrate(120);
      playSuccessSound();

      const paidSuffix = opts.payment ? `, ${METHOD_PAST_TENSE[opts.payment.method]}` : '';
      showUndoToast(`${opts.displayName} checked in${paidSuffix}`, sessionEntry);

      setQuery('');
      setExpandedId(null);
      setWalkInOpen(false);
      setWalkInDraft(emptyWalkIn());
      focusInput();

      // ── The write itself: background, retried, never blocking the queue ──
      try {
        const res = await submitDoorAction(eventId, {
          ticketId: opts.ticketId,
          attendee: opts.attendee,
          payment: opts.payment,
          entryMethod: opts.entry,
          idempotencyKey,
        });
        upsertAttendee(res.attendee);
        setSessionEntries((prev) =>
          prev.map((e) =>
            e.idempotencyKey === idempotencyKey
              ? { ...e, ticketId: res.attendee.ticketId, name: res.attendee.fullName }
              : e,
          ),
        );
        if (res.warnings?.includes('at_capacity') && !capacityWarnedRef.current) {
          capacityWarnedRef.current = true;
          toast('Event at capacity, added anyway', { icon: '⚠️', position: 'bottom-center', duration: 6000 });
        }
      } catch (error: any) {
        // Retries are exhausted: make the failure loud and name the person, so
        // staff know exactly who to redo.
        setSessionEntries((prev) =>
          prev.map((e) => (e.idempotencyKey === idempotencyKey ? { ...e, failed: true } : e)),
        );
        setCheckedInCount((c) => Math.max(0, c - 1));
        if (opts.entry === 'walkin') setDoorCount((c) => Math.max(0, c - 1));
        if (previousRow) upsertAttendee(previousRow);
        playErrorSound();
        vibrate([100, 50, 100]);
        toast.error(`FAILED — ${opts.displayName} is NOT checked in. ${error?.message || ''}`.trim(), {
          duration: 12000,
          position: 'bottom-center',
        });
        loadAttendees(eventId, { silent: true });
      } finally {
        setBusyId((current) => (current === opts.busyKey ? null : current));
      }
    },
    [data?.attendees, patchAttendee, upsertAttendee, showUndoToast, focusInput, loadAttendees],
  );

  // ─── Row interactions ────────────────────────────────────────
  const handleRowTap = useCallback(
    (attendee: DoorAttendee) => {
      // Already inside, or needs money first: expand rather than act. Only a
      // settled, not-yet-arrived attendee is a one-tap check-in.
      const settled = attendee.paymentStatus === 'paid' || attendee.paymentStatus === 'comp';
      if (attendee.checkedIn || attendee.status === 'cancelled' || !settled) {
        setExpandedId((id) => (id === attendee.ticketId ? null : attendee.ticketId));
        return;
      }
      runAction({
        ticketId: attendee.ticketId,
        entry: 'search',
        displayName: attendee.fullName,
        optimisticTicketId: attendee.ticketId,
        busyKey: attendee.ticketId,
      });
    },
    [runAction],
  );

  const handleRowPay = useCallback(
    (attendee: DoorAttendee, method: DoorPaymentMethod, amount: number) => {
      runAction({
        ticketId: attendee.ticketId,
        payment: { method, amount },
        entry: 'search',
        displayName: attendee.fullName,
        optimisticTicketId: attendee.ticketId,
        busyKey: attendee.ticketId,
      });
    },
    [runAction],
  );

  const handleWalkInPay = useCallback(
    (method: DoorPaymentMethod, amount: number) => {
      const firstName = walkInDraft.firstName.trim();
      if (!firstName) return;
      const displayName = walkInDraft.lastName.trim()
        ? `${firstName} ${walkInDraft.lastName.trim()}`
        : firstName;
      runAction({
        attendee: {
          firstName,
          lastName: walkInDraft.lastName.trim() || undefined,
          phone: walkInDraft.phone.trim() || undefined,
          email: walkInDraft.email.trim() || undefined,
          ruc: walkInDraft.ruc.trim() || undefined,
        },
        payment: { method, amount },
        entry: 'walkin',
        displayName,
        busyKey: 'walk-in',
      });
    },
    [walkInDraft, runAction],
  );

  // ─── Scanning ────────────────────────────────────────────────
  const handleScan = useCallback(
    (decodedText: string) => {
      if (decodedText === lastScannedRef.current) return;
      lastScannedRef.current = decodedText;
      setTimeout(() => {
        lastScannedRef.current = '';
      }, 2000);

      // The QR may hold a ticket URL or the bare code.
      const urlMatch = decodedText.match(/\/ticket\/([a-zA-Z0-9-_]+)/);
      const code = urlMatch ? urlMatch[1] : decodedText;

      const attendee = (data?.attendees || []).find(
        (a) => a.qrCode === code || a.ticketId === code,
      );

      setScannerOpen(false);

      if (!attendee) {
        playErrorSound();
        vibrate([100, 50, 100]);
        toast.error('Ticket not found for this event', { position: 'bottom-center', duration: 6000 });
        focusInput();
        return;
      }

      if (attendee.checkedIn) {
        playErrorSound();
        vibrate([100, 50, 100]);
        toast(`${attendee.fullName} already checked in${attendee.checkinAt ? ` at ${clockTime(attendee.checkinAt)}` : ''}`, {
          icon: 'ℹ️',
          position: 'bottom-center',
          duration: 6000,
        });
        setQuery(attendee.fullName);
        setExpandedId(attendee.ticketId);
        return;
      }

      if (attendee.paymentStatus === 'unpaid') {
        // Checking an unpaid ticket in silently would walk the money out the
        // door. Surface them with the tenders open instead.
        playErrorSound();
        vibrate(200);
        toast(`Collect ${formatCurrency(attendee.amountDue || price, currency)} from ${attendee.fullName}`, {
          icon: '💰',
          position: 'bottom-center',
          duration: 8000,
        });
        setQuery(attendee.fullName);
        setExpandedId(attendee.ticketId);
        return;
      }

      runAction({
        ticketId: attendee.ticketId,
        entry: 'scan',
        displayName: attendee.fullName,
        optimisticTicketId: attendee.ticketId,
        busyKey: attendee.ticketId,
      });
    },
    [data?.attendees, runAction, focusInput, price, currency],
  );

  // ─── Session summary ─────────────────────────────────────────
  const loadSummary = useCallback(async () => {
    const eventId = selectedEventIdRef.current;
    if (!eventId) return;
    setSummaryLoading(true);
    try {
      setSummary(await doorApi.summary(eventId));
    } catch (error) {
      console.error('Failed to load door summary:', error);
    } finally {
      setSummaryLoading(false);
    }
  }, []);

  useEffect(() => {
    if (sessionOpen) loadSummary();
  }, [sessionOpen, loadSummary]);

  // ─── Render ──────────────────────────────────────────────────
  if (loadingEvents) {
    return (
      <div className="min-h-screen bg-gray-950 flex items-center justify-center">
        <div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
      </div>
    );
  }

  return (
    <div className="bg-gray-950 flex flex-col overflow-hidden" style={{ height: '100dvh' }}>
      {/* ── Top bar ── */}
      <header className="flex-shrink-0 bg-gray-900 border-b border-gray-800 px-3 py-2.5 safe-area-top">
        <div className="flex items-center gap-2">
          <button
            onClick={() => router.push(backHref)}
            className="flex-shrink-0 min-w-[48px] min-h-[48px] flex items-center justify-center rounded-xl text-gray-400 active:text-white active:scale-95 transition-all"
            aria-label="Back to dashboard"
          >
            <ArrowLeftIcon className="w-5 h-5" />
          </button>

          <select
            value={selectedEventId}
            onChange={(e) => setSelectedEventId(e.target.value)}
            className="flex-1 min-w-0 min-h-[48px] bg-gray-800 border border-gray-700 text-white rounded-xl px-3 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary-yellow truncate"
          >
            <option value="">Select an event…</option>
            {events.map((event) => (
              <option key={event.id} value={event.id}>
                {event.title}
              </option>
            ))}
          </select>

          <div className="flex-shrink-0 flex items-center gap-1.5">
            <div className="bg-gray-800 border border-gray-700 rounded-xl px-2.5 py-1.5 text-center">
              <p className="text-primary-yellow font-bold text-sm leading-tight whitespace-nowrap">
                {checkedInCount}
                {capacity > 0 ? `/${capacity}` : ''} in
              </p>
              <p className="text-gray-500 text-[10px] leading-tight whitespace-nowrap">door {doorCount}</p>
            </div>
            <button
              onClick={() => setSessionOpen(true)}
              className="min-w-[48px] min-h-[48px] flex items-center justify-center rounded-xl bg-gray-800 border border-gray-700 text-gray-400 active:text-white active:scale-95 transition-all"
              aria-label="Session summary and recent check-ins"
            >
              <ClockIcon className="w-5 h-5" />
            </button>
          </div>
        </div>
      </header>

      {/* ── Search row ── */}
      <div className="flex-shrink-0 px-3 pt-3 pb-2 flex items-center gap-2">
        <div className="relative flex-1 min-w-0">
          <MagnifyingGlassIcon className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500 pointer-events-none" />
          <input
            ref={inputRef}
            type="text"
            value={query}
            onChange={(e) => {
              setQuery(e.target.value);
              setExpandedId(null);
              if (walkInOpen) setWalkInOpen(false);
            }}
            autoFocus
            placeholder="Search name or phone…"
            className="w-full min-h-[56px] pl-12 pr-11 bg-gray-800 border border-gray-700 rounded-2xl text-white placeholder:text-gray-500 text-lg focus:outline-none focus:ring-2 focus:ring-primary-yellow focus:border-transparent"
            autoComplete="off"
            autoCorrect="off"
            autoCapitalize="none"
            spellCheck={false}
          />
          {query && (
            <button
              onClick={() => {
                setQuery('');
                focusInput();
              }}
              className="absolute right-1 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center text-gray-500 active:text-white"
              aria-label="Clear search"
            >
              <XMarkIcon className="w-5 h-5" />
            </button>
          )}
        </div>
        <button
          onClick={() => setScannerOpen(true)}
          disabled={!selectedEventId}
          className="flex-shrink-0 w-[56px] h-[56px] flex items-center justify-center rounded-2xl bg-primary-yellow text-gray-900 active:scale-95 transition-transform disabled:opacity-40"
          aria-label="Scan a QR ticket"
        >
          <QrCodeIcon className="w-7 h-7" />
        </button>
      </div>

      {/* ── Results ── */}
      <div className="flex-1 min-h-0 overflow-y-auto px-3 pb-4 space-y-2">
        {!selectedEventId && (
          <p className="text-center text-gray-500 pt-12 text-sm">Pick an event to start checking people in.</p>
        )}

        {selectedEventId && loadingList && (
          <div className="flex justify-center pt-12">
            <div className="animate-spin w-7 h-7 border-4 border-primary-yellow border-t-transparent rounded-full" />
          </div>
        )}

        {selectedEventId && !loadingList && listError && (
          <div className="text-center pt-12 space-y-3">
            <p className="text-red-400 text-sm">{listError}</p>
            <button
              onClick={() => loadAttendees(selectedEventId)}
              className="min-h-[48px] px-5 rounded-xl bg-gray-800 border border-gray-700 text-white font-semibold active:scale-95 transition-transform"
            >
              Retry
            </button>
          </div>
        )}

        {selectedEventId && !loadingList && !listError && (
          <>
            {results.length === 0 && !showWalkInRow && (
              <p className="text-center text-gray-500 pt-12 text-sm">
                {trimmedQuery ? 'No one matches that.' : 'Everyone on the list is already checked in.'}
              </p>
            )}

            {results.map((attendee) => (
              <AttendeeRow
                key={attendee.ticketId}
                attendee={attendee}
                currency={currency}
                price={price}
                expanded={expandedId === attendee.ticketId}
                flashing={flashId === attendee.ticketId}
                busy={busyId === attendee.ticketId}
                onTap={() => handleRowTap(attendee)}
                onPay={(method, amount) => handleRowPay(attendee, method, amount)}
              />
            ))}

            {/* Pinned to the bottom of the results, never above a real person. */}
            {showWalkInRow && (
              <WalkInRow
                typedText={trimmedQuery}
                expanded={walkInOpen}
                draft={walkInDraft}
                price={price}
                currency={currency}
                busy={busyId === 'walk-in'}
                onExpand={() => {
                  setWalkInDraft(emptyWalkIn(trimmedQuery));
                  setWalkInOpen(true);
                  setExpandedId(null);
                }}
                onChange={setWalkInDraft}
                onPay={handleWalkInPay}
                onCancel={() => {
                  setWalkInOpen(false);
                  setWalkInDraft(emptyWalkIn());
                  focusInput();
                }}
              />
            )}
          </>
        )}
      </div>

      {/* ── Overlays ── */}
      {scannerOpen && (
        <QRScannerOverlay
          onScan={handleScan}
          onClose={() => {
            setScannerOpen(false);
            focusInput();
          }}
        />
      )}

      {sessionOpen && (
        <SessionSheet
          entries={sessionEntries}
          summary={summary}
          summaryLoading={summaryLoading}
          currency={currency}
          onRefresh={loadSummary}
          onClose={() => {
            setSessionOpen(false);
            focusInput();
          }}
        />
      )}
    </div>
  );
}
