/** * LibraryManager — fetch, state, handlers. */ import { useState, useEffect, useCallback } from 'react'; import { apiClient } from '@/services/api/client'; import type { Track as ApiTrack } from '@/features/tracks/types/track'; import type { Track as PlayerTrack } from '@/features/player/types'; import { useToast } from '@/hooks/useToast'; import { logger } from '@/utils/logger'; import { parseApiError } from '@/utils/apiErrorHandler'; export interface UseLibraryManagerOverrides { tracksOverride?: ApiTrack[] | null; isLoadingOverride?: boolean; errorOverride?: string | null; } export function useLibraryManager( onTrackSelect?: (track: ApiTrack) => void, overrides?: UseLibraryManagerOverrides, ) { const [tracks, setTracks] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [filterType, setFilterType] = useState('all'); const [pagination, setPagination] = useState({ page: 1, limit: 20, total: 0, }); const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); const [playingTrackId, setPlayingTrackId] = useState(null); const { info: showInfo } = useToast(); const fetchTracks = useCallback(async () => { try { setIsLoading(true); setError(null); const response = await apiClient.get<{ data: ApiTrack[]; total: number; page: number; limit: number; }>('/tracks', { params: { page: pagination.page, limit: pagination.limit, search: searchQuery || undefined, artist: filterType !== 'all' ? filterType : undefined, }, }); const data = response.data; setTracks(data.data ?? []); setPagination((prev) => ({ ...prev, total: data.total ?? 0, })); } catch (err: unknown) { const apiError = parseApiError(err); setError(apiError.message); logger.error('Error fetching tracks:', { message: apiError.message }); } finally { setIsLoading(false); } }, [pagination.page, pagination.limit, searchQuery, filterType]); useEffect(() => { if (overrides?.tracksOverride !== undefined) { setTracks(overrides.tracksOverride ?? []); setPagination((p) => ({ ...p, total: (overrides.tracksOverride ?? []).length })); setIsLoading(false); setError(overrides.errorOverride ?? null); return; } if (overrides?.errorOverride != null) { setError(overrides.errorOverride); setTracks([]); setIsLoading(false); return; } fetchTracks(); }, [fetchTracks, overrides?.tracksOverride, overrides?.errorOverride]); const handleUploadComplete = useCallback(() => { fetchTracks(); }, [fetchTracks]); const handleEditTrack = useCallback( (_track: PlayerTrack) => { showInfo('Edit functionality coming soon — available in the next release'); }, [showInfo], ); const handlePlayTrack = useCallback( (track: PlayerTrack) => { setPlayingTrackId(track.id); const originalTrack = tracks.find((t) => t.id === track.id); if (originalTrack) { onTrackSelect?.(originalTrack); showInfo(`Now playing: ${track.title} by ${track.artist}`); } }, [tracks, onTrackSelect, showInfo], ); const mapToPlayerTrack = useCallback((track: ApiTrack): PlayerTrack => { const t = track as ApiTrack & { album?: string; cover_art_path?: string; stream_manifest_url?: string; genre?: string; }; return { id: track.id, title: track.title, artist: track.artist, album: t.album, duration: track.duration, url: t.stream_manifest_url ?? track.file_path, cover: t.cover_art_path, genre: t.genre, }; }, []); const playerTracks = tracks.map(mapToPlayerTrack); return { tracks, isLoading, error, searchQuery, setSearchQuery, viewMode, setViewMode, filterType, setFilterType, pagination, isUploadModalOpen, setIsUploadModalOpen, playingTrackId, playerTracks, handleUploadComplete, handleEditTrack, handlePlayTrack, }; }