import { useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { ScrollArea } from '@/components/ui/scroll-area' import { filesService } from '@/lib/api/services/files' import { playerService } from '@/lib/api/services/player' import { playlistsService } from '@/lib/api/services/playlists' import { soundsService, type Sound } from '@/lib/api/services/sounds' import { formatDuration } from '@/utils/format-duration' import { Music, PlayCircle, Search, X } from 'lucide-react' import { toast } from 'sonner' interface GlobalSearchProps { isOpen: boolean onClose: () => void } type SearchResultType = 'sound' | 'playlist' | 'playlist-track' interface SearchResult { id: string type: SearchResultType title: string subtitle?: string duration?: number thumbnail?: string soundType?: 'SDB' | 'TTS' | 'EXT' playlistId?: number trackIndex?: number } export function GlobalSearch({ isOpen, onClose }: GlobalSearchProps) { const [searchQuery, setSearchQuery] = useState('') const [results, setResults] = useState([]) const [loading, setLoading] = useState(false) const [currentPlaylistTracks, setCurrentPlaylistTracks] = useState([]) useEffect(() => { if (!isOpen) { setSearchQuery('') setResults([]) setCurrentPlaylistTracks([]) } else { // Load current playlist tracks when opening loadCurrentPlaylistTracks() } }, [isOpen]) useEffect(() => { if (searchQuery.trim()) { performSearch() } else { setResults([]) } }, [searchQuery]) const loadCurrentPlaylistTracks = async () => { try { const state = await playerService.getState() if (state.playlist) { setCurrentPlaylistTracks(state.playlist.sounds as unknown as Sound[]) } } catch (error) { console.error('Failed to load current playlist tracks:', error) } } const performSearch = async () => { setLoading(true) try { const query = searchQuery.trim().toLowerCase() const newResults: SearchResult[] = [] // Search sounds (SDB and TTS) const sounds = await soundsService.getSounds({ search: query, types: ['SDB', 'TTS'], limit: 20, }) sounds.forEach((sound) => { newResults.push({ id: `sound-${sound.id}`, type: 'sound', title: sound.name, subtitle: sound.type, duration: sound.duration, thumbnail: sound.thumbnail, soundType: sound.type, }) }) // Search playlists const playlistsResponse = await playlistsService.getPlaylists({ search: query, limit: 10, }) playlistsResponse.playlists.forEach((playlist) => { newResults.push({ id: `playlist-${playlist.id}`, type: 'playlist', title: playlist.name, subtitle: `${playlist.sound_count} tracks`, duration: playlist.total_duration, playlistId: playlist.id, }) }) // Search current playlist tracks currentPlaylistTracks.forEach((track, index) => { if (track.name.toLowerCase().includes(query)) { newResults.push({ id: `track-${track.id}-${index}`, type: 'playlist-track', title: track.name, subtitle: 'Current playlist', duration: track.duration, thumbnail: track.thumbnail, trackIndex: index, }) } }) setResults(newResults) } catch (error) { console.error('Search failed:', error) toast.error('Search failed') } finally { setLoading(false) } } const handleResultClick = async (result: SearchResult) => { try { if (result.type === 'sound') { const soundId = parseInt(result.id.replace('sound-', '')) await soundsService.playSound(soundId) toast.success(`Playing ${result.title}`) } else if (result.type === 'playlist' && result.playlistId) { await playlistsService.setCurrentPlaylist(result.playlistId) toast.success(`Set ${result.title} as current playlist`) } else if (result.type === 'playlist-track' && result.trackIndex !== undefined) { await playerService.playAtIndex(result.trackIndex) toast.success(`Playing ${result.title}`) } onClose() } catch (error) { console.error('Action failed:', error) toast.error('Action failed') } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { onClose() } } // Handle global escape key useEffect(() => { if (!isOpen) return const handleGlobalKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose() } } window.addEventListener('keydown', handleGlobalKeyDown) return () => window.removeEventListener('keydown', handleGlobalKeyDown) }, [isOpen, onClose]) if (!isOpen) return null return (
e.stopPropagation()} > {/* Search Input */}
setSearchQuery(e.target.value)} onKeyDown={handleKeyDown} className="pl-10 pr-10" autoFocus />
{/* Results */}
{loading && (
Searching...
)} {!loading && searchQuery && results.length === 0 && (
No results found
)} {!loading && results.length > 0 && (
{/* Sounds Section */} {results.filter((r) => r.type === 'sound').length > 0 && (
Sounds ({results.filter((r) => r.type === 'sound').length})
{results .filter((r) => r.type === 'sound') .map((result) => ( ))}
)} {/* Playlists Section */} {results.filter((r) => r.type === 'playlist').length > 0 && (
Playlists ({results.filter((r) => r.type === 'playlist').length})
{results .filter((r) => r.type === 'playlist') .map((result) => ( ))}
)} {/* Playlist Tracks Section */} {results.filter((r) => r.type === 'playlist-track').length > 0 && (
Current Playlist ({results.filter((r) => r.type === 'playlist-track').length})
{results .filter((r) => r.type === 'playlist-track') .map((result) => ( ))}
)}
)} {!searchQuery && (
Type to search sounds, playlists, or tracks
)}
{/* Footer */}
Press ESC to close {results.length > 0 && {results.length} results}
) }