import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { MediaItem } from '../api/jellyfin'; import { useDownloadContext } from '../context/DownloadContext/DownloadContext'; import { useJellyfinContext } from '../context/JellyfinContext/JellyfinContext'; import { useSocket } from '../hooks/useSocket'; import { toast } from 'sonner'; import './MediaDetailModal.css'; // Removed unused MUSIC_BASE_PATH and sanitize function interface MediaDetailModalProps { item: MediaItem | null; isOpen: boolean; onClose: () => void; onDownload?: (item: MediaItem) => void; } export const MediaDetailModal: React.FC = ({ item, isOpen, onClose, onDownload }) => { const navigate = useNavigate(); const downloadContext = useDownloadContext(); const jellyfinApi = useJellyfinContext(); const { socket } = useSocket(); const [albumTracks, setAlbumTracks] = useState([]); const [loadingTracks, setLoadingTracks] = useState(false); const [albumInfo, setAlbumInfo] = useState(null); const [localDownloadStates, setLocalDownloadStates] = useState<{[key: string]: string}>({}); // Enhanced Artist Tab System State const [activeTab, setActiveTab] = useState<'tracks' | 'albums'>('tracks'); const [artistTracks, setArtistTracks] = useState([]); // Separate state for artist tracks const [artistAlbums, setArtistAlbums] = useState([]); const [loadingAlbums, setLoadingAlbums] = useState(false); const [showingAlbumTracks, setShowingAlbumTracks] = useState(false); const [currentAlbumView, setCurrentAlbumView] = useState(null); // Check if current item is an artist const isArtistItem = useMemo(() => { return item?.Type === 'MusicArtist' || (item?.SpotifyUrl && item.SpotifyUrl.includes('/artist/')) || (item?.searchResult && item.searchResult.type === 'artist'); }, [item]); useEffect(() => { if (isOpen && item) { // Check if this is an album-type item that should have tracks const isAlbumType = item.Type === 'MusicAlbum' || item.Type === 'Playlist' || item.SpotifyId; // Any Spotify item might be an album // Check if this is an artist const isArtistType = item.Type === 'MusicArtist' || (item.SpotifyUrl && item.SpotifyUrl.includes('/artist/')) || (item.searchResult && item.searchResult.type === 'artist'); if (isAlbumType || isArtistType) { fetchAlbumTracks(); } else { setAlbumTracks([]); setArtistTracks([]); setAlbumInfo(null); } } else { setAlbumTracks([]); setArtistTracks([]); setAlbumInfo(null); } }, [isOpen, item]); // Listen for download progress updates useEffect(() => { if (!socket) return; const handleDownloadUpdate = (data: any) => { console.log('🔄 MediaDetailModal download update:', data); // Handle the backend's data structure: {history: [array_of_downloads]} const downloads = data.history && Array.isArray(data.history) ? data.history : [data]; downloads.forEach((download: any) => { if (download.id && download.status) { console.log(`📥 Modal updating download ${download.id}: ${download.status}`); // Update local state for immediate UI feedback setLocalDownloadStates(prev => ({ ...prev, [download.id]: download.status })); } }); }; socket.on('update_status', handleDownloadUpdate); return () => { socket.off('update_status', handleDownloadUpdate); }; }, [socket]); // Reset tab state when modal opens/closes or item changes useEffect(() => { if (isOpen && item) { setActiveTab('tracks'); setShowingAlbumTracks(false); setCurrentAlbumView(null); if (isArtistItem) { fetchArtistAlbums(); } } }, [isOpen, item, isArtistItem]); // Function to fetch artist albums const fetchArtistAlbums = async () => { if (!item || !item.SpotifyId) return; setLoadingAlbums(true); try { const response = await fetch(`/api/spotify/artist/${item.SpotifyId}/albums`); if (response.ok) { const data = await response.json(); if (data.success && data.albums) { // Sort albums by release date (newest first) const sortedAlbums = data.albums.sort((a: any, b: any) => { return new Date(b.release_date).getTime() - new Date(a.release_date).getTime(); }); setArtistAlbums(sortedAlbums); } } } catch (error) { console.error('Error fetching artist albums:', error); } finally { setLoadingAlbums(false); } }; // Function to handle album download from grid const handleAlbumDownloadById = async (album: any) => { try { const response = await fetch(`/api/spotify/album/${album.id}/tracks`); if (response.ok) { const data = await response.json(); if (data.success && data.tracks) { // Convert tracks to MediaItem format for downloading const albumTracks = data.tracks.map((track: any) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || 'Unknown Artist'], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), Album: album.name, Type: 'Audio' as const, RunTimeTicks: track.duration_ms || track.duration || 0, SpotifyUrl: track.url, SpotifyImage: album.images?.[0]?.url, IndexNumber: track.track_number || 1, searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), url: track.url, image: album.images?.[0]?.url, album: album.name, type: 'track', duration: track.duration_ms || track.duration } })); // Download all tracks in the album for (const track of albumTracks) { if (onDownload) { onDownload(track); } } toast.success(`Started downloading ${albumTracks.length} tracks from "${album.name}"`); } } } catch (error) { console.error('Error downloading album:', error); toast.error('Failed to download album'); } }; // Function to view album tracks const handleViewAlbumTracks = async (album: any) => { // Only allow album viewing from Albums tab to avoid interfering with Top Tracks if (activeTab !== 'albums') { setActiveTab('albums'); } setLoadingTracks(true); try { const response = await fetch(`/api/spotify/album/${album.id}/tracks`); if (response.ok) { const data = await response.json(); if (data.success && data.tracks) { const convertedTracks = data.tracks.map((track: any, index: number) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || 'Unknown Artist'], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), Album: album.name, Type: 'Audio' as const, RunTimeTicks: track.duration_ms || track.duration || 0, SpotifyUrl: track.url, SpotifyImage: album.images?.[0]?.url, IndexNumber: track.track_number || (index + 1), searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), url: track.url, image: album.images?.[0]?.url, album: album.name, type: 'track', duration: track.duration_ms || track.duration } })); setAlbumTracks(convertedTracks); setCurrentAlbumView(album); setShowingAlbumTracks(true); toast.success(`Loaded ${convertedTracks.length} tracks from "${album.name}"`); } } } catch (error) { console.error('Error fetching album tracks:', error); toast.error('Failed to load album tracks'); } finally { setLoadingTracks(false); } }; // Function to go back to albums grid const handleBackToAlbums = () => { setShowingAlbumTracks(false); setCurrentAlbumView(null); setActiveTab('albums'); // Reload artist top tracks when going back fetchAlbumTracks(); }; const fetchAlbumTracks = async () => { if (!item) return; setLoadingTracks(true); try { // Check if this is a Spotify item (from discover/search) if (item.SpotifyId) { try { // Determine the type of Spotify item const isPlaylist = item.Type === 'Playlist' || (item.SpotifyUrl && item.SpotifyUrl.includes('/playlist/')) || (item.Name && item.Name.toLowerCase().includes('playlist')) || (item.searchResult && item.searchResult.type === 'playlist'); const isArtist = item.Type === 'MusicArtist' || (item.SpotifyUrl && item.SpotifyUrl.includes('/artist/')) || (item.searchResult && item.searchResult.type === 'artist'); const isAlbum = item.Type === 'MusicAlbum' || (item.SpotifyUrl && item.SpotifyUrl.includes('/album/')) || (item.searchResult && item.searchResult.type === 'album'); const isTrack = item.Type === 'Audio' || (item.SpotifyUrl && item.SpotifyUrl.includes('/track/')) || (item.searchResult && item.searchResult.type === 'track'); let endpoint; if (isArtist) { // Fetch both artist info and top tracks endpoint = `/api/spotify/artist/${item.SpotifyId}`; } else if (isPlaylist) { endpoint = `/api/spotify/playlist/${item.SpotifyId}/tracks`; } else if (isAlbum) { endpoint = `/api/spotify/album/${item.SpotifyId}/tracks`; } else if (isTrack) { // For individual tracks, try to fetch the full album if we have album info const albumName = item.Album || (item as any).searchResult?.album; const artistName = item.AlbumArtist || item.Artists?.[0] || (item as any).searchResult?.artist; if (albumName && artistName && albumName !== 'Unknown Album') { // Try to search for the album to get all tracks try { const albumSearchResponse = await fetch(`/api/search-album?album=${encodeURIComponent(albumName)}&artist=${encodeURIComponent(artistName)}`); if (albumSearchResponse.ok) { const albumData = await albumSearchResponse.json(); if (albumData.success && albumData.album_id) { // Found the album, now fetch its tracks const albumTracksResponse = await fetch(`/api/spotify/album/${albumData.album_id}/tracks`); if (albumTracksResponse.ok) { const tracksData = await albumTracksResponse.json(); if (tracksData.success && tracksData.tracks) { const convertedTracks = tracksData.tracks.map((track: any, index: number) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || 'Unknown Artist'], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), Album: track.album || albumName, Type: 'Audio', RunTimeTicks: track.duration_ms || track.duration || 0, SpotifyUrl: track.url, SpotifyImage: track.image || item.SpotifyImage, IndexNumber: track.track_number || (index + 1), // Mark the current track isCurrentTrack: track.name === item.Name || track.spotify_id === item.SpotifyId, explicit: track.explicit, popularity: track.popularity, searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), url: track.url, image: track.image || item.SpotifyImage, album: track.album || albumName, type: 'track', duration: track.duration_ms || track.duration } })); setAlbumTracks(convertedTracks); return; // Exit early since we found the full album } } } } } catch (error) { console.log('Could not fetch full album, showing single track'); } } // Fallback: show just the single track const singleTrack = { Id: item.SpotifyId || item.Id, SpotifyId: item.SpotifyId, Name: item.Name, Artists: item.Artists || [artistName || 'Unknown Artist'], AlbumArtist: artistName || 'Unknown Artist', Album: albumName || 'Unknown Album', Type: 'Audio' as const, RunTimeTicks: item.RunTimeTicks || (item as any).searchResult?.duration || (item as any).duration || 0, SpotifyUrl: item.SpotifyUrl, SpotifyImage: item.SpotifyImage, IndexNumber: 1, searchResult: item.searchResult, isCurrentTrack: true, // Mark as the selected track explicit: (item as any).explicit || (item as any).searchResult?.explicit, popularity: (item as any).popularity, duration: (item as any).searchResult?.duration || (item as any).duration } as MediaItem; setAlbumTracks([singleTrack]); return; // Exit early for tracks } else { // Default to album tracks for unknown types endpoint = `/api/spotify/album/${item.SpotifyId}/tracks`; } console.log('Fetching tracks from:', endpoint); const response = await fetch(endpoint); if (!response.ok) { console.error('Failed to fetch tracks:', response.status, response.statusText); throw new Error(`Failed to fetch tracks: ${response.status}`); } const data = await response.json(); console.log('Received track data:', data); if (data.success && data.tracks) { // Convert Spotify tracks to MediaItem format const convertedTracks = data.tracks.map((track: any, index: number) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || 'Unknown Artist'], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), Album: track.album || item.Name, Type: 'Audio', RunTimeTicks: track.duration_ms || track.duration || 0, // Keep original duration format SpotifyUrl: track.url, SpotifyImage: track.image || item.SpotifyImage, IndexNumber: track.track_number || (index + 1), // Add additional track metadata explicit: track.explicit, popularity: track.popularity, is_top_track: track.is_top_track, // Attach the original-style payload for downloads searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), url: track.url, image: track.image || item.SpotifyImage, album: track.album || item.Name, type: 'track', duration: track.duration_ms || track.duration } })); setAlbumTracks(convertedTracks); // Store album/playlist/artist info for display if (data.album) { setAlbumInfo(data.album); } else if (data.playlist) { setAlbumInfo(data.playlist); } else if (data.artist) { setAlbumInfo(data.artist); } } else { setAlbumTracks([]); setAlbumInfo(null); } } catch (spotifyError) { console.error('Error calling Spotify API:', spotifyError); setAlbumTracks([]); } } // Handle Jellyfin albums/playlists else if (item.Id && (item.Type === 'MusicAlbum' || item.Type === 'Playlist')) { if (item.Type === 'MusicAlbum') { const albumDetails = await jellyfinApi.getAlbumDetails(item.Id); const tracks = albumDetails?.tracks || []; setAlbumTracks(tracks); } else if (item.Type === 'Playlist') { if (typeof jellyfinApi.getPlaylistAllTracks === 'function') { const playlistTracks = await jellyfinApi.getPlaylistAllTracks(item.Id); setAlbumTracks(playlistTracks || []); } else { setAlbumTracks([]); } } } else { setAlbumTracks([]); } } catch (error) { console.error('Error fetching album/playlist tracks:', error); setAlbumTracks([]); } finally { setLoadingTracks(false); } }; // Navigation functions for clickable artist/album const searchForArtist = useCallback(async (artistName: string) => { if (!artistName || artistName === 'Unknown Artist') return; // Set loading state setLoadingTracks(true); // Try to fetch artist profile directly and show in current modal try { // First search for the artist to get their Spotify ID const searchResponse = await fetch(`/api/search?query=${encodeURIComponent(artistName)}&type=artist&limit=1`); if (searchResponse.ok) { const searchData = await searchResponse.json(); if (searchData.success && searchData.results && searchData.results.length > 0) { const artistResult = searchData.results[0]; if (artistResult.type === 'artist' && artistResult.id) { // Fetch artist's top tracks const artistResponse = await fetch(`/api/spotify/artist/${artistResult.id}`); if (artistResponse.ok) { const artistData = await artistResponse.json(); if (artistData.success && artistData.tracks) { console.log('Artist tracks loaded:', artistData.tracks.length, 'tracks'); // Convert tracks and update the current modal to show this artist const convertedTracks = artistData.tracks.map((track: any, index: number) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || artistName], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || artistName), Album: track.album || 'Unknown Album', Type: 'Audio', RunTimeTicks: track.duration_ms || track.duration || 0, SpotifyUrl: track.url, SpotifyImage: track.image || artistResult.image, IndexNumber: index + 1, explicit: track.explicit, popularity: track.popularity, is_top_track: track.is_top_track, searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || artistName), url: track.url, image: track.image || artistResult.image, album: track.album || 'Unknown Album', type: 'track', duration: track.duration_ms || track.duration } })); // Update the current modal to show this artist's top tracks setArtistTracks(convertedTracks); setAlbumTracks(convertedTracks); // Also set albumTracks so they display in the UI setAlbumInfo({ name: artistResult.name, artist: artistResult.name, image: artistResult.image, description: `Top tracks by ${artistResult.name}`, followers: artistData.artist?.followers, genres: artistData.artist?.genres }); toast.success(`Loaded ${convertedTracks.length} top tracks by "${artistResult.name}"`); setLoadingTracks(false); return; // Don't navigate away, stay in current modal } } } } } } catch (error) { console.error('Error fetching artist profile:', error); // Fallback: still set empty arrays to prevent UI issues setAlbumTracks([]); setArtistTracks([]); setAlbumInfo(null); setLoadingTracks(false); } // Fallback: navigate to search page if direct artist fetch failed setLoadingTracks(false); onClose(); navigate('/online-search', { state: { searchQuery: artistName, searchType: 'artist', forceNewSearch: true, timestamp: Date.now() // Force fresh search } }); }, [navigate, onClose]); const searchForAlbum = useCallback(async (albumName: string, artistName?: string) => { if (!albumName || albumName === 'Unknown Album') return; // For album clicks, try to fetch the album directly and show tracks in current modal try { const searchQuery = artistName && artistName !== 'Unknown Artist' ? `${albumName} ${artistName}` : albumName; // First try to search for the specific album const response = await fetch(`/api/search?query=${encodeURIComponent(searchQuery)}&type=album&limit=1`); if (response.ok) { const data = await response.json(); if (data.success && data.results && data.results.length > 0) { const albumResult = data.results[0]; // If we found the album, fetch its tracks and update current modal if (albumResult.type === 'album' && albumResult.id) { const albumTracksResponse = await fetch(`/api/spotify/album/${albumResult.id}/tracks`); if (albumTracksResponse.ok) { const tracksData = await albumTracksResponse.json(); if (tracksData.success && tracksData.tracks) { // Convert tracks and update the current modal to show this album const convertedTracks = tracksData.tracks.map((track: any, index: number) => ({ Id: track.spotify_id || track.id, SpotifyId: track.spotify_id || track.id, Name: track.name, Artists: Array.isArray(track.artists) ? track.artists : [track.artist || 'Unknown Artist'], AlbumArtist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), Album: track.album || albumName, Type: 'Audio', RunTimeTicks: track.duration_ms || track.duration || 0, SpotifyUrl: track.url, SpotifyImage: track.image || albumResult.image, IndexNumber: track.track_number || (index + 1), explicit: track.explicit, popularity: track.popularity, searchResult: { id: track.spotify_id || track.id, name: track.name, artist: Array.isArray(track.artists) ? track.artists.join(', ') : (track.artist || 'Unknown Artist'), url: track.url, image: track.image || albumResult.image, album: track.album || albumName, type: 'track', duration: track.duration_ms || track.duration } })); // Update the current modal to show this album setAlbumTracks(convertedTracks); setAlbumInfo({ name: albumResult.name, artist: albumResult.artist, image: albumResult.image, description: `Album by ${albumResult.artist}` }); toast.success(`Loaded ${convertedTracks.length} tracks from "${albumResult.name}"`); return; // Don't navigate away, stay in current modal } } } } } } catch (error) { console.error('Error fetching album tracks:', error); } // Fallback: navigate to search page if direct album fetch failed const searchQuery = artistName && artistName !== 'Unknown Artist' ? `${albumName} ${artistName}` : albumName; onClose(); navigate('/online-search', { state: { searchQuery: searchQuery, searchType: 'album', forceNewSearch: true, timestamp: Date.now() // Force fresh search } }); }, [navigate, onClose]); const handleDownload = useCallback((targetItem: MediaItem) => { if (onDownload) { onDownload(targetItem); } else if (targetItem.SpotifyId && socket) { // For Spotify items, use the proper download_spotify endpoint const sr = (targetItem as any).searchResult || (targetItem as any).spotifyData; const spotifyData = sr && sr.id ? sr : { id: targetItem.SpotifyId, name: targetItem.Name, artist: targetItem.Artists?.join(', ') || targetItem.AlbumArtist || 'Unknown Artist', url: targetItem.SpotifyUrl, image: targetItem.SpotifyImage || item?.SpotifyImage, album: targetItem.Album || item?.Name || 'Unknown Album', type: 'track' }; // Update local state to show downloading immediately const originalKey = spotifyData.id; const backendKey = `spotify_${spotifyData.id}`; setLocalDownloadStates(prev => ({ ...prev, [originalKey]: 'accepted', [backendKey]: 'accepted' })); // Debug logging to trace the Spotify ID issue console.log('🔍 DEBUG: Download attempt details:'); console.log('- targetItem.SpotifyId:', targetItem.SpotifyId); console.log('- spotifyData.id:', spotifyData.id); console.log('- searchResult:', sr); console.log('- Full targetItem:', targetItem); // Use the Spotify-specific download endpoint via HTTP POST fetch('/api/download/spotify', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ spotifyId: spotifyData.id, trackName: spotifyData.name, artistName: spotifyData.artist }) }).then(response => response.json()).then(data => { console.log('Download queued:', data); }).catch(error => { console.error('Download failed:', error); // Reset download state on error setLocalDownloadStates(prev => ({ ...prev, [originalKey]: '', [backendKey]: '' })); }); toast.info(`Downloading ${spotifyData.name}...`); // Update download context with both ID formats for compatibility if (downloadContext && downloadContext.updateDownloadStatus) { const originalKey = spotifyData.id; const backendKey = `spotify_${spotifyData.id}`; downloadContext.updateDownloadStatus(backendKey, 'accepted', 0); downloadContext.updateDownloadStatus(originalKey, 'accepted', 0); } } else if (downloadContext) { // For Jellyfin items, use regular download context downloadContext.addToDownloads([targetItem], undefined); } }, [onDownload, socket, downloadContext, item]); const handleAlbumDownload = () => { if (albumTracks.length > 0) { // Sort tracks in reverse order (highest track number first) so they appear top-to-bottom when sorted by date descending const sortedTracks = [...albumTracks].sort((a, b) => { const ai = (a as any).IndexNumber || 0; const bi = (b as any).IndexNumber || 0; if (ai === bi) return (b.Name || '').localeCompare(a.Name || ''); return bi - ai; // Reverse order: highest track number first }); if (onDownload) { sortedTracks.forEach(track => onDownload(track)); } else { // Add all tracks to download queue as a batch to maintain order downloadContext.addToDownloads(sortedTracks, item || undefined); toast.info(`Added ${sortedTracks.length} tracks from "${item?.Name}" to download queue`); } } else if (item) { handleDownload(item); } }; const getDownloadState = (targetItem: MediaItem) => { const originalKey = targetItem.SpotifyId || targetItem.Id; const backendKey = targetItem.SpotifyId ? `spotify_${targetItem.SpotifyId}` : originalKey; // Check local state first (for immediate UI feedback) const localState = localDownloadStates[originalKey] || localDownloadStates[backendKey]; if (localState) return localState; // Check both ID formats in download context const contextDownload = downloadContext?.downloads?.[originalKey] || downloadContext?.downloads?.[backendKey]; // Return the most specific status available return targetItem.offlineState || contextDownload?.status; }; // Album-level aggregation: if any track is active → downloading; if all finished → downloaded const getAlbumDownloadState = () => { if (!albumTracks || albumTracks.length === 0) return item ? getDownloadState(item) : undefined; let anyActive = false; let allDone = albumTracks.length > 0; for (const t of albumTracks) { const s = getDownloadState(t); const done = s === 'downloaded' || s === 'finished'; const active = s === 'downloading' || s === 'processing' || s === 'queued' || s === 'Searching...'; if (active) anyActive = true; if (!done) allDone = false; } if (allDone) return 'downloaded'; if (anyActive) return 'downloading'; return undefined; }; const formatDuration = useCallback((ticks?: number) => { if (!ticks) return ''; // Handle different duration formats let totalSeconds: number; if (ticks > 10000000) { // Jellyfin ticks format (10,000,000 ticks per second) totalSeconds = Math.floor(ticks / 10000000); } else if (ticks > 1000) { // Milliseconds format totalSeconds = Math.floor(ticks / 1000); } else { // Already in seconds totalSeconds = Math.floor(ticks); } const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; }, []); // Memoize expensive computations const downloadStates = useMemo(() => { if (!isOpen || !item) return { downloadState: undefined, isDownloaded: false, isDownloading: false }; const downloadState = albumTracks.length > 0 ? getAlbumDownloadState() : getDownloadState(item); const isDownloaded = downloadState === 'downloaded' || downloadState === 'finished'; const isDownloading = downloadState === 'downloading' || downloadState === 'processing' || downloadState === 'queued' || downloadState === 'Searching...'; return { downloadState, isDownloaded, isDownloading }; }, [isOpen, item, albumTracks, downloadContext?.downloads, localDownloadStates]); if (!isOpen || !item) return null; const { downloadState, isDownloaded, isDownloading } = downloadStates; return (
e.stopPropagation()}> {/* Header with close button */}
{/* Main content */}
{/* Hero Section */}
{item.Name} { e.currentTarget.src = '/default-album-art.png'; }} />

{item.Name}

{item.Artists && item.Artists.length > 0 && (

searchForArtist(item.Artists![0])} title={`Search for ${item.Artists![0]} - Fresh search results`}> {item.Artists.join(', ')}

)} {item.Album && (

searchForAlbum(item.Album!, item.Artists?.[0])} title={`View ${item.Album} tracks - Shows album in this modal`}> {item.Album}

)}
{item.Type} {((item as any).explicit || (item as any).searchResult?.explicit || albumInfo?.explicit) && ( EXPLICIT )} {item.RunTimeTicks && (
⏱ {formatDuration(item.RunTimeTicks)}
)} {item.ProductionYear && (
📅 {item.ProductionYear}
)} {albumTracks.length > 0 && (
🎵 {albumTracks.length} tracks
)} {/* Release date from album info or item */} {(albumInfo?.release_date || item.PremiereDate) && (
📅 {albumInfo?.release_date ? new Date(albumInfo.release_date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : new Date(item.PremiereDate!).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}
)} {/* Record label */} {(albumInfo?.label || (item as any).Studio) && (
🏷️ {albumInfo?.label || (item as any).Studio}
)} {item.CommunityRating && (
★ {item.CommunityRating.toFixed(1)}
)}
{/* Album Description/Synopsis */} {(albumInfo?.description || item.Overview) && (

{albumInfo?.description || item.Overview}

)}
{/* Action Buttons */}
{item.SpotifyUrl && ( )}
{/* Enhanced Artist Tab System or Regular Tracks Section */} {isArtistItem ? (
{/* Artist Tabs */}
{/* Tab Content */} {activeTab === 'tracks' && ( artistTracks.length > 0 && (
{/* Back Button */}
{/* Album Header */} {currentAlbumView && (
{currentAlbumView.name}

{currentAlbumView.name}

{item?.Name || item?.AlbumArtist}

{currentAlbumView.release_year} • {currentAlbumView.album_type} • {currentAlbumView.total_tracks} tracks
)} {/* Album Tracks List */} {albumTracks.length > 0 && (
{albumTracks.map((track, index) => { const trackDownloadState = getDownloadState(track); const trackIsDownloaded = trackDownloadState === 'downloaded' || trackDownloadState === 'finished'; const trackIsDownloading = trackDownloadState === 'downloading' || trackDownloadState === 'processing' || trackDownloadState === 'queued' || trackDownloadState === 'Searching...'; return (
{(track as any).IndexNumber || index + 1}
{track.Name} {((track as any).explicit || (track as any).searchResult?.explicit) && ( E )}
{track.Artists?.join(', ') || track.AlbumArtist || 'Unknown Artist'}
{track.RunTimeTicks ? formatDuration(track.RunTimeTicks) : ''}
); })}
)}
) )} {activeTab === 'albums' && ( <> {showingAlbumTracks ? (
{/* Back Button */}
{/* Album Header */} {currentAlbumView && (
{currentAlbumView.name}

{currentAlbumView.name}

{currentAlbumView.artists?.[0] || 'Unknown Artist'}

{currentAlbumView.release_year} • {currentAlbumView.album_type} • {albumTracks.length} tracks
{/* Download Album Button */}
)} {/* Album Tracks List */} {loadingTracks ? (
⏳
Loading tracks...
) : (
{albumTracks.map((track, index) => (
{track.IndexNumber || (index + 1)}
{track.Name}
{Array.isArray(track.Artists) ? track.Artists.join(', ') : track.AlbumArtist}
{track.RunTimeTicks ? formatDuration(track.RunTimeTicks) : '--'}
))}
)}
) : (

💿 Albums

{artistAlbums.length} {artistAlbums.length === 1 ? 'album' : 'albums'}

{loadingAlbums ? (
⏳
Loading albums...
) : (
{artistAlbums.map((album) => (
handleViewAlbumTracks(album)} >
{album.name} {/* Download Button Overlay */} {/* Hover Overlay */}
{album.album_type} {album.total_tracks} tracks

{album.name}

{album.release_year}

))}
)} {!loadingAlbums && artistAlbums.length === 0 && (
💿
No albums found
)}
)} )}
) : ( /* Regular Tracks Section for Non-Artists */ albumTracks.length > 0 ? (

🎵 {albumInfo?.description?.includes('Top tracks') ? 'Top Tracks' : item.Type === 'Playlist' ? 'Playlist Tracks' : item.Type === 'MusicArtist' ? 'Top Tracks' : 'Album Tracks'}

{albumTracks.length} {albumTracks.length === 1 ? 'track' : 'tracks'} {albumTracks.reduce((total, track) => total + (track.RunTimeTicks || 0), 0) > 0 && ` • ${formatDuration(albumTracks.reduce((total, track) => total + (track.RunTimeTicks || 0), 0))}` } {albumInfo?.followers && ` • ${albumInfo.followers.toLocaleString()} followers`}

{albumTracks.map((track, index) => { const trackDownloadState = getDownloadState(track); const trackIsDownloaded = trackDownloadState === 'downloaded' || trackDownloadState === 'finished'; const trackIsDownloading = trackDownloadState === 'downloading' || trackDownloadState === 'processing' || trackDownloadState === 'queued' || trackDownloadState === 'Searching...'; return (
{(track as any).IndexNumber || index + 1}
{track.Name} {((track as any).explicit || (track as any).searchResult?.explicit) && ( E )}
{track.Artists?.join(', ') || track.AlbumArtist || 'Unknown Artist'}
{track.RunTimeTicks ? formatDuration(track.RunTimeTicks) : ''}
); })}
) : null )} {/* No tracks state */} {loadingTracks && (
⏳
Loading tracks...
)} {!loadingTracks && albumTracks.length === 0 && item.SpotifyId && (
🎵
{item.Type === 'MusicAlbum' ? 'No tracks found for this album' : item.Type === 'Playlist' ? 'No tracks found for this playlist' : 'No tracks found'}
This might be a Spotify-only item or the tracks haven't loaded yet.
)}
); };