import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Download, FolderOpen, ImageDown, FileText, BadgeCheck, XCircle, Filter } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { SearchAndSort } from "./SearchAndSort"; import { TrackList } from "./TrackList"; import { DownloadProgress } from "./DownloadProgress"; import type { TrackMetadata, TrackAvailability } from "@/types/api"; import { downloadHeader, downloadGalleryImage, downloadAvatar } from "@/lib/api"; import { getSettings } from "@/lib/settings"; import { toastWithSound as toast } from "@/lib/toast-with-sound"; import { useState, useMemo } from "react"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; interface ArtistInfoProps { artistInfo: { name: string; images: string; header?: string; gallery?: string[]; followers: number; total_albums?: number; genres: string[]; biography?: string; verified?: boolean; listeners?: number; rank?: number; }; albumList: Array<{ id: string; name: string; images: string; release_date: string; album_type: string; external_urls: string; total_tracks?: number; }>; trackList: TrackMetadata[]; searchQuery: string; sortBy: string; selectedTracks: string[]; downloadedTracks: Set; failedTracks: Set; skippedTracks: Set; downloadingTrack: string | null; isDownloading: boolean; bulkDownloadType: "all" | "selected" | null; downloadProgress: number; currentDownloadInfo: { name: string; artists: string; } | null; currentPage: number; itemsPerPage: number; downloadedLyrics?: Set; failedLyrics?: Set; skippedLyrics?: Set; downloadingLyricsTrack?: string | null; checkingAvailabilityTrack?: string | null; availabilityMap?: Map; downloadedCovers?: Set; failedCovers?: Set; skippedCovers?: Set; downloadingCoverTrack?: string | null; isBulkDownloadingCovers?: boolean; isBulkDownloadingLyrics?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (id: string) => void; onToggleSelectAll: (tracks: TrackMetadata[]) => void; onDownloadTrack: (id: string, name: string, artists: string, albumName: string, spotifyId?: string, folderName?: string, durationMs?: number, position?: number, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onCheckAvailability?: (spotifyId: string) => void; onDownloadAllLyrics?: () => void; onDownloadAllCovers?: () => void; onDownloadAll: () => void; onDownloadSelected: () => void; onStopDownload: () => void; onOpenFolder: () => void; onAlbumClick: (album: { id: string; name: string; external_urls: string; }) => void; onArtistClick: (artist: { id: string; name: string; external_urls: string; }) => void; onPageChange: (page: number) => void; onTrackClick?: (track: TrackMetadata) => void; onBack?: () => void; } export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onAlbumClick, onArtistClick, onPageChange, onTrackClick, onBack, }: ArtistInfoProps) { const [downloadingHeader, setDownloadingHeader] = useState(false); const [downloadingAvatar, setDownloadingAvatar] = useState(false); const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState(null); const [downloadingAllGallery, setDownloadingAllGallery] = useState(false); const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums"); const displayedAlbumCount = artistInfo.total_albums || albumList.length; const filteredAlbumGroups = useMemo(() => { const albumTypeMap = new Map(albumList.map(a => [a.name, a.album_type])); const albumGroups = trackList.reduce((acc, track) => { if (!track.album_name) return acc; if (!acc[track.album_name]) { acc[track.album_name] = { count: 0, tracks: [], type: albumTypeMap.get(track.album_name) || "unknown" }; } acc[track.album_name].count++; acc[track.album_name].tracks.push(track); return acc; }, {} as Record); return Object.entries(albumGroups).sort((a, b) => { const dateA = a[1].tracks[0]?.release_date || ""; const dateB = b[1].tracks[0]?.release_date || ""; return dateB.localeCompare(dateA); }); }, [trackList, albumList]); const handleDownloadHeader = async () => { if (!artistInfo.header) return; setDownloadingHeader(true); try { const settings = getSettings(); const response = await downloadHeader({ header_url: artistInfo.header, artist_name: artistInfo.name, output_dir: settings.downloadPath, }); if (response.success) { if (response.already_exists) { toast.info("Header already exists"); } else { toast.success("Header downloaded successfully"); } } else { toast.error(response.error || "Failed to download header"); } } catch (error) { toast.error(`Error downloading header: ${error}`); } finally { setDownloadingHeader(false); } }; const handleDownloadAvatar = async () => { if (!artistInfo.images) return; setDownloadingAvatar(true); try { const settings = getSettings(); const response = await downloadAvatar({ avatar_url: artistInfo.images, artist_name: artistInfo.name, output_dir: settings.downloadPath, }); if (response.success) { if (response.already_exists) { toast.info("Avatar already exists"); } else { toast.success("Avatar downloaded successfully"); } } else { toast.error(response.error || "Failed to download avatar"); } } catch (error) { toast.error(`Error downloading avatar: ${error}`); } finally { setDownloadingAvatar(false); } }; const handleDownloadGalleryImage = async (imageUrl: string, index: number) => { setDownloadingGalleryIndex(index); try { const settings = getSettings(); const response = await downloadGalleryImage({ image_url: imageUrl, artist_name: artistInfo.name, image_index: index, output_dir: settings.downloadPath, }); if (response.success) { if (response.already_exists) { toast.info(`Gallery image ${index + 1} already exists`); } else { toast.success(`Gallery image ${index + 1} downloaded successfully`); } } else { toast.error(response.error || `Failed to download gallery image ${index + 1}`); } } catch (error) { toast.error(`Error downloading gallery image ${index + 1}: ${error}`); } finally { setDownloadingGalleryIndex(null); } }; const handleDownloadAllGallery = async () => { if (!artistInfo.gallery || artistInfo.gallery.length === 0) return; setDownloadingAllGallery(true); try { const settings = getSettings(); let successCount = 0; let existsCount = 0; let failCount = 0; for (let index = 0; index < artistInfo.gallery.length; index++) { const imageUrl = artistInfo.gallery[index]; try { const response = await downloadGalleryImage({ image_url: imageUrl, artist_name: artistInfo.name, image_index: index, output_dir: settings.downloadPath, }); if (response.success) { if (response.already_exists) { existsCount++; } else { successCount++; } } else { failCount++; } } catch (error) { failCount++; } } if (failCount === 0) { if (existsCount > 0 && successCount > 0) { toast.success(`${successCount} images downloaded, ${existsCount} already existed`); } else if (existsCount > 0) { toast.info(`All ${existsCount} images already exist`); } else { toast.success(`All ${successCount} gallery images downloaded successfully`); } } else { toast.error(`${failCount} images failed to download`); } } catch (error) { toast.error(`Error downloading gallery images: ${error}`); } finally { setDownloadingAllGallery(false); } }; const hasGallery = artistInfo.gallery && artistInfo.gallery.length > 0; return (
{artistInfo.header ? (<>
{onBack && (
)}

Download Header

{artistInfo.images && (
{artistInfo.name}

Download Avatar

)}

Artist

{artistInfo.name}

{artistInfo.verified && ()}
{artistInfo.biography && (

{artistInfo.biography}

)}
{artistInfo.rank && (<> #{artistInfo.rank} rank )} {artistInfo.followers.toLocaleString()} {artistInfo.followers === 1 ? "follower" : "followers"} {artistInfo.listeners && (<> {artistInfo.listeners.toLocaleString()} {artistInfo.listeners === 1 ? "listener" : "listeners"} )}
{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"} {trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"} {artistInfo.genres.length > 0 && (<> {artistInfo.genres.join(", ")} )}
) : ( {onBack && (
)}
{artistInfo.images && (
{artistInfo.name}

Download Avatar

)}

Artist

{artistInfo.name}

{artistInfo.verified && ()}
{artistInfo.biography && (

{artistInfo.biography}

)}
{artistInfo.rank && (<> #{artistInfo.rank} rank )} {artistInfo.followers.toLocaleString()} {artistInfo.followers === 1 ? "follower" : "followers"} {artistInfo.listeners && (<> {artistInfo.listeners.toLocaleString()} {artistInfo.listeners === 1 ? "listener" : "listeners"} )}
{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"} {trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"} {artistInfo.genres.length > 0 && (<> {artistInfo.genres.join(", ")} )}
)}
{hasGallery && ()}
{activeTab === "gallery" && hasGallery && (

Gallery ({artistInfo.gallery!.length.toLocaleString()})

Download All Gallery

{artistInfo.gallery!.map((imageUrl, index) => (
{`${artistInfo.name}

Download Image {index + 1}

))}
)} {activeTab === "albums" && albumList.length > 0 && (

Discography

{selectedTracks.length > 0 && ()}
{albumList.map((album) => { const albumTracks = trackList.filter(t => t.album_name === album.name); const tracksWithId = albumTracks.filter(t => t.spotify_id); const isSelected = tracksWithId.length > 0 && tracksWithId.every(t => selectedTracks.includes(t.spotify_id!)); const hasTracks = tracksWithId.length > 0; return (
onAlbumClick({ id: album.id, name: album.name, external_urls: album.external_urls, })}>
{hasTracks && (
e.stopPropagation()}> onToggleSelectAll(albumTracks)} className="bg-black/50 border-white/70 data-[state=checked]:bg-primary data-[state=checked]:border-primary"/>
)} {album.images && ({album.name})}
{album.album_type}

{album.name}

{album.release_date?.split("-")[0]} {album.total_tracks && (<> {album.total_tracks} {album.total_tracks === 1 ? "track" : "tracks"} )}
); })}
)} {activeTab === "tracks" && trackList.length > 0 && (

All Tracks

Select Albums
{filteredAlbumGroups.map(([albumName, data]) => { const tracksWithId = data.tracks.filter(t => t.spotify_id); const isSelected = tracksWithId.length > 0 && tracksWithId.every(t => selectedTracks.includes(t.spotify_id!)); return (
onToggleSelectAll(data.tracks)} className="mt-1"/>
{data.type} {data.count} tracks {data.tracks[0]?.release_date?.split('-')[0] || 'Unknown Year'}
); })}
{selectedTracks.length > 0 && ()} {onDownloadAllLyrics && (

Download All Lyrics

)} {onDownloadAllCovers && (

Download All Covers

)} {downloadedTracks.size > 0 && ()}
{isDownloading && ()}
)}
); }