import { useEffect, useState } from 'react'
import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/react-query'
import { jellyfinClient, getItemsApi, getTvShowsApi, getUserViewsApi, getSearchApi, getLibraryApi, getItemRefreshApi, getMediaInfoApi, getPlaylistsApi, getPlaystateApi, getUserLibraryApi } from '../api/jellyfin'
import { getLiveTvApi } from '@jellyfin/sdk/lib/utils/api/live-tv-api'
import { debugLog } from '../lib/log'
import { browserDeviceProfile } from '../lib/device-profile'
import { usePreferencesStore } from '../stores/preferences-store'

function useApi() {
  return jellyfinClient.getApi()
}

export function useHomeData() {
  const api = useApi()

  const continueWatching = useQuery({
    queryKey: ['jellyfin', 'home', 'continueWatching'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      debugLog('[JF] Fetching continueWatching...')
      const res = await getItemsApi(api).getResumeItems({
        userId: jellyfinClient.getAuthState()!.userId,
        limit: 12,
        fields: ['PrimaryImageAspectRatio', 'Overview', 'ChildCount', 'Genres'],
        enableImageTypes: ['Primary', 'Backdrop', 'Logo', 'Thumb'],
      })
      debugLog('[JF] continueWatching result:', res.data)
      return res.data.Items || []
    },
    enabled: !!api,
  })

  const nextUp = useQuery({
    queryKey: ['jellyfin', 'home', 'nextUp'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      debugLog('[JF] Fetching nextUp...')
      const res = await getTvShowsApi(api).getNextUp({
        userId: jellyfinClient.getAuthState()!.userId,
        limit: 12,
        fields: ['PrimaryImageAspectRatio', 'Overview', 'ChildCount', 'Genres'],
        enableImageTypes: ['Primary', 'Backdrop', 'Logo', 'Thumb'],
      })
      debugLog('[JF] nextUp result:', res.data)
      return res.data.Items || []
    },
    enabled: !!api,
  })

  const recentlyAdded = useQuery({
    queryKey: ['jellyfin', 'home', 'recentlyAdded'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        sortBy: ['DateCreated'],
        sortOrder: ['Descending'],
        limit: 20,
        recursive: true,
        includeItemTypes: ['Movie', 'Series'],
        fields: ['PrimaryImageAspectRatio', 'Overview', 'Genres'],
        enableImageTypes: ['Primary', 'Backdrop', 'Logo', 'Thumb'],
      } as any)
      return res.data.Items || []
    },
    enabled: !!api,
  })

  return { continueWatching, nextUp, recentlyAdded }
}

export function useLibraries() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'libraries'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getUserViewsApi(api).getUserViews({
        userId: jellyfinClient.getAuthState()!.userId,
      })
      return res.data.Items || []
    },
    enabled: !!api,
  })
}

export function useLibraryItems(
  parentId?: string,
  opts?: {
    sortBy?: string[]
    sortOrder?: string[]
    includeItemTypes?: string[]
    filters?: string[]
    genres?: string[]
    years?: number[]
    minCommunityRating?: number
    startIndex?: number
    limit?: number
    enabled?: boolean
    includePeople?: boolean
    recursive?: boolean
  },
) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'library', parentId, opts],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      debugLog('[JF] Fetching library items:', { parentId, opts, userId: jellyfinClient.getAuthState()!.userId })
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        parentId,
        sortBy: opts?.sortBy || ['SortName'],
        sortOrder: opts?.sortOrder || ['Ascending'],
        includeItemTypes: opts?.includeItemTypes,
        filters: opts?.filters,
        genres: opts?.genres,
        years: opts?.years,
        minCommunityRating: opts?.minCommunityRating,
        startIndex: opts?.startIndex,
        limit: opts?.limit || 100,
        recursive: opts?.recursive ?? true,
        fields: [
          'PrimaryImageAspectRatio',
          'Overview',
          'ChildCount',
          'RecursiveItemCount',
          'MediaSources',
          'MediaStreams',
          'DateCreated',
          'Genres',
          'Studios',
          'Tags',
          'ProviderIds',
          'Width',
          'Height',
          'RunTimeTicks',
          ...(opts?.includePeople ? (['People'] as const) : []),
        ],
      } as any)
      debugLog('[JF] Library items result:', res.data)
      return res.data
    },
    enabled: !!api && (opts?.enabled ?? true),
  })
}

/**
 * Bulk-fetch a set of Jellyfin items by their ids in one request. Used
 * by the Profile page's top-picks list which needs items keyed off
 * personal-rating ids (not necessarily played items).
 */
export function useItemsByIds(itemIds: string[]) {
  const api = useApi()
  const sortedIds = [...itemIds].sort()
  return useQuery({
    queryKey: ['jellyfin', 'items-by-ids', sortedIds],
    queryFn: async () => {
      if (!api || itemIds.length === 0) return []
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        ids: itemIds,
        fields: ['PrimaryImageAspectRatio', 'ProviderIds', 'Genres'],
      } as any)
      return res.data.Items || []
    },
    enabled: !!api && itemIds.length > 0,
    staleTime: 5 * 60 * 1000,
  })
}

export function useItemDetails(itemId?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'item', itemId],
    queryFn: async () => {
      if (!api || !itemId) throw new Error('Missing params')
      debugLog('[JF] Fetching item details:', itemId)
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        ids: [itemId],
        fields: [
          'PrimaryImageAspectRatio',
          'Overview',
          'ChildCount',
          'ProviderIds',
          'MediaSources',
          'MediaStreams',
          'Chapters',
          'Trickplay',
          'People',
          'Studios',
          'Tags',
          'Taglines',
          'Genres',
          'RemoteTrailers',
          'ExternalUrls',
          'DateCreated',
          'DateLastMediaAdded',
          'Width',
          'Height',
          'Path',
        ],
        enableImageTypes: ['Primary', 'Backdrop', 'Logo', 'Thumb', 'Banner', 'Disc', 'Art'],
      } as any)
      debugLog('[JF] Item details result:', res.data)
      return res.data.Items?.[0] || null
    },
    enabled: !!api && !!itemId,
  })
}

/**
 * Move a playlist entry to a new index. Optimistic - PlaylistView updates
 * its local copy immediately, and we just invalidate on settle so the next
 * read picks up the server-confirmed order.
 */
export function usePlaylistMove(playlistId?: string) {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async ({ playlistItemId, newIndex }: { playlistItemId: string; newIndex: number }) => {
      if (!api || !playlistId) throw new Error('Not ready')
      await getPlaylistsApi(api).moveItem({ playlistId, itemId: playlistItemId, newIndex })
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ['jellyfin', 'playlist-items', playlistId] })
    },
  })
}

/**
 * Find or lazily create a user-private playlist with the given name.
 * Used by the watchlist feature to keep state on the server (so it
 * survives device changes) without burning a separate API.
 */
export function useNamedPlaylist(name: string) {
  const api = useApi()
  const qc = useQueryClient()
  return useQuery({
    queryKey: ['jellyfin', 'named-playlist', name],
    queryFn: async () => {
      if (!api) return null
      const userId = jellyfinClient.getAuthState()!.userId
      // Look for an existing playlist owned by this user with the exact name.
      const list = await getItemsApi(api).getItems({
        userId,
        recursive: true,
        includeItemTypes: ['Playlist'],
        fields: ['ChildCount'],
      } as any)
      const found = (list.data.Items || []).find(
        p => (p.Name || '').toLowerCase() === name.toLowerCase(),
      )
      if (found?.Id) return found
      // Create a new empty playlist. MediaType "Mixed" is required by
      // some Jellyfin server versions; without it the server defaults
      // to a music playlist that silently rejects movie/series adds.
      const create = await getPlaylistsApi(api).createPlaylist({
        createPlaylistDto: { Name: name, UserId: userId, Ids: [], MediaType: 'Mixed' as any },
      } as any)
      const newId = create.data.Id
      if (!newId) return null
      qc.invalidateQueries({ queryKey: ['jellyfin', 'named-playlist', name] })
      const fresh = await getItemsApi(api).getItems({
        userId,
        ids: [newId],
        fields: ['ChildCount'],
      } as any)
      return fresh.data.Items?.[0] || null
    },
    enabled: !!api,
    staleTime: 60 * 1000,
  })
}

/** Add one or more item ids to a playlist. */
export function usePlaylistAdd(playlistId?: string) {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async (itemIds: string[]) => {
      if (!api || !playlistId || itemIds.length === 0) return
      await getPlaylistsApi(api).addItemToPlaylist({
        playlistId,
        ids: itemIds,
        userId: jellyfinClient.getAuthState()!.userId,
      })
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ['jellyfin', 'playlist-items', playlistId] })
    },
  })
}

/** Remove one or more entries from a playlist by their PlaylistItemId values. */
export function usePlaylistRemove(playlistId?: string) {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async (entryIds: string[]) => {
      if (!api || !playlistId || entryIds.length === 0) return
      await getPlaylistsApi(api).removeItemFromPlaylist({ playlistId, entryIds })
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ['jellyfin', 'playlist-items', playlistId] })
    },
  })
}

/**
 * Bulk mark items as played or unplayed. Used by the playlist selection
 * toolbar - hits the playstate endpoint once per item.
 */
export function useBulkMarkPlayed() {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async ({ itemIds, played }: { itemIds: string[]; played: boolean }) => {
      if (!api || itemIds.length === 0) return
      const userId = jellyfinClient.getAuthState()!.userId
      await Promise.all(
        itemIds.map(id =>
          played
            ? getPlaystateApi(api).markPlayedItem({ userId, itemId: id })
            : getPlaystateApi(api).markUnplayedItem({ userId, itemId: id }),
        ),
      )
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ['jellyfin'] })
    },
  })
}

/**
 * Toggle favorite state for one or more items. Used both by the playlist
 * heart button (single item) and the selection toolbar (bulk).
 */
export function useBulkToggleFavorite() {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async ({ itemIds, favorite }: { itemIds: string[]; favorite: boolean }) => {
      if (!api || itemIds.length === 0) return
      const userId = jellyfinClient.getAuthState()!.userId
      await Promise.all(
        itemIds.map(id =>
          favorite
            ? getUserLibraryApi(api).markFavoriteItem({ userId, itemId: id })
            : getUserLibraryApi(api).unmarkFavoriteItem({ userId, itemId: id }),
        ),
      )
    },
    onSettled: () => {
      qc.invalidateQueries({ queryKey: ['jellyfin'] })
    },
  })
}

/**
 * Items inside a Playlist. Playlist contents come from a different endpoint
 * than regular folders (it preserves user-set order; regular `getItems` with
 * a parentId would re-order).
 */
export function usePlaylistItems(playlistId?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'playlist-items', playlistId],
    queryFn: async () => {
      if (!api || !playlistId) return []
      const res = await getPlaylistsApi(api).getPlaylistItems({
        playlistId,
        userId: jellyfinClient.getAuthState()!.userId,
        fields: [
          'PrimaryImageAspectRatio',
          'Overview',
          'MediaSources',
          'MediaStreams',
          'Genres',
          'ProviderIds',
          'Width',
          'Height',
        ],
        enableImageTypes: ['Primary', 'Backdrop', 'Thumb'],
      } as any)
      return res.data.Items || []
    },
    enabled: !!api && !!playlistId,
  })
}

export function useSeasons(seriesId?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'seasons', seriesId],
    queryFn: async () => {
      if (!api || !seriesId) throw new Error('Missing params')
      debugLog('[JF] Fetching seasons:', seriesId)
      const res = await getTvShowsApi(api).getSeasons({
        seriesId,
        userId: jellyfinClient.getAuthState()!.userId,
        fields: ['PrimaryImageAspectRatio', 'Overview', 'ChildCount'],
      })
      debugLog('[JF] Seasons result:', res.data)
      return res.data.Items || []
    },
    enabled: !!api && !!seriesId,
  })
}

export function useEpisodes(seriesId?: string, seasonId?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'episodes', seriesId, seasonId],
    queryFn: async () => {
      if (!api || !seriesId) throw new Error('Missing params')
      debugLog('[JF] Fetching episodes:', { seriesId, seasonId })
      const res = await getTvShowsApi(api).getEpisodes({
        seriesId,
        userId: jellyfinClient.getAuthState()!.userId,
        seasonId,
        fields: [
          'PrimaryImageAspectRatio',
          'Overview',
          'MediaSources',
          'MediaStreams',
          'Chapters',
          'ProviderIds',
          'Width',
          'Height',
        ],
        enableImageTypes: ['Primary', 'Thumb'],
      } as any)
      debugLog('[JF] Episodes result:', res.data)
      return res.data.Items || []
    },
    enabled: !!api && !!seriesId,
  })
}

/**
 * Fetch episodes for several seasons in parallel. Used by per-season UI
 * (sparklines, recap detection) that needs progress data without waiting
 * for the user to click each season.
 */
export function useSeasonsEpisodes(seriesId: string | undefined, seasonIds: string[]) {
  const api = useApi()
  const queries = useQueries({
    queries: seasonIds.map(seasonId => ({
      queryKey: ['jellyfin', 'episodes', seriesId, seasonId],
      queryFn: async () => {
        if (!api || !seriesId) throw new Error('Missing params')
        const res = await getTvShowsApi(api).getEpisodes({
          seriesId,
          userId: jellyfinClient.getAuthState()!.userId,
          seasonId,
          fields: ['PrimaryImageAspectRatio', 'Overview', 'ProviderIds'],
          enableImageTypes: ['Primary', 'Thumb'],
        } as any)
        return res.data.Items || []
      },
      enabled: !!api && !!seriesId,
    })),
  })
  return queries
}

export function useSearch(query: string) {
  const api = useApi()
  // Debounce the query so we don't fire a request on every keystroke. 250ms
  // is the sweet spot - feels responsive while collapsing rapid typing.
  const [debouncedQuery, setDebouncedQuery] = useState(query)
  useEffect(() => {
    const t = setTimeout(() => setDebouncedQuery(query), 250)
    return () => clearTimeout(t)
  }, [query])
  return useQuery({
    queryKey: ['jellyfin', 'search', debouncedQuery],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getSearchApi(api).getSearchHints({
        userId: jellyfinClient.getAuthState()!.userId,
        searchTerm: debouncedQuery,
        includeItemTypes: ['Movie', 'Series', 'Episode', 'Audio', 'MusicAlbum', 'MusicArtist'],
        limit: 20,
      })
      return res.data.SearchHints || []
    },
    enabled: !!api && debouncedQuery.length > 0,
  })
}

/**
 * Pull the entire searchable library catalog once and cache it for 12h.
 * Returns Movies + Series + MusicAlbum + MusicArtist with full ImageTags
 * and ProviderIds so Fuse.js can rerank and PosterCard can render
 * covers. Capped at 5000 items per type to keep the payload bounded on
 * very large libraries.
 */
export function useFullLibraryCatalog() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'full-catalog'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const userId = jellyfinClient.getAuthState()!.userId
      const fields = [
        'ProviderIds',
        'Overview',
        'PrimaryImageAspectRatio',
        'CommunityRating',
        'OriginalTitle',
        'PremiereDate',
      ] as any
      const res = await getItemsApi(api).getItems({
        userId,
        recursive: true,
        includeItemTypes: ['Movie', 'Series', 'MusicAlbum', 'MusicArtist'] as any,
        sortBy: ['SortName'] as any,
        fields,
        limit: 5000,
      } as any)
      return res.data.Items || []
    },
    enabled: !!api,
    staleTime: 12 * 60 * 60 * 1000,
    gcTime: 24 * 60 * 60 * 1000,
  })
}

/**
 * Server-side keyword search for high-cardinality types (Episodes,
 * Audio tracks) where pre-fetching everything is impractical. Returns
 * full BaseItemDto objects (not lightweight SearchHints) so we get
 * ImageTags + ProviderIds straight away.
 */
export function useEpisodeAndTrackSearch(query: string) {
  const api = useApi()
  const [debounced, setDebounced] = useState(query)
  useEffect(() => {
    const t = setTimeout(() => setDebounced(query), 220)
    return () => clearTimeout(t)
  }, [query])
  return useQuery({
    queryKey: ['jellyfin', 'kw-search', debounced],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const userId = jellyfinClient.getAuthState()!.userId
      const fields = [
        'ProviderIds',
        'Overview',
        'PrimaryImageAspectRatio',
        'SeriesPrimaryImageTag',
        'ParentBackdropImageTags',
        'PremiereDate',
        'CommunityRating',
      ] as any
      const res = await getItemsApi(api).getItems({
        userId,
        recursive: true,
        searchTerm: debounced,
        includeItemTypes: ['Episode', 'Audio'] as any,
        fields,
        limit: 50,
      } as any)
      return res.data.Items || []
    },
    enabled: !!api && debounced.trim().length >= 2,
  })
}

export function useNextUpForSeries(seriesId?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'nextUp-series', seriesId],
    queryFn: async () => {
      if (!api || !seriesId) return null
      const res = await getTvShowsApi(api).getNextUp({
        userId: jellyfinClient.getAuthState()!.userId,
        seriesId,
        limit: 1,
        fields: ['PrimaryImageAspectRatio', 'Overview', 'MediaSources', 'MediaStreams'],
      } as any)
      return res.data.Items?.[0] || null
    },
    enabled: !!api && !!seriesId,
  })
}

export function useSimilarItems(itemId?: string, limit = 12) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'similar', itemId, limit],
    queryFn: async () => {
      if (!api || !itemId) return []
      try {
        const res = await getLibraryApi(api).getSimilarItems({
          itemId,
          userId: jellyfinClient.getAuthState()!.userId,
          limit,
          fields: ['PrimaryImageAspectRatio', 'Overview', 'Genres', 'ProviderIds'],
        } as any)
        return res.data.Items || []
      } catch {
        return []
      }
    },
    enabled: !!api && !!itemId,
  })
}

/**
 * Establishes a server-side playback session for an item and returns the
 * resolved MediaSources with proper TranscodingUrl + PlaySessionId. The
 * server uses our DeviceProfile to decide direct-play vs HLS transcoding.
 * Without this call, the bare /main.m3u8 endpoint generates a playlist with
 * runtimeTicks=0, which makes segment fetches fail with 400.
 *
 * `enabled` lets callers gate the call on the saved resume position
 * being loaded: if a user lands on /play/:id?resume=true, the item
 * has to load first so the first PlaybackInfo request includes the
 * saved StartTimeTicks - otherwise the video streams from 0 and
 * reloads mid-playback when the second query returns.
 *
 * `requireDirectStream` is used for resume: the direct-play
 * /Videos/{id}/stream endpoint with `static=true` returns the file
 * from byte 0 even when StartTimeTicks is passed, because Chromium
 * / WebView2 don't send a Range header for the initial GET. The
 * direct-stream path has the server read the file from the start
 * position itself and stream the bytes, so the response naturally
 * starts at the saved offset. Direct-stream is not transcoding -
 * it's just server-side file I/O with no encoding work.
 */
export function usePlaybackInfo(
  itemId?: string,
  startTimeTicks?: number,
  audioStreamIndex?: number,
  maxStreamingBitrate?: number,
  enabled: boolean = true,
  requireDirectStream: boolean = false,
) {
  const api = useApi()
  const audioPassthrough = usePreferencesStore(s => s.audioPassthrough)
  return useQuery({
    queryKey: [
      'jellyfin',
      'playback-info',
      itemId,
      startTimeTicks,
      audioStreamIndex,
      maxStreamingBitrate,
      requireDirectStream,
    ],
    queryFn: async () => {
      if (!api || !itemId) return null
      const res = await getMediaInfoApi(api).getPostedPlaybackInfo({
        itemId,
        playbackInfoDto: {
          UserId: jellyfinClient.getAuthState()!.userId,
          // 0 = "no cap, prefer Original" - server will pick direct-stream
          // when source matches profile. Otherwise honour the user pick.
          MaxStreamingBitrate: maxStreamingBitrate || 140_000_000,
          StartTimeTicks: startTimeTicks,
          // Server-side audio track selection. When undefined the server
          // picks the default; when set, the returned TranscodingUrl muxes
          // that audio track into the stream.
          AudioStreamIndex: audioStreamIndex,
          DeviceProfile: await browserDeviceProfile(audioPassthrough) as any,
          AutoOpenLiveStream: true,
          // For resume, prefer direct-stream so the server starts the
          // read at StartTimeTicks. Direct-play can't honour start
          // time on the initial GET. Otherwise let the server pick
          // direct-play for the no-resume case so the browser plays
          // the file as-is.
          EnableDirectPlay: !requireDirectStream,
          EnableDirectStream: true,
          EnableTranscoding: true,
          AllowVideoStreamCopy: true,
          AllowAudioStreamCopy: true,
        },
      } as any)
      return res.data
    },
    enabled: !!api && !!itemId && enabled,
    staleTime: 0,
    // Override the global 30-minute gcTime: each PlaybackInfoResponse
    // includes full MediaSources with encoding params, audio/video
    // streams, container-level metadata, etc. Bingeing through 20
    // episodes would otherwise keep ~20 of these objects in memory
    // for half an hour. 60s is plenty for "user clicked play, the
    // stream URL came back" and avoids the buildup.
    gcTime: 60 * 1000,
  })
}

export { useMediaSegments } from './jellyfin/use-media-segments'

/**
 * Returns one representative episode for a series so we can show tech specs
 * (resolution / codec / audio) on a series detail page, since series-level
 * items have no MediaSources of their own.
 */
export function useSampleEpisode(seriesId?: string, enabled = true) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'sample-episode', seriesId],
    queryFn: async () => {
      if (!api || !seriesId) return null
      const res = await getTvShowsApi(api).getEpisodes({
        seriesId,
        userId: jellyfinClient.getAuthState()!.userId,
        limit: 1,
        startIndex: 0,
        fields: ['MediaSources', 'MediaStreams', 'Width', 'Height', 'Path'],
      } as any)
      return res.data.Items?.[0] || null
    },
    enabled: !!api && !!seriesId && enabled,
    staleTime: 5 * 60 * 1000,
  })
}

/**
 * One-time fetch of every movie + series in the user's library, indexed by TMDB id.
 * Used to highlight which TMDB recommendations the user already owns.
 */
/**
 * Lightweight genre tally across the user's Movie + Series library.
 * Used by the Discover "library gap finder" to spot underrepresented
 * categories. Limited to 1500 items - enough to be representative on
 * even big libraries without paying for the whole catalog.
 */
export function useLibraryGenreDistribution() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'lib-genre-distribution'],
    queryFn: async () => {
      if (!api) return null
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        recursive: true,
        includeItemTypes: ['Movie', 'Series'],
        fields: ['Genres'],
        limit: 1500,
      } as any)
      const counts = new Map<string, number>()
      const items = res.data.Items || []
      for (const item of items) {
        for (const g of item.Genres || []) {
          counts.set(g, (counts.get(g) || 0) + 1)
        }
      }
      return { counts, total: items.length }
    },
    enabled: !!api,
    staleTime: 30 * 60 * 1000,
  })
}

export function useLibraryByTmdbId() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'library-by-tmdb-id'],
    queryFn: async () => {
      if (!api) return new Map<string, { id: string; name: string; type: string; played: boolean }>()
      // Cap at 3000 to bound the size of this single big request - the
      // map drives in-library indicators across the whole app, so the
      // round-trip cost matters. Libraries beyond this size will see
      // some out-of-library cards mis-marked as missing; acceptable
      // until we wire pagination.
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        recursive: true,
        includeItemTypes: ['Movie', 'Series'],
        fields: ['ProviderIds'],
        limit: 3000,
      } as any)
      const map = new Map<string, { id: string; name: string; type: string; played: boolean }>()
      for (const item of res.data.Items || []) {
        const tmdb = item.ProviderIds?.Tmdb
        if (tmdb && item.Id) {
          map.set(String(tmdb), {
            id: item.Id,
            name: item.Name || '',
            type: item.Type || 'Movie',
            played: !!item.UserData?.Played,
          })
        }
      }
      return map
    },
    enabled: !!api,
    staleTime: 5 * 60 * 1000,
  })
}

/**
 * Triggers a metadata + image refresh on a specific Jellyfin item.
 */
export function useRefreshItem() {
  const api = useApi()
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async (params: {
      itemId: string
      replaceAllMetadata?: boolean
      replaceAllImages?: boolean
    }) => {
      if (!api) throw new Error('Not authenticated')
      const { itemId, replaceAllMetadata = false, replaceAllImages = false } = params
      await getItemRefreshApi(api).refreshItem({
        itemId,
        metadataRefreshMode: 'FullRefresh' as any,
        imageRefreshMode: 'FullRefresh' as any,
        replaceAllMetadata,
        replaceAllImages,
      } as any)
      // Wait a beat then invalidate so the UI sees fresh data
      await new Promise(r => setTimeout(r, 1500))
    },
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['jellyfin', 'item', vars.itemId] })
      qc.invalidateQueries({ queryKey: ['jellyfin', 'episodes'] })
      qc.invalidateQueries({ queryKey: ['jellyfin', 'seasons'] })
    },
  })
}

export function useLiveTvInfo() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'live-tv', 'info'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getLiveTvApi(api).getLiveTvInfo()
      return res.data
    },
    enabled: !!api,
  })
}

export function useLiveTvChannels() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'live-tv', 'channels'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getLiveTvApi(api).getLiveTvChannels({
        userId: jellyfinClient.getAuthState()!.userId,
        addCurrentProgram: true,
        enableImages: true,
        enableUserData: true,
        limit: 500,
      })
      return res.data.Items || []
    },
    enabled: !!api,
  })
}

export function useLiveTvRecordings() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'live-tv', 'recordings'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getLiveTvApi(api).getRecordings({
        userId: jellyfinClient.getAuthState()!.userId,
        enableImages: true,
        enableUserData: true,
        limit: 200,
      })
      return res.data.Items || []
    },
    enabled: !!api,
  })
}

export function useLiveTvTimers() {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'live-tv', 'timers'],
    queryFn: async () => {
      if (!api) throw new Error('Not authenticated')
      const res = await getLiveTvApi(api).getTimers({ isScheduled: true })
      return res.data.Items || []
    },
    enabled: !!api,
  })
}

export function usePersonItems(personName?: string) {
  const api = useApi()
  return useQuery({
    queryKey: ['jellyfin', 'person-items', personName],
    queryFn: async () => {
      if (!api || !personName) return []
      const res = await getItemsApi(api).getItems({
        userId: jellyfinClient.getAuthState()!.userId,
        person: personName,
        sortBy: ['PremiereDate'],
        sortOrder: ['Descending'],
        recursive: true,
        includeItemTypes: ['Movie', 'Series'],
        limit: 60,
        fields: ['PrimaryImageAspectRatio', 'Overview', 'Genres', 'ProviderIds'],
      } as any)
      return res.data.Items || []
    },
    enabled: !!api && !!personName,
  })
}
