/**
 * React Query Hooks for API Caching
 *
 * This file provides custom React Query hooks for the most frequently called APIs
 * in the music streaming app. These hooks implement smart caching strategies to:
 * - Reduce unnecessary API calls
 * - Improve perceived performance
 * - Provide automatic background refetching
 * - Handle loading and error states consistently
 *
 * Stale time configuration rationale:
 * - Artist data: 10 minutes (rarely changes)
 * - Album data: 10 minutes (rarely changes)
 * - Library/Home data: 2 minutes (may change as user adds music)
 * - Search results: 5 minutes (relatively static for same query)
 * - Playlists: 1 minute (user may be actively modifying)
 */

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "@/lib/api";

// ============================================================================
// QUERY KEY FACTORIES
// ============================================================================
// These functions generate consistent query keys for caching
// See: https://tanstack.com/query/latest/docs/react/guides/query-keys

export const queryKeys = {
    // Artist queries
    artist: (id: string) => ["artist", id] as const,
    artistLibrary: (id: string) => ["artist", "library", id] as const,
    artistDiscovery: (id: string) => ["artist", "discovery", id] as const,

    // Album queries
    album: (id: string) => ["album", id] as const,
    albumLibrary: (id: string) => ["album", "library", id] as const,
    albumDiscovery: (id: string) => ["album", "discovery", id] as const,
    albums: (filters?: Record<string, any>) => ["albums", filters] as const,

    // Library queries
    library: () => ["library"] as const,
    recentlyListened: (limit?: number) => ["library", "recently-listened", limit] as const,
    recentlyAdded: (limit?: number) => ["library", "recently-added", limit] as const,

    // Recommendations
    recommendations: (limit?: number) => ["recommendations", limit] as const,
    similarArtists: (seedArtistId: string, limit?: number) => ["recommendations", "artists", seedArtistId, limit] as const,
    similarAlbums: (seedAlbumId: string, limit?: number) => ["recommendations", "albums", seedAlbumId, limit] as const,

    // Search
    search: (query: string, type?: string, limit?: number) => ["search", query, type, limit] as const,
    discoverSearch: (query: string, type?: string, limit?: number) => ["search", "discover", query, type, limit] as const,

    // Playlists
    playlists: () => ["playlists"] as const,
    playlist: (id: string) => ["playlist", id] as const,

    // Mixes
    mixes: () => ["mixes"] as const,
    mix: (id: string) => ["mix", id] as const,

    // Popular artists
    popularArtists: (limit?: number) => ["popular-artists", limit] as const,

    // Audiobooks
    audiobooks: () => ["audiobooks"] as const,
    audiobook: (id: string) => ["audiobook", id] as const,

    // Podcasts
    podcasts: () => ["podcasts"] as const,
    podcast: (id: string) => ["podcast", id] as const,
    topPodcasts: (limit?: number, genreId?: number) => ["podcasts", "top", limit, genreId] as const,

    // Browse (Deezer playlists/radios)
    browseAll: () => ["browse", "all"] as const,
    browseFeatured: (limit?: number) => ["browse", "featured", limit] as const,
    browseRadios: (limit?: number) => ["browse", "radios", limit] as const,
};

// ============================================================================
// ARTIST QUERIES
// ============================================================================

/**
 * Hook to fetch artist data with automatic library/discovery fallback
 *
 * Tries library first, falls back to discovery if not found.
 * Cache time: 10 minutes (artist data rarely changes)
 *
 * @param id - Artist ID or MusicBrainz ID
 * @returns Query result with artist data
 *
 * @example
 * const { data: artist, isLoading, error } = useArtistQuery("artist-123");
 */
export function useArtistQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.artist(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Artist ID is required");

            // Try library first
            try {
                return await api.getArtist(id);
            } catch (error) {
                // Fallback to discovery
                return await api.getArtistDiscovery(id);
            }
        },
        enabled: !!id,
        staleTime: 10 * 60 * 1000, // 10 minutes
        retry: 1,
    });
}

/**
 * Hook to fetch artist data from library only
 *
 * @param id - Artist ID
 * @returns Query result with artist data from library
 */
export function useArtistLibraryQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.artistLibrary(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Artist ID is required");
            return await api.getArtist(id);
        },
        enabled: !!id,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

/**
 * Hook to fetch artist data from discovery only
 *
 * @param id - Artist name or MusicBrainz ID
 * @returns Query result with artist data from Last.fm
 */
export function useArtistDiscoveryQuery(nameOrMbid: string | undefined) {
    return useQuery({
        queryKey: queryKeys.artistDiscovery(nameOrMbid || ""),
        queryFn: async () => {
            if (!nameOrMbid) throw new Error("Artist name or MBID is required");
            return await api.getArtistDiscovery(nameOrMbid);
        },
        enabled: !!nameOrMbid,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

// ============================================================================
// ALBUM QUERIES
// ============================================================================

/**
 * Hook to fetch album data with automatic library/discovery fallback
 *
 * Cache time: 10 minutes (album data rarely changes)
 *
 * @param id - Album ID or Release Group MBID
 * @returns Query result with album data
 *
 * @example
 * const { data: album, isLoading, error } = useAlbumQuery("album-123");
 */
export function useAlbumQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.album(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Album ID is required");

            // Try library first
            try {
                return await api.getAlbum(id);
            } catch (error) {
                // Fallback to discovery
                return await api.getAlbumDiscovery(id);
            }
        },
        enabled: !!id,
        staleTime: 10 * 60 * 1000, // 10 minutes
        retry: 1,
    });
}

/**
 * Hook to fetch album data from library only
 *
 * @param id - Album ID
 * @returns Query result with album data from library
 */
export function useAlbumLibraryQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.albumLibrary(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Album ID is required");
            return await api.getAlbum(id);
        },
        enabled: !!id,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

/**
 * Hook to fetch album data from discovery only
 *
 * @param rgMbid - Release Group MusicBrainz ID
 * @returns Query result with album data from Last.fm
 */
export function useAlbumDiscoveryQuery(rgMbid: string | undefined) {
    return useQuery({
        queryKey: queryKeys.albumDiscovery(rgMbid || ""),
        queryFn: async () => {
            if (!rgMbid) throw new Error("Album MBID is required");
            return await api.getAlbumDiscovery(rgMbid);
        },
        enabled: !!rgMbid,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

/**
 * Hook to fetch albums list with optional filters
 *
 * @param params - Filter parameters (artistId, limit, offset)
 * @returns Query result with albums array
 *
 * @example
 * const { data } = useAlbumsQuery({ artistId: "123", limit: 20 });
 */
export function useAlbumsQuery(params?: {
    artistId?: string;
    limit?: number;
    offset?: number;
}) {
    return useQuery({
        queryKey: queryKeys.albums(params),
        queryFn: () => api.getAlbums(params),
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

// ============================================================================
// LIBRARY QUERIES
// ============================================================================

/**
 * Hook to fetch recently listened items (Continue Listening)
 *
 * Cache time: 2 minutes (may change frequently)
 *
 * @param limit - Number of items to fetch (default: 10)
 * @returns Query result with recently listened items
 *
 * @example
 * const { data } = useRecentlyListenedQuery(10);
 */
export function useRecentlyListenedQuery(limit: number = 10) {
    return useQuery({
        queryKey: queryKeys.recentlyListened(limit),
        queryFn: () => api.getRecentlyListened(limit),
        staleTime: 2 * 60 * 1000, // 2 minutes
    });
}

/**
 * Hook to fetch recently added artists
 *
 * Cache time: 2 minutes (may change as user adds music)
 *
 * @param limit - Number of items to fetch (default: 10)
 * @returns Query result with recently added artists
 *
 * @example
 * const { data } = useRecentlyAddedQuery(10);
 */
export function useRecentlyAddedQuery(limit: number = 10) {
    return useQuery({
        queryKey: queryKeys.recentlyAdded(limit),
        queryFn: () => api.getRecentlyAdded(limit),
        staleTime: 2 * 60 * 1000, // 2 minutes
    });
}

// ============================================================================
// RECOMMENDATION QUERIES
// ============================================================================

/**
 * Hook to fetch personalized recommendations
 *
 * Cache time: 5 minutes
 *
 * @param limit - Number of recommendations (default: 10)
 * @returns Query result with recommended artists
 *
 * @example
 * const { data } = useRecommendationsQuery(10);
 */
export function useRecommendationsQuery(limit: number = 10) {
    return useQuery({
        queryKey: queryKeys.recommendations(limit),
        queryFn: () => api.getRecommendationsForYou(limit),
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

/**
 * Hook to fetch similar artists based on a seed artist
 *
 * @param seedArtistId - Artist ID to find similar artists for
 * @param limit - Number of recommendations (default: 20)
 * @returns Query result with similar artists
 *
 * @example
 * const { data } = useSimilarArtistsQuery("artist-123", 20);
 */
export function useSimilarArtistsQuery(seedArtistId: string | undefined, limit: number = 20) {
    return useQuery({
        queryKey: queryKeys.similarArtists(seedArtistId || "", limit),
        queryFn: async () => {
            if (!seedArtistId) throw new Error("Seed artist ID is required");
            return await api.getSimilarArtists(seedArtistId, limit);
        },
        enabled: !!seedArtistId,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

/**
 * Hook to fetch similar albums based on a seed album
 *
 * @param seedAlbumId - Album ID to find similar albums for
 * @param limit - Number of recommendations (default: 20)
 * @returns Query result with similar albums
 */
export function useSimilarAlbumsQuery(seedAlbumId: string | undefined, limit: number = 20) {
    return useQuery({
        queryKey: queryKeys.similarAlbums(seedAlbumId || "", limit),
        queryFn: async () => {
            if (!seedAlbumId) throw new Error("Seed album ID is required");
            return await api.getSimilarAlbums(seedAlbumId, limit);
        },
        enabled: !!seedAlbumId,
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

// ============================================================================
// SEARCH QUERIES
// ============================================================================

/**
 * Hook to search library with debouncing
 *
 * Cache time: 5 minutes (search results are relatively static)
 *
 * @param query - Search query string
 * @param type - Type filter (all, artists, albums, tracks, audiobooks, podcasts)
 * @param limit - Number of results per type (default: 20)
 * @returns Query result with search results
 *
 * @example
 * const { data } = useSearchQuery("radiohead", "all", 20);
 */
export function useSearchQuery(
    query: string,
    type: "all" | "artists" | "albums" | "tracks" | "audiobooks" | "podcasts" = "all",
    limit: number = 20
) {
    return useQuery({
        queryKey: queryKeys.search(query, type, limit),
        queryFn: () => api.search(query, type, limit),
        enabled: query.length >= 2, // Only search if query is at least 2 characters
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

/**
 * Hook to search discovery/Last.fm with debouncing
 *
 * @param query - Search query string
 * @param type - Type filter (music, podcasts, all)
 * @param limit - Number of results (default: 20)
 * @returns Query result with discovery search results
 *
 * @example
 * const { data } = useDiscoverSearchQuery("radiohead", "music", 20);
 */
export function useDiscoverSearchQuery(
    query: string,
    type: "music" | "podcasts" | "all" = "music",
    limit: number = 20
) {
    return useQuery({
        queryKey: queryKeys.discoverSearch(query, type, limit),
        queryFn: () => api.discoverSearch(query, type, limit),
        enabled: query.length >= 2,
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

// ============================================================================
// PLAYLIST QUERIES
// ============================================================================

/**
 * Hook to fetch all playlists
 *
 * Cache time: 1 minute (playlists may be actively modified)
 *
 * @returns Query result with playlists array
 *
 * @example
 * const { data: playlists } = usePlaylistsQuery();
 */
export function usePlaylistsQuery() {
    return useQuery({
        queryKey: queryKeys.playlists(),
        queryFn: () => api.getPlaylists(),
        staleTime: 1 * 60 * 1000, // 1 minute
    });
}

/**
 * Hook to fetch a single playlist
 *
 * @param id - Playlist ID
 * @returns Query result with playlist data
 *
 * @example
 * const { data: playlist } = usePlaylistQuery("playlist-123");
 */
export function usePlaylistQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.playlist(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Playlist ID is required");
            return await api.getPlaylist(id);
        },
        enabled: !!id,
        staleTime: 1 * 60 * 1000, // 1 minute
    });
}

// ============================================================================
// MIX QUERIES
// ============================================================================

/**
 * Hook to fetch all mixes (Made For You)
 *
 * Cache time: 5 minutes
 *
 * @returns Query result with mixes array
 *
 * @example
 * const { data: mixes } = useMixesQuery();
 */
export function useMixesQuery() {
    return useQuery({
        queryKey: queryKeys.mixes(),
        queryFn: () => api.getMixes(),
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

/**
 * Hook to fetch a single mix
 *
 * @param id - Mix ID
 * @returns Query result with mix data
 */
export function useMixQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.mix(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Mix ID is required");
            return await api.getMix(id);
        },
        enabled: !!id,
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

// ============================================================================
// POPULAR ARTISTS QUERY
// ============================================================================

/**
 * Hook to fetch popular artists from Last.fm
 *
 * Cache time: 10 minutes (popular charts don't change frequently)
 *
 * @param limit - Number of artists to fetch (default: 20)
 * @returns Query result with popular artists
 *
 * @example
 * const { data } = usePopularArtistsQuery(20);
 */
export function usePopularArtistsQuery(limit: number = 20) {
    return useQuery({
        queryKey: queryKeys.popularArtists(limit),
        queryFn: () => api.getPopularArtists(limit),
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

// ============================================================================
// AUDIOBOOK QUERIES
// ============================================================================

/**
 * Hook to fetch all audiobooks
 *
 * @returns Query result with audiobooks array
 */
export function useAudiobooksQuery() {
    return useQuery({
        queryKey: queryKeys.audiobooks(),
        queryFn: () => api.getAudiobooks(),
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

/**
 * Hook to fetch a single audiobook
 *
 * @param id - Audiobook ID
 * @returns Query result with audiobook data
 */
export function useAudiobookQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.audiobook(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Audiobook ID is required");
            return await api.getAudiobook(id);
        },
        enabled: !!id,
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

// ============================================================================
// PODCAST QUERIES
// ============================================================================

/**
 * Hook to fetch all subscribed podcasts
 *
 * @returns Query result with podcasts array
 */
export function usePodcastsQuery() {
    return useQuery({
        queryKey: queryKeys.podcasts(),
        queryFn: () => api.getPodcasts(),
        staleTime: 5 * 60 * 1000, // 5 minutes
    });
}

/**
 * Hook to fetch a single podcast
 *
 * Returns null if podcast is not found (404), allowing the page to handle preview mode.
 *
 * @param id - Podcast ID
 * @returns Query result with podcast data
 */
export function usePodcastQuery(id: string | undefined) {
    return useQuery({
        queryKey: queryKeys.podcast(id || ""),
        queryFn: async () => {
            if (!id) throw new Error("Podcast ID is required");

            try {
                return await api.getPodcast(id);
            } catch (error: any) {
                // If podcast not found (404), return null to allow preview mode
                if (error?.status === 404 || error?.message?.includes('not found') || error?.message?.includes('not subscribed')) {
                    return null;
                }
                // For other errors, throw to trigger error state
                throw error;
            }
        },
        enabled: !!id,
        staleTime: 5 * 60 * 1000, // 5 minutes
        retry: false, // Don't retry 404 errors
    });
}

/**
 * Hook to fetch top podcasts
 *
 * @param limit - Number of podcasts (default: 20)
 * @param genreId - Optional genre ID filter
 * @returns Query result with top podcasts
 */
export function useTopPodcastsQuery(limit: number = 20, genreId?: number) {
    return useQuery({
        queryKey: queryKeys.topPodcasts(limit, genreId),
        queryFn: () => api.getTopPodcasts(limit, genreId),
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

// ============================================================================
// MUTATION HOOKS
// ============================================================================
// These hooks handle data modifications and automatically invalidate related queries

/**
 * Hook to refresh mixes with cache invalidation
 *
 * @returns Mutation object with mutate function
 *
 * @example
 * const { mutate: refreshMixes, isPending } = useRefreshMixesMutation();
 * refreshMixes();
 */
export function useRefreshMixesMutation() {
    const queryClient = useQueryClient();

    return useMutation({
        mutationFn: () => api.refreshMixes(),
        onSuccess: () => {
            // Invalidate mixes query to refetch
            queryClient.invalidateQueries({ queryKey: queryKeys.mixes() });
        },
    });
}

/**
 * Hook to add track to playlist with cache invalidation
 *
 * @returns Mutation object with mutate function
 *
 * @example
 * const { mutate: addToPlaylist } = useAddToPlaylistMutation();
 * addToPlaylist({ playlistId: "123", trackId: "456" });
 */
export function useAddToPlaylistMutation() {
    const queryClient = useQueryClient();

    return useMutation({
        mutationFn: ({ playlistId, trackId }: { playlistId: string; trackId: string }) =>
            api.addTrackToPlaylist(playlistId, trackId),
        onSuccess: (_, variables) => {
            // Invalidate the specific playlist query
            queryClient.invalidateQueries({
                queryKey: queryKeys.playlist(variables.playlistId)
            });
            // Also invalidate the playlists list
            queryClient.invalidateQueries({
                queryKey: queryKeys.playlists()
            });
        },
    });
}

/**
 * Hook to create a new playlist with cache invalidation
 *
 * @returns Mutation object with mutate function
 *
 * @example
 * const { mutate: createPlaylist } = useCreatePlaylistMutation();
 * createPlaylist({ name: "My Playlist", isPublic: false });
 */
export function useCreatePlaylistMutation() {
    const queryClient = useQueryClient();

    return useMutation({
        mutationFn: ({ name, isPublic }: { name: string; isPublic?: boolean }) =>
            api.createPlaylist(name, isPublic),
        onSuccess: () => {
            // Invalidate playlists list to show new playlist
            queryClient.invalidateQueries({
                queryKey: queryKeys.playlists()
            });
        },
    });
}

/**
 * Hook to delete a playlist with cache invalidation
 *
 * @returns Mutation object with mutate function
 *
 * @example
 * const { mutate: deletePlaylist } = useDeletePlaylistMutation();
 * deletePlaylist("playlist-123");
 */
export function useDeletePlaylistMutation() {
    const queryClient = useQueryClient();

    return useMutation({
        mutationFn: (playlistId: string) => api.deletePlaylist(playlistId),
        onSuccess: () => {
            // Invalidate playlists list
            queryClient.invalidateQueries({
                queryKey: queryKeys.playlists()
            });
        },
    });
}

// ============================================================================
// BROWSE QUERIES (Deezer Playlists/Radios)
// ============================================================================

interface PlaylistPreview {
    id: string;
    source: string;
    type: string;
    title: string;
    description: string | null;
    creator: string;
    imageUrl: string | null;
    trackCount: number;
    url: string;
}

interface Genre {
    id: number;
    name: string;
    picture?: string;
}

interface GenreWithRadios {
    id: number;
    name: string;
    radios: PlaylistPreview[];
}

interface BrowseAllResponse {
    playlists: PlaylistPreview[];
    radios: PlaylistPreview[];
    genres: Genre[];
    radiosByGenre: GenreWithRadios[];
}

/**
 * Hook to fetch all browse content (playlists, radios, genres) from Deezer
 *
 * @returns Query result with all browse content
 */
export function useBrowseAllQuery() {
    return useQuery({
        queryKey: queryKeys.browseAll(),
        queryFn: async (): Promise<BrowseAllResponse> => {
            return api.get<BrowseAllResponse>("/browse/all");
        },
        staleTime: 10 * 60 * 1000, // 10 minutes - playlists don't change often
    });
}

/**
 * Hook to fetch featured playlists from Deezer
 *
 * @param limit - Maximum number of playlists to fetch
 * @returns Query result with featured playlists
 */
export function useFeaturedPlaylistsQuery(limit: number = 50) {
    return useQuery({
        queryKey: queryKeys.browseFeatured(limit),
        queryFn: async (): Promise<PlaylistPreview[]> => {
            const response = await api.get<{ playlists: PlaylistPreview[] }>(
                `/browse/playlists/featured?limit=${limit}`
            );
            return response.playlists;
        },
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}

/**
 * Hook to fetch radio stations from Deezer
 *
 * @param limit - Maximum number of radios to fetch
 * @returns Query result with radio stations
 */
export function useRadiosQuery(limit: number = 50) {
    return useQuery({
        queryKey: queryKeys.browseRadios(limit),
        queryFn: async (): Promise<PlaylistPreview[]> => {
            const response = await api.get<{ radios: PlaylistPreview[] }>(
                `/browse/radios?limit=${limit}`
            );
            return response.radios;
        },
        staleTime: 10 * 60 * 1000, // 10 minutes
    });
}
