import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { cn } from '@/lib/utils'
import type { Photo } from '../../types/photo'
import { photos as photosApi } from '../../services/api'
import { useActiveHeapMembers } from '../../hooks/useActiveHeapMembersQuery'

interface PreviewFilmstripProps {
  photos: Photo[]
  currentIndex: number
  onSelect: (id: string) => void
}

const CELL_SIZE = 72
const GAP = 4

/**
 * Horizontal virtualised filmstrip. Mounting thousands of `<button>`
 * nodes on preview open used to dominate the perceived open latency
 * for large libraries — with virtualisation only a viewport's worth
 * (~15-20 cells) ever hits the DOM.
 */
export function PreviewFilmstrip({
  photos,
  currentIndex,
  onSelect,
}: PreviewFilmstripProps) {
  const scrollRef = useRef<HTMLDivElement>(null)
  const { memberIds: activeHeapMembers } = useActiveHeapMembers()

  const virtualizer = useVirtualizer({
    horizontal: true,
    count: photos.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => CELL_SIZE + GAP,
    overscan: 10,
    getItemKey: (idx) => photos[idx].id,
  })

  // Centre the active cell when the index changes. Using layout-effect
  // so the scroll happens before paint — avoids a visible jump.
  useLayoutEffect(() => {
    if (currentIndex < 0 || currentIndex >= photos.length) return
    virtualizer.scrollToIndex(currentIndex, { align: 'center' })
  }, [currentIndex, photos.length, virtualizer])

  return (
    <div
      ref={scrollRef}
      className="relative h-24 shrink-0 overflow-x-auto overflow-y-hidden border-t border-border bg-surface py-2"
    >
      <div
        style={{
          width: virtualizer.getTotalSize(),
          height: '100%',
          position: 'relative',
          paddingLeft: 8,
          paddingRight: 8,
        }}
      >
        {virtualizer.getVirtualItems().map((vItem) => {
          const photo = photos[vItem.index]
          const isActive = vItem.index === currentIndex
          const isInActiveHeap = activeHeapMembers.has(photo.id)
          return (
            <button
              key={photo.id}
              onClick={() => onSelect(photo.id)}
              className={cn(
                'absolute top-0 overflow-hidden rounded-sm will-change-transform transition-[transform,box-shadow,opacity] duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)]',
                isActive &&
                  'scale-90 opacity-100 ring-2 ring-blue-500 ring-offset-2 ring-offset-surface',
                !isActive && isInActiveHeap && 'opacity-100',
                !isActive &&
                  !isInActiveHeap &&
                  'opacity-60 hover:opacity-100 hover:ring-1 hover:ring-text-muted/70'
              )}
              style={{
                width: CELL_SIZE,
                height: CELL_SIZE,
                transform: `translateX(${vItem.start}px)`,
              }}
              title={photo.filename}
            >
              <FilmstripThumb photo={photo} />
              {isInActiveHeap && (
                <div className="pointer-events-none absolute inset-0 bg-emerald-500/40" />
              )}
              {isActive && (
                <div className="pointer-events-none absolute inset-0 bg-blue-500/50" />
              )}
            </button>
          )
        })}
      </div>
    </div>
  )
}

function FilmstripThumb({ photo }: { photo: Photo }) {
  const [loaded, setLoaded] = useState(false)
  const [errored, setErrored] = useState(false)

  useEffect(() => {
    setLoaded(false)
    setErrored(false)
  }, [photo.id])

  return (
    <>
      {!loaded && !errored && (
        <div className="absolute inset-0 animate-pulse bg-bg" />
      )}
      {errored && (
        <div className="absolute inset-0 flex items-center justify-center bg-bg">
          <div className="h-1.5 w-1.5 rounded-full bg-text-muted/50" />
        </div>
      )}
      <img
        src={photosApi.getThumbnailUrl(photo.id, 'small')}
        alt=""
        loading="lazy"
        decoding="async"
        onLoad={() => setLoaded(true)}
        onError={() => setErrored(true)}
        className={cn(
          'h-full w-full object-cover transition-opacity duration-200',
          loaded ? 'opacity-100' : 'opacity-0',
          // Match the grid's discarded styling so the filmstrip mirrors
          // what the user sees behind the preview.
          photo.is_discarded && 'opacity-50 grayscale'
        )}
      />
    </>
  )
}
