import { useMemo, useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
  useSeasons,
  useSeasonsEpisodes,
  useEpisodes,
} from '../../hooks/use-jellyfin'
import { useTmdbTvShow, useTmdbEpisodeGroup } from '../../hooks/use-tmdb'
import { useCinemeta } from '../../hooks/use-external'
import { buildCinemetaEpisodeMap } from '../../api/cinemeta'
import { EpisodeMetaProvider } from '../../lib/episode-meta-context'
import { parseImdbRating } from '../../lib/episode-meta'
import { useAnimeFiller, classifyEpisode } from '../../lib/anime-filler'
import type { FillerFlag } from '../../lib/anime-filler'
import { applyEpisodeGroupOrder } from '../../lib/episode-order'
import { usePreferencesStore } from '../../stores/preferences-store'
import { formatRuntime } from '../../lib/format'
import { getImageUrl, getStoredServerUrl } from '../../api/jellyfin'
import type { BaseItemDto } from '../../api/types'
import SeasonProgressSparkline from './SeasonProgressSparkline'
import EpisodeOrderToggle from './EpisodeOrderToggle'
import TimeSavedBadge from './TimeSavedBadge'
import EpisodeRow from './EpisodeRow'
import { Section } from '../ui/SectionLabel'

type FillerOf = (
  season: number | null | undefined,
  episode: number | null | undefined,
) => FillerFlag

interface Props {
  seriesId: string
  imdbId: string | null
  tmdbId: number | null
}

export default function SeriesSection({ seriesId, imdbId, tmdbId }: Props) {
  const [selectedSeason, setSelectedSeason] = useState<string | null>(null)
  const { data: seasonsRaw } = useSeasons(seriesId)
  const showSparklines = usePreferencesStore(s => s.episode.show.sparklines)
  // Episode-group ordering. The selected id is either 'aired' (Jellyfin
  // sequence, season-scoped) or a TMDB episode-group id (cross-season,
  // reordered). Persisted per-series in localStorage so users keep the
  // ordering they prefer for each show without polluting global prefs.
  const orderStorageKey = `episodeOrder:${seriesId}`
  const [orderId, setOrderId] = useState<string>(() => {
    if (typeof window === 'undefined') return 'aired'
    return localStorage.getItem(orderStorageKey) || 'aired'
  })
  const handleOrderChange = (id: string) => {
    setOrderId(id)
    try { localStorage.setItem(orderStorageKey, id) } catch { /* noop */ }
  }
  const tmdbTv = useTmdbTvShow(tmdbId)
  const episodeGroups = tmdbTv.data?.episode_groups?.results || []
  const groupQuery = useTmdbEpisodeGroup(orderId !== 'aired' ? orderId : null)
  // Fetch Cinemeta for the show so we can pull per-episode IMDB ratings.
  // Same call DetailPage already makes - React Query dedups it.
  const { data: cinemetaSeriesData } = useCinemeta(imdbId, 'series')
  const cinemetaMap = useMemo(
    () => buildCinemetaEpisodeMap(cinemetaSeriesData),
    [cinemetaSeriesData],
  )
  // Anime filler classification (only matches when the show is in the
  // bundled list, otherwise always returns null).
  const fillerData = useAnimeFiller(tmdbId)

  // Specials (IndexNumber 0) get pushed to the end so the tabs open on Season 1.
  const seasons = useMemo(() => {
    if (!seasonsRaw) return seasonsRaw
    return [...seasonsRaw].sort((a, b) => {
      const ai = a.IndexNumber ?? 0
      const bi = b.IndexNumber ?? 0
      const aSpecial = ai === 0 ? 1 : 0
      const bSpecial = bi === 0 ? 1 : 0
      if (aSpecial !== bSpecial) return aSpecial - bSpecial
      return ai - bi
    })
  }, [seasonsRaw])

  const firstSeason = seasons?.[0]?.Id
  const activeSeason = selectedSeason || firstSeason || null
  const serverUrl = getStoredServerUrl()

  // Per-season episode fetches feed the progress sparkline on each tab.
  // Skipped on huge shows (>20 seasons) where the request burst isn't worth it.
  const seasonIds = useMemo(
    () => (seasons && seasons.length <= 20 ? seasons.map(s => s.Id!).filter(Boolean) : []),
    [seasons],
  )
  const seasonEpisodeQueries = useSeasonsEpisodes(seriesId, seasonIds)
  const episodesBySeason = useMemo(() => {
    const m = new Map<string, BaseItemDto[]>()
    seasonIds.forEach((id, i) => {
      const data = seasonEpisodeQueries[i]?.data
      if (data) m.set(id, data)
    })
    return m
  }, [seasonIds, seasonEpisodeQueries])

  // When a non-aired group is selected, fetch all episodes across seasons
  // so the group can reorder freely. Otherwise stay scoped to the active
  // season for performance.
  const episodesScope = orderId === 'aired' ? activeSeason || undefined : undefined
  const { data: episodes } = useEpisodes(seriesId, episodesScope)

  const sortMode = usePreferencesStore(s => s.episode.behavior.sortMode)
  const setEpisodeBehavior = usePreferencesStore(s => s.setEpisodeBehavior)

  // Map a (season, episode) pair to its absolute episode number, summing
  // ChildCount across earlier seasons. Filler lists count this way.
  const seasonStarts = useMemo(() => {
    const m = new Map<number, number>()
    if (!seasons) return m
    let running = 0
    // Specials (season 0) don't add to the canon count
    const ordered = seasons.filter(s => (s.IndexNumber ?? 0) > 0)
    for (const s of ordered) {
      const idx = s.IndexNumber ?? 0
      m.set(idx, running)
      running += s.ChildCount ?? 0
    }
    return m
  }, [seasons])

  const fillerOf = useMemo<FillerOf>(() => {
    return (season, episode) => {
      if (!fillerData || season == null || episode == null) return null
      const start = seasonStarts.get(season)
      if (start == null) return null // probably specials
      return classifyEpisode(start + episode, fillerData)
    }
  }, [fillerData, seasonStarts])

  // Apply group reorder first (if a non-aired group is active), then any
  // user sort. When sorting by rating we look up each episode's IMDB
  // score in the cinemeta map; episodes without a rating sort to the end
  // regardless of direction so unrated entries don't pollute the top.
  const orderedEpisodes = useMemo(() => {
    if (!episodes) return episodes
    const reordered = orderId !== 'aired' && groupQuery.data
      ? applyEpisodeGroupOrder(episodes, groupQuery.data)
      : episodes
    if (sortMode === 'order') return reordered
    const direction = sortMode === 'rating-desc' ? -1 : 1
    return [...reordered].sort((a, b) => {
      const ar = parseImdbRating(
        cinemetaMap.get(`${a.ParentIndexNumber ?? -1}:${a.IndexNumber ?? -1}`)?.imdbRating,
      )
      const br = parseImdbRating(
        cinemetaMap.get(`${b.ParentIndexNumber ?? -1}:${b.IndexNumber ?? -1}`)?.imdbRating,
      )
      if (ar == null && br == null) return (a.IndexNumber ?? 0) - (b.IndexNumber ?? 0)
      if (ar == null) return 1
      if (br == null) return -1
      return (ar - br) * direction
    })
  }, [episodes, sortMode, cinemetaMap, orderId, groupQuery.data])

  return (
    <EpisodeMetaProvider cinemetaMap={cinemetaMap} fillerOf={fillerOf}>
    <Section label="Episodes">
      <div className="flex items-center gap-3 mb-4 -mx-1 px-1">
        {orderId === 'aired' && seasons && seasons.length > 1 ? (
          <div className="flex-1 flex gap-1.5 overflow-x-auto hide-scrollbar">
            {seasons.map(s => {
              const isActive = activeSeason === s.Id
              return (
                <button
                  key={s.Id}
                  onClick={() => setSelectedSeason(s.Id!)}
                  className={`relative flex items-center gap-2 pr-3.5 h-9 text-[12px] font-medium rounded-md transition-all duration-200 whitespace-nowrap focus-ring ${
                    isActive ? 'text-accent' : 'text-text-3 hover:text-text-1'
                  }`}
                >
                  {isActive && (
                    <motion.span
                      layoutId="season-active"
                      className="absolute inset-0 bg-accent/15 border border-accent/25 rounded-md"
                      transition={{ type: 'spring', stiffness: 380, damping: 30 }}
                    />
                  )}
                  {s.ImageTags?.Primary && s.Id && (
                    <img
                      src={getImageUrl(serverUrl, s.Id, 'Primary', 64, s.ImageTags.Primary)}
                      alt=""
                      className="relative z-10 w-7 h-[42px] rounded-l-md object-cover shrink-0"
                      loading="lazy"
                    />
                  )}
                  <span className="relative inline-flex items-center">
                    {s.Name}
                    {showSparklines && (
                      <SeasonProgressSparkline episodes={episodesBySeason.get(s.Id!)} />
                    )}
                  </span>
                </button>
              )
            })}
          </div>
        ) : (
          <div className="flex-1" />
        )}
        {episodeGroups.length > 0 && (
          <EpisodeOrderToggle
            groups={episodeGroups}
            selectedId={orderId}
            onChange={handleOrderChange}
          />
        )}
        {cinemetaMap.size > 0 && (
          <div className="flex p-0.5 bg-void/60 rounded-md border border-border shrink-0">
            {([
              { value: 'order', label: 'Order' },
              { value: 'rating-desc', label: 'Best' },
              { value: 'rating-asc', label: 'Worst' },
            ] as const).map(opt => {
              const active = sortMode === opt.value
              return (
                <button
                  key={opt.value}
                  onClick={() => setEpisodeBehavior('sortMode', opt.value)}
                  className={`h-7 px-2.5 text-[11px] font-medium tracking-tight rounded transition-colors duration-150 focus-ring ${
                    active ? 'bg-accent text-void' : 'text-text-3 hover:text-text-1'
                  }`}
                >
                  {opt.label}
                </button>
              )
            })}
          </div>
        )}
      </div>

      {/* Season totals + time-saved badge */}
      <div className="flex items-center gap-3 mb-3 -mx-1 px-1 text-[11.5px] text-text-3">
        {(() => {
          const list = orderId === 'aired' && activeSeason
            ? episodesBySeason.get(activeSeason) || episodes || []
            : episodes || []
          const total = list.reduce((acc, ep) => acc + Number(ep.RunTimeTicks ?? 0), 0)
          const count = list.length
          if (count === 0) return null
          const runtimeLabel = total > 0 ? formatRuntime(total) : null
          return (
            <span className="tracking-tight">
              <span className="tabular-nums text-text-2 font-medium">{count}</span>
              <span className="text-text-4"> {count === 1 ? 'episode' : 'episodes'}</span>
              {runtimeLabel && (
                <>
                  <span className="text-text-5 mx-1.5">·</span>
                  <span className="tabular-nums text-text-2 font-medium">{runtimeLabel}</span>
                  <span className="text-text-4"> total</span>
                </>
              )}
            </span>
          )
        })()}
        <div className="flex-1" />
        <TimeSavedBadge seriesId={seriesId} />
      </div>

      <AnimatePresence mode="wait">
        <motion.div
          key={activeSeason || 'no-season'}
          initial={{ opacity: 0, y: 4 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -4 }}
          transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
          className="space-y-1"
        >
          {orderedEpisodes?.map((ep, i) => (
            <EpisodeRow key={ep.Id} episode={ep} index={i} />
          ))}
        </motion.div>
      </AnimatePresence>
    </Section>
    </EpisodeMetaProvider>
  )
}


