"use client"; import { useState, useMemo, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useAudio } from "@/lib/audio-context"; import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; import { Tab, DeleteDialogState } from "@/features/library/types"; import { useLibraryData, LibraryFilter, } from "@/features/library/hooks/useLibraryData"; import { api } from "@/lib/api"; import { useLibraryActions } from "@/features/library/hooks/useLibraryActions"; import { LibraryHeader } from "@/features/library/components/LibraryHeader"; import { LibraryTabs } from "@/features/library/components/LibraryTabs"; import { ArtistsGrid } from "@/features/library/components/ArtistsGrid"; import { AlbumsGrid } from "@/features/library/components/AlbumsGrid"; import { TracksList } from "@/features/library/components/TracksList"; import { Shuffle, ListFilter } from "lucide-react"; type SortOption = "name" | "name-desc" | "recent" | "tracks"; export default function LibraryPage() { const router = useRouter(); const searchParams = useSearchParams(); const { currentTrack, playTracks } = useAudio(); // Get active tab from URL params, default to "artists" const activeTab = (searchParams.get("tab") as Tab) || "artists"; // Filter state (owned = your library, discovery = discovery weekly artists) const [filter, setFilter] = useState("owned"); // Sort and pagination state const [sortBy, setSortBy] = useState("name"); const [itemsPerPage, setItemsPerPage] = useState(50); const [currentPage, setCurrentPage] = useState(1); const [showFilters, setShowFilters] = useState(false); // Use custom hooks const { artists, albums, tracks, isLoading, reloadData } = useLibraryData({ activeTab, filter, }); const { playArtist, playAlbum, addTrackToQueue, addTrackToPlaylist, deleteArtist, deleteAlbum, deleteTrack, } = useLibraryActions(); // Reset page and filter when tab changes useEffect(() => { setCurrentPage(1); // Reset filter to 'owned' when switching to tracks tab (which doesn't support filter) if (activeTab === "tracks") { setFilter("owned"); } }, [activeTab]); // Sort and paginate data const sortedArtists = useMemo(() => { const sorted = [...artists]; switch (sortBy) { case "name": sorted.sort((a, b) => a.name.localeCompare(b.name)); break; case "name-desc": sorted.sort((a, b) => b.name.localeCompare(a.name)); break; case "tracks": sorted.sort( (a, b) => (b.trackCount || 0) - (a.trackCount || 0) ); break; default: sorted.sort((a, b) => a.name.localeCompare(b.name)); } return sorted; }, [artists, sortBy]); const sortedAlbums = useMemo(() => { const sorted = [...albums]; switch (sortBy) { case "name": sorted.sort((a, b) => a.title.localeCompare(b.title)); break; case "name-desc": sorted.sort((a, b) => b.title.localeCompare(a.title)); break; case "recent": sorted.sort((a, b) => (b.year || 0) - (a.year || 0)); break; default: sorted.sort((a, b) => a.title.localeCompare(b.title)); } return sorted; }, [albums, sortBy]); const sortedTracks = useMemo(() => { const sorted = [...tracks]; switch (sortBy) { case "name": sorted.sort((a, b) => a.title.localeCompare(b.title)); break; case "name-desc": sorted.sort((a, b) => b.title.localeCompare(a.title)); break; default: sorted.sort((a, b) => a.title.localeCompare(b.title)); } return sorted; }, [tracks, sortBy]); // Paginate const paginatedArtists = useMemo(() => { const start = (currentPage - 1) * itemsPerPage; return sortedArtists.slice(start, start + itemsPerPage); }, [sortedArtists, currentPage, itemsPerPage]); const paginatedAlbums = useMemo(() => { const start = (currentPage - 1) * itemsPerPage; return sortedAlbums.slice(start, start + itemsPerPage); }, [sortedAlbums, currentPage, itemsPerPage]); const paginatedTracks = useMemo(() => { const start = (currentPage - 1) * itemsPerPage; return sortedTracks.slice(start, start + itemsPerPage); }, [sortedTracks, currentPage, itemsPerPage]); // Total counts and pages const totalItems = activeTab === "artists" ? artists.length : activeTab === "albums" ? albums.length : tracks.length; const totalPages = Math.ceil(totalItems / itemsPerPage); // Delete confirmation dialog state const [deleteConfirm, setDeleteConfirm] = useState({ isOpen: false, type: "track", id: "", title: "", }); // Change tab function const changeTab = (tab: Tab) => { router.push(`/library?tab=${tab}`, { scroll: false }); }; // Helper to convert library Track to audio context Track format const formatTracksForAudio = (libraryTracks: typeof tracks) => { return libraryTracks.map((track) => ({ id: track.id, title: track.title, duration: track.duration, artist: { id: track.album?.artist?.id, name: track.album?.artist?.name || "Unknown Artist", }, album: { id: track.album?.id, title: track.album?.title || "Unknown Album", coverArt: track.album?.coverArt, }, })); }; // Wrapper for playTracks that converts track format const handlePlayTracks = ( libraryTracks: typeof tracks, startIndex?: number ) => { const formattedTracks = formatTracksForAudio(libraryTracks); playTracks(formattedTracks, startIndex); }; // Shuffle entire library const handleShuffleLibrary = async () => { try { // Get all tracks if not already loaded let allTracks = tracks; if (activeTab !== "tracks" || tracks.length === 0) { const { tracks: fetchedTracks } = await api.getTracks({ limit: 1000, }); allTracks = fetchedTracks; } if (allTracks.length === 0) { return; } // Shuffle the tracks const shuffled = [...allTracks].sort(() => Math.random() - 0.5); const formattedTracks = formatTracksForAudio(shuffled); playTracks(formattedTracks, 0); } catch (error) { console.error("Failed to shuffle library:", error); } }; // Handle delete confirmation const handleDelete = async () => { try { switch (deleteConfirm.type) { case "artist": await deleteArtist(deleteConfirm.id); break; case "album": await deleteAlbum(deleteConfirm.id); break; case "track": await deleteTrack(deleteConfirm.id); break; } // Reload data and close dialog - the item disappearing is feedback enough await reloadData(); setDeleteConfirm({ isOpen: false, type: "track", id: "", title: "", }); } catch (error) { console.error(`Failed to delete ${deleteConfirm.type}:`, error); // Keep dialog open on error so user can retry } }; return (
{/* Tabs and Controls Row */}
{/* Shuffle Button */} {/* Filter Toggle */} {/* Item Count */} {totalItems}{" "} {activeTab === "artists" ? "artists" : activeTab === "albums" ? "albums" : "songs"}
{/* Expandable Filters Row */} {showFilters && (
{/* Filter Toggle (Owned / Discovery / All) - Only show for artists and albums */} {(activeTab === "artists" || activeTab === "albums") && (
)} {/* Sort Dropdown */} {/* Items per page */}
)} {activeTab === "artists" && ( setDeleteConfirm({ isOpen: true, type: "artist", id, title: name, }) } /> )} {activeTab === "albums" && ( setDeleteConfirm({ isOpen: true, type: "album", id, title, }) } /> )} {activeTab === "tracks" && ( setDeleteConfirm({ isOpen: true, type: "track", id, title, }) } /> )} {/* Pagination */} {totalPages > 1 && (
{currentPage} / {totalPages}
)} setDeleteConfirm({ isOpen: false, type: "track", id: "", title: "", }) } onConfirm={handleDelete} title={`Delete ${ deleteConfirm.type === "artist" ? "Artist" : deleteConfirm.type === "album" ? "Album" : "Track" }?`} message={ deleteConfirm.type === "track" ? `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete the file from your system.` : deleteConfirm.type === "album" ? `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete all tracks and files from your system.` : `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete all albums, tracks, and files from your system.` } confirmText="Delete" cancelText="Cancel" variant="danger" />
); }