import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { useState, useMemo, useRef, useEffect, useCallback, memo } from 'react'
import { api, GalleryMediaItem, PaidContentPost, PaidContentAttachment } from '../../lib/api'
import { THUMB_CACHE_V } from '../../lib/utils'
import { ArrowLeft, Image as ImageIcon, Video, Loader2, Film, Play, X } from 'lucide-react'
import BundleLightbox from './BundleLightbox'
import TimelineScrubber from './TimelineScrubber'

interface Props {
  groupId: number
  onBack: () => void
}

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: GalleryMediaItem): number {
  return (item.width && item.height && item.height > 0) ? item.width / item.height : 1
}

/** Build a synthetic PaidContentPost from a GalleryMediaItem for BundleLightbox compatibility */
function buildSyntheticPost(item: GalleryMediaItem): PaidContentPost {
  return {
    id: item.post_id,
    creator_id: item.creator_id,
    post_id: String(item.post_id),
    title: item.post_title,
    content: null,
    published_at: item.media_date,
    added_at: null,
    has_attachments: 1,
    attachment_count: 1,
    downloaded: 1,
    download_date: item.downloaded_at,
    is_favorited: item.is_favorited,
    is_viewed: 1,
    is_pinned: 0,
    pinned_at: null,
    username: item.username,
    platform: item.platform,
    service_id: item.service_id,
    display_name: item.display_name,
    profile_image_url: item.profile_image_url,
    identity_id: null,
    identity_name: item.identity_name,
    attachments: [],
    tags: [],
    tagged_users: [],
  }
}

/** Build a synthetic PaidContentAttachment from a GalleryMediaItem */
function buildSyntheticAttachment(item: GalleryMediaItem): PaidContentAttachment {
  return {
    id: item.id,
    post_id: item.post_id,
    attachment_index: 0,
    name: item.name,
    file_type: item.file_type,
    extension: item.extension,
    server_path: null,
    download_url: null,
    file_size: item.file_size,
    width: item.width,
    height: item.height,
    duration: item.duration,
    status: 'completed',
    local_path: item.local_path,
    local_filename: item.local_filename,
    file_hash: item.file_hash,
    error_message: null,
    download_attempts: 0,
    downloaded_at: item.downloaded_at,
    created_at: null,
  }
}

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

/** Compute justified rows from items */
function buildJustifiedRows(
  items: GalleryMediaItem[],
  containerWidth: number,
  targetHeight: number,
  gap: number
): JustifiedRow[] {
  if (containerWidth <= 0 || items.length === 0) return []

  const rows: JustifiedRow[] = []
  let currentRow: GalleryMediaItem[] = []
  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) {
      // Finalize row: compute exact height so items fill the container width
      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
    }
  }

  // Last incomplete row: use target height (don't stretch)
  if (currentRow.length > 0) {
    rows.push({ items: currentRow, height: targetHeight })
  }

  return rows
}

/** Memoized section component — only re-renders when its own props change */
const JustifiedSection = memo(function JustifiedSection({
  sectionKey,
  label,
  items,
  rows,
  onClickItem,
}: {
  sectionKey: string
  label: string
  items: GalleryMediaItem[]
  rows: JustifiedRow[]
  onClickItem: (item: GalleryMediaItem) => void
}) {
  return (
    <div id={`gallery-section-${sectionKey}`} className="mb-6">
      {/* Section header */}
      <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>

      {/* Justified layout rows */}
      <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 = item.file_type === 'video'
              const thumbUrl = `/api/paid-content/files/thumbnail/${item.id}?size=large&${
                item.file_hash ? `v=${item.file_hash.slice(0, 8)}` : THUMB_CACHE_V
              }`
              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.display_name || item.username}</div>
                  </div>
                </button>
              )
            })}
          </div>
        ))}
      </div>
    </div>
  )
})

export default function GalleryTimeline({ groupId, onBack }: Props) {
  const [contentType, setContentType] = useState<string | undefined>(undefined)
  const [lightboxIndex, setLightboxIndex] = useState(-1)
  const [lightboxPost, setLightboxPost] = useState<PaidContentPost | null>(null)
  const [slideshowActive, setSlideshowActive] = useState(false)
  const [slideshowShuffled, setSlideshowShuffled] = useState(true) // shuffle on by default
  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)

  // Callback ref: fires when the container div mounts/unmounts (it's conditionally rendered)
  const containerRef = useCallback((node: HTMLDivElement | null) => {
    if (roRef.current) {
      roRef.current.disconnect()
      roRef.current = null
    }
    if (node) {
      // Sync initial read — no blank frame
      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
    }
  }, [])

  // Group name
  const { data: groups } = useQuery({
    queryKey: ['gallery-groups'],
    queryFn: () => api.paidContent.getGalleryGroups(),
    staleTime: 60_000,
  })
  const groupName = groupId === 0 ? 'All Media' : groups?.find(g => g.id === groupId)?.name ?? 'Gallery'

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

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

  // Date range for scrubber
  const { data: dateRanges } = useQuery({
    queryKey: ['gallery-date-range', groupId, contentType],
    queryFn: () => api.paidContent.getGalleryDateRange({
      creator_group_id: groupId > 0 ? groupId : undefined,
      content_type: contentType,
    }),
  })

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

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

  // Group items by YYYY-MM-DD
  const sections = useMemo(() => {
    const map = new Map<string, GalleryMediaItem[]>()
    for (const item of items) {
      const d = item.media_date ? new Date(item.media_date) : null
      const key = d ? `${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 all justified rows (memoized — only recomputes when sections or width change)
  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])

  // After data loads with a jump target, scroll to the first day-section in that month
  useEffect(() => {
    if (!jumpTarget || items.length === 0) return
    const prefix = `${jumpTarget.year}-${String(jumpTarget.month).padStart(2, '0')}`
    // Small delay to let DOM render
    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)
    }, 100)
    return () => clearTimeout(timer)
  }, [jumpTarget, items.length, sections])

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

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

  // Build flat attachment list for lightbox
  const flatAttachments = useMemo(() => lightboxItems.map(buildSyntheticAttachment), [lightboxItems])

  // Lightbox attachment-to-post mapping
  const itemMap = useMemo(() => {
    const m = new Map<number, GalleryMediaItem>()
    for (const item of lightboxItems) m.set(item.id, item)
    return m
  }, [lightboxItems])

  // 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: GalleryMediaItem) => {
    const idx = items.findIndex(i => i.id === item.id)
    if (idx >= 0) {
      setSlideshowActive(false)
      setLightboxPost(buildSyntheticPost(item))
      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) {
      setLightboxPost(buildSyntheticPost(source[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)
    const source = enabled ? shuffledItems : items
    if (source.length > 0) {
      setLightboxPost(buildSyntheticPost(source[0]))
    }
  }, [shuffleSeed, shuffledItems, items])

  const handleLightboxNavigate = useCallback((idx: number) => {
    setLightboxIndex(idx)
    const att = flatAttachments[idx]
    if (att) {
      const sourceItem = itemMap.get(att.id)
      if (sourceItem) setLightboxPost(buildSyntheticPost(sourceItem))
    }
    if (idx >= flatAttachments.length - 20) {
      if (slideshowActive && slideshowShuffled && shuffledHasNextPage && !isFetchingNextShuffledPage) {
        fetchNextShuffledPage()
      } else if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage()
      }
    }
  }, [flatAttachments, itemMap, slideshowActive, slideshowShuffled, hasNextPage, isFetchingNextPage, fetchNextPage, shuffledHasNextPage, isFetchingNextShuffledPage, fetchNextShuffledPage])

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

  // Handle timeline jump to a date not yet loaded
  const handleJumpToDate = useCallback((year: number, month: number) => {
    const df = `${year}-${String(month).padStart(2, '0')}-01`
    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 className="flex items-center gap-3">
          <button
            type="button"
            onClick={onBack}
            className="p-2 rounded-lg hover:bg-secondary transition-colors"
          >
            <ArrowLeft className="w-5 h-5" />
          </button>
          <div>
            <h1 className="text-2xl font-bold">{groupName}</h1>
            <p className="text-muted-foreground text-sm">
              {totalCount.toLocaleString()} items
            </p>
          </div>
        </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">
          {/* Main content with right margin for scrubber */}
          <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 */}
      {lightboxPost && lightboxIndex >= 0 && flatAttachments.length > 0 && (
        <BundleLightbox
          post={lightboxPost}
          attachments={flatAttachments}
          currentIndex={lightboxIndex}
          initialSlideshow={slideshowActive}
          initialInterval={3000}
          initialShuffle={false}
          autoPlayVideo={true}
          isShuffled={slideshowShuffled}
          onShuffleChange={slideshowActive ? handleShuffleChange : undefined}
          onClose={handleLightboxClose}
          onNavigate={handleLightboxNavigate}
          totalCount={totalCount}
          hasMore={(slideshowActive && slideshowShuffled) ? !!shuffledHasNextPage : !!hasNextPage}
          onLoadMore={() => (slideshowActive && slideshowShuffled) ? fetchNextShuffledPage() : fetchNextPage()}
        />
      )}
    </div>
  )
}
