import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { useState, useMemo, useRef, useEffect, useCallback, memo } from 'react'
import { api, type MediaGalleryItem } from '../lib/api'
import { Image as ImageIcon, Video, Loader2, Film, Play, X, GalleryHorizontalEnd } from 'lucide-react'
import { isVideoFile as isVideoFileUtil } from '../lib/utils'
import GalleryLightbox from '../components/GalleryLightbox'
import TimelineScrubber from '../components/paid-content/TimelineScrubber'
import { useBreadcrumb } from '../hooks/useBreadcrumb'
import { breadcrumbConfig } from '../config/breadcrumbConfig'

const LIMIT = 200
const TARGET_ROW_HEIGHT = 180
const ROW_GAP = 4

const MONTH_NAMES = ['', 'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December']

const SHORT_MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
const SHORT_DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

function formatDayLabel(dateStr: string): string {
  const now = new Date()
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
  const [y, m, d] = dateStr.split('-').map(Number)
  const date = new Date(y, m - 1, d)
  const diffDays = Math.round((today.getTime() - date.getTime()) / (1000 * 60 * 60 * 24))

  if (diffDays === 0) return 'Today'
  if (diffDays === 1) return 'Yesterday'
  if (diffDays > 1 && diffDays < 7) return DAY_NAMES[date.getDay()]
  if (y === now.getFullYear()) return `${SHORT_DAY_NAMES[date.getDay()]}, ${SHORT_MONTH_NAMES[date.getMonth()]} ${d}`
  return `${SHORT_DAY_NAMES[date.getDay()]}, ${SHORT_MONTH_NAMES[date.getMonth()]} ${d}, ${y}`
}

function formatDuration(seconds: number): string {
  const m = Math.floor(seconds / 60)
  const s = Math.floor(seconds % 60)
  return `${m}:${s.toString().padStart(2, '0')}`
}

function getAspectRatio(item: MediaGalleryItem): number {
  return (item.width && item.height && item.height > 0) ? item.width / item.height : 1
}

function isVideoItem(item: MediaGalleryItem): boolean {
  if (item.media_type === 'video') return true
  return isVideoFileUtil(item.filename)
}

interface JustifiedRow {
  items: MediaGalleryItem[]
  height: number
}

function buildJustifiedRows(
  items: MediaGalleryItem[],
  containerWidth: number,
  targetHeight: number,
  gap: number
): JustifiedRow[] {
  if (containerWidth <= 0 || items.length === 0) return []

  const rows: JustifiedRow[] = []
  let currentRow: MediaGalleryItem[] = []
  let currentWidthSum = 0

  for (const item of items) {
    const scaledWidth = getAspectRatio(item) * targetHeight
    currentRow.push(item)
    currentWidthSum += scaledWidth

    const totalGap = (currentRow.length - 1) * gap
    if (currentWidthSum + totalGap >= containerWidth) {
      const arSum = currentRow.reduce((sum, it) => sum + getAspectRatio(it), 0)
      const rowHeight = (containerWidth - totalGap) / arSum
      rows.push({ items: [...currentRow], height: Math.min(rowHeight, targetHeight * 1.5) })
      currentRow = []
      currentWidthSum = 0
    }
  }

  if (currentRow.length > 0) {
    rows.push({ items: currentRow, height: targetHeight })
  }

  return rows
}

const JustifiedSection = memo(function JustifiedSection({
  sectionKey,
  label,
  items,
  rows,
  onClickItem,
}: {
  sectionKey: string
  label: string
  items: MediaGalleryItem[]
  rows: JustifiedRow[]
  onClickItem: (item: MediaGalleryItem) => void
}) {
  return (
    <div id={`gallery-section-${sectionKey}`} className="mb-6">
      <div className="flex items-center justify-between mb-2 sticky top-0 z-10 bg-background/95 backdrop-blur-sm py-2">
        <h2 className="text-sm font-medium">{label}</h2>
        <span className="text-sm text-muted-foreground">{items.length} items</span>
      </div>

      <div className="flex flex-col" style={{ gap: `${ROW_GAP}px` }}>
        {rows.map((row, rowIdx) => (
          <div key={rowIdx} className="flex" style={{ gap: `${ROW_GAP}px`, height: `${row.height}px` }}>
            {row.items.map(item => {
              const itemWidth = getAspectRatio(item) * row.height
              const isVideo = isVideoItem(item)
              const thumbUrl = api.getMediaThumbnailUrl(item.file_path, isVideo ? 'video' : 'image', item.file_token)
              return (
                <button
                  key={item.id}
                  type="button"
                  className="relative bg-secondary rounded overflow-hidden cursor-pointer group hover:ring-2 hover:ring-primary transition-all flex-shrink-0"
                  style={{ width: `${itemWidth}px`, height: `${row.height}px` }}
                  onClick={() => onClickItem(item)}
                >
                  <img
                    src={thumbUrl}
                    alt=""
                    className="w-full h-full object-cover"
                    loading="lazy"
                    onError={(e) => {
                      (e.target as HTMLImageElement).style.display = 'none'
                    }}
                  />
                  {isVideo && (
                    <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
                      <div className="w-8 h-8 rounded-full bg-black/50 flex items-center justify-center">
                        <Video className="w-4 h-4 text-white" />
                      </div>
                    </div>
                  )}
                  {isVideo && item.duration != null && (
                    <div className="absolute bottom-0.5 right-0.5 bg-black/70 text-white text-[10px] px-1 py-0.5 rounded">
                      {formatDuration(item.duration)}
                    </div>
                  )}
                  <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-1.5 opacity-0 group-hover:opacity-100 transition-opacity">
                    <div className="text-white text-[10px] truncate">{item.filename}</div>
                  </div>
                </button>
              )
            })}
          </div>
        ))}
      </div>
    </div>
  )
})

export default function Gallery() {
  useBreadcrumb(breadcrumbConfig['/gallery'])

  const [contentType, setContentType] = useState<string | undefined>(undefined)
  const [lightboxIndex, setLightboxIndex] = useState(-1)
  const [slideshowActive, setSlideshowActive] = useState(false)
  const [slideshowShuffled, setSlideshowShuffled] = useState(true)
  const [shuffleSeed, setShuffleSeed] = useState<number | null>(null)
  const [dateFrom, setDateFrom] = useState<string | undefined>(undefined)
  const [dateTo, setDateTo] = useState<string | undefined>(undefined)
  const [jumpTarget, setJumpTarget] = useState<{ year: number; month: number } | null>(null)
  const sentinelRef = useRef<HTMLDivElement>(null)
  const [containerWidth, setContainerWidth] = useState(0)
  const roRef = useRef<ResizeObserver | null>(null)

  const containerRef = useCallback((node: HTMLDivElement | null) => {
    if (roRef.current) {
      roRef.current.disconnect()
      roRef.current = null
    }
    if (node) {
      setContainerWidth(node.getBoundingClientRect().width)
      const ro = new ResizeObserver((entries) => {
        const w = entries[0]?.contentRect.width
        if (w != null) setContainerWidth(prev => Math.abs(prev - w) > 1 ? w : prev)
      })
      ro.observe(node)
      roRef.current = ro
    }
  }, [])

  // Gallery media with infinite scroll
  const {
    data,
    isLoading,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
  } = useInfiniteQuery({
    queryKey: ['gallery-timeline', contentType, dateFrom, dateTo],
    queryFn: ({ pageParam = 0 }) =>
      api.getMediaGallery({
        media_type: contentType,
        date_from: dateFrom,
        date_to: dateTo,
        sort_by: 'post_date',
        sort_order: 'desc',
        limit: LIMIT,
        offset: pageParam * LIMIT,
      }),
    getNextPageParam: (lastPage, allPages) => {
      if (!lastPage?.media || lastPage.media.length < LIMIT) return undefined
      return allPages.length
    },
    initialPageParam: 0,
    staleTime: 0,
    gcTime: 5 * 60 * 1000,
  })

  // Date range for scrubber
  const { data: dateRanges } = useQuery({
    queryKey: ['gallery-timeline-date-range', contentType],
    queryFn: () => api.getMediaGalleryDateRange({
      media_type: contentType,
    }),
  })

  // Shuffled gallery media (only active during slideshow)
  const {
    data: shuffledData,
    hasNextPage: shuffledHasNextPage,
    fetchNextPage: fetchNextShuffledPage,
    isFetchingNextPage: isFetchingNextShuffledPage,
  } = useInfiniteQuery({
    queryKey: ['gallery-shuffled', contentType, shuffleSeed],
    queryFn: ({ pageParam = 0 }) =>
      api.getMediaGallery({
        media_type: contentType,
        shuffle: true,
        shuffle_seed: shuffleSeed ?? 0,
        limit: LIMIT,
        offset: pageParam * LIMIT,
      }),
    getNextPageParam: (lastPage, allPages) => {
      if (!lastPage?.media || lastPage.media.length < LIMIT) return undefined
      return allPages.length
    },
    initialPageParam: 0,
    enabled: shuffleSeed !== null,
    gcTime: 5 * 60 * 1000,
  })

  // Flatten shuffled pages
  const shuffledItems = useMemo(() => {
    if (!shuffledData?.pages) return []
    const seen = new Set<number>()
    return shuffledData.pages.flatMap(page => page.media || []).filter(item => {
      if (seen.has(item.id)) return false
      seen.add(item.id)
      return true
    })
  }, [shuffledData])

  // Flatten all pages
  const items = useMemo(() => {
    if (!data?.pages) return []
    const seen = new Set<number>()
    return data.pages.flatMap(page => page.media || []).filter(item => {
      if (seen.has(item.id)) return false
      seen.add(item.id)
      return true
    })
  }, [data])

  const totalCount = data?.pages?.[0]?.total ?? 0

  // Pick the right item list depending on mode
  const lightboxItems = (slideshowActive && slideshowShuffled) ? shuffledItems : items

  // Group items by YYYY-MM-DD
  const sections = useMemo(() => {
    const map = new Map<string, MediaGalleryItem[]>()
    for (const item of items) {
      const dateStr = item.post_date || item.download_date
      const d = dateStr ? new Date(dateStr) : null
      const key = d && !isNaN(d.getTime())
        ? `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
        : 'unknown'
      if (!map.has(key)) map.set(key, [])
      map.get(key)!.push(item)
    }
    return Array.from(map.entries()).map(([key, sectionItems]) => {
      if (key === 'unknown') return { key, label: 'Unknown Date', year: 0, month: 0, items: sectionItems }
      const [y, m] = key.split('-').map(Number)
      return { key, label: formatDayLabel(key), year: y, month: m, items: sectionItems }
    })
  }, [items])

  // Pre-compute justified rows
  const sectionRowsMap = useMemo(() => {
    const map = new Map<string, JustifiedRow[]>()
    for (const section of sections) {
      map.set(section.key, buildJustifiedRows(section.items, containerWidth, TARGET_ROW_HEIGHT, ROW_GAP))
    }
    return map
  }, [sections, containerWidth])

  // Jump to date after data loads
  useEffect(() => {
    if (!jumpTarget || items.length === 0) return
    const prefix = `${jumpTarget.year}-${String(jumpTarget.month).padStart(2, '0')}`
    const timer = setTimeout(() => {
      const firstDaySection = sections.find(s => s.key.startsWith(prefix))
      if (firstDaySection) {
        const el = document.getElementById(`gallery-section-${firstDaySection.key}`)
        if (el) {
          el.scrollIntoView({ behavior: 'smooth', block: 'start' })
        }
      }
      setJumpTarget(null)
    }, 200)
    return () => clearTimeout(timer)
  }, [jumpTarget, items.length, sections])

  // Infinite scroll observer
  useEffect(() => {
    if (!sentinelRef.current) return
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage()
        }
      },
      { rootMargin: '600px' }
    )
    observer.observe(sentinelRef.current)
    return () => observer.disconnect()
  }, [hasNextPage, isFetchingNextPage, fetchNextPage])

  const openLightbox = useCallback((item: MediaGalleryItem) => {
    const idx = items.findIndex(i => i.id === item.id)
    if (idx >= 0) {
      setSlideshowActive(false)
      setLightboxIndex(idx)
    }
  }, [items])

  const startSlideshow = useCallback(() => {
    const seed = Math.floor(Math.random() * 2147483647)
    setShuffleSeed(seed)
    setSlideshowShuffled(true)
    setSlideshowActive(true)
  }, [])

  // Open lightbox when slideshow data is ready
  useEffect(() => {
    if (!slideshowActive || lightboxIndex !== -1) return
    const source = slideshowShuffled ? shuffledItems : items
    if (source.length > 0) {
      setLightboxIndex(0)
    }
  }, [slideshowActive, slideshowShuffled, shuffledItems, items, lightboxIndex])

  // Handle shuffle toggle from lightbox
  const handleShuffleChange = useCallback((enabled: boolean) => {
    setSlideshowShuffled(enabled)
    if (enabled && shuffleSeed === null) {
      setShuffleSeed(Math.floor(Math.random() * 2147483647))
    }
    setLightboxIndex(0)
  }, [shuffleSeed])

  const handleLightboxNavigate = useCallback((idx: number) => {
    setLightboxIndex(idx)
    // Pre-fetch more if near the end
    if (slideshowActive && slideshowShuffled) {
      if (idx >= shuffledItems.length - 20 && shuffledHasNextPage && !isFetchingNextShuffledPage) {
        fetchNextShuffledPage()
      }
    } else {
      if (idx >= items.length - 20 && hasNextPage && !isFetchingNextPage) {
        fetchNextPage()
      }
    }
  }, [items.length, hasNextPage, isFetchingNextPage, fetchNextPage, slideshowActive, slideshowShuffled, shuffledItems.length, shuffledHasNextPage, isFetchingNextShuffledPage, fetchNextShuffledPage])

  const handleLightboxClose = useCallback(() => {
    setLightboxIndex(-1)
    setSlideshowActive(false)
  }, [])

  const handleJumpToDate = useCallback((year: number, month: number) => {
    const df = `${year}-${String(month).padStart(2, '0')}-01`
    // Last day of the month
    const lastDay = new Date(year, month, 0).getDate()
    const dt = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`
    setDateFrom(df)
    setDateTo(dt)
    setJumpTarget({ year, month })
  }, [])

  const clearDateFrom = useCallback(() => {
    setDateFrom(undefined)
    setDateTo(undefined)
    setJumpTarget(null)
  }, [])

  const dateFromLabel = useMemo(() => {
    if (!dateFrom) return ''
    const [y, m] = dateFrom.split('-').map(Number)
    return `${MONTH_NAMES[m]} ${y}`
  }, [dateFrom])

  return (
    <div className="space-y-4">
      {/* Header */}
      <div className="flex flex-wrap items-center justify-between gap-2 sm:gap-4">
        <div>
          <h1 className="text-2xl sm:text-3xl font-bold flex items-center gap-2 sm:gap-3">
            <GalleryHorizontalEnd className="w-8 h-8 text-purple-500" />
            Gallery
          </h1>
          <p className="text-muted-foreground text-sm">
            {totalCount.toLocaleString()} items
          </p>
        </div>

        <div className="flex items-center gap-2 w-full sm:w-auto justify-center sm:justify-end">
          {/* Slideshow button */}
          <button
            type="button"
            onClick={startSlideshow}
            disabled={totalCount === 0}
            className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
          >
            <Play className="w-4 h-4" />
            Slideshow
          </button>

          {/* Content type toggle */}
          <div className="flex items-center bg-secondary rounded-lg p-1 gap-0.5">
            <button
              type="button"
              className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
                !contentType ? 'bg-background shadow-sm' : 'hover:bg-background/50'
              }`}
              onClick={() => setContentType(undefined)}
            >
              All
            </button>
            <button
              type="button"
              className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors flex items-center gap-1.5 ${
                contentType === 'image' ? 'bg-background shadow-sm' : 'hover:bg-background/50'
              }`}
              onClick={() => setContentType('image')}
            >
              <ImageIcon className="w-4 h-4" />
              Images
            </button>
            <button
              type="button"
              className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors flex items-center gap-1.5 ${
                contentType === 'video' ? 'bg-background shadow-sm' : 'hover:bg-background/50'
              }`}
              onClick={() => setContentType('video')}
            >
              <Film className="w-4 h-4" />
              Videos
            </button>
          </div>
        </div>
      </div>

      {/* Date filter chip */}
      {dateFrom && (
        <div className="flex items-center gap-2">
          <div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-primary/10 text-primary text-sm font-medium">
            Showing {dateFromLabel}
            <button
              type="button"
              onClick={clearDateFrom}
              className="p-0.5 rounded-full hover:bg-primary/20 transition-colors"
            >
              <X className="w-3.5 h-3.5" />
            </button>
          </div>
        </div>
      )}

      {/* Loading */}
      {isLoading && (
        <div className="flex items-center justify-center py-20">
          <Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
        </div>
      )}

      {/* Empty state */}
      {!isLoading && items.length === 0 && (
        <div className="text-center py-20 text-muted-foreground">
          <ImageIcon className="w-12 h-12 mx-auto mb-3 opacity-30" />
          <p>No media found</p>
        </div>
      )}

      {/* Timeline sections */}
      {!isLoading && items.length > 0 && (
        <div className="relative">
          <div className="md:mr-12" ref={containerRef}>
            {sections.map(section => (
              <JustifiedSection
                key={section.key}
                sectionKey={section.key}
                label={section.label}
                items={section.items}
                rows={sectionRowsMap.get(section.key) || []}
                onClickItem={openLightbox}
              />
            ))}
          </div>

          {/* Timeline Scrubber */}
          {dateRanges && dateRanges.length > 0 && (
            <TimelineScrubber
              ranges={dateRanges}
              sections={sections}
              onJumpToDate={handleJumpToDate}
            />
          )}
        </div>
      )}

      {/* Infinite scroll sentinel */}
      <div ref={sentinelRef} className="h-4" />
      {isFetchingNextPage && (
        <div className="flex items-center justify-center py-4">
          <Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
        </div>
      )}

      {/* Lightbox */}
      {lightboxIndex >= 0 && lightboxItems.length > 0 && (
        <GalleryLightbox
          items={lightboxItems}
          currentIndex={lightboxIndex}
          onClose={handleLightboxClose}
          onNavigate={handleLightboxNavigate}
          initialSlideshow={slideshowActive}
          initialInterval={3000}
          autoPlayVideo={true}
          isShuffled={slideshowActive && slideshowShuffled}
          onShuffleChange={handleShuffleChange}
          totalCount={totalCount}
          hasMore={(slideshowActive && slideshowShuffled) ? !!shuffledHasNextPage : !!hasNextPage}
          onLoadMore={() => (slideshowActive && slideshowShuffled) ? fetchNextShuffledPage() : fetchNextPage()}
        />
      )}
    </div>
  )
}
