'use client';

import { useState, useEffect, useRef } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';
import { Person, Tag, Photo } from '@prisma/client';
import { CollapsibleSearch } from '@/components/search/CollapsibleSearch';
import { PhotoGrid } from '@/components/PhotoGrid';
import { PhotoViewerClient } from '@/components/PhotoViewerClient';
import { SearchFilters } from '@/components/search/FilterPanel';
import { Loader2, CheckSquare, Square } from 'lucide-react';
import { TagSelectionDialog } from '@/components/TagSelectionDialog';
import { PageHeader } from '@/components/PageHeader';
import JSZip from 'jszip';

interface HomePageContentProps {
  initialPhotos: Photo[];
  people: Person[];
  tags: Tag[];
}

export function HomePageContent({ initialPhotos, people, tags }: HomePageContentProps) {
  const router = useRouter();
  const searchParams = useSearchParams();
  const { data: session } = useSession();
  const isLoggedIn = Boolean(session);
  
  // Initialize filters from URL params to persist state across navigation
  const [filters, setFilters] = useState<SearchFilters>(() => {
    const peopleParam = searchParams.get('people');
    const tagsParam = searchParams.get('tags');
    const dateFromParam = searchParams.get('dateFrom');
    const dateToParam = searchParams.get('dateTo');
    const peopleModeParam = searchParams.get('peopleMode');
    const tagsModeParam = searchParams.get('tagsMode');
    const mediaTypeParam = searchParams.get('mediaType');
    const favoritesOnlyParam = searchParams.get('favoritesOnly');

    return {
      people: peopleParam ? peopleParam.split(',').map(Number).filter(Boolean) : [],
      tags: tagsParam ? tagsParam.split(',').map(Number).filter(Boolean) : [],
      dateFrom: dateFromParam ? new Date(dateFromParam) : undefined,
      dateTo: dateToParam ? new Date(dateToParam) : undefined,
      peopleMode: peopleModeParam === 'all' ? 'all' : 'any',
      tagsMode: tagsModeParam === 'all' ? 'all' : 'any',
      mediaType: (mediaTypeParam as 'all' | 'photos' | 'videos') || 'all',
      favoritesOnly: favoritesOnlyParam === 'true',
    };
  });
  
  // Check if we have active filters from URL on initial load
  const hasInitialFilters = Boolean(
    filters.people.length > 0 ||
    filters.tags.length > 0 ||
    filters.dateFrom ||
    filters.dateTo ||
    (filters.mediaType && filters.mediaType !== 'all') ||
    filters.favoritesOnly === true
  );
  
  // Only use initialPhotos if there are no filters from URL
  const [photos, setPhotos] = useState<Photo[]>(hasInitialFilters ? [] : initialPhotos);
  const [loading, setLoading] = useState<boolean>(hasInitialFilters); // Start loading if filters are active
  const [loadingMore, setLoadingMore] = useState(false);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(initialPhotos.length === 30);
  const observerTarget = useRef<HTMLDivElement>(null);
  const pageRef = useRef(1);
  const isLoadingRef = useRef(false);
  const scrollRestoredRef = useRef(false);
  const isInitialMount = useRef(true);
  const photosInitializedRef = useRef(false);
  const isClosingModalRef = useRef(false);
  
  // Modal state - read photo query param
  const photoParam = searchParams.get('photo');
  const photosParam = searchParams.get('photos');
  const indexParam = searchParams.get('index');
  const autoplayParam = searchParams.get('autoplay') === 'true';
  const [modalPhoto, setModalPhoto] = useState<any>(null);
  const [modalPhotos, setModalPhotos] = useState<any[]>([]);
  const [modalIndex, setModalIndex] = useState(0);
  const [modalLoading, setModalLoading] = useState(false);
  const [selectionMode, setSelectionMode] = useState(false);
  const [selectedPhotoIds, setSelectedPhotoIds] = useState<number[]>([]);
  const [isPreparingDownload, setIsPreparingDownload] = useState(false);
  const [tagDialogOpen, setTagDialogOpen] = useState(false);
  const [isBulkFavoriting, setIsBulkFavoriting] = useState(false);
  const [refreshFavoritesKey, setRefreshFavoritesKey] = useState(0);

  const hasActiveFilters =
    filters.people.length > 0 ||
    filters.tags.length > 0 ||
    filters.dateFrom ||
    filters.dateTo ||
    (filters.mediaType && filters.mediaType !== 'all') ||
    filters.favoritesOnly === true;

  // Update URL when filters change (without page reload)
  // Skip on initial mount since filters are already initialized from URL
  useEffect(() => {
    // Skip URL update on initial mount
    if (isInitialMount.current) {
      isInitialMount.current = false;
      return;
    }

    // Skip if we're closing the modal (to prevent reload)
    if (isClosingModalRef.current) {
      return;
    }

    const params = new URLSearchParams();
    
    if (filters.people.length > 0) {
      params.set('people', filters.people.join(','));
      if (filters.peopleMode && filters.peopleMode !== 'any') {
        params.set('peopleMode', filters.peopleMode);
      }
    }
    if (filters.tags.length > 0) {
      params.set('tags', filters.tags.join(','));
      if (filters.tagsMode && filters.tagsMode !== 'any') {
        params.set('tagsMode', filters.tagsMode);
      }
    }
    if (filters.dateFrom) {
      params.set('dateFrom', filters.dateFrom.toISOString().split('T')[0]);
    }
    if (filters.dateTo) {
      params.set('dateTo', filters.dateTo.toISOString().split('T')[0]);
    }
    if (filters.mediaType && filters.mediaType !== 'all') {
      params.set('mediaType', filters.mediaType);
    }
    if (filters.favoritesOnly) {
      params.set('favoritesOnly', 'true');
    }

    const newUrl = params.toString() ? `/?${params.toString()}` : '/';
    router.replace(newUrl, { scroll: false });
    
    // Clear saved scroll position when filters change (user is starting a new search)
    sessionStorage.removeItem('homePageScrollY');
    scrollRestoredRef.current = false;
    photosInitializedRef.current = false; // Reset photos initialization flag
  }, [filters, router]);

  // Restore scroll position when returning from photo viewer
  // Wait for photos to be loaded and rendered before restoring scroll to prevent flash
  useEffect(() => {
    if (scrollRestoredRef.current) return;
    
    const scrollY = sessionStorage.getItem('homePageScrollY');
    if (!scrollY) {
      scrollRestoredRef.current = true;
      return;
    }

    // Wait for loading to complete
    if (loading) return;
    
    // Wait for photos to be initialized (either from initial state or fetched)
    // This prevents flash by ensuring we only restore after everything is stable
    if (!photosInitializedRef.current && photos.length === 0) {
      // Photos not ready yet, wait
      return;
    }
    
    photosInitializedRef.current = true;
    
    // Restore scroll after DOM is fully rendered
    // Use multiple animation frames to ensure all images are laid out
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        if (!scrollRestoredRef.current) {
          window.scrollTo({ top: parseInt(scrollY, 10), behavior: 'instant' });
          scrollRestoredRef.current = true;
        }
      });
    });
  }, [loading, photos.length]);

  // Save scroll position before navigating away
  useEffect(() => {
    const handleScroll = () => {
      sessionStorage.setItem('homePageScrollY', window.scrollY.toString());
    };
    
    // Throttle scroll events
    let timeoutId: NodeJS.Timeout;
    const throttledScroll = () => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(handleScroll, 100);
    };
    
    window.addEventListener('scroll', throttledScroll, { passive: true });
    return () => {
      window.removeEventListener('scroll', throttledScroll);
      clearTimeout(timeoutId);
    };
  }, []);

  // Handle photo modal - use existing photos data, only fetch if not available
  useEffect(() => {
    // Skip if we're intentionally closing the modal
    if (isClosingModalRef.current) {
      isClosingModalRef.current = false;
      return;
    }

    if (!photoParam) {
      setModalPhoto(null);
      setModalPhotos([]);
      return;
    }

    const photoId = parseInt(photoParam, 10);
    if (isNaN(photoId)) return;

      // If we already have this photo in modalPhotos, just update the index - no fetch needed!
      if (modalPhotos.length > 0) {
        const existingModalPhoto = modalPhotos.find((p) => p.id === photoId);
        if (existingModalPhoto) {
          console.log('[HomePageContent] Using existing modal photo:', {
            photoId: existingModalPhoto.id,
            hasFaces: !!existingModalPhoto.faces,
            hasFace: !!existingModalPhoto.Face,
            facesCount: existingModalPhoto.faces?.length || existingModalPhoto.Face?.length || 0,
            photoKeys: Object.keys(existingModalPhoto),
          });
        // Photo is already in modal list, just update index - instant, no API calls!
        const photoIds = photosParam ? photosParam.split(',').map(Number).filter(Boolean) : [];
        const parsedIndex = indexParam ? parseInt(indexParam, 10) : 0;
        if (photoIds.length > 0 && !isNaN(parsedIndex) && parsedIndex >= 0 && parsedIndex < modalPhotos.length) {
          setModalIndex(parsedIndex);
          setModalPhoto(existingModalPhoto);
          return; // Skip all fetching!
        }
      }
    }

    // First, try to find the photo in the already-loaded photos
    const existingPhoto = photos.find((p) => p.id === photoId);
    
    if (existingPhoto) {
      // Photo is already loaded, use it directly - no database access!
      console.log('[HomePageContent] Using existing photo from photos array:', {
        photoId: existingPhoto.id,
        hasFaces: !!(existingPhoto as any).faces,
        hasFace: !!(existingPhoto as any).Face,
        facesCount: (existingPhoto as any).faces?.length || (existingPhoto as any).Face?.length || 0,
        photoKeys: Object.keys(existingPhoto),
      });
      setModalPhoto(existingPhoto);
      
      // If we have a photo list context, use existing photos
      if (photosParam && indexParam) {
        const photoIds = photosParam.split(',').map(Number).filter(Boolean);
        const parsedIndex = parseInt(indexParam, 10);
        
        if (photoIds.length > 0 && !isNaN(parsedIndex)) {
          // Check if modalPhotos already has all these photos in the right order
          const currentPhotoIds = modalPhotos.map((p) => p.id);
          const needsRebuild = 
            currentPhotoIds.length !== photoIds.length ||
            currentPhotoIds.some((id, idx) => id !== photoIds[idx]);
          
          if (needsRebuild) {
            // Build photo list from existing photos - no API calls!
            const photoMap = new Map(photos.map((p) => [p.id, p]));
            const orderedPhotos = photoIds
              .map((id) => photoMap.get(id))
              .filter(Boolean) as typeof photos;
            
            setModalPhotos(orderedPhotos);
          }
          setModalIndex(parsedIndex);
        } else {
          if (modalPhotos.length !== 1 || modalPhotos[0]?.id !== existingPhoto.id) {
            setModalPhotos([existingPhoto]);
          }
          setModalIndex(0);
        }
      } else {
        if (modalPhotos.length !== 1 || modalPhotos[0]?.id !== existingPhoto.id) {
          setModalPhotos([existingPhoto]);
        }
        setModalIndex(0);
      }
      setModalLoading(false);
      return;
    }

    // Photo not in loaded list, need to fetch it (should be rare)
    const fetchPhotoData = async () => {
      setModalLoading(true);
      try {
        const photoResponse = await fetch(`/api/photos/${photoId}`);
        if (!photoResponse.ok) throw new Error('Failed to fetch photo');
        const photoData = await photoResponse.json();
        
        // Serialize the photo (handle Decimal fields)
        console.log('[HomePageContent] Photo data from API:', {
          photoId: photoData.id,
          hasFaces: !!photoData.faces,
          facesCount: photoData.faces?.length || 0,
          facesRaw: photoData.faces,
          photoDataKeys: Object.keys(photoData),
        });
        
        const serializedPhoto = {
          ...photoData,
          faces: photoData.faces?.map((face: any) => ({
            ...face,
            confidence: face.confidence ? Number(face.confidence) : 0,
            qualityScore: face.qualityScore ? Number(face.qualityScore) : 0,
            faceConfidence: face.faceConfidence ? Number(face.faceConfidence) : 0,
            yawAngle: face.yawAngle ? Number(face.yawAngle) : null,
            pitchAngle: face.pitchAngle ? Number(face.pitchAngle) : null,
            rollAngle: face.rollAngle ? Number(face.rollAngle) : null,
          })),
        };
        
        console.log('[HomePageContent] Serialized photo:', {
          photoId: serializedPhoto.id,
          hasFaces: !!serializedPhoto.faces,
          facesCount: serializedPhoto.faces?.length || 0,
          faces: serializedPhoto.faces,
        });
        
        setModalPhoto(serializedPhoto);

        // For navigation, try to use existing photos first, then fetch missing ones
        if (photosParam && indexParam) {
          const photoIds = photosParam.split(',').map(Number).filter(Boolean);
          const parsedIndex = parseInt(indexParam, 10);
          
          if (photoIds.length > 0 && !isNaN(parsedIndex)) {
            const photoMap = new Map(photos.map((p) => [p.id, p]));
            const missingIds = photoIds.filter((id) => !photoMap.has(id));
            
            // Fetch only missing photos
            let fetchedPhotos: any[] = [];
            if (missingIds.length > 0) {
              const photoPromises = missingIds.map((id) =>
                fetch(`/api/photos/${id}`).then((res) => res.json())
              );
              const fetchedData = await Promise.all(photoPromises);
              fetchedPhotos = fetchedData.map((p: any) => ({
                ...p,
                faces: p.faces?.map((face: any) => ({
                  ...face,
                  confidence: face.confidence ? Number(face.confidence) : 0,
                  qualityScore: face.qualityScore ? Number(face.qualityScore) : 0,
                  faceConfidence: face.faceConfidence ? Number(face.faceConfidence) : 0,
                  yawAngle: face.yawAngle ? Number(face.yawAngle) : null,
                  pitchAngle: face.pitchAngle ? Number(face.pitchAngle) : null,
                  rollAngle: face.rollAngle ? Number(face.rollAngle) : null,
                })),
              }));
            }
            
            // Combine existing and fetched photos
            fetchedPhotos.forEach((p) => photoMap.set(p.id, p));
            // Include all photos (videos and images) for navigation
            const orderedPhotos = photoIds
              .map((id) => photoMap.get(id))
              .filter(Boolean) as typeof photos;
            
            setModalPhotos(orderedPhotos);
            // Use the original index (videos are included in navigation)
            setModalIndex(Math.min(parsedIndex, orderedPhotos.length - 1));
          } else {
            setModalPhotos([serializedPhoto]);
            setModalIndex(0);
          }
        } else {
          setModalPhotos([serializedPhoto]);
          setModalIndex(0);
        }
      } catch (error) {
        console.error('Error fetching photo data:', error);
      } finally {
        setModalLoading(false);
      }
    };

    fetchPhotoData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [photoParam, photosParam, indexParam]); // Only depend on URL params - photos is accessed but we check modalPhotos first

  // Handle starting slideshow
  const handleStartSlideshow = () => {
    if (photos.length === 0) return;
    
    // Filter out videos from slideshow (only show images)
    const imagePhotos = photos.filter((p) => p.media_type !== 'video');
    if (imagePhotos.length === 0) return;
    
    // Set first image as modal photo
    setModalPhoto(imagePhotos[0]);
    setModalPhotos(imagePhotos);
    setModalIndex(0);
    
    // Update URL to open first photo with autoPlay
    const params = new URLSearchParams(window.location.search);
    params.set('photo', imagePhotos[0].id.toString());
    params.set('photos', imagePhotos.map((p) => p.id).join(','));
    params.set('index', '0');
    params.set('autoplay', 'true');
    router.replace(`/?${params.toString()}`, { scroll: false });
  };

  const handleToggleSelectionMode = () => {
    setSelectionMode((prev) => {
      if (prev) {
        setSelectedPhotoIds([]);
      }
      return !prev;
    });
  };

  const handleTogglePhotoSelection = (photoId: number) => {
    setSelectedPhotoIds((prev) => {
      if (prev.includes(photoId)) {
        return prev.filter((id) => id !== photoId);
      }
      return [...prev, photoId];
    });
  };

  const handleSelectAll = () => {
    const allPhotoIds = photos.map((photo) => photo.id);
    setSelectedPhotoIds(allPhotoIds);
  };

  const handleClearAll = () => {
    setSelectedPhotoIds([]);
  };

  const getPhotoFilename = (photo: Photo) => {
    if (photo.filename?.trim()) {
      return photo.filename.trim();
    }
    const path = photo.path || '';
    if (path) {
      const segments = path.split(/[/\\]/);
      const lastSegment = segments.pop();
      if (lastSegment) {
        return lastSegment;
      }
    }
    return `photo-${photo.id}.jpg`;
  };

  const getPhotoDownloadUrl = (
    photo: Photo,
    options?: { forceProxy?: boolean; watermark?: boolean }
  ) => {
    const path = photo.path || '';
    const isExternal = path.startsWith('http://') || path.startsWith('https://');
    if (isExternal && !options?.forceProxy) {
      return path;
    }

    const params = new URLSearchParams();
    if (options?.watermark) {
      params.set('watermark', 'true');
    }
    const query = params.toString();

    return `/api/photos/${photo.id}/image${query ? `?${query}` : ''}`;
  };

  const downloadPhotosAsZip = async (
    photoIds: number[],
    photoMap: Map<number, Photo>
  ) => {
    const zip = new JSZip();
    let filesAdded = 0;

    for (const photoId of photoIds) {
      const photo = photoMap.get(photoId);
      if (!photo) continue;

      const response = await fetch(
        getPhotoDownloadUrl(photo, {
          forceProxy: true,
          watermark: !isLoggedIn,
        })
      );
      if (!response.ok) {
        throw new Error(`Failed to download photo ${photoId}`);
      }

      const blob = await response.blob();
      zip.file(getPhotoFilename(photo), blob);
      filesAdded += 1;
    }

    if (filesAdded === 0) {
      throw new Error('No photos available to download.');
    }

    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `photos-${new Date().toISOString().split('T')[0]}.zip`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  };

  const handleDownloadSelected = async () => {
    if (
      selectedPhotoIds.length === 0 ||
      typeof window === 'undefined' ||
      isPreparingDownload
    ) {
      return;
    }

    const photoMap = new Map(photos.map((photo) => [photo.id, photo]));

    if (selectedPhotoIds.length === 1) {
      const photo = photoMap.get(selectedPhotoIds[0]);
      if (!photo) {
        return;
      }
      const link = document.createElement('a');
      link.href = getPhotoDownloadUrl(photo, { watermark: !isLoggedIn });
      link.download = getPhotoFilename(photo);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      return;
    }

    try {
      setIsPreparingDownload(true);
      await downloadPhotosAsZip(selectedPhotoIds, photoMap);
    } catch (error) {
      console.error('Error downloading selected photos:', error);
      alert('Failed to download selected photos. Please try again.');
    } finally {
      setIsPreparingDownload(false);
    }
  };

  const handleTagSelected = () => {
    if (selectedPhotoIds.length === 0) {
      return;
    }
    setTagDialogOpen(true);
  };

  const handleBulkFavorite = async () => {
    if (selectedPhotoIds.length === 0 || !isLoggedIn || isBulkFavoriting) {
      return;
    }

    setIsBulkFavoriting(true);

    try {
      const response = await fetch('/api/photos/favorites/bulk', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          photoIds: selectedPhotoIds,
          action: 'add', // Always add to favorites (skip if already favorited)
        }),
      });

      if (!response.ok) {
        const error = await response.json();
        if (response.status === 401) {
          alert('Please sign in to favorite photos');
        } else {
          alert(error.error || 'Failed to update favorites');
        }
        return;
      }

      const data = await response.json();
      
      // Trigger PhotoGrid to refetch favorite statuses
      setRefreshFavoritesKey(prev => prev + 1);
    } catch (error) {
      console.error('Error bulk favoriting photos:', error);
      alert('Failed to update favorites. Please try again.');
    } finally {
      setIsBulkFavoriting(false);
    }
  };

  useEffect(() => {
    if (selectedPhotoIds.length === 0) {
      return;
    }
    const availableIds = new Set(photos.map((photo) => photo.id));
    setSelectedPhotoIds((prev) => {
      const filtered = prev.filter((id) => availableIds.has(id));
      return filtered.length === prev.length ? prev : filtered;
    });
  }, [photos, selectedPhotoIds.length]);

  useEffect(() => {
    if (tagDialogOpen && selectedPhotoIds.length === 0) {
      setTagDialogOpen(false);
    }
  }, [tagDialogOpen, selectedPhotoIds.length]);

  // Handle closing the modal
  const handleCloseModal = () => {
    // Set flag to prevent useEffect from running
    isClosingModalRef.current = true;
    
    // Clear modal state immediately (no reload, instant close)
    setModalPhoto(null);
    setModalPhotos([]);
    setModalIndex(0);
    
    // Update URL directly using history API to avoid triggering Next.js router effects
    // This prevents any reload or re-fetch when closing
    const params = new URLSearchParams(window.location.search);
    params.delete('photo');
    params.delete('photos');
    params.delete('index');
    params.delete('autoplay');
    
    const newUrl = params.toString() ? `/?${params.toString()}` : '/';
    // Use window.history directly to avoid Next.js router processing
    window.history.replaceState(
      { ...window.history.state, as: newUrl, url: newUrl },
      '',
      newUrl
    );
    
    // Reset flag after a short delay to allow effects to see it
    setTimeout(() => {
      isClosingModalRef.current = false;
    }, 100);
  };

  // Fetch photos when filters change (reset to page 1)
  useEffect(() => {
    // If no filters, use initial photos and fetch total count
    if (!hasActiveFilters) {
      // Only update photos if they're different to prevent unnecessary re-renders
      const photosChanged = photos.length !== initialPhotos.length || 
          photos.some((p, i) => p.id !== initialPhotos[i]?.id);
      
      if (photosChanged) {
        setPhotos(initialPhotos);
        photosInitializedRef.current = true;
      } else if (photos.length > 0) {
        // Photos are already set correctly
        photosInitializedRef.current = true;
      }
      
      setPage(1);
      pageRef.current = 1;
      isLoadingRef.current = false;
      // Fetch total count for display (use search API with no filters)
      fetch('/api/search?page=1&pageSize=1')
        .then((res) => res.json())
        .then((data) => {
          setTotal(data.total);
          setHasMore(initialPhotos.length < data.total);
        })
        .catch(() => {
          setTotal(initialPhotos.length);
          setHasMore(false);
        });
      return;
    }

    const fetchPhotos = async () => {
      setLoading(true);
      try {
        const params = new URLSearchParams();
        
        if (filters.people.length > 0) {
          params.set('people', filters.people.join(','));
          if (filters.peopleMode) {
            params.set('peopleMode', filters.peopleMode);
          }
        }
        if (filters.tags.length > 0) {
          params.set('tags', filters.tags.join(','));
          if (filters.tagsMode) {
            params.set('tagsMode', filters.tagsMode);
          }
        }
        if (filters.dateFrom) {
          params.set('dateFrom', filters.dateFrom.toISOString().split('T')[0]);
        }
        if (filters.dateTo) {
          params.set('dateTo', filters.dateTo.toISOString().split('T')[0]);
        }
        if (filters.mediaType && filters.mediaType !== 'all') {
          params.set('mediaType', filters.mediaType);
        }
        if (filters.favoritesOnly) {
          params.set('favoritesOnly', 'true');
        }
        params.set('page', '1');
        params.set('pageSize', '30');

        const response = await fetch(`/api/search?${params.toString()}`);
        if (!response.ok) throw new Error('Failed to search photos');
        
        const data = await response.json();
        setPhotos(data.photos);
        photosInitializedRef.current = true;
        setTotal(data.total);
        setHasMore(data.photos.length < data.total);
        setPage(1);
        pageRef.current = 1;
        isLoadingRef.current = false;
      } catch (error) {
        console.error('Error searching photos:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchPhotos();
  }, [filters, hasActiveFilters, initialPhotos]);

  // Infinite scroll observer
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        // Don't load if we've already loaded all photos
        if (photos.length >= total && total > 0) {
          setHasMore(false);
          return;
        }
        
        if (entries[0].isIntersecting && hasMore && !loadingMore && !loading && photos.length < total && !isLoadingRef.current) {
          const fetchMore = async () => {
            if (isLoadingRef.current) {
              console.log('Already loading, skipping observer trigger');
              return;
            }
            isLoadingRef.current = true;
            setLoadingMore(true);
            const nextPage = pageRef.current + 1;
            pageRef.current = nextPage;
            
            console.log('Observer triggered - loading page', nextPage, { currentPhotos: photos.length, total });
            
            try {
              const params = new URLSearchParams();
              
              if (filters.people.length > 0) {
                params.set('people', filters.people.join(','));
                if (filters.peopleMode) {
                  params.set('peopleMode', filters.peopleMode);
                }
              }
              if (filters.tags.length > 0) {
                params.set('tags', filters.tags.join(','));
                if (filters.tagsMode) {
                  params.set('tagsMode', filters.tagsMode);
                }
              }
              if (filters.dateFrom) {
                params.set('dateFrom', filters.dateFrom.toISOString().split('T')[0]);
              }
              if (filters.dateTo) {
                params.set('dateTo', filters.dateTo.toISOString().split('T')[0]);
              }
              if (filters.mediaType && filters.mediaType !== 'all') {
                params.set('mediaType', filters.mediaType);
              }
              if (filters.favoritesOnly) {
                params.set('favoritesOnly', 'true');
              }
              params.set('page', nextPage.toString());
              params.set('pageSize', '30');

              const response = await fetch(`/api/search?${params.toString()}`);
              if (!response.ok) throw new Error('Failed to load more photos');
              
              const data = await response.json();
              
              // If we got 0 photos, we've reached the end
              if (data.photos.length === 0) {
                console.log('Got 0 photos, reached the end. Setting hasMore to false');
                setHasMore(false);
                return;
              }
              
              setPhotos((prev) => {
                // Filter out duplicates by photo ID
                const existingIds = new Set(prev.map((p) => p.id));
                const uniqueNewPhotos = data.photos.filter((p: Photo) => !existingIds.has(p.id));
                const newPhotos = [...prev, ...uniqueNewPhotos];
                
                // Stop loading if we've loaded all photos or got no new photos
                const hasMorePhotos = newPhotos.length < data.total && 
                                     uniqueNewPhotos.length > 0;
                
                console.log('Loading page', nextPage, {
                  prevCount: prev.length,
                  newCount: data.photos.length,
                  uniqueNew: uniqueNewPhotos.length,
                  totalNow: newPhotos.length,
                  totalExpected: data.total,
                  hasMore: hasMorePhotos,
                  loadedAll: newPhotos.length >= data.total
                });
                
                // Always set hasMore to false if we've loaded all photos or got no new unique photos
                if (newPhotos.length >= data.total || uniqueNewPhotos.length === 0) {
                  console.log('All photos loaded or no new photos! Setting hasMore to false', { 
                    newPhotos: newPhotos.length, 
                    total: data.total,
                    uniqueNew: uniqueNewPhotos.length
                  });
                  setHasMore(false);
                } else {
                  setHasMore(hasMorePhotos);
                }
                
                return newPhotos;
              });
              setPage(nextPage);
            } catch (error) {
              console.error('Error loading more photos:', error);
              setHasMore(false); // Stop on error
            } finally {
              setLoadingMore(false);
              isLoadingRef.current = false;
            }
          };

          fetchMore();
        } else {
          // If we have all photos, make sure hasMore is false
          if (photos.length >= total && total > 0) {
            console.log('Observer: Already have all photos, setting hasMore to false', { photos: photos.length, total });
            setHasMore(false);
          }
        }
      },
      { threshold: 0.1 }
    );

    const currentTarget = observerTarget.current;
    if (currentTarget) {
      observer.observe(currentTarget);
    }

    return () => {
      if (currentTarget) {
        observer.unobserve(currentTarget);
      }
    };
  }, [hasMore, loadingMore, loading, filters, photos.length, total]);

  // Ensure we load the last page when we're close to the end
  useEffect(() => {
    // Don't run if we've already loaded all photos
    if (photos.length >= total && total > 0) {
      if (hasMore) {
        console.log('All photos loaded, setting hasMore to false', { photos: photos.length, total });
        setHasMore(false);
      }
      return;
    }
    
    // If we're very close to the end (1-5 photos remaining), load immediately
    const remaining = total - photos.length;
    if (remaining > 0 && remaining <= 5 && !loadingMore && !loading && !isLoadingRef.current && hasMore) {
      console.log('Very close to end, loading remaining photos immediately', { remaining, photos: photos.length, total });
      
      const fetchRemaining = async () => {
        isLoadingRef.current = true;
        setLoadingMore(true);
        const nextPage = pageRef.current + 1;
        pageRef.current = nextPage;
        
        try {
          const params = new URLSearchParams();
          
          if (filters.people.length > 0) {
            params.set('people', filters.people.join(','));
            if (filters.peopleMode) {
              params.set('peopleMode', filters.peopleMode);
            }
          }
          if (filters.tags.length > 0) {
            params.set('tags', filters.tags.join(','));
            if (filters.tagsMode) {
              params.set('tagsMode', filters.tagsMode);
            }
          }
          if (filters.dateFrom) {
            params.set('dateFrom', filters.dateFrom.toISOString().split('T')[0]);
          }
          if (filters.dateTo) {
            params.set('dateTo', filters.dateTo.toISOString().split('T')[0]);
          }
          if (filters.mediaType && filters.mediaType !== 'all') {
            params.set('mediaType', filters.mediaType);
          }
          if (filters.favoritesOnly) {
            params.set('favoritesOnly', 'true');
          }
          params.set('page', nextPage.toString());
          params.set('pageSize', '30');

          const response = await fetch(`/api/search?${params.toString()}`);
          if (!response.ok) throw new Error('Failed to load more photos');
          
          const data = await response.json();
          
          if (data.photos.length === 0) {
            console.log('Got 0 photos, reached the end');
            setHasMore(false);
            return;
          }
          
          setPhotos((prev) => {
            const existingIds = new Set(prev.map((p) => p.id));
            const uniqueNewPhotos = data.photos.filter((p: Photo) => !existingIds.has(p.id));
            const newPhotos = [...prev, ...uniqueNewPhotos];
            
            const allLoaded = newPhotos.length >= data.total;
            const noNewPhotos = uniqueNewPhotos.length === 0;
            
            console.log('Loaded remaining photos:', {
              prevCount: prev.length,
              newCount: data.photos.length,
              uniqueNew: uniqueNewPhotos.length,
              totalNow: newPhotos.length,
              totalExpected: data.total,
              allLoaded,
              noNewPhotos
            });
            
            if (allLoaded || noNewPhotos) {
              console.log('All photos loaded or no new photos, stopping');
              setHasMore(false);
            } else {
              setHasMore(newPhotos.length < data.total && uniqueNewPhotos.length > 0);
            }
            
            return newPhotos;
          });
          setPage(nextPage);
        } catch (error) {
          console.error('Error loading remaining photos:', error);
          setHasMore(false);
        } finally {
          setLoadingMore(false);
          isLoadingRef.current = false;
        }
      };
      
      // Small delay to avoid race conditions
      const timeoutId = setTimeout(() => {
        if (!isLoadingRef.current && !loadingMore && !loading && photos.length < total) {
          fetchRemaining();
        }
      }, 50);
      
      return () => clearTimeout(timeoutId);
    }
  }, [photos.length, total, hasMore, loadingMore, loading, filters]);

  return (
    <>
      <PageHeader
        photosCount={photos.length}
        isLoggedIn={isLoggedIn}
        selectedPhotoIds={selectedPhotoIds}
        selectionMode={selectionMode}
        isBulkFavoriting={isBulkFavoriting}
        isPreparingDownload={isPreparingDownload}
        onStartSlideshow={handleStartSlideshow}
        onTagSelected={handleTagSelected}
        onBulkFavorite={handleBulkFavorite}
        onDownloadSelected={handleDownloadSelected}
        onToggleSelectionMode={handleToggleSelectionMode}
      />
      <div className="flex gap-4">
        <CollapsibleSearch
          people={people}
          tags={tags}
          filters={filters}
          onFiltersChange={setFilters}
        />
        <div className="flex-1 min-w-0">
          {loading ? (
            <div className="flex items-center justify-center py-12">
              <Loader2 className="h-8 w-8 animate-spin text-gray-400" />
            </div>
          ) : (
            <>
              <div className="mb-4">
                <div className="flex items-center justify-between gap-4">
                  <div className="text-sm text-gray-600 dark:text-gray-400">
                    {hasActiveFilters ? (
                      `Found ${total} photo${total !== 1 ? 's' : ''} - Showing ${photos.length}`
                    ) : (
                      total > 0 ? (
                        `Showing ${photos.length} of ${total} photo${total !== 1 ? 's' : ''}`
                      ) : (
                        `Showing ${photos.length} photo${photos.length !== 1 ? 's' : ''}`
                      )
                    )}
                  </div>
                  {selectionMode && (
                    <div className="flex items-center gap-2">
                      <button
                        onClick={handleSelectAll}
                        className="p-2 text-secondary hover:text-secondary/80 hover:bg-secondary/10 rounded transition-colors"
                        title="Select all photos"
                        aria-label="Select all photos"
                      >
                        <CheckSquare className="h-5 w-5" />
                      </button>
                      <button
                        onClick={handleClearAll}
                        className="p-2 text-secondary hover:text-secondary/80 hover:bg-secondary/10 rounded transition-colors"
                        title="Clear selection"
                        aria-label="Clear selection"
                      >
                        <Square className="h-5 w-5" />
                      </button>
                    </div>
                  )}
                </div>
              </div>
              <PhotoGrid
                photos={photos}
                selectionMode={selectionMode}
                selectedPhotoIds={selectedPhotoIds}
                onToggleSelect={handleTogglePhotoSelection}
                refreshFavoritesKey={refreshFavoritesKey}
              />
              
              {/* Infinite scroll sentinel */}
              <div ref={observerTarget} className="h-10 flex items-center justify-center">
                {loadingMore && (
                  <Loader2 className="h-6 w-6 animate-spin text-gray-400" />
                )}
              </div>
              
              {!hasMore && photos.length > 0 && (
                <div className="text-center py-8 text-sm text-gray-500 dark:text-gray-400">
                  No more photos to load
                </div>
              )}
            </>
          )}
        </div>
      </div>

      {/* Photo Modal Overlay */}
      {photoParam && modalPhoto && !modalLoading && (
        <PhotoViewerClient
          initialPhoto={modalPhoto}
          allPhotos={modalPhotos}
          currentIndex={modalIndex}
          onClose={handleCloseModal}
          autoPlay={autoplayParam}
        />
      )}
      
      {photoParam && modalLoading && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black">
          <Loader2 className="h-8 w-8 animate-spin text-white" />
        </div>
      )}

      <TagSelectionDialog
        open={tagDialogOpen}
        onOpenChange={setTagDialogOpen}
        photoIds={selectedPhotoIds}
        tags={tags}
        onSuccess={() => {
          setSelectionMode(false);
          setSelectedPhotoIds([]);
        }}
      />
    </>
  );
}

