import { useState, useEffect, useRef, useCallback } from 'react'; import { Search as SearchIcon, Play, Heart, PlusCircle, Loader2, Music2, Disc, User } from 'lucide-react'; import { useSearchParams, Link } from 'react-router-dom'; import { usePlayer } from '../context/PlayerContext'; import { libraryService } from '../services/library'; import { Track } from '../types'; import CoverImage from '../components/CoverImage'; import AddToPlaylistModal from '../components/AddToPlaylistModal'; import Skeleton from '../components/Skeleton'; import { useInfiniteScroll } from '../hooks/useInfiniteScroll'; // Helper to extract unique items function extractUniqueItems(tracks: Track[], key: 'artist' | 'album') { const seen = new Set(); const items: { name: string, image?: string, id: string }[] = []; tracks.forEach(t => { const val = t[key]; if (val && !seen.has(val)) { seen.add(val); items.push({ name: val, image: t.cover_url, // Use track cover as proxy for now id: `${key}-${val}` // robust id }); } }); return items.slice(0, 5); // Limit to top 5 } export default function Search() { const [searchParams, setSearchParams] = useSearchParams(); const routerQuery = searchParams.get('q') || ''; // Initialize state from local storage or router const [query, setQuery] = useState(() => { return routerQuery || localStorage.getItem('last_search_query') || ''; }); const [results, setResults] = useState(() => { const cached = localStorage.getItem('last_search_results'); return cached ? JSON.parse(cached) : []; }); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const [selectedTrack, setSelectedTrack] = useState(null); const { playTrack, likedTracks, toggleLike } = usePlayer(); const debounceRef = useRef | null>(null); // Derived State for Categories const relatedArtists = extractUniqueItems(results, 'artist'); const relatedAlbums = extractUniqueItems(results, 'album'); // Persistence Effect useEffect(() => { if (query) localStorage.setItem('last_search_query', query); if (results.length > 0) localStorage.setItem('last_search_results', JSON.stringify(results)); }, [query, results]); // Perform Search const performSearch = useCallback(async (searchQuery: string, isLoadMore = false) => { if (!searchQuery.trim()) { if (!isLoadMore) setResults([]); return; } if (!isLoadMore) setLoading(true); try { // "Smart Crawling" - fetching data const tracks = await libraryService.search(searchQuery); if (isLoadMore) { setResults(prev => { const existingIds = new Set(prev.map(t => t.id)); const newTracks = tracks.filter(t => !existingIds.has(t.id)); if (newTracks.length === 0) setHasMore(false); return [...prev, ...newTracks]; }); } else { setResults(tracks); if (tracks.length < 5) setHasMore(false); } } catch (error) { console.error("Search error:", error); } finally { setLoading(false); } }, []); const loadMore = () => { if (!loading && hasMore && query) { performSearch(query, true); } }; const lastElementRef = useInfiniteScroll(loadMore, loading); // Sync URL with State useEffect(() => { if (routerQuery && routerQuery !== query) { setQuery(routerQuery); performSearch(routerQuery); } else if (!routerQuery && query && results.length === 0) { // If nothing in URL but we have a stored query + no results, fetch // But if we have results from storage, maybe don't fetch immediately? // Let's refetch to be fresh if nothing in URL strictly (or just rely on storage) // For now, if we have results, we show them. if (!results.length) performSearch(query); // Update URL to match restored query setSearchParams({ q: query }, { replace: true }); } }, [routerQuery]); const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value; setQuery(value); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { if (value.trim()) setSearchParams({ q: value }); performSearch(value); }, 500); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (debounceRef.current) clearTimeout(debounceRef.current); setSearchParams({ q: query }); performSearch(query); }; return (
{/* Search Bar */}
{loading && results.length === 0 ? (
{[1, 2, 3, 4].map(i => )}
) : results.length > 0 ? (
{/* Top Result - Related Artists */} {relatedArtists.length > 0 && (

Artists

{relatedArtists.map(artist => (

{artist.name}

Artist

))}
)} {/* Related Albums */} {relatedAlbums.length > 0 && (

Albums

{relatedAlbums.map(album => (

{album.name}

Album

))}
)} {/* Songs List */}

Songs

{results.map((track, index) => (
playTrack(track, results)} >
{index + 1}

{track.title}

{track.artist}

{track.album}

{track.duration ? `${Math.floor(track.duration / 60)}:${(track.duration % 60).toString().padStart(2, '0')}` : '--:--'}
))}
{/* Infinite Scroll & Skeleton */}
{loading && (
{[1, 2, 3].map(i => (
))}
)}
) : (

Search for music

Find your favorite songs, artists, and albums.

)} {selectedTrack && ( setSelectedTrack(null)} /> )}
); }