import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import { Search, X, Loader2 } from '../lib/icons'
import { useLibraryByTmdbId } from '../hooks/use-jellyfin'
import { useFuzzyLibrarySearch } from '../hooks/use-fuzzy-search'
import { useTmdbSearch } from '../hooks/use-tmdb'
import { mapTmdbToJf } from '../lib/tmdb-mapping'
import type { BaseItemDto } from '../api/types'
import { PeopleSection, MoviesShowsSection, EpisodesSection, MusicSection } from './search/sections'
import { EmptyState, LoadingState, NoResults } from './search/empty-states'

const RECENT_KEY = 'jf_recent_searches'
const RECENT_MAX = 8

function readRecent(): string[] {
  if (typeof window === 'undefined') return []
  try {
    const raw = localStorage.getItem(RECENT_KEY)
    if (!raw) return []
    const arr = JSON.parse(raw)
    return Array.isArray(arr) ? arr.filter(s => typeof s === 'string').slice(0, RECENT_MAX) : []
  } catch {
    return []
  }
}

function pushRecent(q: string) {
  const trimmed = q.trim()
  if (!trimmed) return
  try {
    const cur = readRecent()
    const next = [trimmed, ...cur.filter(s => s.toLowerCase() !== trimmed.toLowerCase())].slice(0, RECENT_MAX)
    localStorage.setItem(RECENT_KEY, JSON.stringify(next))
  } catch { /* noop */ }
}

function clearRecent() {
  try { localStorage.removeItem(RECENT_KEY) } catch { /* noop */ }
}

export default function SearchPage() {
  const [query, setQuery] = useState('')
  const [debounced, setDebounced] = useState('')
  const [recents, setRecents] = useState<string[]>(() => readRecent())
  const inputRef = useRef<HTMLInputElement>(null)
  const navigate = useNavigate()

  const fuzzy = useFuzzyLibrarySearch(query)
  const tmdb = useTmdbSearch(debounced)
  const libraryByTmdb = useLibraryByTmdbId()
  const jfLoading = fuzzy.isLoading
  const jfFetching = fuzzy.isFetching

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  useEffect(() => {
    const t = setTimeout(() => setDebounced(query), 220)
    return () => clearTimeout(t)
  }, [query])

  // Persist successful searches once results land. Avoids polluting the
  // recents list with mid-typing fragments by waiting for a stable query.
  useEffect(() => {
    if (!debounced) return
    if (debounced.length < 2) return
    if (jfLoading || jfFetching) return
    pushRecent(debounced)
    setRecents(readRecent())
  }, [debounced, jfLoading, jfFetching])

  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === 'Escape') {
        if (query) setQuery('')
        else navigate(-1)
      }
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [navigate, query])

  const isSearching = !!query && (jfLoading || tmdb.isLoading) && !fuzzy.catalogReady
  const hasQuery = !!query.trim()

  const grouped = useMemo(() => {
    const out: Record<string, BaseItemDto[]> = {}
    for (const it of fuzzy.catalogResults) {
      const t = (it.Type || 'Other') as string
      if (!out[t]) out[t] = []
      out[t].push(it)
    }
    for (const it of fuzzy.episodesAndTracks) {
      const t = (it.Type || 'Other') as string
      if (!out[t]) out[t] = []
      out[t].push(it)
    }
    return out
  }, [fuzzy.catalogResults, fuzzy.episodesAndTracks])

  const tmdbResults = tmdb.data?.results || []
  const tmdbPeople = tmdbResults.filter(r => r.media_type === 'person').slice(0, 8)
  const tmdbMovies = tmdbResults.filter(r => r.media_type === 'movie')
  const tmdbTv = tmdbResults.filter(r => r.media_type === 'tv')

  const tmdbMappedMovies = useMemo(
    () => mapTmdbToJf(tmdbMovies, libraryByTmdb.data),
    [tmdbMovies, libraryByTmdb.data],
  )
  const tmdbMappedTv = useMemo(
    () => mapTmdbToJf(tmdbTv, libraryByTmdb.data),
    [tmdbTv, libraryByTmdb.data],
  )

  // Local items first (full BaseItemDto with ImageTags + ProviderIds).
  // TMDB extras filtered against local TMDB ids so library hits don't
  // duplicate. Anything in the library overrides its TMDB twin.
  const localMovies = (grouped.Movie || []) as BaseItemDto[]
  const localSeries = (grouped.Series || []) as BaseItemDto[]
  const localTmdbIds = new Set<string>()
  for (const it of [...localMovies, ...localSeries]) {
    const t = it.ProviderIds?.Tmdb
    if (t) localTmdbIds.add(String(t))
  }
  const tmdbExtras = [...tmdbMappedMovies, ...tmdbMappedTv].filter(it => {
    const t = it.ProviderIds?.Tmdb
    return !!t && !localTmdbIds.has(String(t))
  })

  const localCards: BaseItemDto[] = [...localMovies, ...localSeries].map(
    it => ({ ...it, _inLibrary: true } as BaseItemDto),
  )
  const mixedCards: BaseItemDto[] = [...localCards, ...tmdbExtras]

  const episodes = (grouped.Episode || []) as BaseItemDto[]
  const albums = (grouped.MusicAlbum || []) as BaseItemDto[]
  const artists = (grouped.MusicArtist || []) as BaseItemDto[]
  const tracks = (grouped.Audio || []) as BaseItemDto[]

  const totalLocal =
    localMovies.length + localSeries.length + episodes.length + albums.length + artists.length + tracks.length
  const noResults =
    hasQuery &&
    !isSearching &&
    totalLocal === 0 &&
    tmdbPeople.length === 0 &&
    tmdbExtras.length === 0

  return (
    <div className="min-h-full" style={{ isolation: 'isolate' }}>
      {/* Sticky search input - fully opaque + isolation: isolate + a high
          z so cards (which may have transform/perspective stacking) cannot
          render on top during scroll. */}
      <div
        className="sticky top-0 border-b border-border px-7 pt-5 pb-4"
        style={{ zIndex: 50, backgroundColor: 'var(--color-void)' }}
      >
        <div className="relative max-w-2xl mx-auto">
          <Search size={18} className="absolute left-4 top-1/2 -translate-y-1/2 text-text-3" strokeWidth={2} />
          <input
            ref={inputRef}
            type="text"
            value={query}
            onChange={e => setQuery(e.target.value)}
            placeholder="Search movies, shows, episodes, people, music..."
            className="w-full h-12 pl-12 pr-12 bg-elevated/60 hover:bg-elevated rounded-xl text-[15px] text-text-1 placeholder:text-text-4 border border-border focus:outline-none focus:border-accent/60 focus:ring-2 focus:ring-accent/20 focus:bg-elevated transition-all duration-200"
            aria-label="Search"
          />
          <div className="absolute right-3.5 top-1/2 -translate-y-1/2 flex items-center gap-2">
            {isSearching && (
              <Loader2 size={14} className="text-text-3 animate-[spin-soft_1s_linear_infinite]" />
            )}
            {query && !isSearching && (
              <button
                onClick={() => { setQuery(''); inputRef.current?.focus() }}
                className="w-7 h-7 grid place-items-center rounded-md text-text-3 hover:text-text-1 hover:bg-glass-light transition-colors focus-ring"
                aria-label="Clear search"
              >
                <X size={14} />
              </button>
            )}
            <kbd className="hidden sm:inline-flex items-center px-1.5 h-5 rounded text-[10px] font-mono text-text-4 bg-void/60 border border-border">
              esc
            </kbd>
          </div>
        </div>
      </div>

      {/* Body */}
      <div className="px-7 py-8 max-w-[1600px] mx-auto">
        <AnimatePresence mode="wait">
          {!hasQuery && (
            <EmptyState
              key="empty"
              recents={recents}
              onPick={s => { setQuery(s); inputRef.current?.focus() }}
              onClearRecents={() => { clearRecent(); setRecents([]) }}
            />
          )}

          {hasQuery && isSearching && <LoadingState key="loading" />}

          {hasQuery && noResults && <NoResults key="noresults" query={query} />}

          {hasQuery && !isSearching && !noResults && (
            <motion.div
              key="results"
              initial={{ opacity: 0, y: 10 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0 }}
              transition={{ duration: 0.28, ease: [0.16, 1, 0.3, 1] }}
              className="space-y-12"
            >
              {tmdbPeople.length > 0 && <PeopleSection people={tmdbPeople} />}

              {mixedCards.length > 0 && (
                <MoviesShowsSection
                  cards={mixedCards}
                  localCount={localCards.length}
                  tmdbExtraCount={tmdbExtras.length}
                />
              )}

              {episodes.length > 0 && <EpisodesSection items={episodes} />}

              {(albums.length > 0 || artists.length > 0 || tracks.length > 0) && (
                <MusicSection albums={albums} artists={artists} tracks={tracks} />
              )}
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </div>
  )
}
