import React from 'react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Button } from '@tamagui/button';
import { Camera, ChevronLeft, ChevronRight, Heart, Loader2, Share2, Sparkles, X } from 'lucide-react';
import AppShell from '../components/AppShell';
import PhotoFrameTile from '../components/PhotoFrameTile';
import ShareSheet from '../components/ShareSheet';
import { useEventData } from '../context/EventDataContext';
import { createPhotoShareLink, fetchGallery, fetchPhoto, likePhoto, unlikePhoto } from '../services/photosApi';
import { usePollGalleryDelta } from '../hooks/usePollGalleryDelta';
import { useGuestThemeVariant } from '../lib/guestTheme';
import { useTranslation } from '@/guest/i18n/useTranslation';
import { useLocale } from '@/guest/i18n/LocaleContext';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { buildEventPath } from '../lib/routes';
import { getBentoSurfaceTokens } from '../lib/bento';
import { usePollStats } from '../hooks/usePollStats';
import { pushGuestToast } from '../lib/toast';

type GalleryFilter = 'latest' | 'popular' | 'mine' | 'photobooth';

type GalleryTile = {
  id: number;
  imageUrl: string;
  likes: number;
  createdAt?: string | null;
  ingestSource?: string | null;
  sessionId?: string | null;
};

type LightboxPhoto = {
  id: number;
  imageUrl: string;
  likes: number;
};

function normalizeImageUrl(src?: string | null) {
  if (!src) {
    return '';
  }

  if (/^https?:/i.test(src)) {
    return src;
  }

  let cleanPath = src.replace(/^\/+/g, '').replace(/\/+/g, '/');
  if (!cleanPath.startsWith('storage/')) {
    cleanPath = `storage/${cleanPath}`;
  }

  return `/${cleanPath}`.replace(/\/+/g, '/');
}

export default function GalleryScreen() {
  const { token, event } = useEventData();
  const { t } = useTranslation();
  const { locale } = useLocale();
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const { isDark } = useGuestThemeVariant();
  const bentoSurface = getBentoSurfaceTokens(isDark);
  const cardShadow = bentoSurface.shadow;
  const hardShadow = isDark
    ? '0 18px 0 rgba(2, 6, 23, 0.55), 0 32px 40px rgba(2, 6, 23, 0.55)'
    : '0 18px 0 rgba(15, 23, 42, 0.22), 0 30px 36px rgba(15, 23, 42, 0.2)';
  const mutedButton = isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)';
  const mutedButtonBorder = isDark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)';
  const [photos, setPhotos] = React.useState<GalleryTile[]>([]);
  const [loading, setLoading] = React.useState(false);
  const { data: delta } = usePollGalleryDelta(token ?? null, { locale });
  const { stats } = usePollStats(token ?? null, 12000);
  const [filter, setFilter] = React.useState<GalleryFilter>('latest');
  const uploadPath = React.useMemo(() => buildEventPath(token ?? null, '/upload'), [token]);
  const numberFormatter = React.useMemo(() => new Intl.NumberFormat(locale), [locale]);
  const [lightboxPhoto, setLightboxPhoto] = React.useState<LightboxPhoto | null>(null);
  const [lightboxLoading, setLightboxLoading] = React.useState(false);
  const [lightboxError, setLightboxError] = React.useState<'notFound' | 'loadFailed' | null>(null);
  const [likesById, setLikesById] = React.useState<Record<number, number>>({});
  const [shareSheet, setShareSheet] = React.useState<{ url: string | null; loading: boolean }>({
    url: null,
    loading: false,
  });
  const [likedIds, setLikedIds] = React.useState<Set<number>>(new Set());
  const touchStartX = React.useRef<number | null>(null);
  const fallbackAttemptedRef = React.useRef(false);
  const pendingNotFoundRef = React.useRef(false);
  const photosRef = React.useRef<GalleryTile[]>([]);
  const galleryLoadingRef = React.useRef(Boolean(token));

  React.useEffect(() => {
    if (!token) {
      setPhotos([]);
      galleryLoadingRef.current = false;
      return;
    }

    let active = true;
    setLoading(true);
    galleryLoadingRef.current = true;

    fetchGallery(token, { limit: 18, locale })
      .then((response) => {
        if (!active) return;
        const list = Array.isArray(response.data) ? response.data : [];
        const mapped = list
          .map((photo) => {
            const record = photo as Record<string, unknown>;
            const id = Number(record.id ?? 0);
            const likesCount = typeof record.likes_count === 'number' ? record.likes_count : 0;
            const imageUrl = normalizeImageUrl(
              (record.thumbnail_url as string | null | undefined)
                ?? (record.thumbnail_path as string | null | undefined)
                ?? (record.file_path as string | null | undefined)
                ?? (record.full_url as string | null | undefined)
                ?? (record.url as string | null | undefined)
                ?? (record.image_url as string | null | undefined)
            );
            return {
              id,
              imageUrl,
              likes: likesCount,
              createdAt: typeof record.created_at === 'string' ? record.created_at : null,
              ingestSource: typeof record.ingest_source === 'string' ? record.ingest_source : null,
              sessionId: typeof record.session_id === 'string' ? record.session_id : null,
            };
          })
          .filter((item) => item.id && item.imageUrl);
        setPhotos(mapped);
      })
      .catch((error) => {
        console.error('Failed to load gallery', error);
        if (active) {
          setPhotos([]);
        }
      })
      .finally(() => {
        if (active) {
          setLoading(false);
        }
        galleryLoadingRef.current = false;
      });

    return () => {
      active = false;
    };
  }, [token, locale]);

  React.useEffect(() => {
    photosRef.current = photos;
  }, [photos]);

  const myPhotoIds = React.useMemo(() => {
    try {
      const raw = localStorage.getItem('my-photo-ids');
      return new Set<number>(raw ? JSON.parse(raw) : []);
    } catch {
      return new Set<number>();
    }
  }, [token]);

  const filteredPhotos = React.useMemo(() => {
    let list = photos.slice();
    if (filter === 'popular') {
      list.sort((a, b) => (b.likes ?? 0) - (a.likes ?? 0));
    } else if (filter === 'mine') {
      list = list.filter((photo) => myPhotoIds.has(photo.id));
    } else if (filter === 'photobooth') {
      list = list.filter((photo) => photo.ingestSource === 'photobooth');
      list.sort((a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime());
    } else {
      list.sort((a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime());
    }
    return list;
  }, [filter, myPhotoIds, photos]);

  const displayPhotos = filteredPhotos;
  const leftColumn = displayPhotos.filter((_, index) => index % 2 === 0);
  const rightColumn = displayPhotos.filter((_, index) => index % 2 === 1);
  const isEmpty = !loading && displayPhotos.length === 0;
  const isSingle = !loading && displayPhotos.length === 1;
  const selectedPhotoId = Number(searchParams.get('photo') ?? 0);
  const lightboxIndex = React.useMemo(() => {
    if (!selectedPhotoId) {
      return -1;
    }
    return displayPhotos.findIndex((item) => item.id === selectedPhotoId);
  }, [displayPhotos, selectedPhotoId]);
  const lightboxSelected = lightboxIndex >= 0 ? displayPhotos[lightboxIndex] : null;
  const lightboxOpen = Boolean(selectedPhotoId);

  React.useEffect(() => {
    if (filter === 'photobooth' && !photos.some((photo) => photo.ingestSource === 'photobooth')) {
      setFilter('latest');
    }
  }, [filter, photos]);

  React.useEffect(() => {
    fallbackAttemptedRef.current = false;
  }, [selectedPhotoId]);

  React.useEffect(() => {
    if (!lightboxOpen || !selectedPhotoId) {
      return;
    }
    if (lightboxIndex >= 0) {
      return;
    }
    if (filter !== 'latest' && !fallbackAttemptedRef.current) {
      fallbackAttemptedRef.current = true;
      setFilter('latest');
    }
  }, [filter, lightboxIndex, lightboxOpen, selectedPhotoId]);
  const newUploads = React.useMemo(() => {
    if (delta.photos.length === 0) {
      return 0;
    }
    const existing = new Set(photos.map((item) => item.id));
    return delta.photos.reduce((count, photo) => {
      const id = Number((photo as Record<string, unknown>).id ?? 0);
      if (id && !existing.has(id)) {
        return count + 1;
      }
      return count;
    }, 0);
  }, [delta.photos, photos]);
  const openLightbox = React.useCallback(
    (photoId: number) => {
      if (!token) return;
      const next = new URLSearchParams(searchParams);
      next.set('photo', String(photoId));
      setSearchParams(next, { replace: false });
    },
    [searchParams, setSearchParams, token]
  );
  const closeLightbox = React.useCallback(() => {
    const next = new URLSearchParams(searchParams);
    next.delete('photo');
    setSearchParams(next, { replace: true });
  }, [searchParams, setSearchParams]);

  React.useEffect(() => {
    if (delta.photos.length === 0) {
      return;
    }
    setPhotos((prev) => {
      const existing = new Set(prev.map((item) => item.id));
      const mapped = delta.photos
        .map((photo) => {
          const record = photo as Record<string, unknown>;
          const id = Number(record.id ?? 0);
          const likesCount = typeof record.likes_count === 'number' ? record.likes_count : 0;
          const imageUrl = normalizeImageUrl(
            (record.thumbnail_url as string | null | undefined)
              ?? (record.thumbnail_path as string | null | undefined)
              ?? (record.file_path as string | null | undefined)
              ?? (record.full_url as string | null | undefined)
              ?? (record.url as string | null | undefined)
              ?? (record.image_url as string | null | undefined)
          );
          if (!id || !imageUrl || existing.has(id)) {
            return null;
          }
          return {
            id,
            imageUrl,
            likes: likesCount,
            createdAt: typeof record.created_at === 'string' ? record.created_at : null,
            ingestSource: typeof record.ingest_source === 'string' ? record.ingest_source : null,
            sessionId: typeof record.session_id === 'string' ? record.session_id : null,
          } satisfies GalleryTile;
        })
        .filter(Boolean) as GalleryTile[];
      if (mapped.length === 0) {
        return prev;
      }
      return [...mapped, ...prev];
    });
  }, [delta.photos]);

  const heroStatsLine = t(
    'galleryPage.hero.stats',
    {
      photoCount: numberFormatter.format(photos.length),
      likeCount: numberFormatter.format(stats.likesCount ?? 0),
      guestCount: numberFormatter.format(stats.onlineGuests || stats.guestCount || 0),
    },
    `${numberFormatter.format(photos.length)} Fotos · ${numberFormatter.format(stats.likesCount ?? 0)} ❤️ · ${numberFormatter.format(stats.onlineGuests || stats.guestCount || 0)} Gäste online`
  );

  React.useEffect(() => {
    setLikesById((prev) => {
      const next = { ...prev };
      for (const photo of photos) {
        if (next[photo.id] === undefined) {
          next[photo.id] = photo.likes;
        }
      }
      return next;
    });
  }, [photos]);

  React.useEffect(() => {
    if (!lightboxOpen) {
      setLightboxPhoto(null);
      setLightboxLoading(false);
      setLightboxError(null);
      pendingNotFoundRef.current = false;
      return;
    }

    const seed = lightboxSelected
      ? { id: lightboxSelected.id, imageUrl: lightboxSelected.imageUrl, likes: lightboxSelected.likes }
      : null;
    if (seed) {
      setLightboxPhoto(seed);
    }
    const hasSeed = Boolean(seed);

    if (hasSeed) {
      setLightboxLoading(false);
      return;
    }

    let active = true;
    const maxRetryMs = 10_000;
    const retryDelayMs = 1500;
    let timedOut = false;
    const timeoutId = window.setTimeout(() => {
      timedOut = true;
    }, maxRetryMs);
    setLightboxLoading(true);
    setLightboxError(null);
    const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

    const loadPhoto = async () => {
      let lastError: unknown = null;

      while (active && !timedOut) {
        try {
          const photo = await fetchPhoto(selectedPhotoId, locale);
          if (!active) return;
          if (photo) {
            const mapped = mapFullPhoto(photo as Record<string, unknown>);
            if (mapped) {
              setLightboxPhoto(mapped);
              setLikesById((prev) => ({ ...prev, [mapped.id]: mapped.likes }));
              setLightboxLoading(false);
              return;
            }
          }
          lastError = { status: 404 };
        } catch (error) {
          console.error('Lightbox photo load failed', error);
          lastError = error;
        }

        if (!active || timedOut) break;
        await sleep(retryDelayMs);
      }

      if (!active) return;
      const status = (lastError as { status?: number } | null)?.status;
      setLightboxError(status === 404 ? 'notFound' : 'loadFailed');
      setLightboxLoading(false);
    };

    void loadPhoto();

    return () => {
      active = false;
      window.clearTimeout(timeoutId);
    };
  }, [lightboxOpen, lightboxSelected, locale, selectedPhotoId]);

  React.useEffect(() => {
    if (!lightboxOpen) {
      document.body.style.overflow = '';
      return;
    }
    document.body.style.overflow = 'hidden';
    const handleKey = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        closeLightbox();
      }
    };
    window.addEventListener('keydown', handleKey);
    return () => {
      document.body.style.overflow = '';
      window.removeEventListener('keydown', handleKey);
    };
  }, [lightboxOpen]);

  React.useEffect(() => {
    if (!pendingNotFoundRef.current) return;
    if (loading || galleryLoadingRef.current) return;
    if (lightboxSelected || photosRef.current.some((photo) => photo.id === selectedPhotoId)) {
      pendingNotFoundRef.current = false;
      return;
    }
    pendingNotFoundRef.current = false;
    setLightboxError('notFound');
  }, [lightboxSelected, loading]);

  React.useEffect(() => {
    if (!lightboxOpen || !lightboxError) {
      return;
    }
    pushGuestToast({
      text: lightboxError === 'notFound'
        ? t('lightbox.errors.notFound', 'Photo not found')
        : t('lightbox.errors.loadFailed', 'Failed to load photo'),
      type: 'warning',
    });
    closeLightbox();
  }, [closeLightbox, lightboxError, lightboxOpen, t]);

  const goPrev = React.useCallback(() => {
    if (lightboxIndex <= 0) return;
    const prevId = displayPhotos[lightboxIndex - 1]?.id;
    if (prevId) {
      openLightbox(prevId);
    }
  }, [displayPhotos, lightboxIndex, openLightbox]);

  const goNext = React.useCallback(() => {
    if (lightboxIndex < 0 || lightboxIndex >= displayPhotos.length - 1) return;
    const nextId = displayPhotos[lightboxIndex + 1]?.id;
    if (nextId) {
      openLightbox(nextId);
    }
  }, [displayPhotos, lightboxIndex, openLightbox]);

  const handleLike = React.useCallback(async () => {
    if (!lightboxPhoto) return;
    const isLiked = likedIds.has(lightboxPhoto.id);
    const current = likesById[lightboxPhoto.id] ?? lightboxPhoto.likes;
    const nextCount = Math.max(0, current + (isLiked ? -1 : 1));
    setLikedIds((prev) => {
      const next = new Set(prev);
      if (isLiked) {
        next.delete(lightboxPhoto.id);
      } else {
        next.add(lightboxPhoto.id);
      }
      return next;
    });
    setLikesById((prev) => ({ ...prev, [lightboxPhoto.id]: nextCount }));
    try {
      const count = isLiked ? await unlikePhoto(lightboxPhoto.id) : await likePhoto(lightboxPhoto.id);
      setLikesById((prev) => ({ ...prev, [lightboxPhoto.id]: count }));
    } catch (error) {
      console.error('Like failed', error);
      setLikedIds((prev) => {
        const next = new Set(prev);
        if (isLiked) {
          next.add(lightboxPhoto.id);
        } else {
          next.delete(lightboxPhoto.id);
        }
        return next;
      });
      setLikesById((prev) => ({ ...prev, [lightboxPhoto.id]: current }));
    }
  }, [lightboxPhoto, likedIds, likesById]);

  const shareTitle = event?.name ?? t('share.title', 'Shared photo');
  const shareText = t('share.shareText', 'Check out this moment on Fotospiel.');

  const openShareSheet = React.useCallback(async () => {
    if (!lightboxPhoto || !token) return;
    setShareSheet({ url: null, loading: true });
    try {
      const payload = await createPhotoShareLink(token, lightboxPhoto.id);
      const url = payload?.url ?? null;
      setShareSheet({ url, loading: false });
    } catch (error) {
      console.error('Share failed', error);
      pushGuestToast({ text: t('share.error', 'Share failed'), type: 'error' });
      setShareSheet({ url: null, loading: false });
    }
  }, [lightboxPhoto, t, token]);

  const closeShareSheet = React.useCallback(() => {
    setShareSheet({ url: null, loading: false });
  }, []);

  const shareWhatsApp = React.useCallback(
    (url?: string | null) => {
      if (!url) return;
      const waUrl = `https://wa.me/?text=${encodeURIComponent(`${shareText} ${url}`)}`;
      window.open(waUrl, '_blank', 'noopener');
      closeShareSheet();
    },
    [closeShareSheet, shareText]
  );

  const shareMessages = React.useCallback(
    (url?: string | null) => {
      if (!url) return;
      const smsUrl = `sms:?&body=${encodeURIComponent(`${shareText} ${url}`)}`;
      window.open(smsUrl, '_blank', 'noopener');
      closeShareSheet();
    },
    [closeShareSheet, shareText]
  );

  const copyLink = React.useCallback(
    async (url?: string | null) => {
      if (!url) return;
      try {
        await navigator.clipboard?.writeText(url);
        pushGuestToast({ text: t('share.copySuccess', 'Link copied!') });
      } catch (error) {
        console.error('Copy failed', error);
        pushGuestToast({ text: t('share.copyError', 'Link could not be copied.'), type: 'error' });
      } finally {
        closeShareSheet();
      }
    },
    [closeShareSheet, t]
  );

  const shareNative = React.useCallback(
    (url?: string | null) => {
      if (!url) return;
      const data: ShareData = {
        title: shareTitle,
        text: shareText,
        url,
      };
      if (navigator.share && (!navigator.canShare || navigator.canShare(data))) {
        navigator.share(data).catch(() => undefined);
        closeShareSheet();
        return;
      }
      void copyLink(url);
    },
    [closeShareSheet, copyLink, shareText, shareTitle]
  );

  const handleTouchStart = (event: React.TouchEvent) => {
    touchStartX.current = event.touches[0]?.clientX ?? null;
  };

  const handleTouchEnd = (event: React.TouchEvent) => {
    if (touchStartX.current === null) {
      return;
    }
    const endX = event.changedTouches[0]?.clientX ?? null;
    if (endX === null) {
      touchStartX.current = null;
      return;
    }
    const delta = endX - touchStartX.current;
    touchStartX.current = null;
    if (Math.abs(delta) < 60) {
      return;
    }
    if (delta > 0) {
      goPrev();
      return;
    }
    goNext();
  };

  return (
    <AppShell>
      <YStack gap="$4">
        <YStack
          padding="$2"
          borderRadius="$bentoLg"
          backgroundColor={bentoSurface.backgroundColor}
          borderWidth={1}
          borderBottomWidth={3}
          borderColor={bentoSurface.borderColor}
          borderBottomColor={bentoSurface.borderBottomColor}
          gap="$2"
          position="relative"
          overflow="hidden"
          style={{
            backgroundImage: isDark
              ? 'radial-gradient(120% 120% at 20% 20%, rgba(56, 189, 248, 0.18), transparent 55%), radial-gradient(120% 120% at 80% 15%, rgba(251, 113, 133, 0.18), transparent 60%)'
              : 'radial-gradient(130% 130% at 20% 20%, color-mix(in oklab, var(--guest-primary, #0EA5E9) 25%, white), transparent 55%), radial-gradient(120% 120% at 80% 0%, color-mix(in oklab, var(--guest-secondary, #F43F5E) 18%, white), transparent 60%)',
            boxShadow: hardShadow,
          }}
        >
          <XStack alignItems="center" gap="$2">
            <Sparkles size={18} color={isDark ? '#F8FAFF' : '#0F172A'} />
            <Text fontSize="$1" color="$color" opacity={0.7} textTransform="uppercase" letterSpacing={1.2}>
              {t('galleryPage.hero.label', 'Live-Galerie')}
            </Text>
          </XStack>
          <XStack alignItems="center" justifyContent="space-between" flexWrap="wrap" gap="$2">
            <Text fontSize="$6" fontFamily="$display" fontWeight="$8">
              {event?.name ?? t('galleryPage.hero.eventFallback', 'Euer Event')}
            </Text>
            <Button
              size="$3"
              backgroundColor="$primary"
              borderRadius="$pill"
              onPress={() => navigate(uploadPath)}
              pressStyle={{ y: 2 }}
              style={{
                boxShadow: isDark ? '0 8px 0 rgba(2, 6, 23, 0.55)' : '0 8px 0 rgba(15, 23, 42, 0.18)',
              }}
            >
              {t('galleryPage.hero.upload', 'Neues Foto hochladen')}
            </Button>
          </XStack>
          {newUploads > 0 ? (
            <YStack
              paddingHorizontal="$2.5"
              paddingVertical="$1"
              borderRadius="$pill"
              backgroundColor={mutedButton}
              borderWidth={1}
              borderColor={mutedButtonBorder}
              alignSelf="flex-start"
            >
              <Text fontSize="$1" fontWeight="$6">
                {t('galleryPage.feed.newUploads', { count: newUploads }, '{count} neue Uploads sind da.')}
              </Text>
            </YStack>
          ) : null}
          <Text fontSize="$1" color="$color" opacity={0.7}>
            {heroStatsLine}
          </Text>
          <YStack
            gap="$1"
            paddingTop="$1"
            borderTopWidth={1}
            borderTopColor={bentoSurface.borderColor}
          >
            <XStack gap="$1.5" flexWrap="wrap" justifyContent="center">
              {(
                [
                  { value: 'latest', label: t('galleryPage.filters.latest', 'Newest') },
                  { value: 'popular', label: t('galleryPage.filters.popular', 'Popular') },
                  { value: 'mine', label: t('galleryPage.filters.mine', 'My photos') },
                  photos.some((photo) => photo.ingestSource === 'photobooth')
                    ? { value: 'photobooth', label: t('galleryPage.filters.photobooth', 'Photo booth') }
                    : null,
                ].filter(Boolean) as Array<{ value: GalleryFilter; label: string }>
              ).map((chip) => (
                <Button
                  key={chip.value}
                  size="$2"
                  backgroundColor={filter === chip.value ? '$primary' : mutedButton}
                  borderRadius="$pill"
                  borderWidth={1}
                  borderColor={filter === chip.value ? '$primary' : mutedButtonBorder}
                  onPress={() => setFilter(chip.value)}
                >
                  <Text fontSize="$1" fontWeight="$6" color={filter === chip.value ? '#FFFFFF' : undefined}>
                    {chip.label}
                  </Text>
                </Button>
              ))}
            </XStack>
          </YStack>
        </YStack>

        {isEmpty ? (
          <YStack
            padding="$4"
            borderRadius="$bento"
            backgroundColor={bentoSurface.backgroundColor}
            borderWidth={1}
            borderBottomWidth={3}
            borderColor={bentoSurface.borderColor}
            borderBottomColor={bentoSurface.borderBottomColor}
            gap="$3"
            alignItems="center"
            style={{
              boxShadow: cardShadow,
            }}
          >
            <YStack
              width={64}
              height={64}
              borderRadius={20}
              backgroundColor="$primary"
              alignItems="center"
              justifyContent="center"
              style={{ boxShadow: cardShadow }}
            >
              <Camera size={28} color="#FFFFFF" />
            </YStack>
            <Text fontSize="$4" fontWeight="$7" textAlign="center">
              {t('galleryPage.emptyTitle', 'Noch keine Fotos')}
            </Text>
            <Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
              {t('galleryPage.emptyDescription', 'Lade das erste Foto hoch und starte die Galerie.')}
            </Text>
            <Button
              size="$3"
              backgroundColor="$primary"
              borderRadius="$pill"
              onPress={() => navigate(uploadPath)}
            >
              <Text fontSize="$2" fontWeight="$7" color="#FFFFFF">
                {t('galleryPage.emptyCta', 'Foto hochladen')}
              </Text>
            </Button>
          </YStack>
        ) : isSingle ? (
          <YStack gap="$3">
            <Button unstyled onPress={() => openLightbox(displayPhotos[0].id)}>
              <PhotoFrameTile height={360} borderRadius="$bento">
                <YStack flex={1} width="100%" height="100%" alignItems="center" justifyContent="center">
                  <img
                    src={displayPhotos[0].imageUrl}
                    alt={t('galleryPage.photo.alt', { id: displayPhotos[0].id, suffix: '' }, `Foto ${displayPhotos[0].id}`)}
                    loading="eager"
                    decoding="async"
                    style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                  />
                </YStack>
              </PhotoFrameTile>
            </Button>
            <Button unstyled onPress={() => navigate(uploadPath)}>
              <PhotoFrameTile height={160} borderRadius="$bento">
                <YStack flex={1} alignItems="center" justifyContent="center" gap="$2" padding="$3">
                  <YStack
                    width={48}
                    height={48}
                    borderRadius={16}
                    backgroundColor="$primary"
                    alignItems="center"
                    justifyContent="center"
                    style={{ boxShadow: cardShadow }}
                  >
                    <Camera size={20} color="#FFFFFF" />
                  </YStack>
                  <Text fontSize="$3" fontWeight="$7" textAlign="center">
                    {t('galleryPage.promptTitle', 'Füge dein nächstes Foto hinzu')}
                  </Text>
                  <Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
                    {t('galleryPage.promptDescription', 'Teile den Moment mit allen Gästen.')}
                  </Text>
                </YStack>
              </PhotoFrameTile>
            </Button>
          </YStack>
        ) : (
          <XStack gap="$3">
            <YStack flex={1} gap="$3">
              {(loading ? Array.from({ length: 5 }, (_, index) => index) : leftColumn).map((tile, index) => {
                if (typeof tile === 'number') {
                  return <PhotoFrameTile key={`left-${tile}`} height={140 + (index % 3) * 24} shimmer shimmerDelayMs={200 + index * 120} />;
                }
                const altText = t('galleryPage.photo.alt', { id: tile.id, suffix: '' }, `Foto ${tile.id}`);
                return (
                  <Button
                    key={tile.id}
                    unstyled
                    onPress={() => openLightbox(tile.id)}
                  >
                    <PhotoFrameTile height={140 + (index % 3) * 24}>
                      <YStack flex={1} width="100%" height="100%" alignItems="center" justifyContent="center">
                        <img
                          src={tile.imageUrl}
                          alt={altText}
                          loading={index < 4 ? 'eager' : 'lazy'}
                          decoding="async"
                          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                        />
                      </YStack>
                    </PhotoFrameTile>
                  </Button>
                );
              })}
            </YStack>
            <YStack flex={1} gap="$3">
              {(loading ? Array.from({ length: 5 }, (_, index) => index) : rightColumn).map((tile, index) => {
                if (typeof tile === 'number') {
                  return <PhotoFrameTile key={`right-${tile}`} height={120 + (index % 3) * 28} shimmer shimmerDelayMs={260 + index * 140} />;
                }
                const altText = t('galleryPage.photo.alt', { id: tile.id, suffix: '' }, `Foto ${tile.id}`);
                return (
                  <Button
                    key={tile.id}
                    unstyled
                    onPress={() => openLightbox(tile.id)}
                  >
                    <PhotoFrameTile height={120 + (index % 3) * 28}>
                      <YStack flex={1} width="100%" height="100%" alignItems="center" justifyContent="center">
                        <img
                          src={tile.imageUrl}
                          alt={altText}
                          loading={index < 4 ? 'eager' : 'lazy'}
                          decoding="async"
                          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                        />
                      </YStack>
                    </PhotoFrameTile>
                  </Button>
                );
              })}
              {!loading && rightColumn.length === 0 ? (
                <Button unstyled onPress={() => navigate(uploadPath)}>
                  <PhotoFrameTile height={180}>
                    <YStack flex={1} alignItems="center" justifyContent="center" gap="$2" padding="$3">
                      <YStack
                        width={46}
                        height={46}
                        borderRadius={16}
                        backgroundColor="$primary"
                        alignItems="center"
                        justifyContent="center"
                        style={{ boxShadow: cardShadow }}
                      >
                        <Camera size={20} color="#FFFFFF" />
                      </YStack>
                      <Text fontSize="$3" fontWeight="$7" textAlign="center">
                        {t('galleryPage.promptTitle', 'Füge dein nächstes Foto hinzu')}
                      </Text>
                      <Text fontSize="$2" color="$color" opacity={0.7} textAlign="center">
                        {t('galleryPage.promptDescription', 'Teile den Moment mit allen Gästen.')}
                      </Text>
                    </YStack>
                  </PhotoFrameTile>
                </Button>
              ) : null}
            </YStack>
          </XStack>
        )}

      </YStack>
      {lightboxOpen ? (
        <YStack
          position="fixed"
          top={0}
          left={0}
          right={0}
          bottom={0}
          zIndex={2000}
          padding="$3"
          alignItems="center"
          justifyContent="center"
          style={{
            backgroundColor: isDark ? 'rgba(15, 23, 42, 0.75)' : 'rgba(15, 23, 42, 0.45)',
            backdropFilter: 'blur(12px)',
          }}
        >
          <Button
            unstyled
            onPress={closeLightbox}
            style={{ position: 'absolute', inset: 0, zIndex: 0 }}
            aria-label={t('common.actions.close', 'Close')}
          />
          <YStack
            width="100%"
            maxWidth={780}
            gap="$2"
            padding="$3"
            borderRadius="$bentoLg"
            backgroundColor={bentoSurface.backgroundColor}
            borderWidth={1}
            borderBottomWidth={3}
            borderColor={bentoSurface.borderColor}
            borderBottomColor={bentoSurface.borderBottomColor}
            style={{ boxShadow: hardShadow, zIndex: 1 }}
          >
            <XStack alignItems="center" justifyContent="space-between">
              <YStack gap="$1">
                <Text fontSize="$1" color="$color" opacity={0.7} textTransform="uppercase" letterSpacing={1.1}>
                  {t('galleryPage.hero.label', 'Live-Galerie')}
                </Text>
                <Text fontSize="$4" fontWeight="$7">
                  {event?.name ?? t('galleryPage.hero.eventFallback', 'Euer Event')}
                </Text>
              </YStack>
              <Button
                size="$3"
                circular
                backgroundColor={mutedButton}
                borderWidth={1}
                borderColor={mutedButtonBorder}
                onPress={closeLightbox}
                aria-label={t('common.actions.close', 'Close')}
              >
                <X size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
              </Button>
            </XStack>
            <YStack
              borderRadius="$bento"
              backgroundColor="$muted"
              borderWidth={1}
              borderBottomWidth={3}
              borderColor={bentoSurface.borderColor}
              borderBottomColor={bentoSurface.borderBottomColor}
              overflow="hidden"
              style={{ height: 'min(70vh, 520px)', boxShadow: cardShadow }}
            >
              <YStack flex={1} alignItems="center" justifyContent="center" padding="$2">
                {lightboxPhoto ? (
                  <YStack
                    flex={1}
                    width="100%"
                    height="100%"
                    position="relative"
                    alignItems="center"
                    justifyContent="center"
                    onTouchStart={handleTouchStart}
                    onTouchEnd={handleTouchEnd}
                    style={{ zIndex: 0 }}
                  >
                    <img
                      src={lightboxPhoto.imageUrl}
                      alt={t('galleryPage.photo.alt', { id: lightboxPhoto.id, suffix: '' }, `Foto ${lightboxPhoto.id}`)}
                      style={{
                        width: '100%',
                        height: '100%',
                        objectFit: 'contain',
                        position: 'absolute',
                        inset: 0,
                        zIndex: 0,
                        pointerEvents: 'none',
                      }}
                    />
                    <Button
                      unstyled
                      onPress={handleLike}
                      aria-label={t('galleryPage.photo.likeAria', 'Like')}
                      style={{
                        position: 'absolute',
                        right: 14,
                        bottom: 14,
                        zIndex: 10,
                      }}
                    >
                      <YStack
                        padding="$2.5"
                        borderRadius="$pill"
                        backgroundColor={likedIds.has(lightboxPhoto.id) ? '#F43F5E' : mutedButton}
                        borderWidth={1}
                        borderColor={likedIds.has(lightboxPhoto.id) ? '#F43F5E' : mutedButtonBorder}
                        alignItems="center"
                        justifyContent="center"
                        style={{
                          boxShadow: isDark ? '0 8px 0 rgba(2, 6, 23, 0.55)' : '0 8px 0 rgba(15, 23, 42, 0.18)',
                        }}
                      >
                        <Heart size={22} color={likedIds.has(lightboxPhoto.id) ? '#FFFFFF' : (isDark ? '#F8FAFF' : '#0F172A')} />
                      </YStack>
                    </Button>
                  </YStack>
                ) : (
                  <YStack alignItems="center" gap="$2">
                    {lightboxLoading ? (
                      <>
                        <Loader2 size={24} className="animate-spin" color={isDark ? '#F8FAFF' : '#0F172A'} />
                        <Text fontSize="$2" color="$color" opacity={0.7}>
                          {t('galleryPage.loading', 'Loading…')}
                        </Text>
                      </>
                    ) : (
                      <Text fontSize="$2" color="$color" opacity={0.7}>
                        {t('lightbox.errors.notFound', 'Photo not found')}
                      </Text>
                    )}
                  </YStack>
                )}
              </YStack>
              <XStack
                alignItems="center"
                justifyContent="space-between"
                padding="$2"
                borderTopWidth={1}
                borderColor={bentoSurface.borderColor}
                backgroundColor={isDark ? 'rgba(15, 23, 42, 0.35)' : 'rgba(255, 255, 255, 0.8)'}
              >
                <Text fontSize="$1" color="$color" opacity={0.7}>
                  {lightboxIndex >= 0 ? `${lightboxIndex + 1} / ${displayPhotos.length}` : ''}
                </Text>
                <XStack
                  gap="$1"
                  padding="$1"
                  borderRadius="$pill"
                  backgroundColor={mutedButton}
                  borderWidth={1}
                  borderColor={mutedButtonBorder}
                  alignItems="center"
                >
                  <Button
                    unstyled
                    paddingHorizontal="$2.5"
                    paddingVertical="$1.5"
                    onPress={goPrev}
                    disabled={lightboxIndex <= 0}
                    opacity={lightboxIndex <= 0 ? 0.4 : 1}
                  >
                    <XStack alignItems="center" gap="$1.5">
                      <ChevronLeft size={14} color={isDark ? '#F8FAFF' : '#0F172A'} />
                      <Text fontSize="$1" fontWeight="$6">
                        {t('galleryPage.lightbox.prev', 'Prev')}
                      </Text>
                    </XStack>
                  </Button>
                  <Button
                    unstyled
                    paddingHorizontal="$2.5"
                    paddingVertical="$1.5"
                    onPress={goNext}
                    disabled={lightboxIndex < 0 || lightboxIndex >= displayPhotos.length - 1}
                    opacity={lightboxIndex < 0 || lightboxIndex >= displayPhotos.length - 1 ? 0.4 : 1}
                  >
                    <XStack alignItems="center" gap="$1.5">
                      <Text fontSize="$1" fontWeight="$6">
                        {t('galleryPage.lightbox.next', 'Next')}
                      </Text>
                      <ChevronRight size={14} color={isDark ? '#F8FAFF' : '#0F172A'} />
                    </XStack>
                  </Button>
                </XStack>
              </XStack>
            </YStack>
            <XStack alignItems="center" justifyContent="space-between" flexWrap="wrap" gap="$2">
              <Text fontSize="$3" fontWeight="$7">
                {lightboxPhoto
                  ? t(
                    'galleryPage.lightbox.likes',
                    { count: likesById[lightboxPhoto.id] ?? lightboxPhoto.likes },
                    '{count} likes'
                  )
                  : ''}
              </Text>
              <XStack
                gap="$1"
                padding="$1"
                borderRadius="$pill"
                backgroundColor={mutedButton}
                borderWidth={1}
                borderColor={mutedButtonBorder}
                alignItems="center"
                flexWrap="wrap"
                justifyContent="flex-end"
              >
                <Button unstyled onPress={openShareSheet} paddingHorizontal="$3" paddingVertical="$2">
                  <XStack alignItems="center" gap="$2">
                    <Share2 size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
                    <Text fontSize="$2" fontWeight="$6">
                      {shareSheet.loading ? t('share.loading', 'Sharing...') : t('share.button', 'Share')}
                    </Text>
                  </XStack>
                </Button>
              </XStack>
            </XStack>
          </YStack>
        </YStack>
      ) : null}
      <ShareSheet
        open={shareSheet.loading || Boolean(shareSheet.url)}
        onOpenChange={(open) => {
          if (!open) {
            closeShareSheet();
          }
        }}
        photoId={lightboxPhoto?.id}
        eventName={event?.name ?? null}
        url={shareSheet.url}
        loading={shareSheet.loading}
        onShareNative={() => shareNative(shareSheet.url)}
        onShareWhatsApp={() => shareWhatsApp(shareSheet.url)}
        onShareMessages={() => shareMessages(shareSheet.url)}
        onCopyLink={() => copyLink(shareSheet.url)}
      />
    </AppShell>
  );
}

function mapFullPhoto(photo: Record<string, unknown>): LightboxPhoto | null {
  const id = Number(photo.id ?? 0);
  if (!id) return null;
  const imageUrl = normalizeImageUrl(
    (photo.full_url as string | null | undefined)
      ?? (photo.file_path as string | null | undefined)
      ?? (photo.thumbnail_url as string | null | undefined)
      ?? (photo.thumbnail_path as string | null | undefined)
      ?? (photo.url as string | null | undefined)
      ?? (photo.image_url as string | null | undefined)
  );
  if (!imageUrl) return null;
  return {
    id,
    imageUrl,
    likes: typeof photo.likes_count === 'number' ? photo.likes_count : 0,
  };
}
