'use client';

import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useLanguage } from '@/context/LanguageContext';
import { useAuth } from '@/context/AuthContext';
import { photosApi, PhotoGallery, Photo } from '@/lib/api';
import Button from '@/components/ui/Button';
import GallerySkeleton from '@/components/gallery/GallerySkeleton';
import PhotoTile from '@/components/gallery/PhotoTile';
import {
  GalleryContainer,
  GalleryHeroFrame,
  MasonryGrid,
} from '@/components/gallery/GalleryLayout';
import { useDownloads } from '@/components/gallery/useDownloads';
import SaveSheet, { SavePhoto, useMobileSave } from '@/components/gallery/SaveSheet';
import Lightbox from '@/components/Lightbox';
import LoginModal from '@/components/LoginModal';
import {
  CalendarIcon,
  CameraIcon,
  LinkIcon,
  LockClosedIcon,
  TicketIcon,
} from '@heroicons/react/24/outline';

interface GalleryClientProps {
  /** Fetch by gallery slug (standalone galleries at /photos/[slug]) … */
  slug?: string;
  /** … or by event slug (event galleries at /events/[slug]/gallery). */
  eventSlug?: string;
  initial: { gallery: PhotoGallery; photos: Photo[] } | null;
}

type DeniedState = 'login' | 'ticket' | 'private' | 'link' | 'notfound' | null;

export default function GalleryClient({ slug, eventSlug, initial }: GalleryClientProps) {
  const { locale } = useLanguage();
  const es = locale === 'es';
  const { user, isLoading: authLoading } = useAuth();
  const searchParams = useSearchParams();
  const shareToken = searchParams.get('token') || undefined;

  const [gallery, setGallery] = useState<PhotoGallery | null>(initial?.gallery ?? null);
  const [photos, setPhotos] = useState<Photo[]>(initial?.photos ?? []);
  const [loading, setLoading] = useState(!initial);
  const [denied, setDenied] = useState<DeniedState>(null);
  const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
  const [loginOpen, setLoginOpen] = useState(false);
  const downloads = useDownloads(es);
  const downloadLabels = {
    download: es ? 'Descargar' : 'Download',
    downloading: es ? 'Descargando…' : 'Downloading…',
  };

  // On a phone a download cannot reach the photo library, so the button
  // opens a sheet offering the shareable preview instead (SaveSheet).
  // Desktop keeps downloading the original straight away.
  const mobileSave = useMobileSave();
  const [savePhoto, setSavePhoto] = useState<SavePhoto | null>(null);

  const downloadPhoto = (photo: Photo) => {
    if (mobileSave) {
      setSavePhoto({
        id: photo.id,
        previewUrl: photo.urls.download || photo.urls.preview || photo.urls.original,
        originalUrl: photo.urls.downloadOriginal || photo.urls.original,
        previewSize: photo.previewSizeBytes,
        originalSize: photo.sizeBytes,
      });
      return;
    }
    downloads.start({
      id: photo.id,
      url: photo.urls.original,
      filename: photo.originalFilename,
    });
  };

  // Server-rendered public galleries need no client fetch. Everything else
  // (link/ticket/private) is fetched here with the share token and/or the
  // viewer's own auth token attached.
  useEffect(() => {
    if (initial) return;
    if (authLoading) return; // wait until the session state has resolved
    let cancelled = false;
    setLoading(true);
    const fetcher = eventSlug
      ? photosApi.getEventGallery(eventSlug, shareToken)
      : photosApi.getPublicGallery(slug || '', shareToken);
    fetcher
      .then((data) => {
        if (cancelled) return;
        setGallery(data.gallery);
        setPhotos(data.photos);
        setDenied(null);
      })
      .catch((err: Error) => {
        if (cancelled) return;
        // The photo-api returns a distinct message per visibility mode so
        // each gets its own gate page (see accessDenial in access.go).
        const msg = err.message || '';
        if (msg.includes('Authentication required')) setDenied('login');
        else if (msg.includes('attendees')) setDenied('ticket');
        else if (msg.includes('private')) setDenied('private');
        else if (msg.includes('share link')) setDenied('link');
        else setDenied('notfound');
      })
      .finally(() => !cancelled && setLoading(false));
    return () => {
      cancelled = true;
    };
  }, [slug, eventSlug, shareToken, initial, authLoading, user?.id]);

  // Mirrors the hero + masonry layout below, so the real page drops straight
  // into the placeholder's geometry instead of replacing it.
  if (loading || (authLoading && !initial)) {
    return <GallerySkeleton count={gallery?.photoCount || undefined} />;
  }

  // Gate pages for restricted galleries. After a successful login in the
  // pop-up, AuthContext's user changes, which re-runs the fetch effect —
  // the gate resolves by itself when access is granted.
  const loginModal = (
    <LoginModal
      open={loginOpen}
      onClose={() => setLoginOpen(false)}
      message={
        es
          ? 'Inicia sesión para ver esta galería.'
          : 'Log in to view this gallery.'
      }
    />
  );

  if (denied === 'login') {
    return (
      <>
        <GateMessage
          icon={TicketIcon}
          title={es ? 'Solo para asistentes' : 'Attendees only'}
          body={
            es
              ? 'Esta galería es para asistentes del evento. Inicia sesión con la cuenta que usaste para reservar.'
              : 'This gallery is for event attendees. Log in with the account you used to book.'
          }
          action={
            <Button onClick={() => setLoginOpen(true)}>{es ? 'Iniciar sesión' : 'Log in'}</Button>
          }
        />
        {loginModal}
      </>
    );
  }

  if (denied === 'ticket') {
    return (
      <>
        <GateMessage
          icon={TicketIcon}
          title={es ? 'Solo para asistentes' : 'Attendees only'}
          body={
            es
              ? 'Esta galería es para quienes asistieron al evento con una entrada confirmada. ¿Reservaste con otra cuenta?'
              : 'This gallery is only available to people who attended the event with a confirmed ticket. Booked with a different account?'
          }
          action={
            <div className="flex flex-wrap justify-center gap-3">
              {(eventSlug || gallery?.event) && (
                <Link href={`/events/${eventSlug || gallery?.event?.slug}`}>
                  <Button variant="outline">{es ? 'Ver el evento' : 'View the event'}</Button>
                </Link>
              )}
              <Button onClick={() => setLoginOpen(true)}>
                {es ? 'Cambiar de cuenta' : 'Switch account'}
              </Button>
            </div>
          }
        />
        {loginModal}
      </>
    );
  }

  if (denied === 'private') {
    return (
      <>
        <GateMessage
          icon={LockClosedIcon}
          title={es ? 'Esta galería es privada' : 'This gallery is private'}
          body={
            es
              ? 'Solo los organizadores pueden verla. Si eres parte del equipo, inicia sesión.'
              : 'Only the organizers can see it. If that’s you, log in.'
          }
          action={
            <div className="flex flex-wrap justify-center gap-3">
              <Link href="/photos">
                <Button variant="outline">{es ? 'Ver galerías públicas' : 'Browse public galleries'}</Button>
              </Link>
              <Button onClick={() => setLoginOpen(true)}>{es ? 'Iniciar sesión' : 'Log in'}</Button>
            </div>
          }
        />
        {loginModal}
      </>
    );
  }

  if (denied === 'link') {
    return (
      <>
        <GateMessage
          icon={LinkIcon}
          title={es ? 'Esta galería necesita su enlace' : 'This gallery needs its share link'}
          body={
            es
              ? 'Solo se puede abrir con el enlace que compartieron los organizadores. Pídeles el enlace completo, o inicia sesión si eres parte del equipo.'
              : 'It can only be opened with the link the organizers shared. Ask them for the full link, or log in if you’re part of the team.'
          }
          action={
            <div className="flex flex-wrap justify-center gap-3">
              <Link href="/photos">
                <Button variant="outline">{es ? 'Ver galerías públicas' : 'Browse public galleries'}</Button>
              </Link>
              <Button onClick={() => setLoginOpen(true)}>{es ? 'Iniciar sesión' : 'Log in'}</Button>
            </div>
          }
        />
        {loginModal}
      </>
    );
  }

  if (denied === 'notfound' || !gallery) {
    return (
      <GateMessage
        icon={CameraIcon}
        title={es ? 'Galería no encontrada' : 'Gallery not found'}
        body={
          es
            ? 'Este enlace no existe o ya no está disponible.'
            : 'This link does not exist or is no longer available.'
        }
        action={
          <Link href="/photos">
            <Button variant="outline">{es ? 'Ver galerías públicas' : 'Browse public galleries'}</Button>
          </Link>
        }
      />
    );
  }

  const readyPhotos = photos.filter((p) => p.status === 'ready' && p.urls.thumb);
  // previewUrl is the download endpoint's preview: rendering and saving from
  // the same URL is what lets "Save photo" be answered by the HTTP cache.
  const lightboxItems = readyPhotos.map((p) => ({
    id: p.id,
    previewUrl: p.urls.download || p.urls.preview || p.urls.original,
    downloadUrl: p.urls.downloadOriginal || p.urls.original,
    filename: p.originalFilename,
    thumbUrl: p.urls.thumb,
  }));

  const title = es && gallery.titleEs ? gallery.titleEs : gallery.title;
  const description = es && gallery.descriptionEs ? gallery.descriptionEs : gallery.description;
  const eventTitle = gallery.event
    ? es && gallery.event.titleEs
      ? gallery.event.titleEs
      : gallery.event.title
    : null;
  const eventDate = gallery.event?.startDatetime
    ? new Date(gallery.event.startDatetime).toLocaleDateString(es ? 'es-ES' : 'en-US', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        timeZone: 'America/Asuncion',
      })
    : null;

  // Hero backdrop: the cover photo's preview when available, else the
  // first photo. Falls back to a navy gradient with no image.
  const coverPhoto =
    readyPhotos.find((p) => p.id === gallery.coverPhotoId) || readyPhotos[0] || null;
  // Same preview URL the lightbox uses, so the cover photo's bytes are
  // fetched once for both.
  const heroUrl = coverPhoto
    ? coverPhoto.urls.download || coverPhoto.urls.preview || coverPhoto.urls.thumb
    : null;

  return (
    <div>
      {/* Hero — same frame the skeleton renders (GalleryLayout). */}
      <GalleryHeroFrame
        backdrop={
          heroUrl ? (
            <>
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src={heroUrl}
                alt=""
                className="absolute inset-0 w-full h-full object-cover opacity-60"
              />
              <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-black/20" />
            </>
          ) : null
        }
      >
        <h1 className="font-heading font-bold text-3xl md:text-5xl text-white drop-shadow-sm">
          {title}
        </h1>
        {description && (
          <p className="mt-2 max-w-2xl text-white/90 text-sm md:text-base">{description}</p>
        )}
        <div className="mt-4 flex flex-wrap items-center gap-2 text-sm">
          <span className="inline-flex items-center gap-1.5 rounded-full bg-white/15 backdrop-blur px-3 py-1 text-white">
            <CameraIcon className="w-4 h-4" />
            {readyPhotos.length} {es ? 'fotos' : 'photos'}
          </span>
          {gallery.event && (
            <Link
              href={`/events/${gallery.event.slug}`}
              className="inline-flex items-center gap-1.5 rounded-full bg-primary-yellow px-3 py-1 text-primary-dark font-medium hover:brightness-105"
            >
              <CalendarIcon className="w-4 h-4" />
              {eventTitle}
              {eventDate && <span className="hidden sm:inline font-normal">· {eventDate}</span>}
            </Link>
          )}
        </div>
      </GalleryHeroFrame>

      {/* Masonry grid */}
      <GalleryContainer>
        {readyPhotos.length === 0 ? (
          <div className="text-center py-16">
            <CameraIcon className="w-16 h-16 mx-auto text-gray-300 mb-4" />
            <p className="text-gray-600">
              {es ? 'Las fotos estarán disponibles pronto.' : 'Photos will be available soon.'}
            </p>
          </div>
        ) : (
          <MasonryGrid>
            {readyPhotos.map((photo, i) => (
              <PhotoTile
                key={photo.id}
                photo={photo}
                eager={i < 8}
                onOpen={() => setLightboxIndex(i)}
                onDownload={() => downloadPhoto(photo)}
                downloading={downloads.isPending(photo.id)}
                labels={downloadLabels}
              />
            ))}
          </MasonryGrid>
        )}

        {lightboxIndex !== null && (
          <Lightbox
            items={lightboxItems}
            index={lightboxIndex}
            onClose={() => setLightboxIndex(null)}
            onNavigate={setLightboxIndex}
            onDownload={(it) => {
              const photo = readyPhotos.find((p) => p.id === it.id);
              if (photo) downloadPhoto(photo);
            }}
            downloadingId={
              lightboxItems.find((it) => downloads.isPending(it.id))?.id ?? null
            }
            downloadLabels={downloadLabels}
          />
        )}

        {/* Shared by both entry points: the tile button and the lightbox's.
            Fetches nothing until it is open. */}
        <SaveSheet photo={savePhoto} onClose={() => setSavePhoto(null)} />
      </GalleryContainer>

      {/* Call to action: send attendees to their dashboard, everyone else to
          the next event. Auth state comes from the same useAuth() the gate
          pages use. */}
      <div className="bg-brand-navy">
        <div className="container-page px-4 py-12 md:py-16 text-center">
          <h2 className="font-heading font-bold text-2xl md:text-3xl text-white">
            {user
              ? es
                ? '¿Listo para lo que sigue?'
                : 'Ready for what’s next?'
              : es
                ? '¿Te gustó lo que viste?'
                : 'Liked what you saw?'}
          </h2>
          <p className="mt-2 max-w-xl mx-auto text-white/80 text-sm md:text-base">
            {user
              ? es
                ? 'Revisa tus entradas y próximos eventos en tu panel.'
                : 'Check your tickets and upcoming events from your dashboard.'
              : es
                ? 'Únete a nuestro próximo evento y sé parte de las próximas fotos.'
                : 'Join our next event and be part of the next set of photos.'}
          </p>
          <div className="mt-6">
            <Link href={user ? '/dashboard' : '/next'}>
              <Button size="lg">
                {user
                  ? es
                    ? 'Ir a mi panel'
                    : 'Go to dashboard'
                  : es
                    ? 'Únete al próximo evento'
                    : 'Join the next event'}
              </Button>
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}

function GateMessage({
  icon: Icon,
  title,
  body,
  action,
}: {
  icon: typeof CameraIcon;
  title: string;
  body: string;
  action?: React.ReactNode;
}) {
  return (
    <div className="section-padding">
      <div className="container-page max-w-lg mx-auto text-center py-16">
        <Icon className="w-16 h-16 mx-auto text-gray-300 mb-4" />
        <h1 className="text-2xl font-bold text-primary-dark mb-2">{title}</h1>
        <p className="text-gray-600 mb-6">{body}</p>
        {action}
      </div>
    </div>
  );
}
