'use client'; import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { Song, Album, getNavidromeAPI } from '@/lib/navidrome'; import { useOfflineNavidrome } from '@/app/components/OfflineNavidromeProvider'; import { useAudioPlayer } from '@/app/components/AudioPlayerContext'; import { useIsMobile } from '@/hooks/use-mobile'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Play, Heart, Music, Shuffle } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; import { UserProfile } from './UserProfile'; interface SongRecommendationsProps { userName?: string; } export function SongRecommendations({ userName }: SongRecommendationsProps) { const offline = useOfflineNavidrome(); const { playTrack, shuffle, toggleShuffle } = useAudioPlayer(); const isMobile = useIsMobile(); const [recommendedSongs, setRecommendedSongs] = useState([]); const [recommendedAlbums, setRecommendedAlbums] = useState([]); const [loading, setLoading] = useState(true); const [songStates, setSongStates] = useState>({}); // Memoize the greeting to prevent recalculation const greeting = useMemo(() => { const hour = new Date().getHours(); return hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening'; }, []); // Memoized callbacks to prevent re-renders const handleImageLoad = useCallback(() => { // Image loaded - no state update needed to prevent re-renders }, []); const handleImageError = useCallback(() => { // Image error - no state update needed to prevent re-renders }, []); useEffect(() => { const loadRecommendations = async () => { setLoading(true); try { const api = getNavidromeAPI(); const isOnline = !offline.isOfflineMode && !!api; if (isOnline && api) { // Online: use server-side recommendations const randomAlbums = await api.getAlbums('random', 10); if (isMobile) { setRecommendedAlbums(randomAlbums.slice(0, 6)); } else { const allSongs: Song[] = []; for (let i = 0; i < Math.min(3, randomAlbums.length); i++) { try { const albumSongs = await api.getAlbumSongs(randomAlbums[i].id); allSongs.push(...albumSongs); } catch (error) { console.error('Failed to get album songs:', error); } } const shuffled = allSongs.sort(() => Math.random() - 0.5); const recommendations = shuffled.slice(0, 6); setRecommendedSongs(recommendations); const states: Record = {}; recommendations.forEach((song: Song) => { states[song.id] = !!song.starred; }); setSongStates(states); } } else { // Offline: use cached library const albums = await offline.getAlbums(false); const shuffledAlbums = [...(albums || [])].sort(() => Math.random() - 0.5); if (isMobile) { setRecommendedAlbums(shuffledAlbums.slice(0, 6)); } else { const pick = shuffledAlbums.slice(0, 3); const allSongs: Song[] = []; for (const a of pick) { try { const songs = await offline.getSongs(a.id); allSongs.push(...songs); } catch (e) { // ignore per-album errors } } const recommendations = allSongs.sort(() => Math.random() - 0.5).slice(0, 6); setRecommendedSongs(recommendations); const states: Record = {}; recommendations.forEach((song: Song) => { states[song.id] = !!song.starred; }); setSongStates(states); } } } catch (error) { console.error('Failed to load recommendations:', error); setRecommendedAlbums([]); setRecommendedSongs([]); } finally { setLoading(false); } }; loadRecommendations(); }, [offline, isMobile]); const handlePlaySong = async (song: Song) => { try { const api = getNavidromeAPI(); const url = api ? api.getStreamUrl(song.id) : `offline-song-${song.id}`; const coverArt = song.coverArt && api ? api.getCoverArtUrl(song.coverArt, 300) : undefined; const track = { id: song.id, name: song.title, url, artist: song.artist || 'Unknown Artist', artistId: song.artistId || '', album: song.album || 'Unknown Album', albumId: song.albumId || '', duration: song.duration || 0, coverArt, starred: !!song.starred }; await playTrack(track, true); } catch (error) { console.error('Failed to play song:', error); } }; const handlePlayAlbum = async (album: Album) => { try { const api = getNavidromeAPI(); let albumSongs: Song[] = []; if (api) { albumSongs = await api.getAlbumSongs(album.id); } else { albumSongs = await offline.getSongs(album.id); } if (albumSongs.length > 0) { const first = albumSongs[0]; const url = api ? api.getStreamUrl(first.id) : `offline-song-${first.id}`; const coverArt = first.coverArt && api ? api.getCoverArtUrl(first.coverArt, 300) : undefined; const track = { id: first.id, name: first.title, url, artist: first.artist || 'Unknown Artist', artistId: first.artistId || '', album: first.album || 'Unknown Album', albumId: first.albumId || '', duration: first.duration || 0, coverArt, starred: !!first.starred }; await playTrack(track, true); } } catch (error) { console.error('Failed to play album:', error); } }; const handleShuffleAll = async () => { if (isMobile && recommendedAlbums.length === 0) return; if (!isMobile && recommendedSongs.length === 0) return; // Enable shuffle if not already on if (!shuffle) { toggleShuffle(); } if (isMobile) { // Play a random album const randomAlbum = recommendedAlbums[Math.floor(Math.random() * recommendedAlbums.length)]; await handlePlayAlbum(randomAlbum); } else { // Play a random song from recommendations const randomSong = recommendedSongs[Math.floor(Math.random() * recommendedSongs.length)]; await handlePlaySong(randomSong); } }; const formatDuration = (duration: number): string => { const minutes = Math.floor(duration / 60); const seconds = duration % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; if (loading) { return (
{isMobile ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : (
{Array.from({ length: 6 }).map((_, i) => (
))}
)}
); } return (

{greeting}{userName ? `, ${userName}` : ''}!

{isMobile ? 'Here are some albums you might enjoy' : 'Here are some songs you might enjoy'}

{/* Mobile User Profile */} {isMobile && } {/* Shuffle All Button (Desktop only) */} {(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && !isMobile && ( )}
{isMobile ? ( /* Mobile: Show albums in 3x2 grid */ recommendedAlbums.length > 0 ? (
{recommendedAlbums.map((album) => (
{album.coverArt && !offline.isOfflineMode && getNavidromeAPI() ? ( {album.name} ) : (
)}
{album.name} {album.artist}
))}
) : (

No albums available for recommendations

) ) : ( /* Desktop: Show songs in original format */ recommendedSongs.length > 0 ? (
{recommendedSongs.map((song) => ( handlePlaySong(song)} >
{song.coverArt && !offline.isOfflineMode && getNavidromeAPI() ? ( <> {song.title}
) : (
)}

{song.title}

e.stopPropagation()} > {song.artist} {song.duration && ( <> {formatDuration(song.duration)} )}
{songStates[song.id] && ( )}
))}
) : (

No songs available for recommendations

) )}
); }