'use client';

import { useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { MapPin, Clock, Plus, Minus, X, ChevronRight, CheckCircle2, Loader2, Search, Star } from 'lucide-react';
import { isOpenNow } from './hours';

const DEFAULT_DIAL_CODE = '+91';

function normalizePhoneE164(raw: string): string {
  const trimmed = raw.trim();
  if (!trimmed) return '';

  let candidate: string;
  if (trimmed.startsWith('+')) {
    candidate = '+' + trimmed.slice(1).replace(/\D/g, '');
  } else {
    const digits = trimmed.replace(/\D/g, '').replace(/^0+/, '');
    if (!digits) return '';
    if (digits.length === 12 && digits.startsWith('91')) {
      candidate = `+${digits}`;
    } else if (digits.length === 10) {
      candidate = `${DEFAULT_DIAL_CODE}${digits}`;
    } else {
      return '';
    }
  }

  return /^\+[1-9]\d{6,14}$/.test(candidate) ? candidate : '';
}

function firstFieldError(details: unknown): string | null {
  if (!details || typeof details !== 'object') return null;
  for (const v of Object.values(details as Record<string, unknown>)) {
    if (Array.isArray(v) && typeof v[0] === 'string') return v[0];
    if (typeof v === 'string') return v;
  }
  return null;
}

interface Branch {
  id: string;
  name: string;
  address: string | null;
  phone: string | null;
  hours: Record<string, unknown>;
  timezone: string | null;
  slug: string;
  storefront_enabled: boolean;
  accepts_dine_in: boolean;
  accepts_takeaway: boolean;
  accepts_delivery: boolean;
  min_order_value: number;
  storefront_message: string | null;
}

interface Restaurant {
  id: string;
  name: string;
  slug: string;
  logo_url: string | null;
  currency: string;
  description: string | null;
}

interface ModifierOption {
  id: string;
  name: string;
  price_delta: number;
  is_default: boolean;
}

interface ModifierGroup {
  id: string;
  name: string;
  is_required: boolean;
  min_select: number;
  max_select: number;
  options: ModifierOption[];
}

interface MenuItem {
  id: string;
  name: string;
  description: string | null;
  price: number;
  image_url: string | null;
  is_veg: boolean | null;
  spice_level: number | null;
  modifier_groups: ModifierGroup[];
  /**
   * Per-item availability for the current branch + time, computed
   * server-side. When false, the storefront renders a "Sold out" /
   * "Available from HH:MM" / "Not at this branch" badge and disables
   * Add-to-cart so customers can't queue an item the kitchen will
   * reject.
   */
  orderable?: boolean;
  unavailable_reason?: 'out_of_stock' | 'off_schedule' | 'wrong_branch' | 'disabled' | 'unknown_item' | null;
  unavailable_message?: string | null;
  available_from?: string | null;
}

interface MenuCategory {
  id: string;
  name: string;
  description: string | null;
  display_order: number;
  items: MenuItem[];
}

interface CartLine {
  uid: string;
  menu_item_id: string;
  name: string;
  quantity: number;
  unit_price: number;
  modifiers: Array<{ id: string; name: string; price_delta: number }>;
  note?: string;
}

interface PlacedOrder {
  id: string;
  order_number: number;
  status: string;
  total: number;
  branch_id: string;
  subtotal?: number;
  tax?: number;
  discount_amount?: number;
  coupon_code?: string | null;
  loyalty_discount?: number;
  loyalty_points_redeemed?: number;
  loyalty_points_earned?: number;
  gift_card_applied?: number;
  gift_card_remaining_balance?: number;
}

interface AppliedCoupon {
  code: string;
  description: string;
  discount: number;
  type: 'percent' | 'fixed' | 'bogo';
}

const STATUS_LABEL: Record<string, string> = {
  pending: 'Order received',
  confirmed: 'Confirmed by kitchen',
  kitchen: 'Being prepared',
  ready: 'Ready',
  delivered: 'Delivered',
  completed: 'Completed',
  cancelled: 'Cancelled',
};

interface Props {
  restaurantSlug: string;
  branchSlug: string;
  showPoweredBy?: boolean;
  poweredByLabel?: string;
}

export default function StorefrontApp({ restaurantSlug, branchSlug, showPoweredBy = true, poweredByLabel = 'Powered by RestroAgent' }: Props) {
  const search = useSearchParams();
  const tableParam = search.get('table');
  const typeParam = (search.get('type') ?? '').toLowerCase();
  const initialTypeFromQuery: 'dine-in' | 'takeaway' | 'delivery' | null =
    typeParam === 'takeaway' || typeParam === 'pickup'
      ? 'takeaway'
      : typeParam === 'delivery'
      ? 'delivery'
      : typeParam === 'dine-in' || typeParam === 'dinein'
      ? 'dine-in'
      : null;

  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [branch, setBranch] = useState<Branch | null>(null);
  const [restaurant, setRestaurant] = useState<Restaurant | null>(null);
  const [categories, setCategories] = useState<MenuCategory[]>([]);

  const [activeItem, setActiveItem] = useState<MenuItem | null>(null);
  const [activeItemSelections, setActiveItemSelections] = useState<Record<string, string[]>>({});
  const [activeItemQty, setActiveItemQty] = useState(1);
  const [activeItemNote, setActiveItemNote] = useState('');

  const [cart, setCart] = useState<CartLine[]>([]);
  const [cartOpen, setCartOpen] = useState(false);
  const [checkoutOpen, setCheckoutOpen] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [placedOrder, setPlacedOrder] = useState<PlacedOrder | null>(null);
  const [placedOrderLines, setPlacedOrderLines] = useState<CartLine[]>([]);
  const [placedOrderPhone, setPlacedOrderPhone] = useState('');
  const [submitError, setSubmitError] = useState<string | null>(null);
  const [searchQuery, setSearchQuery] = useState('');
  const tableLocked = !!tableParam;

  const [customerName, setCustomerName] = useState('');
  const [customerPhone, setCustomerPhone] = useState('');
  const [customerEmail, setCustomerEmail] = useState('');
  const [deliveryType, setDeliveryType] = useState<'dine-in' | 'takeaway' | 'delivery'>('dine-in');
  const [tableNumber, setTableNumber] = useState(tableParam ?? '');
  const [addressLine1, setAddressLine1] = useState('');
  const [addressLine2, setAddressLine2] = useState('');
  const [addressCity, setAddressCity] = useState('');
  const [addressPostal, setAddressPostal] = useState('');
  const [addressNotes, setAddressNotes] = useState('');
  const [orderNotes, setOrderNotes] = useState('');
  const [giftCardCode, setGiftCardCode] = useState('');
  const [giftCardApplying, setGiftCardApplying] = useState(false);
  const [giftCardError, setGiftCardError] = useState<string | null>(null);
  const [giftCardApplied, setGiftCardApplied] = useState<{ code: string; applied: number; remaining: number } | null>(null);

  // Loyalty redemption state
  type LoyaltyLookup = {
    enabled: boolean;
    found: boolean;
    balance: number;
    lifetime_points: number;
    tier: { name: string; badge_color: string | null } | null;
    next_tier: { name: string; threshold: number } | null;
    progress_to_next: number;
    settings: { min_redeem_points: number; redeem_points_per_unit: number; redeem_unit_value: number };
    preview: { allowed_points: number; discount_amount: number; reason: string } | null;
  };
  const [loyalty, setLoyalty] = useState<LoyaltyLookup | null>(null);
  const [loyaltyLoading, setLoyaltyLoading] = useState(false);
  const [pointsToRedeem, setPointsToRedeem] = useState(0);
  const [useLoyalty, setUseLoyalty] = useState(false);

  // Coupon redemption state
  const [couponInput, setCouponInput] = useState('');
  const [appliedCoupon, setAppliedCoupon] = useState<AppliedCoupon | null>(null);
  const [couponBusy, setCouponBusy] = useState(false);
  const [couponError, setCouponError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setError(null);
      try {
        const [profRes, menuRes] = await Promise.all([
          fetch(`/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}`),
          fetch(`/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/menu`),
        ]);
        if (!profRes.ok) {
          const e = (await profRes.json().catch(() => ({}))) as { error?: string };
          throw new Error(e.error || 'This storefront is unavailable.');
        }
        if (!menuRes.ok) {
          throw new Error('Menu is unavailable right now.');
        }
        const profJson = (await profRes.json()) as { branch: Branch; restaurant: Restaurant };
        const menuJson = (await menuRes.json()) as { categories: MenuCategory[] };
        if (cancelled) return;
        setBranch(profJson.branch);
        setRestaurant(profJson.restaurant);
        setCategories(menuJson.categories ?? []);
        // ?table= forces dine-in. Otherwise honor ?type= when allowed, falling back to first allowed.
        const b = profJson.branch;
        const allowed = (opt: 'dine-in' | 'takeaway' | 'delivery') =>
          (opt === 'dine-in' && b.accepts_dine_in) ||
          (opt === 'takeaway' && b.accepts_takeaway) ||
          (opt === 'delivery' && b.accepts_delivery);
        let def: 'dine-in' | 'takeaway' | 'delivery';
        if (tableParam) {
          def = 'dine-in';
        } else if (initialTypeFromQuery && allowed(initialTypeFromQuery)) {
          def = initialTypeFromQuery;
        } else {
          def = b.accepts_dine_in ? 'dine-in' : b.accepts_takeaway ? 'takeaway' : 'delivery';
        }
        setDeliveryType(def);
      } catch (err) {
        if (!cancelled) setError((err as Error).message ?? 'Failed to load storefront');
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [restaurantSlug, branchSlug]);

  useEffect(() => {
    // Restore saved customer info from the current browsing session only.
    // sessionStorage (not localStorage) is used so PII is never written to
    // persistent device storage — it is cleared automatically when the tab
    // or browser session ends (GDPR Art. 5 / CCPA data-minimisation).
    try {
      const stored = sessionStorage.getItem('storefront:customer');
      if (stored) {
        const c = JSON.parse(stored) as { name?: string; phone?: string; email?: string };
        if (c.name) setCustomerName(c.name);
        if (c.phone) setCustomerPhone(c.phone);
        if (c.email) setCustomerEmail(c.email);
      }
    } catch { /* ignore */ }
  }, []);

  const currency = restaurant?.currency ?? 'USD';
  const fmt = useMemo(
    () => new Intl.NumberFormat(undefined, { style: 'currency', currency, currencyDisplay: 'narrowSymbol' }),
    [currency]
  );

  const subtotal = useMemo(
    () => cart.reduce((s, l) => s + l.unit_price * l.quantity, 0),
    [cart]
  );
  const cartCount = useMemo(() => cart.reduce((s, l) => s + l.quantity, 0), [cart]);

  const minOrderValue = branch?.min_order_value ?? 0;
  const meetsMin = subtotal >= minOrderValue;

  // Lookup loyalty wallet when phone changes (debounced)
  useEffect(() => {
    const phone = customerPhone.trim();
    if (phone.length < 6) { setLoyalty(null); return; }
    let cancelled = false;
    setLoyaltyLoading(true);
    const t = setTimeout(async () => {
      try {
        const res = await fetch(
          `/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/loyalty/lookup`,
          { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone }) },
        );
        if (!res.ok) { if (!cancelled) setLoyalty(null); return; }
        const json = (await res.json()) as LoyaltyLookup;
        if (!cancelled) {
          setLoyalty(json);
          if (!json.enabled || !json.found || json.balance < (json.settings?.min_redeem_points ?? 1)) {
            setUseLoyalty(false);
            setPointsToRedeem(0);
          }
        }
      } catch {
        if (!cancelled) setLoyalty(null);
      } finally {
        if (!cancelled) setLoyaltyLoading(false);
      }
    }, 400);
    return () => { cancelled = true; clearTimeout(t); };
  }, [customerPhone, restaurantSlug, branchSlug]);

  // Re-preview redemption whenever points or subtotal change
  useEffect(() => {
    if (!loyalty?.enabled || !loyalty?.found || !useLoyalty || pointsToRedeem <= 0 || subtotal <= 0) {
      // Clear preview but keep settings
      setLoyalty((prev) => prev ? { ...prev, preview: prev.preview ? { ...prev.preview, allowed_points: 0, discount_amount: 0 } : null } : prev);
      return;
    }
    let cancelled = false;
    const t = setTimeout(async () => {
      try {
        const res = await fetch(
          `/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/loyalty/lookup`,
          {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ phone: customerPhone.trim(), subtotal, points_to_redeem: pointsToRedeem }),
          },
        );
        if (!res.ok) return;
        const json = (await res.json()) as LoyaltyLookup;
        if (!cancelled) setLoyalty(json);
      } catch { /* ignore */ }
    }, 250);
    return () => { cancelled = true; clearTimeout(t); };
  }, [pointsToRedeem, useLoyalty, subtotal, customerPhone, restaurantSlug, branchSlug, loyalty?.enabled, loyalty?.found]);

  const loyaltyDiscount = useLoyalty ? Number(loyalty?.preview?.discount_amount ?? 0) : 0;
  const allowedPoints = useLoyalty ? Number(loyalty?.preview?.allowed_points ?? 0) : 0;
  const orderTotal = Math.max(0, Math.round((subtotal - loyaltyDiscount) * 100) / 100);
  const openStatus = useMemo(() => isOpenNow(branch?.hours, branch?.timezone), [branch?.hours, branch?.timezone]);

  const visibleCategories = useMemo(() => {
    const q = searchQuery.trim().toLowerCase();
    if (!q) return categories;
    return categories
      .map((c) => ({
        ...c,
        items: (c.items ?? []).filter((it) =>
          it.name.toLowerCase().includes(q) ||
          (it.description ?? '').toLowerCase().includes(q)
        ),
      }))
      .filter((c) => c.items.length > 0);
  }, [categories, searchQuery]);

  const openItem = (item: MenuItem) => {
    // Block opening the modifier sheet for items that the storefront has
    // already flagged as unavailable — there's nothing the customer can do
    // and showing a working "Add to cart" button on a sold-out item would
    // race against the server-side validation.
    if (item.orderable === false) return;
    setActiveItem(item);
    setActiveItemQty(1);
    setActiveItemNote('');
    const initial: Record<string, string[]> = {};
    for (const g of item.modifier_groups ?? []) {
      const defaults = (g.options ?? []).filter((o) => o.is_default).map((o) => o.id);
      if (defaults.length > 0) initial[g.id] = defaults.slice(0, Math.max(1, g.max_select));
    }
    setActiveItemSelections(initial);
  };

  const itemBadge = (item: MenuItem): { label: string; tone: 'sold' | 'sched' | 'branch' | 'off' } | null => {
    if (item.orderable !== false) return null;
    if (item.unavailable_reason === 'out_of_stock') return { label: 'Sold out', tone: 'sold' };
    if (item.unavailable_reason === 'off_schedule') {
      return { label: item.available_from ? `Available from ${item.available_from}` : 'Not available right now', tone: 'sched' };
    }
    if (item.unavailable_reason === 'wrong_branch') return { label: 'Not at this branch', tone: 'branch' };
    return { label: 'Temporarily unavailable', tone: 'off' };
  };

  const itemUnitPrice = (item: MenuItem, sel: Record<string, string[]>): number => {
    let total = Number(item.price);
    for (const g of item.modifier_groups ?? []) {
      const ids = sel[g.id] ?? [];
      for (const o of g.options ?? []) {
        if (ids.includes(o.id)) total += Number(o.price_delta);
      }
    }
    return total;
  };

  const validateActiveItem = (): string | null => {
    if (!activeItem) return null;
    for (const g of activeItem.modifier_groups ?? []) {
      const ids = activeItemSelections[g.id] ?? [];
      if (g.is_required && ids.length < (g.min_select || 1)) {
        return `Please choose ${g.min_select || 1} option(s) for ${g.name}`;
      }
      if (g.max_select && ids.length > g.max_select) {
        return `You can only choose ${g.max_select} option(s) for ${g.name}`;
      }
    }
    return null;
  };

  const addActiveToCart = () => {
    if (!activeItem) return;
    const err = validateActiveItem();
    if (err) {
      alert(err);
      return;
    }
    const mods: Array<{ id: string; name: string; price_delta: number }> = [];
    for (const g of activeItem.modifier_groups ?? []) {
      const ids = activeItemSelections[g.id] ?? [];
      for (const o of g.options ?? []) {
        if (ids.includes(o.id)) mods.push({ id: o.id, name: o.name, price_delta: Number(o.price_delta) });
      }
    }
    const line: CartLine = {
      uid: `${activeItem.id}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
      menu_item_id: activeItem.id,
      name: activeItem.name,
      quantity: activeItemQty,
      unit_price: itemUnitPrice(activeItem, activeItemSelections),
      modifiers: mods,
      note: activeItemNote.trim() || undefined,
    };
    setCart((c) => [...c, line]);
    setActiveItem(null);
  };

  const updateQty = (uid: string, delta: number) => {
    setCart((c) =>
      c
        .map((l) => (l.uid === uid ? { ...l, quantity: Math.max(0, l.quantity + delta) } : l))
        .filter((l) => l.quantity > 0)
    );
  };

  const cartItemsForCoupon = useMemo(
    () => cart.map((l) => ({ menu_item_id: l.menu_item_id, quantity: l.quantity })),
    [cart]
  );

  const applyCouponCode = async (rawCode: string) => {
    const code = rawCode.trim();
    if (!code) return;
    if (cart.length === 0) {
      setCouponError('Add items to your cart first');
      return;
    }
    setCouponBusy(true);
    setCouponError(null);
    try {
      const res = await fetch(
        `/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/apply-coupon`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ code, delivery_type: deliveryType, items: cartItemsForCoupon }),
        }
      );
      const json = await res.json().catch(() => ({}));
      if (!res.ok || !json.ok) {
        setAppliedCoupon(null);
        setCouponError(json.message || json.error || 'Could not apply coupon');
      } else {
        setAppliedCoupon({
          code: json.code, description: json.description, discount: Number(json.discount), type: json.type,
        });
        setCouponError(null);
      }
    } catch {
      setCouponError('Network error checking coupon');
    } finally {
      setCouponBusy(false);
    }
  };

  // Re-validate any applied coupon when the cart, delivery type, or branch changes.
  useEffect(() => {
    if (!appliedCoupon) return;
    if (cart.length === 0) { setAppliedCoupon(null); return; }
    applyCouponCode(appliedCoupon.code);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [subtotal, deliveryType]);

  const discount = appliedCoupon?.discount ?? 0;
  const totalAfterDiscount = Math.max(0, Math.round((subtotal - loyaltyDiscount - discount) * 100) / 100);

  const placeOrder = async () => {
    if (!branch) return;
    setSubmitError(null);
    if (!customerName.trim()) { setSubmitError('Please enter your name'); return; }
    if (!customerPhone.trim()) { setSubmitError('Please enter a phone number'); return; }
    if (deliveryType === 'dine-in' && !tableNumber.trim()) {
      setSubmitError('Please enter your table number');
      return;
    }
    if (deliveryType === 'delivery' && (!addressLine1.trim() || !addressCity.trim())) {
      setSubmitError('Please enter your delivery address');
      return;
    }
    if (!meetsMin) {
      setSubmitError(`Minimum order is ${fmt.format(minOrderValue)}`);
      return;
    }

    const normalizedPhone = normalizePhoneE164(customerPhone);
    if (!normalizedPhone) { setSubmitError('Please enter a valid phone number'); return; }

    setSubmitting(true);
    try {
      const payload = {
        customer_name: customerName.trim(),
        customer_phone: normalizedPhone,
        customer_email: customerEmail.trim() || undefined,
        delivery_type: deliveryType,
        table_number: deliveryType === 'dine-in' ? tableNumber.trim() : undefined,
        delivery_address_json: deliveryType === 'delivery' ? {
          line1: addressLine1.trim(),
          line2: addressLine2.trim() || undefined,
          city: addressCity.trim(),
          postal_code: addressPostal.trim() || undefined,
          notes: addressNotes.trim() || undefined,
        } : undefined,
        special_instructions: orderNotes.trim() || undefined,
        items: cart.map((l) => ({
          menu_item_id: l.menu_item_id,
          quantity: l.quantity,
          selected_modifier_option_ids: l.modifiers.map((m) => m.id),
          note: l.note,
        })),
        points_to_redeem: useLoyalty && allowedPoints > 0 ? allowedPoints : undefined,
        gift_card_code: giftCardApplied?.code,
        coupon_code: appliedCoupon?.code ?? undefined,
      };
      const res = await fetch(`/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/orders`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
      const json = (await res.json().catch(() => ({}))) as {
        order?: PlacedOrder;
        error?: string;
        fields?: Record<string, string[] | string>;
      };
      if (!res.ok || !json.order) {
        const fieldMsg = firstFieldError(json.fields);
        throw new Error(fieldMsg || json.error || 'Failed to place order');
      }

      try {
        // sessionStorage: clears on tab/session close — no persistent PII on device.
        sessionStorage.setItem('storefront:customer', JSON.stringify({
          name: customerName, phone: customerPhone, email: customerEmail,
        }));
      } catch { /* ignore */ }

      setPlacedOrder(json.order);
      setPlacedOrderLines(cart);
      setPlacedOrderPhone(normalizedPhone);
      setCart([]);
      setAppliedCoupon(null);
      setCouponInput('');
      setCheckoutOpen(false);
      setCartOpen(false);
    } catch (err) {
      setSubmitError((err as Error).message ?? 'Failed to place order');
    } finally {
      setSubmitting(false);
    }
  };

  if (loading) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-white text-slate-700">
        <Loader2 className="animate-spin" size={28} />
      </div>
    );
  }
  if (error || !branch || !restaurant) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-white p-6 text-center">
        <div>
          <p className="text-lg font-semibold text-slate-900">Storefront unavailable</p>
          <p className="text-sm text-slate-500 mt-2">{error ?? 'Please check the link and try again.'}</p>
        </div>
      </div>
    );
  }

  if (placedOrder) {
    return (
      <OrderTracker
        restaurantSlug={restaurantSlug}
        branchSlug={branchSlug}
        order={placedOrder}
        items={placedOrderLines}
        phone={placedOrderPhone}
        currency={currency}
        showPoweredBy={showPoweredBy}
        poweredByLabel={poweredByLabel}
        onNew={() => { setPlacedOrder(null); setPlacedOrderLines([]); }}
      />
    );
  }

  return (
    <div className="min-h-screen bg-slate-50">
      {/* Header */}
      <header className="sticky top-0 z-30 bg-white border-b border-slate-200">
        <div className="max-w-3xl mx-auto px-4 py-3 flex items-center gap-3">
          {restaurant.logo_url ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={restaurant.logo_url} alt="" className="w-10 h-10 rounded-lg object-cover bg-slate-100" />
          ) : (
            <div className="w-10 h-10 rounded-lg bg-orange-500 text-white flex items-center justify-center font-bold">
              {(restaurant.name?.[0] ?? 'R').toUpperCase()}
            </div>
          )}
          <div className="min-w-0 flex-1">
            <h1 className="font-bold text-slate-900 truncate">{restaurant.name}</h1>
            <p className="text-xs text-slate-500 truncate flex items-center gap-1">
              <MapPin size={11} />
              {branch.name}{branch.address ? ` — ${branch.address}` : ''}
            </p>
          </div>
          {openStatus !== null && (
            <span
              className={`shrink-0 text-[11px] font-bold uppercase tracking-wide rounded-full px-2 py-0.5 ${
                openStatus
                  ? 'bg-emerald-100 text-emerald-700'
                  : 'bg-rose-100 text-rose-700'
              }`}
            >
              {openStatus ? 'Open now' : 'Closed'}
            </span>
          )}
        </div>
        {branch.storefront_message && (
          <div className="bg-amber-50 border-t border-amber-100 px-4 py-2 text-xs text-amber-800 text-center">
            {branch.storefront_message}
          </div>
        )}
      </header>

      <main className="max-w-3xl mx-auto px-4 py-4 pb-32">
        {tableLocked && (
          <div className="mb-3 rounded-lg bg-emerald-50 border border-emerald-200 px-3 py-2 text-xs text-emerald-800 flex items-center gap-2">
            <Clock size={14} /> Ordering dine-in for table <strong>{tableParam}</strong>
          </div>
        )}

        <div className="relative mb-3">
          <Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
          <input
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            placeholder="Search the menu"
            className="w-full bg-white border border-slate-200 rounded-xl pl-9 pr-3 py-2.5 text-sm placeholder:text-slate-400"
          />
        </div>

        {categories.length === 0 && (
          <div className="text-center text-slate-500 py-12">No items available right now.</div>
        )}
        {categories.length > 0 && visibleCategories.length === 0 && (
          <div className="text-center text-slate-500 py-12">No items match &ldquo;{searchQuery}&rdquo;.</div>
        )}

        {!searchQuery && (
          <nav className="sticky top-[68px] z-20 -mx-4 px-4 py-2 bg-slate-50 overflow-x-auto whitespace-nowrap mb-2">
            {categories.map((c) => (
              <a key={c.id} href={`#cat-${c.id}`} className="inline-block mr-2 px-3 py-1.5 rounded-full bg-white border border-slate-200 text-xs font-medium text-slate-700">
                {c.name}
              </a>
            ))}
          </nav>
        )}

        {visibleCategories.map((cat) => (
          <section key={cat.id} id={`cat-${cat.id}`} className="mb-6 scroll-mt-32">
            <h2 className="text-base font-bold text-slate-900 mb-2">{cat.name}</h2>
            <div className="space-y-2">
              {cat.items.map((item) => {
                const badge = itemBadge(item);
                const unavailable = item.orderable === false;
                const badgeTone =
                  badge?.tone === 'sold' ? 'bg-rose-100 text-rose-700 border-rose-200'
                  : badge?.tone === 'sched' ? 'bg-amber-100 text-amber-800 border-amber-200'
                  : badge?.tone === 'branch' ? 'bg-slate-200 text-slate-700 border-slate-300'
                  : 'bg-slate-100 text-slate-600 border-slate-200';
                return (
                  <button
                    key={item.id}
                    onClick={() => openItem(item)}
                    disabled={unavailable}
                    aria-disabled={unavailable}
                    aria-label={unavailable && badge ? `${item.name} — ${badge.label}` : item.name}
                    data-testid={`menu-item-${item.id}`}
                    className={`w-full bg-white rounded-xl border p-3 flex gap-3 text-left transition-colors ${
                      unavailable
                        ? 'border-slate-200 opacity-60 cursor-not-allowed'
                        : 'border-slate-200 hover:border-orange-300'
                    }`}
                  >
                    {item.image_url ? (
                      // eslint-disable-next-line @next/next/no-img-element
                      <img src={item.image_url} alt="" className={`w-20 h-20 rounded-lg object-cover bg-slate-100 shrink-0 ${unavailable ? 'grayscale' : ''}`} />
                    ) : (
                      <div className="w-20 h-20 rounded-lg bg-slate-100 shrink-0" />
                    )}
                    <div className="flex-1 min-w-0">
                      <div className="flex items-center gap-2 flex-wrap">
                        <h3 className="font-semibold text-slate-900 truncate">{item.name}</h3>
                        {item.is_veg ? <span className="w-3 h-3 border border-emerald-600 flex items-center justify-center"><span className="w-1.5 h-1.5 rounded-full bg-emerald-600" /></span> : null}
                        {badge && (
                          <span
                            data-testid={`badge-${item.id}`}
                            className={`text-[10px] font-semibold px-1.5 py-0.5 rounded-md border ${badgeTone}`}
                          >
                            {badge.label}
                          </span>
                        )}
                      </div>
                      {item.description && <p className="text-xs text-slate-500 line-clamp-2 mt-0.5">{item.description}</p>}
                      <p className="text-sm font-semibold text-slate-900 mt-2">{fmt.format(Number(item.price))}</p>
                    </div>
                    {unavailable
                      ? <div className="self-start text-slate-400 text-[10px] font-semibold uppercase tracking-wide">N/A</div>
                      : <div className="self-start text-orange-500"><Plus size={16} /></div>}
                  </button>
                );
              })}
            </div>
          </section>
        ))}

        {showPoweredBy && (
          <p className="text-center text-xs text-slate-400 mt-8">{poweredByLabel}</p>
        )}
      </main>

      {/* Floating cart bar */}
      {cart.length > 0 && (
        <div className="fixed bottom-0 inset-x-0 z-40 bg-white border-t border-slate-200 p-3 safe-bottom">
          <button
            onClick={() => setCartOpen(true)}
            className="w-full max-w-3xl mx-auto bg-orange-500 hover:bg-orange-600 text-white rounded-xl px-4 py-3 flex items-center justify-between gap-3"
          >
            <span className="flex items-center gap-2">
              <span className="bg-white/20 rounded-full w-7 h-7 flex items-center justify-center font-bold text-sm">{cartCount}</span>
              <span className="font-semibold">View cart</span>
            </span>
            <span className="font-bold">{fmt.format(subtotal)}</span>
          </button>
        </div>
      )}

      {/* Item modal */}
      {activeItem && (
        <Sheet onClose={() => setActiveItem(null)} title={activeItem.name}>
          {activeItem.description && <p className="text-sm text-slate-600 mb-4">{activeItem.description}</p>}
          {(activeItem.modifier_groups ?? []).map((g) => (
            <div key={g.id} className="mb-4">
              <div className="flex items-center justify-between mb-1">
                <h4 className="font-semibold text-slate-900 text-sm">{g.name}</h4>
                {g.is_required && <span className="text-xs text-red-600 font-medium">Required</span>}
              </div>
              <p className="text-xs text-slate-500 mb-2">
                {g.max_select && g.max_select > 1 ? `Choose up to ${g.max_select}` : 'Choose one'}
              </p>
              {(g.options ?? []).map((o) => {
                const ids = activeItemSelections[g.id] ?? [];
                const checked = ids.includes(o.id);
                const single = (g.max_select ?? 1) === 1;
                return (
                  <label key={o.id} className="flex items-center justify-between py-2 border-b border-slate-100 cursor-pointer">
                    <span className="flex items-center gap-2 text-sm text-slate-800">
                      <input
                        type={single ? 'radio' : 'checkbox'}
                        name={`g-${g.id}`}
                        checked={checked}
                        onChange={() => {
                          setActiveItemSelections((prev) => {
                            const cur = prev[g.id] ?? [];
                            if (single) return { ...prev, [g.id]: [o.id] };
                            if (cur.includes(o.id)) return { ...prev, [g.id]: cur.filter((x) => x !== o.id) };
                            const max = g.max_select ?? 99;
                            const next = [...cur, o.id].slice(-max);
                            return { ...prev, [g.id]: next };
                          });
                        }}
                      />
                      {o.name}
                    </span>
                    {Number(o.price_delta) !== 0 && (
                      <span className="text-xs text-slate-500">+{fmt.format(Number(o.price_delta))}</span>
                    )}
                  </label>
                );
              })}
            </div>
          ))}
          <div className="mb-4">
            <label className="text-xs font-semibold text-slate-700">Special request</label>
            <input
              className="mt-1 w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
              placeholder="No onions, extra spicy, …"
              value={activeItemNote}
              maxLength={140}
              onChange={(e) => setActiveItemNote(e.target.value)}
            />
          </div>
          <div className="flex items-center justify-between gap-4 sticky bottom-0 bg-white pt-3 border-t border-slate-100">
            <div className="flex items-center gap-2 bg-slate-100 rounded-full p-1">
              <button onClick={() => setActiveItemQty((q) => Math.max(1, q - 1))} className="w-8 h-8 rounded-full bg-white text-slate-700"><Minus size={14} className="mx-auto" /></button>
              <span className="font-semibold text-sm w-6 text-center">{activeItemQty}</span>
              <button onClick={() => setActiveItemQty((q) => Math.min(99, q + 1))} className="w-8 h-8 rounded-full bg-white text-slate-700"><Plus size={14} className="mx-auto" /></button>
            </div>
            <button
              onClick={addActiveToCart}
              className="flex-1 bg-orange-500 hover:bg-orange-600 text-white rounded-xl py-3 text-sm font-semibold"
            >
              Add — {fmt.format(itemUnitPrice(activeItem, activeItemSelections) * activeItemQty)}
            </button>
          </div>
        </Sheet>
      )}

      {/* Cart drawer */}
      {cartOpen && cart.length > 0 && !checkoutOpen && (
        <Sheet onClose={() => setCartOpen(false)} title="Your order">
          <div className="space-y-3 mb-4">
            {cart.map((l) => (
              <div key={l.uid} className="flex items-start gap-2 border-b border-slate-100 pb-3">
                <div className="flex-1 min-w-0">
                  <p className="font-semibold text-sm text-slate-900">{l.name}</p>
                  {l.modifiers.length > 0 && (
                    <p className="text-xs text-slate-500 mt-0.5">{l.modifiers.map((m) => m.name).join(', ')}</p>
                  )}
                  {l.note && <p className="text-xs italic text-slate-500 mt-0.5">“{l.note}”</p>}
                  <p className="text-sm font-semibold mt-1">{fmt.format(l.unit_price * l.quantity)}</p>
                </div>
                <div className="flex items-center gap-1 bg-slate-100 rounded-full p-0.5">
                  <button onClick={() => updateQty(l.uid, -1)} className="w-7 h-7 rounded-full bg-white text-slate-700"><Minus size={12} className="mx-auto" /></button>
                  <span className="text-sm w-5 text-center">{l.quantity}</span>
                  <button onClick={() => updateQty(l.uid, +1)} className="w-7 h-7 rounded-full bg-white text-slate-700"><Plus size={12} className="mx-auto" /></button>
                </div>
              </div>
            ))}
          </div>
          <CouponBlock
            input={couponInput}
            setInput={setCouponInput}
            applied={appliedCoupon}
            busy={couponBusy}
            error={couponError}
            onApply={() => applyCouponCode(couponInput)}
            onRemove={() => { setAppliedCoupon(null); setCouponInput(''); setCouponError(null); }}
          />
          <div className="flex items-center justify-between text-sm mb-1">
            <span className="text-slate-500">Subtotal</span>
            <span className="font-semibold">{fmt.format(subtotal)}</span>
          </div>
          {appliedCoupon && (
            <div className="flex items-center justify-between text-sm mb-1 text-emerald-700">
              <span>Discount ({appliedCoupon.code})</span>
              <span className="font-semibold">−{fmt.format(discount)}</span>
            </div>
          )}
          {appliedCoupon && (
            <div className="flex items-center justify-between text-base mb-2 border-t border-slate-100 pt-2">
              <span className="text-slate-700 font-semibold">Total</span>
              <span className="font-bold">{fmt.format(totalAfterDiscount)}</span>
            </div>
          )}
          {minOrderValue > 0 && (
            <p className={`text-xs ${meetsMin ? 'text-emerald-700' : 'text-amber-700'} mb-3`}>
              {meetsMin ? `Minimum order met` : `Add ${fmt.format(minOrderValue - subtotal)} more to meet minimum (${fmt.format(minOrderValue)})`}
            </p>
          )}
          {openStatus === false && (
            <p className="text-xs text-rose-700 bg-rose-50 border border-rose-200 rounded-lg px-3 py-2 mb-3">
              This branch is currently closed and not accepting orders.
            </p>
          )}
          <button
            disabled={!meetsMin || openStatus === false}
            onClick={() => setCheckoutOpen(true)}
            className="w-full bg-orange-500 disabled:bg-slate-300 hover:bg-orange-600 text-white rounded-xl py-3 text-sm font-semibold flex items-center justify-center gap-2"
          >
            Continue to checkout <ChevronRight size={16} />
          </button>
        </Sheet>
      )}

      {/* Checkout sheet */}
      {checkoutOpen && (
        <Sheet onClose={() => setCheckoutOpen(false)} title="Checkout">
          <div className="space-y-3 mb-4">
            {tableLocked ? (
              <div className="rounded-lg bg-emerald-50 border border-emerald-200 px-3 py-2 text-xs text-emerald-800 flex items-center gap-2">
                <Clock size={14} /> Dine-in at table <strong>{tableParam}</strong>
              </div>
            ) : (
              <div className="flex gap-2">
                {(['dine-in', 'takeaway', 'delivery'] as const).map((opt) => {
                  const allowed = (opt === 'dine-in' && branch.accepts_dine_in) || (opt === 'takeaway' && branch.accepts_takeaway) || (opt === 'delivery' && branch.accepts_delivery);
                  if (!allowed) return null;
                  return (
                    <button
                      key={opt}
                      onClick={() => setDeliveryType(opt)}
                      className={`flex-1 rounded-lg border px-2 py-2 text-xs font-semibold capitalize ${deliveryType === opt ? 'border-orange-500 bg-orange-50 text-orange-700' : 'border-slate-200 bg-white text-slate-600'}`}
                    >
                      {opt.replace('-', ' ')}
                    </button>
                  );
                })}
              </div>
            )}

            <input
              className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
              placeholder="Full name *"
              value={customerName}
              onChange={(e) => setCustomerName(e.target.value)}
            />
            <input
              className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
              placeholder="Phone *"
              type="tel"
              value={customerPhone}
              onChange={(e) => setCustomerPhone(e.target.value)}
            />
            <input
              className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
              placeholder="Email (optional)"
              type="email"
              value={customerEmail}
              onChange={(e) => setCustomerEmail(e.target.value)}
            />

            {deliveryType === 'dine-in' && (
              <input
                className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
                placeholder="Table number *"
                value={tableNumber}
                onChange={(e) => setTableNumber(e.target.value)}
              />
            )}

            {deliveryType === 'delivery' && (
              <div className="space-y-2">
                <input className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="Address line 1 *" value={addressLine1} onChange={(e) => setAddressLine1(e.target.value)} />
                <input className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="Address line 2" value={addressLine2} onChange={(e) => setAddressLine2(e.target.value)} />
                <div className="grid grid-cols-2 gap-2">
                  <input className="rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="City *" value={addressCity} onChange={(e) => setAddressCity(e.target.value)} />
                  <input className="rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="ZIP / Postal" value={addressPostal} onChange={(e) => setAddressPostal(e.target.value)} />
                </div>
                <input className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="Delivery notes" value={addressNotes} onChange={(e) => setAddressNotes(e.target.value)} />
              </div>
            )}

            <textarea
              className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm"
              placeholder="Order notes (allergies, instructions)"
              rows={2}
              maxLength={280}
              value={orderNotes}
              onChange={(e) => setOrderNotes(e.target.value)}
            />
          </div>

          {loyalty?.enabled && loyalty?.found && loyalty.balance >= (loyalty.settings.min_redeem_points || 1) && (
            <div className="rounded-lg border border-orange-200 bg-orange-50 px-3 py-2.5 mb-3 text-xs text-orange-900">
              <div className="flex items-center justify-between">
                <label className="flex items-center gap-2 font-semibold">
                  <input
                    type="checkbox"
                    checked={useLoyalty}
                    onChange={(e) => {
                      const v = e.target.checked;
                      setUseLoyalty(v);
                      if (v && pointsToRedeem === 0) setPointsToRedeem(loyalty.balance);
                    }}
                  />
                  Use loyalty points
                </label>
                <span className="tabular-nums">{loyalty.balance.toLocaleString()} pts available</span>
              </div>
              {loyalty.tier && (
                <div className="mt-1 flex items-center gap-1.5 text-[11px] text-orange-800">
                  <span className="w-2 h-2 rounded-full" style={{ background: loyalty.tier.badge_color || '#f59e0b' }} />
                  {loyalty.tier.name} tier
                </div>
              )}
              {useLoyalty && (
                <div className="mt-2 space-y-1.5">
                  <div className="flex items-center gap-2">
                    <input
                      type="number" min={0} max={loyalty.balance}
                      className="flex-1 rounded-md border border-orange-200 px-2 py-1 text-sm tabular-nums"
                      value={pointsToRedeem || ''}
                      onChange={(e) => setPointsToRedeem(Math.max(0, Math.min(loyalty.balance, Math.floor(Number(e.target.value) || 0))))}
                      placeholder="Points to redeem"
                    />
                    <button
                      type="button"
                      className="text-xs underline"
                      onClick={() => setPointsToRedeem(loyalty.balance)}
                    >
                      Use max
                    </button>
                  </div>
                  {allowedPoints > 0 ? (
                    <p className="text-[11px]">
                      Redeeming <span className="font-semibold tabular-nums">{allowedPoints.toLocaleString()}</span> pts → save{' '}
                      <span className="font-semibold">{fmt.format(loyaltyDiscount)}</span>
                    </p>
                  ) : pointsToRedeem > 0 && loyalty.preview ? (
                    <p className="text-[11px] text-orange-700">
                      {loyalty.preview.reason === 'below_min'
                        ? `Minimum redemption is ${loyalty.settings.min_redeem_points} pts.`
                        : 'Redemption not available for this cart.'}
                    </p>
                  ) : null}
                </div>
              )}
            </div>
          )}
          {loyalty?.enabled && loyalty?.found === false && customerPhone.trim().length >= 6 && !loyaltyLoading && (
            <p className="text-[11px] text-slate-500 mb-3">No loyalty account yet — points will start accruing on this order.</p>
          )}

          <div className="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 mb-3">
            <div className="text-xs font-semibold text-slate-700 mb-1">Gift card</div>
            {giftCardApplied ? (
              <div className="flex items-center justify-between text-sm">
                <div>
                  <div className="font-mono text-xs text-slate-700">{giftCardApplied.code}</div>
                  <div className="text-xs text-emerald-700">−{fmt.format(giftCardApplied.applied)} applied · {fmt.format(giftCardApplied.remaining)} left</div>
                </div>
                <button
                  onClick={() => { setGiftCardApplied(null); setGiftCardCode(''); setGiftCardError(null); }}
                  className="text-xs text-slate-500 hover:text-slate-700 underline"
                >
                  Remove
                </button>
              </div>
            ) : (
              <div>
                <div className="flex gap-2">
                  <input
                    className="flex-1 rounded-lg border border-slate-200 px-2 py-1.5 text-sm font-mono uppercase"
                    placeholder="Enter code"
                    value={giftCardCode}
                    onChange={(e) => setGiftCardCode(e.target.value.toUpperCase())}
                    disabled={giftCardApplying}
                  />
                  <button
                    type="button"
                    disabled={giftCardApplying || !giftCardCode.trim()}
                    onClick={async () => {
                      setGiftCardApplying(true);
                      setGiftCardError(null);
                      try {
                        const res = await fetch(
                          `/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/gift-cards/preview`,
                          {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ code: giftCardCode.trim(), subtotal, total: subtotal }),
                          },
                        );
                        const json = await res.json().catch(() => ({}));
                        if (!res.ok) throw new Error(json?.error || 'Could not apply gift card');
                        setGiftCardApplied({ code: giftCardCode.trim(), applied: Number(json.applied || 0), remaining: Number(json.remaining_balance || 0) });
                      } catch (err) {
                        setGiftCardError((err as Error).message);
                      } finally {
                        setGiftCardApplying(false);
                      }
                    }}
                    className="rounded-lg bg-slate-900 text-white text-xs font-semibold px-3 disabled:bg-slate-300"
                  >
                    {giftCardApplying ? '…' : 'Apply'}
                  </button>
                </div>
                {giftCardError && <div className="text-xs text-red-600 mt-1">{giftCardError}</div>}
              </div>
            )}
          </div>

          <div className="border-t border-slate-100 pt-3 mb-3 text-sm space-y-1">
            <div className="flex items-center justify-between"><span className="text-slate-500">Subtotal</span><span>{fmt.format(subtotal)}</span></div>
            {loyaltyDiscount > 0 && (
              <div className="flex items-center justify-between text-emerald-700">
                <span>Loyalty discount ({allowedPoints.toLocaleString()} pts)</span>
                <span>−{fmt.format(loyaltyDiscount)}</span>
              </div>
            )}
            {appliedCoupon && (
              <div className="flex items-center justify-between text-emerald-700">
                <span>Discount ({appliedCoupon.code})</span>
                <span className="font-semibold">−{fmt.format(discount)}</span>
              </div>
            )}
            {giftCardApplied && giftCardApplied.applied > 0 && (
              <div className="flex items-center justify-between text-emerald-700">
                <span>Gift card</span><span>−{fmt.format(giftCardApplied.applied)}</span>
              </div>
            )}
            {(loyaltyDiscount > 0 || appliedCoupon || (giftCardApplied && giftCardApplied.applied > 0)) && (
              <div className="flex items-center justify-between font-semibold text-slate-900 pt-1 border-t border-slate-100">
                <span>Estimated total</span>
                <span>{fmt.format(Math.max(0, totalAfterDiscount - (giftCardApplied?.applied ?? 0)))}</span>
              </div>
            )}
            <p className="text-xs text-slate-500 mt-1">Tax and any service fees are confirmed by the restaurant.</p>
          </div>

          <div className="rounded-lg bg-amber-50 border border-amber-200 p-2 text-xs text-amber-800 mb-3">
            Pay in person — cash on delivery / pay at counter.
          </div>

          {openStatus === false && (
            <p className="text-xs text-rose-700 bg-rose-50 border border-rose-200 rounded-lg px-3 py-2 mb-3">
              This branch is currently closed and not accepting orders.
            </p>
          )}

          {submitError && (
            <div className="text-sm text-red-600 mb-2">{submitError}</div>
          )}

          <button
            disabled={submitting || openStatus === false}
            onClick={placeOrder}
            className="w-full bg-orange-500 disabled:bg-slate-300 hover:bg-orange-600 text-white rounded-xl py-3 text-sm font-semibold flex items-center justify-center gap-2"
          >
            {submitting ? (<><Loader2 size={16} className="animate-spin" /> Placing…</>) : `Place order — ${fmt.format(totalAfterDiscount)}`}
          </button>
        </Sheet>
      )}
    </div>
  );
}

function CouponBlock({ input, setInput, applied, busy, error, onApply, onRemove }: {
  input: string;
  setInput: (s: string) => void;
  applied: AppliedCoupon | null;
  busy: boolean;
  error: string | null;
  onApply: () => void;
  onRemove: () => void;
}) {
  if (applied) {
    return (
      <div className="mb-3 rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 flex items-center justify-between">
        <div>
          <p className="text-xs font-bold text-emerald-700 font-mono">{applied.code}</p>
          <p className="text-[11px] text-emerald-700">{applied.description} applied</p>
        </div>
        <button
          onClick={onRemove}
          className="text-xs text-emerald-700 underline font-semibold"
        >Remove</button>
      </div>
    );
  }
  return (
    <div className="mb-3">
      <label className="block text-xs font-semibold text-slate-600 mb-1">Have a code?</label>
      <div className="flex gap-2">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value.toUpperCase())}
          placeholder="Enter promo code"
          className="flex-1 rounded-lg border border-slate-200 px-3 py-2 text-sm font-mono uppercase"
          onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); onApply(); } }}
        />
        <button
          onClick={onApply}
          disabled={busy || !input.trim()}
          className="px-3 py-2 rounded-lg text-sm font-semibold border border-orange-300 bg-orange-50 text-orange-700 disabled:opacity-50"
        >
          {busy ? '…' : 'Apply'}
        </button>
      </div>
      {error && <p className="text-xs text-red-600 mt-1">{error}</p>}
    </div>
  );
}

function Sheet({ onClose, title, children }: { onClose: () => void; title: string; children: React.ReactNode }) {
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = ''; };
  }, []);
  return (
    <div className="fixed inset-0 z-50 flex items-end justify-center bg-black/40">
      <div className="bg-white w-full max-w-3xl max-h-[90vh] rounded-t-2xl flex flex-col">
        <div className="flex items-center justify-between p-4 border-b border-slate-100">
          <h3 className="font-bold text-slate-900">{title}</h3>
          <button onClick={onClose} className="text-slate-400"><X size={18} /></button>
        </div>
        <div className="overflow-y-auto p-4 flex-1">{children}</div>
      </div>
    </div>
  );
}

function OrderTracker({
  restaurantSlug, branchSlug, order, items, phone, currency, onNew, showPoweredBy, poweredByLabel,
}: {
  restaurantSlug: string;
  branchSlug: string;
  order: PlacedOrder;
  items: CartLine[];
  phone: string;
  currency: string;
  onNew: () => void;
  showPoweredBy: boolean;
  poweredByLabel: string;
}) {
  const [status, setStatus] = useState(order.status);
  const [pointsEarned, setPointsEarned] = useState(order.loyalty_points_earned ?? 0);
  const fmt = useMemo(
    () => new Intl.NumberFormat(undefined, { style: 'currency', currency, currencyDisplay: 'narrowSymbol' }),
    [currency]
  );

  useEffect(() => {
    let cancelled = false;
    const tick = async () => {
      try {
        const res = await fetch(
          `/api/public/storefront/${encodeURIComponent(restaurantSlug)}/${encodeURIComponent(branchSlug)}/orders/${encodeURIComponent(order.id)}?phone=${encodeURIComponent(phone)}`
        );
        if (res.ok) {
          const j = (await res.json()) as { order?: { status: string; loyalty_points_earned?: number } };
          if (!cancelled && j.order) {
            setStatus(j.order.status);
            if (j.order.loyalty_points_earned != null) {
              setPointsEarned(Number(j.order.loyalty_points_earned));
            }
          }
        }
      } catch { /* ignore */ }
    };
    tick();
    const id = setInterval(tick, 15000);
    return () => { cancelled = true; clearInterval(id); };
  }, [order.id, phone, restaurantSlug, branchSlug]);

  const showEarnedPoints = pointsEarned > 0 && (status === 'delivered' || status === 'completed');
  const pointsRedeemed = order.loyalty_points_redeemed ?? 0;

  return (
    <div className="min-h-screen bg-slate-50 flex flex-col">
      <main className="max-w-md mx-auto w-full p-6 flex-1 flex flex-col items-center text-center">
        <div className="w-16 h-16 rounded-full bg-emerald-100 flex items-center justify-center mb-4 mt-8">
          <CheckCircle2 size={32} className="text-emerald-600" />
        </div>
        <h2 className="text-xl font-bold text-slate-900">Order #{order.order_number} placed</h2>
        <p className="text-sm text-slate-500 mt-1">We&rsquo;ve sent it to the kitchen.</p>

        <div className="mt-6 w-full bg-white border border-slate-200 rounded-xl p-4 text-left">
          <p className="text-xs uppercase font-semibold text-slate-500">Status</p>
          <p className="text-lg font-bold text-slate-900 mt-1">{STATUS_LABEL[status] ?? status}</p>
        </div>

        {showEarnedPoints && (
          <div className="mt-3 w-full bg-amber-50 border border-amber-200 rounded-xl p-4 text-left flex items-center gap-3">
            <div className="w-9 h-9 rounded-full bg-amber-100 flex items-center justify-center shrink-0">
              <Star size={18} className="text-amber-600" fill="currentColor" />
            </div>
            <div>
              <p className="text-sm font-bold text-amber-900">+{pointsEarned} points earned</p>
              <p className="text-xs text-amber-700">Added to your loyalty wallet.</p>
            </div>
          </div>
        )}

        {items.length > 0 && (
          <div className="mt-3 w-full bg-white border border-slate-200 rounded-xl p-4 text-left">
            <p className="text-xs uppercase font-semibold text-slate-500 mb-2">Your order</p>
            <ul className="divide-y divide-slate-100">
              {items.map((l) => (
                <li key={l.uid} className="py-2 flex items-start gap-2">
                  <span className="font-semibold text-slate-700 text-sm w-6 shrink-0">{l.quantity}×</span>
                  <div className="flex-1 min-w-0">
                    <p className="text-sm font-medium text-slate-900">{l.name}</p>
                    {l.modifiers.length > 0 && (
                      <p className="text-xs text-slate-500">{l.modifiers.map((m) => m.name).join(', ')}</p>
                    )}
                    {l.note && <p className="text-xs italic text-slate-500">&ldquo;{l.note}&rdquo;</p>}
                  </div>
                  <span className="text-sm text-slate-700 shrink-0">{fmt.format(l.unit_price * l.quantity)}</span>
                </li>
              ))}
            </ul>
            {order.subtotal != null && (
              <div className="border-t border-slate-100 mt-2 pt-2 flex items-center justify-between text-sm text-slate-600">
                <span>Subtotal</span>
                <span>{fmt.format(order.subtotal)}</span>
              </div>
            )}
            {order.coupon_code && order.discount_amount && order.discount_amount > 0 && (
              <div className="mt-1 flex items-center justify-between text-emerald-700 text-sm">
                <span>Coupon ({order.coupon_code})</span>
                <span className="font-semibold">−{fmt.format(order.discount_amount)}</span>
              </div>
            )}
            {order.loyalty_discount != null && order.loyalty_discount > 0 && (
              <div className="mt-1 flex items-center justify-between text-emerald-700 text-sm">
                <span>Loyalty{pointsRedeemed > 0 ? ` (${pointsRedeemed} pts)` : ''}</span>
                <span className="font-semibold">−{fmt.format(order.loyalty_discount)}</span>
              </div>
            )}
            {order.tax != null && order.tax > 0 && (
              <div className="mt-1 flex items-center justify-between text-sm text-slate-600">
                <span>Tax</span>
                <span>{fmt.format(order.tax)}</span>
              </div>
            )}
            {order.gift_card_applied != null && order.gift_card_applied > 0 && (
              <div className="mt-1 flex items-center justify-between text-sky-700 text-sm">
                <span>Gift card</span>
                <span className="font-semibold">−{fmt.format(order.gift_card_applied)}</span>
              </div>
            )}
            <div className="border-t border-slate-100 mt-2 pt-2 flex items-center justify-between">
              <span className="text-xs uppercase font-semibold text-slate-500">
                {order.gift_card_applied && order.gift_card_applied > 0 ? 'Total paid' : 'Total'}
              </span>
              <span className="text-base font-bold text-slate-900">{fmt.format(order.total)}</span>
            </div>
            {order.gift_card_applied != null && order.gift_card_applied > 0 && order.gift_card_remaining_balance != null && (
              <div className="mt-1 text-right text-xs text-sky-700">
                Gift card balance remaining: {fmt.format(order.gift_card_remaining_balance)}
              </div>
            )}
          </div>
        )}

        <button onClick={onNew} className="mt-6 bg-orange-500 hover:bg-orange-600 text-white px-6 py-2 rounded-lg text-sm font-semibold">
          Place another order
        </button>

        {showPoweredBy && (
          <p className="text-xs text-slate-400 mt-8 mb-4">{poweredByLabel}</p>
        )}
      </main>
    </div>
  );
}
