import React from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Image as ImageIcon, RefreshCcw, Filter, Check, Eye, EyeOff, Sparkles } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { AnimatePresence, motion, useAnimationControls, type PanInfo } from 'framer-motion';
import { MobileShell, HeaderActionButton } from './components/MobileShell';
import { MobileCard, PillBadge, CTAButton, SkeletonCard } from './components/Primitives';
import { MobileField, MobileInput, MobileSelect } from './components/FormControls';
import {
  getEventPhotos,
  getEventPhoto,
  updatePhotoVisibility,
  featurePhoto,
  unfeaturePhoto,
  updatePhotoStatus,
  TenantPhoto,
  EventAddonCatalogItem,
  createEventAddonCheckout,
  getAddonCatalog,
  EventAddonSummary,
  EventLimitSummary,
  getEvent,
} from '../api';
import toast from 'react-hot-toast';
import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import { MobileSheet } from './components/Sheet';
import { useEventContext } from '../context/EventContext';
import { useAdminTheme } from './theme';
import { buildLimitWarnings } from '../lib/limitWarnings';
import { adminPath } from '../constants';
import { scopeDefaults, selectAddonKeyForScope } from './addons';
import { LegalConsentSheet } from './components/LegalConsentSheet';
import { triggerHaptic } from './lib/haptics';
import { useOnlineStatus } from './hooks/useOnlineStatus';
import { useBackNavigation } from './hooks/useBackNavigation';
import {
  enqueuePhotoAction,
  loadPhotoQueue,
  removePhotoAction,
  replacePhotoQueue,
  type PhotoModerationAction,
} from './lib/photoModerationQueue';
import { resolvePhotoSwipeAction, type SwipeModerationAction } from './lib/photoModerationSwipe';
import { resolveLightboxSources } from './lib/lightboxImage';

type FilterKey = 'all' | 'featured' | 'hidden' | 'pending';

export default function MobileEventPhotosPage() {
  const { slug: slugParam, photoId: photoIdParam } = useParams<{ slug?: string; photoId?: string }>();
  const { activeEvent, selectEvent } = useEventContext();
  const slug = slugParam ?? activeEvent?.slug ?? null;
  const navigate = useNavigate();
  const location = useLocation();
  const { t } = useTranslation('management');

  const [photos, setPhotos] = React.useState<TenantPhoto[]>([]);
  const [filter, setFilter] = React.useState<FilterKey>('all');
  const [page, setPage] = React.useState(1);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState<string | null>(null);
  const [busyId, setBusyId] = React.useState<number | null>(null);
  const [totalCount, setTotalCount] = React.useState<number>(0);
  const [hasMore, setHasMore] = React.useState(false);
  const [search, setSearch] = React.useState('');
  const [showFilters, setShowFilters] = React.useState(false);
  const [uploaderFilter, setUploaderFilter] = React.useState('');
  const [onlyFeatured, setOnlyFeatured] = React.useState(false);
  const [onlyHidden, setOnlyHidden] = React.useState(false);
  const [lightboxId, setLightboxId] = React.useState<number | null>(null);
  const [lightboxImageSrc, setLightboxImageSrc] = React.useState<string | null>(null);
  const [pendingPhotoId, setPendingPhotoId] = React.useState<number | null>(null);
  const [syncingQueue, setSyncingQueue] = React.useState(false);
  const [selectionMode, setSelectionMode] = React.useState(false);
  const [selectedIds, setSelectedIds] = React.useState<number[]>([]);
  const [bulkBusy, setBulkBusy] = React.useState(false);
  const [limits, setLimits] = React.useState<EventLimitSummary | null>(null);
  const [catalogAddons, setCatalogAddons] = React.useState<EventAddonCatalogItem[]>([]);
  const [eventAddons, setEventAddons] = React.useState<EventAddonSummary[]>([]);
  const [busyScope, setBusyScope] = React.useState<string | null>(null);
  const back = useBackNavigation(slug ? adminPath(`/mobile/events/${slug}`) : adminPath('/mobile/events'));
  const [consentOpen, setConsentOpen] = React.useState(false);
  const [consentTarget, setConsentTarget] = React.useState<{ scope: 'photos' | 'gallery' | 'guests'; addonKey: string } | null>(null);
  const [consentBusy, setConsentBusy] = React.useState(false);
  const [queuedActions, setQueuedActions] = React.useState<PhotoModerationAction[]>(() => loadPhotoQueue());
  const online = useOnlineStatus();
  const syncingQueueRef = React.useRef(false);
  const { text, muted, border, accentSoft, accent, danger, surface, backdrop, primary } = useAdminTheme();
  const infoBg = accentSoft;
  const infoBorder = accent;

  const basePhotosPath = slug ? adminPath(`/mobile/events/${slug}/photos`) : adminPath('/mobile/events');
  const photoQuery = React.useMemo(() => {
    return new URLSearchParams(location.search).get('photo');
  }, [location.search]);
  const sourcePhotoParam = photoQuery ?? photoIdParam ?? null;
  const lightboxIndex = React.useMemo(() => {
    if (lightboxId === null) {
      return -1;
    }
    return photos.findIndex((photo) => photo.id === lightboxId);
  }, [photos, lightboxId]);
  const lightbox = lightboxIndex >= 0 ? photos[lightboxIndex] : null;
  const parsedPhotoId = React.useMemo(() => {
    if (!sourcePhotoParam) {
      return null;
    }
    const parsed = Number(sourcePhotoParam);
    return Number.isFinite(parsed) ? parsed : null;
  }, [sourcePhotoParam]);

  React.useEffect(() => {
    if (lightboxId !== null && lightboxIndex === -1 && !loading && pendingPhotoId !== lightboxId) {
      setLightboxId(null);
    }
  }, [lightboxId, lightboxIndex, loading, pendingPhotoId]);

  React.useEffect(() => {
    if (lightboxId !== null) {
      setSelectionMode(false);
      setSelectedIds([]);
    }
  }, [lightboxId]);

  React.useEffect(() => {
    if (!lightbox) {
      setLightboxImageSrc(null);
      return;
    }
    const sources = resolveLightboxSources(lightbox);
    setLightboxImageSrc(sources.initial);

    if (!sources.full) {
      return;
    }

    const loader = new Image();
    loader.onload = () => setLightboxImageSrc(sources.full);
    loader.src = sources.full;
  }, [lightbox]);

  React.useEffect(() => {
    if (!sourcePhotoParam) {
      setLightboxId(null);
      setPendingPhotoId(null);
      return;
    }

    if (parsedPhotoId === null) {
      setPendingPhotoId(null);
      return;
    }

    setLightboxId(parsedPhotoId);
    setPendingPhotoId(parsedPhotoId);
  }, [parsedPhotoId, sourcePhotoParam]);

  React.useEffect(() => {
    if (slugParam && activeEvent?.slug !== slugParam) {
      selectEvent(slugParam);
    }
  }, [slugParam, activeEvent?.slug, selectEvent]);

  const load = React.useCallback(async () => {
    if (!slug) return;
    setLoading(true);
    setError(null);
    try {
      const status =
        filter === 'hidden' || onlyHidden
          ? 'hidden'
          : filter === 'pending'
            ? 'pending'
            : undefined;
      const result = await getEventPhotos(slug, {
        page,
        perPage: 20,
        sort: 'desc',
        featured: filter === 'featured' || onlyFeatured,
        status,
        search: search || undefined,
      });
      setPhotos((prev) => (page === 1 ? result.photos : [...prev, ...result.photos]));
      setTotalCount(result.meta?.total ?? result.photos.length);
      setLimits(result.limits ?? null);
      const lastPage = result.meta?.last_page ?? 1;
      setHasMore(page < lastPage);
      const [addons, event] = await Promise.all([
        getAddonCatalog().catch(() => [] as EventAddonCatalogItem[]),
        getEvent(slug).catch(() => null),
      ]);
      setCatalogAddons(addons ?? []);
      setEventAddons(event?.addons ?? []);
    } catch (err) {
      if (!isAuthError(err)) {
        setError(getApiErrorMessage(err, t('mobilePhotos.loadFailed', 'Fotos konnten nicht geladen werden.')));
      }
    } finally {
      setLoading(false);
    }
  }, [slug, filter, t, page, onlyFeatured, onlyHidden, search]);

  React.useEffect(() => {
    void load();
  }, [load]);

  React.useEffect(() => {
    if (!location.search || !slug) return;
    const params = new URLSearchParams(location.search);
    if (params.get('addon_success')) {
      toast.success(t('mobileBilling.addonApplied', 'Add-on applied. Limits update shortly.'));
      setPage(1);
      void load();
      params.delete('addon_success');
      navigate({ pathname: location.pathname, search: params.toString() ? `?${params.toString()}` : '' }, { replace: true });
    }
  }, [location.search, slug, load, navigate, t, location.pathname]);

  React.useEffect(() => {
    setPage(1);
  }, [filter, slug]);

  const updateQueueState = React.useCallback((queue: PhotoModerationAction[]) => {
    replacePhotoQueue(queue);
    setQueuedActions(queue);
  }, []);

  const applyOptimisticUpdate = React.useCallback((photoId: number, action: PhotoModerationAction['action']) => {
    setPhotos((prev) =>
      prev.map((photo) => {
        if (photo.id !== photoId) {
          return photo;
        }

        if (action === 'approve') {
          return { ...photo, status: 'approved' };
        }

        if (action === 'hide') {
          return { ...photo, status: 'hidden' };
        }

        if (action === 'show') {
          return { ...photo, status: 'approved' };
        }

        if (action === 'feature') {
          return { ...photo, is_featured: true };
        }

        if (action === 'unfeature') {
          return { ...photo, is_featured: false };
        }

        return photo;
      }),
    );
  }, []);

  const enqueueModerationAction = React.useCallback(
    (action: PhotoModerationAction['action'], photoId: number) => {
      if (!slug) {
        return;
      }
      const nextQueue = enqueuePhotoAction({ eventSlug: slug, photoId, action });
      setQueuedActions(nextQueue);
      applyOptimisticUpdate(photoId, action);
      toast.success(t('mobilePhotos.queued', 'Aktion gespeichert. Wird synchronisiert, sobald du online bist.'));
      triggerHaptic('selection');
    },
    [applyOptimisticUpdate, slug, t],
  );

  const syncQueuedActions = React.useCallback(
    async (options?: { silent?: boolean }) => {
      if (!online || syncingQueueRef.current) {
        return;
      }

      const queue = loadPhotoQueue();
      if (queue.length === 0) {
        return;
      }

      syncingQueueRef.current = true;
      setSyncingQueue(true);

      let remaining = queue;

      for (const entry of queue) {
        try {
          let updated: TenantPhoto | null = null;
          if (entry.action === 'approve') {
            updated = await updatePhotoStatus(entry.eventSlug, entry.photoId, 'approved');
          } else if (entry.action === 'hide') {
            updated = await updatePhotoVisibility(entry.eventSlug, entry.photoId, true);
          } else if (entry.action === 'show') {
            updated = await updatePhotoVisibility(entry.eventSlug, entry.photoId, false);
          } else if (entry.action === 'feature') {
            updated = await featurePhoto(entry.eventSlug, entry.photoId);
          } else if (entry.action === 'unfeature') {
            updated = await unfeaturePhoto(entry.eventSlug, entry.photoId);
          }

          remaining = removePhotoAction(remaining, entry.id);

          if (updated && entry.eventSlug === slug) {
            setPhotos((prev) => prev.map((photo) => (photo.id === updated!.id ? updated! : photo)));
          }
        } catch (err) {
          if (!options?.silent) {
            toast.error(t('mobilePhotos.syncFailed', 'Synchronisierung fehlgeschlagen. Bitte später erneut versuchen.'));
          }
          if (isAuthError(err)) {
            break;
          }
        }
      }

      updateQueueState(remaining);
      setSyncingQueue(false);
      syncingQueueRef.current = false;
    },
    [online, slug, t, updateQueueState],
  );

  React.useEffect(() => {
    if (online) {
      void syncQueuedActions({ silent: true });
    }
  }, [online, syncQueuedActions]);

  const setLightboxWithUrl = React.useCallback(
    (photoId: number | null) => {
      setLightboxId(photoId);
      if (typeof window === 'undefined' || !slug) {
        return;
      }
      const params = new URLSearchParams(window.location.search);
      if (photoId) {
        params.set('photo', String(photoId));
      } else {
        params.delete('photo');
      }
      const nextSearch = params.toString();
      const nextPath = `${basePhotosPath}${nextSearch ? `?${nextSearch}` : ''}`;
      if (`${window.location.pathname}${window.location.search}` !== nextPath) {
        window.history.replaceState(null, '', nextPath);
      }
    },
    [basePhotosPath, slug],
  );

  const handleModerationAction = React.useCallback(
    async (action: PhotoModerationAction['action'], photo: TenantPhoto) => {
      if (!slug) {
        return;
      }
      if (!online) {
        enqueueModerationAction(action, photo.id);
        return;
      }

      setBusyId(photo.id);

      const successMessage = () => {
        if (action === 'approve') {
          return t('mobilePhotos.approveSuccess', 'Photo approved');
        }
        if (action === 'hide') {
          return t('mobilePhotos.hideSuccess', 'Photo hidden');
        }
        if (action === 'show') {
          return t('mobilePhotos.showSuccess', 'Photo shown');
        }
        if (action === 'feature') {
          return t('mobilePhotos.featureSuccess', 'Als Highlight markiert');
        }
        return t('mobilePhotos.unfeatureSuccess', 'Highlight entfernt');
      };

      const errorMessage = () => {
        if (action === 'approve') {
          return t('mobilePhotos.approveFailed', 'Freigabe fehlgeschlagen.');
        }
        if (action === 'hide' || action === 'show') {
          return t('mobilePhotos.visibilityFailed', 'Sichtbarkeit konnte nicht geändert werden.');
        }
        return t('mobilePhotos.featureFailed', 'Feature konnte nicht geändert werden.');
      };

      try {
        let updated: TenantPhoto;
        if (action === 'approve') {
          updated = await updatePhotoStatus(slug, photo.id, 'approved');
        } else if (action === 'hide') {
          updated = await updatePhotoVisibility(slug, photo.id, true);
        } else if (action === 'show') {
          updated = await updatePhotoVisibility(slug, photo.id, false);
        } else if (action === 'feature') {
          updated = await featurePhoto(slug, photo.id);
        } else {
          updated = await unfeaturePhoto(slug, photo.id);
        }

        setPhotos((prev) => prev.map((p) => (p.id === photo.id ? updated : p)));
        toast.success(successMessage());
        triggerHaptic(action === 'approve' ? 'success' : 'medium');
      } catch (err) {
        if (!isAuthError(err)) {
          setError(getApiErrorMessage(err, errorMessage()));
          toast.error(errorMessage());
        }
      } finally {
        setBusyId(null);
      }
    },
    [enqueueModerationAction, online, slug, t],
  );

  React.useEffect(() => {
    if (!slug || pendingPhotoId === null) {
      return;
    }

    if (photos.some((photo) => photo.id === pendingPhotoId)) {
      setPendingPhotoId(null);
      return;
    }

    if (loading) {
      return;
    }

    let active = true;
    void (async () => {
      try {
        const fetched = await getEventPhoto(slug, pendingPhotoId);
        if (!active) {
          return;
        }
        setPhotos((prev) => {
          if (prev.some((photo) => photo.id === fetched.id)) {
            return prev;
          }
          return [fetched, ...prev];
        });
      } catch (err) {
        if (!isAuthError(err)) {
          toast.error(t('mobilePhotos.loadFailed', 'Fotos konnten nicht geladen werden.'));
        }
        if (active) {
          setLightboxWithUrl(null);
        }
      } finally {
        if (active) {
          setPendingPhotoId(null);
        }
      }
    })();

    return () => {
      active = false;
    };
  }, [pendingPhotoId, slug, photos, loading, t, setLightboxWithUrl]);

  async function toggleVisibility(photo: TenantPhoto) {
    const action = photo.status === 'hidden' ? 'show' : 'hide';
    await handleModerationAction(action, photo);
  }

  async function toggleFeature(photo: TenantPhoto) {
    const action = photo.is_featured ? 'unfeature' : 'feature';
    await handleModerationAction(action, photo);
  }

  async function approvePhoto(photo: TenantPhoto) {
    await handleModerationAction('approve', photo);
  }

  const selectedPhotos = React.useMemo(
    () => photos.filter((photo) => selectedIds.includes(photo.id)),
    [photos, selectedIds],
  );
  const hasPendingSelection = selectedPhotos.some((photo) => photo.status === 'pending');
  const hasHiddenSelection = selectedPhotos.some((photo) => photo.status === 'hidden');
  const hasVisibleSelection = selectedPhotos.some((photo) => photo.status !== 'hidden');
  const hasFeaturedSelection = selectedPhotos.some((photo) => photo.is_featured);
  const hasUnfeaturedSelection = selectedPhotos.some((photo) => !photo.is_featured);
  const queuedEventCount = React.useMemo(() => {
    if (!slug) {
      return queuedActions.length;
    }
    return queuedActions.filter((action) => action.eventSlug === slug).length;
  }, [queuedActions, slug]);

  function toggleSelection(id: number) {
    setSelectedIds((prev) => (prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]));
  }

  function clearSelection() {
    setSelectedIds([]);
    setSelectionMode(false);
  }

  const handleLightboxDragEnd = React.useCallback(
    (_event: PointerEvent, info: { offset: { x: number; y: number } }) => {
      if (lightboxIndex < 0) {
        return;
      }
      const { x, y } = info.offset;
      const absX = Math.abs(x);
      const absY = Math.abs(y);
      const swipeThreshold = 80;
      const dismissThreshold = 90;

      if (absY > absX && y > dismissThreshold) {
        setLightboxWithUrl(null);
        return;
      }

      if (absX > swipeThreshold) {
        const nextIndex = x < 0 ? lightboxIndex + 1 : lightboxIndex - 1;
        if (nextIndex >= 0 && nextIndex < photos.length) {
          setLightboxWithUrl(photos[nextIndex]?.id ?? null);
        }
      }
    },
    [lightboxIndex, photos, setLightboxWithUrl],
  );

  async function applyBulkAction(action: 'approve' | 'hide' | 'show' | 'feature' | 'unfeature') {
    if (!slug || bulkBusy || selectedPhotos.length === 0) return;
    setBulkBusy(true);
    const targets = selectedPhotos.filter((photo) => {
      if (action === 'approve') return photo.status === 'pending';
      if (action === 'hide') return photo.status !== 'hidden';
      if (action === 'show') return photo.status === 'hidden';
      if (action === 'feature') return !photo.is_featured;
      if (action === 'unfeature') return photo.is_featured;
      return false;
    });
    if (targets.length === 0) {
      setBulkBusy(false);
      return;
    }

    if (!online) {
      let nextQueue: PhotoModerationAction[] = [];
      targets.forEach((photo) => {
        nextQueue = enqueuePhotoAction({ eventSlug: slug, photoId: photo.id, action });
        applyOptimisticUpdate(photo.id, action);
      });
      setQueuedActions(nextQueue);
      toast.success(t('mobilePhotos.queued', 'Aktion gespeichert. Wird synchronisiert, sobald du online bist.'));
      triggerHaptic('selection');
      setBulkBusy(false);
      return;
    }
    try {
      const results = await Promise.allSettled(
        targets.map(async (photo) => {
          if (action === 'approve') {
            return await updatePhotoStatus(slug, photo.id, 'approved');
          }
          if (action === 'hide') {
            return await updatePhotoVisibility(slug, photo.id, true);
          }
          if (action === 'show') {
            return await updatePhotoVisibility(slug, photo.id, false);
          }
          if (action === 'feature') {
            return await featurePhoto(slug, photo.id);
          }
          return await unfeaturePhoto(slug, photo.id);
        }),
      );

      const updates = results
        .filter((result): result is PromiseFulfilledResult<TenantPhoto> => result.status === 'fulfilled')
        .map((result) => result.value);

      if (updates.length) {
        setPhotos((prev) => prev.map((photo) => updates.find((update) => update.id === photo.id) ?? photo));
        toast.success(t('mobilePhotos.bulkUpdated', 'Bulk update applied'));
        triggerHaptic('success');
      }
    } catch {
      toast.error(t('mobilePhotos.bulkFailed', 'Bulk update failed'));
    } finally {
      setBulkBusy(false);
    }
  }

  function resolveScopeAndAddonKey(scopeOrKey: 'photos' | 'gallery' | 'guests' | string) {
    const scope =
      scopeOrKey === 'photos' || scopeOrKey === 'gallery' || scopeOrKey === 'guests'
        ? scopeOrKey
        : scopeOrKey.includes('gallery')
          ? 'gallery'
          : scopeOrKey.includes('guest')
            ? 'guests'
            : 'photos';

    const addonKey =
      scopeOrKey === 'photos' || scopeOrKey === 'gallery' || scopeOrKey === 'guests'
        ? selectAddonKeyForScope(catalogAddons, scope)
        : scopeOrKey;

    return { scope, addonKey };
  }

  function startAddonCheckout(scopeOrKey: 'photos' | 'gallery' | 'guests' | string) {
    if (!slug) return;
    const { scope, addonKey } = resolveScopeAndAddonKey(scopeOrKey);
    setConsentTarget({ scope: scope as any, addonKey });
    setConsentOpen(true);
  }

  async function confirmAddonCheckout(consents: { acceptedTerms: boolean; acceptedWaiver: boolean }) {
    if (!slug || !consentTarget) return;

    const currentUrl = typeof window !== 'undefined' ? `${window.location.origin}${adminPath(`/mobile/events/${slug}/photos`)}` : '';
    const successUrl = `${currentUrl}?addon_success=1`;
    setBusyScope(consentTarget.scope);
    setConsentBusy(true);
    try {
      const checkout = await createEventAddonCheckout(slug, {
        addon_key: consentTarget.addonKey,
        quantity: 1,
        success_url: successUrl,
        cancel_url: currentUrl,
        accepted_terms: consents.acceptedTerms,
        accepted_waiver: consents.acceptedWaiver,
      } as any);
      if (checkout.checkout_url) {
        window.location.href = checkout.checkout_url;
      } else {
        toast.error(t('events.errors.checkoutMissing', 'Checkout could not be started.'));
      }
    } catch (err) {
      toast.error(getApiErrorMessage(err, t('events.errors.checkoutFailed', 'Add-on checkout failed.')));
    } finally {
      setConsentBusy(false);
      setConsentOpen(false);
      setConsentTarget(null);
      setBusyScope(null);
    }
  }

  return (
    <MobileShell
      activeTab="uploads"
      title={t('mobilePhotos.title', 'Photo moderation')}
      onBack={back}
      headerActions={
        <XStack space="$3">
          <HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
            <RefreshCcw size={18} color={text} />
          </HeaderActionButton>
        </XStack>
      }
    >
      {error ? (
        <MobileCard>
          <Text fontWeight="700" color={danger}>
            {error}
          </Text>
          <CTAButton
            label={t('common.retry', 'Retry')}
            tone="ghost"
            fullWidth={false}
            onPress={() => load()}
          />
        </MobileCard>
      ) : null}

      {queuedEventCount > 0 ? (
        <MobileCard>
          <XStack alignItems="center" justifyContent="space-between" space="$2">
            <YStack space="$1" flex={1}>
              <Text fontSize="$sm" fontWeight="700" color={text}>
                {t('mobilePhotos.queueTitle', 'Änderungen warten auf Sync')}
              </Text>
              <Text fontSize="$xs" color={muted}>
                {online
                  ? t('mobilePhotos.queueOnline', '{{count}} Aktionen bereit zum Synchronisieren.', {
                      count: queuedEventCount,
                    })
                  : t('mobilePhotos.queueOffline', '{{count}} Aktionen gespeichert – offline.', {
                      count: queuedEventCount,
                    })}
              </Text>
            </YStack>
            <CTAButton
              label={online ? t('mobilePhotos.queueSync', 'Sync') : t('mobilePhotos.queueWaiting', 'Offline')}
              onPress={() => syncQueuedActions()}
              tone="ghost"
              fullWidth={false}
              disabled={!online}
              loading={syncingQueue}
            />
          </XStack>
        </MobileCard>
      ) : null}

      <XStack space="$2">
        <CTAButton
          label={selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
          tone={selectionMode ? 'primary' : 'ghost'}
          fullWidth={false}
          onPress={() => {
            if (selectionMode) {
              clearSelection();
            } else {
              setSelectionMode(true);
            }
          }}
        />
        <CTAButton
          label={t('mobilePhotos.filtersTitle', 'Filter')}
          tone="ghost"
          fullWidth={false}
          onPress={() => setShowFilters(true)}
        />
      </XStack>

      <MobileInput
        type="search"
        value={search}
        onChange={(e) => {
          setSearch(e.target.value);
          setPage(1);
        }}
        placeholder={t('photos.filters.search', 'Search uploads …')}
        compact
        style={{ marginBottom: 12 }}
      />

      <XStack space="$2" flexWrap="wrap">
        {(['all', 'featured', 'pending', 'hidden'] as FilterKey[]).map((key) => (
          <Pressable key={key} onPress={() => setFilter(key)} style={{ flex: 1 }}>
            <MobileCard
              backgroundColor={filter === key ? infoBg : surface}
              borderColor={filter === key ? infoBorder : border}
              padding="$2.5"
            >
              <Text fontSize="$sm" fontWeight="700" textAlign="center" color={text}>
                {key === 'all'
                  ? t('common.all', 'All')
                  : key === 'featured'
                    ? t('photos.filters.featured', 'Featured')
                    : key === 'pending'
                      ? t('photos.filters.pending', 'Pending')
                      : t('photos.filters.hidden', 'Hidden')}
              </Text>
            </MobileCard>
          </Pressable>
        ))}
      </XStack>

      {!loading ? (
        <LimitWarnings
          limits={limits}
          addons={catalogAddons}
          onCheckout={startAddonCheckout}
          busyScope={busyScope}
          translate={translateLimits(t as any)}
          textColor={text}
          borderColor={border}
        />
      ) : null}

      {loading ? (
        <YStack space="$2">
          {Array.from({ length: 4 }).map((_, idx) => (
            <SkeletonCard key={`ph-${idx}`} height={100} />
          ))}
        </YStack>
      ) : photos.length === 0 ? (
        <MobileCard alignItems="center" justifyContent="center" space="$2">
          <ImageIcon size={28} color={muted} />
          <Text fontSize="$sm" fontWeight="700" color={text}>
            {t('mobilePhotos.emptyTitle', 'No uploads yet')}
          </Text>
          <Text fontSize="$xs" color={muted} textAlign="center">
            {t('mobilePhotos.emptyBody', 'Share the QR code so guests can start uploading photos.')}
          </Text>
          {slug ? (
            <CTAButton
              label={t('mobilePhotos.emptyAction', 'Share QR code')}
              tone="ghost"
              fullWidth={false}
              onPress={() => navigate(adminPath(`/mobile/events/${slug}/qr`))}
            />
          ) : null}
        </MobileCard>
      ) : (
        <YStack space="$3">
          <Text fontSize="$sm" color={muted}>
            {t('mobilePhotos.count', '{{count}} photos', { count: totalCount })}
          </Text>
          <div
            style={{
              display: 'grid',
              gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
              gap: 8,
            }}
          >
            {photos.map((photo) => {
              const isSelected = selectedIds.includes(photo.id);
              const swipeDisabled = selectionMode || busyId === photo.id;
              return (
                <PhotoSwipeCard
                  key={photo.id}
                  photo={photo}
                  disabled={swipeDisabled}
                  onOpen={() => (selectionMode ? toggleSelection(photo.id) : setLightboxWithUrl(photo.id))}
                  onModerate={(action) => handleModerationAction(action, photo)}
                >
                  <YStack
                    borderRadius={10}
                    overflow="hidden"
                    borderWidth={1}
                    borderColor={isSelected ? infoBorder : border}
                    position="relative"
                  >
                    <motion.img
                      src={photo.thumbnail_url ?? photo.url ?? undefined}
                      alt={photo.caption ?? 'Photo'}
                      loading="lazy"
                      decoding="async"
                      style={{ width: '100%', height: 110, objectFit: 'cover' }}
                    />
                    <XStack position="absolute" top={6} left={6} space="$1">
                      {photo.is_featured ? <PillBadge tone="warning">{t('photos.filters.featured', 'Featured')}</PillBadge> : null}
                      {photo.status === 'pending' ? (
                        <PillBadge tone="warning">{t('photos.filters.pending', 'Pending')}</PillBadge>
                      ) : null}
                      {photo.status === 'hidden' ? <PillBadge tone="muted">{t('photos.filters.hidden', 'Hidden')}</PillBadge> : null}
                    </XStack>
                    {selectionMode ? (
                      <XStack
                        position="absolute"
                        top={6}
                        right={6}
                        width={24}
                        height={24}
                        borderRadius={999}
                        alignItems="center"
                        justifyContent="center"
                        backgroundColor={isSelected ? primary : 'rgba(255,255,255,0.85)'}
                        borderWidth={1}
                        borderColor={isSelected ? primary : border}
                      >
                        {isSelected ? <Check size={14} color="white" /> : null}
                      </XStack>
                    ) : null}
                    {!selectionMode ? (
                      <PhotoQuickActions
                        photo={photo}
                        disabled={busyId === photo.id}
                        onAction={(action) => handleModerationAction(action, photo)}
                        muted={muted}
                        surface={surface}
                      />
                    ) : null}
                  </YStack>
                </PhotoSwipeCard>
              );
            })}
          </div>
          {hasMore ? (
            <CTAButton label={t('common.loadMore', 'Mehr laden')} onPress={() => setPage((prev) => prev + 1)} />
          ) : null}
        </YStack>
      )}

      {selectionMode ? (
        <YStack
          position="fixed"
          left={12}
          right={12}
          bottom="calc(env(safe-area-inset-bottom, 0px) + 96px)"
          padding="$3"
          borderRadius={18}
          backgroundColor={surface}
          borderWidth={1}
          borderColor={border}
          shadowColor={backdrop}
          shadowOpacity={0.18}
          shadowRadius={16}
          shadowOffset={{ width: 0, height: 8 }}
          zIndex={60}
          space="$2"
        >
          <XStack alignItems="center" justifyContent="space-between">
            <Text fontSize="$sm" fontWeight="800" color={text}>
              {t('mobilePhotos.selectedCount', '{{count}} selected', { count: selectedIds.length })}
            </Text>
            <Pressable onPress={() => clearSelection()}>
              <Text fontSize="$sm" color={primary} fontWeight="700">
                {t('common.clear', 'Clear')}
              </Text>
            </Pressable>
          </XStack>
          <XStack space="$2" flexWrap="wrap">
            {hasPendingSelection ? (
              <CTAButton
                label={t('photos.actions.approve', 'Approve')}
                onPress={() => applyBulkAction('approve')}
                fullWidth={false}
                disabled={bulkBusy}
                loading={bulkBusy}
              />
            ) : null}
            {hasVisibleSelection ? (
              <CTAButton
                label={t('photos.actions.hide', 'Hide')}
                onPress={() => applyBulkAction('hide')}
                tone="ghost"
                fullWidth={false}
                disabled={bulkBusy}
                loading={bulkBusy}
              />
            ) : null}
            {hasHiddenSelection ? (
              <CTAButton
                label={t('photos.actions.show', 'Show')}
                onPress={() => applyBulkAction('show')}
                tone="ghost"
                fullWidth={false}
                disabled={bulkBusy}
                loading={bulkBusy}
              />
            ) : null}
            {hasUnfeaturedSelection ? (
              <CTAButton
                label={t('photos.actions.feature', 'Set highlight')}
                onPress={() => applyBulkAction('feature')}
                tone="ghost"
                fullWidth={false}
                disabled={bulkBusy}
                loading={bulkBusy}
              />
            ) : null}
            {hasFeaturedSelection ? (
              <CTAButton
                label={t('photos.actions.unfeature', 'Remove highlight')}
                onPress={() => applyBulkAction('unfeature')}
                tone="ghost"
                fullWidth={false}
                disabled={bulkBusy}
                loading={bulkBusy}
              />
            ) : null}
          </XStack>
        </YStack>
      ) : null}

      <AnimatePresence>
        {lightbox ? (
          <motion.div
            className="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-center justify-center"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            <motion.div
              initial={{ y: 12, scale: 0.98, opacity: 0 }}
              animate={{ y: 0, scale: 1, opacity: 1 }}
              exit={{ y: 12, scale: 0.98, opacity: 0 }}
              transition={{ duration: 0.2, ease: 'easeOut' }}
              style={{
                width: '100%',
                maxWidth: 520,
                margin: '0 16px',
                background: surface,
                borderRadius: 20,
                overflow: 'hidden',
                boxShadow: '0 10px 30px rgba(0,0,0,0.25)',
              }}
            >
              <motion.div
                drag
                dragElastic={0.2}
                dragConstraints={{ left: -120, right: 120, top: -120, bottom: 120 }}
                dragSnapToOrigin
                onDragEnd={handleLightboxDragEnd}
                style={{ touchAction: 'none' }}
              >
                <motion.img
                  src={lightboxImageSrc ?? undefined}
                  alt={lightbox.caption ?? 'Photo'}
                  loading="eager"
                  style={{ width: '100%', maxHeight: '60vh', objectFit: 'contain', background: backdrop }}
                  onError={() => {
                    if (lightbox?.thumbnail_url && lightboxImageSrc !== lightbox.thumbnail_url) {
                      setLightboxImageSrc(lightbox.thumbnail_url);
                    }
                  }}
                />
              </motion.div>
              <YStack padding="$3" space="$2">
                <XStack space="$2" alignItems="center">
                  <PillBadge tone="muted">{lightbox.uploader_name || t('events.members.roles.guest', 'Guest')}</PillBadge>
                  <PillBadge tone="muted">❤️ {lightbox.likes_count ?? 0}</PillBadge>
                  {lightbox.status === 'pending' ? (
                    <PillBadge tone="warning">{t('photos.filters.pending', 'Pending')}</PillBadge>
                  ) : null}
                  {lightbox.status === 'hidden' ? (
                    <PillBadge tone="muted">{t('photos.filters.hidden', 'Hidden')}</PillBadge>
                  ) : null}
                </XStack>
                <XStack space="$2" flexWrap="wrap">
                  {lightbox.status === 'pending' ? (
                    <CTAButton
                      label={
                        busyId === lightbox.id
                          ? t('common.processing', '...')
                          : t('photos.actions.approve', 'Approve')
                      }
                      onPress={() => approvePhoto(lightbox)}
                      style={{ flex: 1, minWidth: 140 }}
                    />
                  ) : null}
                  <CTAButton
                    label={
                      busyId === lightbox.id
                        ? t('common.processing', '...')
                        : lightbox.is_featured
                          ? t('photos.actions.unfeature', 'Remove highlight')
                          : t('photos.actions.feature', 'Set highlight')
                    }
                    onPress={() => toggleFeature(lightbox)}
                    style={{ flex: 1, minWidth: 140 }}
                  />
                  <CTAButton
                    label={
                      busyId === lightbox.id
                        ? t('common.processing', '...')
                        : lightbox.status === 'hidden'
                          ? t('photos.actions.show', 'Show')
                          : t('photos.actions.hide', 'Hide')
                    }
                    onPress={() => toggleVisibility(lightbox)}
                    style={{ flex: 1, minWidth: 140 }}
                  />
                </XStack>
                <CTAButton
                  label={t('common.close', 'Close')}
                  tone="ghost"
                    onPress={() => setLightboxWithUrl(null)}
                />
              </YStack>
            </motion.div>
          </motion.div>
        ) : null}
      </AnimatePresence>

      <MobileSheet
        open={showFilters}
        onClose={() => setShowFilters(false)}
        title={t('mobilePhotos.filtersTitle', 'Filter')}
        footer={
          <CTAButton
            label={t('mobilePhotos.applyFilters', 'Apply filters')}
            onPress={() => {
              setPage(1);
              setShowFilters(false);
              void load();
            }}
          />
        }
      >
        <YStack space="$2">
          <MobileField label={t('mobilePhotos.uploader', 'Uploader')}>
            <MobileInput
              type="text"
              value={uploaderFilter}
              onChange={(e) => setUploaderFilter(e.target.value)}
              placeholder={t('mobilePhotos.uploaderPlaceholder', 'Name or email')}
              compact
            />
          </MobileField>
          <XStack space="$2" alignItems="center">
            <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <input
                type="checkbox"
                checked={onlyFeatured}
                onChange={(e) => setOnlyFeatured(e.target.checked)}
              />
              <Text fontSize="$sm" color={text}>
                {t('mobilePhotos.onlyFeatured', 'Only featured')}
              </Text>
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <input
                type="checkbox"
                checked={onlyHidden}
                onChange={(e) => setOnlyHidden(e.target.checked)}
              />
              <Text fontSize="$sm" color={text}>
                {t('mobilePhotos.onlyHidden', 'Only hidden')}
              </Text>
            </label>
          </XStack>
          <CTAButton
            label={t('common.reset', 'Reset')}
            tone="ghost"
            onPress={() => {
              setUploaderFilter('');
              setOnlyFeatured(false);
              setOnlyHidden(false);
            }}
          />
        </YStack>
      </MobileSheet>

      {eventAddons.length ? (
        <YStack marginTop="$3" space="$2">
          <Text fontSize="$sm" color={text} fontWeight="700">
            {t('events.sections.addons.title', 'Add-ons & Upgrades')}
          </Text>
          <EventAddonList addons={eventAddons} textColor={text} mutedColor={muted} />
        </YStack>
      ) : null}

      <LegalConsentSheet
        open={consentOpen}
        onClose={() => {
          if (consentBusy) return;
          setConsentOpen(false);
          setConsentTarget(null);
        }}
        onConfirm={confirmAddonCheckout}
        busy={consentBusy}
        t={t}
      />
    </MobileShell>
  );
}

type PhotoSwipeCardProps = {
  photo: TenantPhoto;
  disabled?: boolean;
  onOpen: () => void;
  onModerate: (action: PhotoModerationAction['action']) => void;
  children: React.ReactNode;
};

type SwipeActionConfig = {
  label: string;
  bg: string;
  text: string;
  icon: typeof Eye;
};

function PhotoSwipeCard({ photo, disabled = false, onOpen, onModerate, children }: PhotoSwipeCardProps) {
  const { t } = useTranslation('management');
  const { successBg, successText, dangerBg, dangerText, infoBg, infoText } = useAdminTheme();
  const controls = useAnimationControls();
  const dragged = React.useRef(false);
  const leftAction = resolvePhotoSwipeAction(photo, 'left');
  const rightAction = resolvePhotoSwipeAction(photo, 'right');
  const canSwipe = !disabled && (leftAction || rightAction);

  const resolveActionConfig = (action: SwipeModerationAction): SwipeActionConfig | null => {
    if (!action) {
      return null;
    }
    if (action === 'approve') {
      return {
        label: t('photos.actions.approve', 'Approve'),
        bg: successBg,
        text: successText,
        icon: Check,
      };
    }
    if (action === 'hide') {
      return {
        label: t('photos.actions.hide', 'Hide'),
        bg: dangerBg,
        text: dangerText,
        icon: EyeOff,
      };
    }
    return {
      label: t('photos.actions.show', 'Show'),
      bg: infoBg,
      text: infoText,
      icon: Eye,
    };
  };

  const leftConfig = resolveActionConfig(leftAction);
  const rightConfig = resolveActionConfig(rightAction);

  const handleDrag = (_event: PointerEvent, info: PanInfo) => {
    if (!canSwipe) {
      return;
    }
    dragged.current = Math.abs(info.offset.x) > 6;
  };

  const handleDragEnd = (_event: PointerEvent, info: PanInfo) => {
    if (!canSwipe) {
      return;
    }
    const swipeThreshold = 64;
    if (info.offset.x > swipeThreshold && rightAction) {
      onModerate(rightAction);
    } else if (info.offset.x < -swipeThreshold && leftAction) {
      onModerate(leftAction);
    }
    dragged.current = false;
    void controls.start({ x: 0, transition: { type: 'spring', stiffness: 320, damping: 26 } });
  };

  const handlePress = () => {
    if (dragged.current) {
      dragged.current = false;
      return;
    }
    onOpen();
  };

  return (
    <div style={{ position: 'relative' }}>
      {leftConfig || rightConfig ? (
        <XStack
          alignItems="center"
          justifyContent="space-between"
          paddingHorizontal="$2"
          borderRadius="$3"
          pointerEvents="none"
          style={{ position: 'absolute', inset: 0 }}
        >
          <XStack flex={1} alignItems="center" justifyContent="flex-start">
            {rightConfig ? (
              <XStack
                alignItems="center"
                space="$1"
                paddingHorizontal="$2"
                paddingVertical="$1"
                borderRadius={999}
                backgroundColor={rightConfig.bg}
              >
                <rightConfig.icon size={12} color={rightConfig.text} />
                <Text fontSize="$xs" fontWeight="700" color={rightConfig.text}>
                  {rightConfig.label}
                </Text>
              </XStack>
            ) : null}
          </XStack>
          <XStack flex={1} alignItems="center" justifyContent="flex-end">
            {leftConfig ? (
              <XStack
                alignItems="center"
                space="$1"
                paddingHorizontal="$2"
                paddingVertical="$1"
                borderRadius={999}
                backgroundColor={leftConfig.bg}
              >
                <leftConfig.icon size={12} color={leftConfig.text} />
                <Text fontSize="$xs" fontWeight="700" color={leftConfig.text}>
                  {leftConfig.label}
                </Text>
              </XStack>
            ) : null}
          </XStack>
        </XStack>
      ) : null}
      <motion.div
        drag={canSwipe ? 'x' : false}
        dragElastic={0.2}
        dragConstraints={{ left: -80, right: 80 }}
        onDrag={handleDrag}
        onDragEnd={handleDragEnd}
        animate={controls}
        initial={{ x: 0 }}
        style={{ touchAction: 'pan-y', position: 'relative', zIndex: 1 }}
      >
        <Pressable onPress={handlePress}>{children}</Pressable>
      </motion.div>
    </div>
  );
}

type PhotoQuickActionsProps = {
  photo: TenantPhoto;
  disabled?: boolean;
  muted: string;
  surface: string;
  onAction: (action: PhotoModerationAction['action']) => void;
};

function PhotoQuickActions({ photo, disabled = false, muted, surface, onAction }: PhotoQuickActionsProps) {
  const { t } = useTranslation('management');
  const actionButtons: Array<{ key: PhotoModerationAction['action']; icon: typeof Check; label: string }> = [];

  if (photo.status === 'pending') {
    actionButtons.push({ key: 'approve', icon: Check, label: t('photos.actions.approve', 'Approve') });
    actionButtons.push({ key: 'hide', icon: EyeOff, label: t('photos.actions.hide', 'Hide') });
  } else if (photo.status === 'hidden') {
    actionButtons.push({ key: 'show', icon: Eye, label: t('photos.actions.show', 'Show') });
  } else {
    actionButtons.push({ key: 'hide', icon: EyeOff, label: t('photos.actions.hide', 'Hide') });
  }

  if (photo.status !== 'hidden') {
    actionButtons.push({
      key: photo.is_featured ? 'unfeature' : 'feature',
      icon: Sparkles,
      label: photo.is_featured ? t('photos.actions.unfeature', 'Remove highlight') : t('photos.actions.feature', 'Set highlight'),
    });
  }

  if (actionButtons.length === 0) {
    return null;
  }

  return (
    <XStack
      position="absolute"
      bottom={6}
      left={6}
      right={6}
      paddingHorizontal="$1"
      paddingVertical="$1"
      borderRadius={12}
      backgroundColor="rgba(15, 23, 42, 0.45)"
      alignItems="center"
      justifyContent="flex-start"
      space="$1"
    >
      {actionButtons.map((action) => (
        <Pressable
          key={action.key}
          disabled={disabled}
          aria-label={action.label}
          onPress={(event: any) => {
            event.stopPropagation();
            if (!disabled) {
              onAction(action.key);
            }
          }}
        >
          <XStack
            alignItems="center"
            justifyContent="center"
            width={28}
            height={28}
            borderRadius={999}
            backgroundColor={surface}
          >
            <action.icon size={14} color={muted} />
          </XStack>
        </Pressable>
      ))}
    </XStack>
  );
}

type LimitTranslator = (key: string, options?: Record<string, unknown>) => string;

function translateLimits(t: (key: string, defaultValue?: string, options?: Record<string, unknown>) => string): LimitTranslator {
  const defaults: Record<string, string> = {
    photosBlocked: 'Upload limit reached. Buy more photos to continue.',
    photosWarning: '{{remaining}} of {{limit}} photos remaining.',
    guestsBlocked: 'Guest limit reached.',
    guestsWarning: '{{remaining}} of {{limit}} guests remaining.',
    galleryExpired: 'Gallery expired. Extend to keep it online.',
    galleryWarningHour: 'Gallery expires in {{hours}} hour.',
    galleryWarningHours: 'Gallery expires in {{hours}} hours.',
    galleryWarningDay: 'Gallery expires in {{days}} day.',
    galleryWarningDays: 'Gallery expires in {{days}} days.',
    buyMorePhotos: 'Buy more photos',
    extendGallery: 'Extend gallery',
    buyMoreGuests: 'Add more guests',
  };
  return (key, options) => t(`limits.${key}`, defaults[key] ?? key, options);
}

export function LimitWarnings({
  limits,
  addons,
  onCheckout,
  busyScope,
  translate,
  textColor,
  borderColor,
}: {
  limits: EventLimitSummary | null;
  addons: EventAddonCatalogItem[];
  onCheckout: (scopeOrKey: 'photos' | 'gallery' | string) => void;
  busyScope: string | null;
  translate: LimitTranslator;
  textColor: string;
  borderColor: string;
}) {
  const warnings = React.useMemo(() => buildLimitWarnings(limits, translate), [limits, translate]);

  if (!warnings.length) {
    return null;
  }

  return (
    <YStack space="$2">
      {warnings.map((warning) => (
        <MobileCard key={warning.id} borderColor={borderColor} space="$2">
          <Text fontSize="$sm" color={textColor} fontWeight="700">
            {warning.message}
          </Text>
          {(warning.scope === 'photos' || warning.scope === 'gallery' || warning.scope === 'guests')
          && resolveAddonOptions(addons, warning.scope).length ? (
              <MobileAddonsPicker
                scope={warning.scope}
                addons={addons}
                busy={busyScope === warning.scope}
                onCheckout={onCheckout}
                translate={translate}
              />
            ) : (
              <CTAButton
                label={
                  warning.scope === 'photos'
                    ? translate('buyMorePhotos')
                    : warning.scope === 'gallery'
                      ? translate('extendGallery')
                      : translate('buyMoreGuests')
                }
                onPress={() => onCheckout(warning.scope)}
                loading={busyScope === warning.scope}
              />
            )}
        </MobileCard>
      ))}
    </YStack>
  );
}

function resolveAddonOptions(addons: EventAddonCatalogItem[], scope: 'photos' | 'gallery' | 'guests'): EventAddonCatalogItem[] {
  const whitelist = scopeDefaults[scope];
  const filtered = addons.filter((addon) => addon.price_id && whitelist.includes(addon.key));
  return filtered.length ? filtered : addons.filter((addon) => addon.price_id);
}

function MobileAddonsPicker({
  scope,
  addons,
  busy,
  onCheckout,
  translate,
}: {
  scope: 'photos' | 'gallery' | 'guests';
  addons: EventAddonCatalogItem[];
  busy: boolean;
  onCheckout: (addonKey: string) => void;
  translate: LimitTranslator;
}) {
  const options = React.useMemo(() => resolveAddonOptions(addons, scope), [addons, scope]);

  const [selected, setSelected] = React.useState<string>(() => options[0]?.key ?? selectAddonKeyForScope(addons, scope));

  React.useEffect(() => {
    if (options[0]?.key) {
      setSelected(options[0].key);
    }
  }, [options]);

  if (!options.length) {
    return null;
  }

  return (
    <XStack space="$2" alignItems="center">
      <MobileSelect
        value={selected}
        onChange={(event) => setSelected(event.target.value)}
        containerStyle={{ flex: 1, minWidth: 0 }}
        compact
      >
        {options.map((addon) => (
          <option key={addon.key} value={addon.key}>
            {addon.label ?? addon.key}
          </option>
        ))}
      </MobileSelect>
      <CTAButton
        label={
          scope === 'gallery'
            ? translate('extendGallery')
            : scope === 'guests'
              ? translate('buyMoreGuests')
              : translate('buyMorePhotos')
        }
        disabled={!selected || busy}
        onPress={() => selected && onCheckout(selected)}
        loading={busy}
        fullWidth={false}
      />
    </XStack>
  );
}

function EventAddonList({ addons, textColor, mutedColor }: { addons: EventAddonSummary[]; textColor: string; mutedColor: string }) {
  const { border } = useAdminTheme();
  return (
    <YStack space="$2">
      {addons.map((addon) => (
        <MobileCard key={addon.id} borderColor={border} space="$1.5">
          <XStack alignItems="center" justifyContent="space-between">
            <Text fontSize="$sm" fontWeight="700" color={textColor}>
              {addon.label ?? addon.key}
            </Text>
            <PillBadge tone={addon.status === 'completed' ? 'success' : addon.status === 'pending' ? 'warning' : 'muted'}>
              {addon.status}
            </PillBadge>
          </XStack>
          <Text fontSize="$xs" color={mutedColor}>
            {addon.purchased_at ? new Date(addon.purchased_at).toLocaleString() : '—'}
          </Text>
          <XStack space="$2" marginTop="$1" flexWrap="wrap">
            {addon.extra_photos ? <PillBadge tone="muted">+{addon.extra_photos} photos</PillBadge> : null}
            {addon.extra_guests ? <PillBadge tone="muted">+{addon.extra_guests} guests</PillBadge> : null}
            {addon.extra_gallery_days ? <PillBadge tone="muted">+{addon.extra_gallery_days} days</PillBadge> : null}
          </XStack>
        </MobileCard>
      ))}
    </YStack>
  );
}
