import { create } from "zustand"; import type { SoundEffect, SavedSound } from "@/sounds/types"; import { storageService } from "@/services/storage/service"; import { toast } from "sonner"; import { EditorCore } from "@/core"; import { buildLibraryAudioElement } from "@/timeline/element-utils"; import { mediaTimeFromSeconds } from "@/wasm"; interface SoundsStore { topSoundEffects: SoundEffect[]; isLoading: boolean; error: string | null; hasLoaded: boolean; showCommercialOnly: boolean; toggleCommercialFilter: () => void; searchQuery: string; searchResults: SoundEffect[]; isSearching: boolean; searchError: string | null; lastSearchQuery: string; scrollPosition: number; currentPage: number; hasNextPage: boolean; totalCount: number; isLoadingMore: boolean; savedSounds: SavedSound[]; isSavedSoundsLoaded: boolean; isLoadingSavedSounds: boolean; savedSoundsError: string | null; addSoundToTimeline: ({ sound }: { sound: SoundEffect }) => Promise; setTopSoundEffects: ({ sounds }: { sounds: SoundEffect[] }) => void; setLoading: ({ loading }: { loading: boolean }) => void; setError: ({ error }: { error: string | null }) => void; setHasLoaded: ({ loaded }: { loaded: boolean }) => void; setSearchQuery: ({ query }: { query: string }) => void; setSearchResults: ({ results }: { results: SoundEffect[] }) => void; setSearching: ({ searching }: { searching: boolean }) => void; setSearchError: ({ error }: { error: string | null }) => void; setLastSearchQuery: ({ query }: { query: string }) => void; setScrollPosition: ({ position }: { position: number }) => void; setCurrentPage: ({ page }: { page: number }) => void; setHasNextPage: ({ hasNext }: { hasNext: boolean }) => void; setTotalCount: ({ count }: { count: number }) => void; setLoadingMore: ({ loading }: { loading: boolean }) => void; appendSearchResults: ({ results }: { results: SoundEffect[] }) => void; appendTopSounds: ({ results }: { results: SoundEffect[] }) => void; resetPagination: () => void; loadSavedSounds: () => Promise; saveSoundEffect: ({ soundEffect, }: { soundEffect: SoundEffect; }) => Promise; removeSavedSound: ({ soundId }: { soundId: number }) => Promise; isSoundSaved: ({ soundId }: { soundId: number }) => boolean; toggleSavedSound: ({ soundEffect, }: { soundEffect: SoundEffect; }) => Promise; clearSavedSounds: () => Promise; } export const useSoundsStore = create((set, get) => ({ topSoundEffects: [], isLoading: false, error: null, hasLoaded: false, showCommercialOnly: true, toggleCommercialFilter: () => { set((state) => ({ showCommercialOnly: !state.showCommercialOnly })); }, searchQuery: "", searchResults: [], isSearching: false, searchError: null, lastSearchQuery: "", scrollPosition: 0, currentPage: 1, hasNextPage: false, totalCount: 0, isLoadingMore: false, savedSounds: [], isSavedSoundsLoaded: false, isLoadingSavedSounds: false, savedSoundsError: null, setTopSoundEffects: ({ sounds }) => set({ topSoundEffects: sounds }), setLoading: ({ loading }) => set({ isLoading: loading }), setError: ({ error }) => set({ error }), setHasLoaded: ({ loaded }) => set({ hasLoaded: loaded }), setSearchQuery: ({ query }) => set({ searchQuery: query }), setSearchResults: ({ results }) => set({ searchResults: results, currentPage: 1 }), setSearching: ({ searching }) => set({ isSearching: searching }), setSearchError: ({ error }) => set({ searchError: error }), setLastSearchQuery: ({ query }) => set({ lastSearchQuery: query }), setScrollPosition: ({ position }) => set({ scrollPosition: position }), setCurrentPage: ({ page }) => set({ currentPage: page }), setHasNextPage: ({ hasNext }) => set({ hasNextPage: hasNext }), setTotalCount: ({ count }) => set({ totalCount: count }), setLoadingMore: ({ loading }) => set({ isLoadingMore: loading }), appendSearchResults: ({ results }) => set((state) => ({ searchResults: [...state.searchResults, ...results], })), appendTopSounds: ({ results }) => set((state) => ({ topSoundEffects: [...state.topSoundEffects, ...results], })), resetPagination: () => set({ currentPage: 1, hasNextPage: false, totalCount: 0, isLoadingMore: false, }), loadSavedSounds: async () => { if (get().isSavedSoundsLoaded) return; try { set({ isLoadingSavedSounds: true, savedSoundsError: null }); const savedSoundsData = await storageService.loadSavedSounds(); set({ savedSounds: savedSoundsData.sounds, isSavedSoundsLoaded: true, isLoadingSavedSounds: false, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to load saved sounds"; set({ savedSoundsError: errorMessage, isLoadingSavedSounds: false, }); console.error("Failed to load saved sounds:", error); } }, saveSoundEffect: async ({ soundEffect }) => { try { await storageService.saveSoundEffect({ soundEffect }); const savedSoundsData = await storageService.loadSavedSounds(); set({ savedSounds: savedSoundsData.sounds }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to save sound"; set({ savedSoundsError: errorMessage }); toast.error("Failed to save sound"); console.error("Failed to save sound:", error); } }, removeSavedSound: async ({ soundId }) => { try { await storageService.removeSavedSound({ soundId }); set((state) => ({ savedSounds: state.savedSounds.filter((sound) => sound.id !== soundId), })); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to remove sound"; set({ savedSoundsError: errorMessage }); toast.error("Failed to remove sound"); console.error("Failed to remove sound:", error); } }, isSoundSaved: ({ soundId }) => { const { savedSounds } = get(); return savedSounds.some((sound) => sound.id === soundId); }, toggleSavedSound: async ({ soundEffect }) => { const { isSoundSaved, saveSoundEffect, removeSavedSound } = get(); if (isSoundSaved({ soundId: soundEffect.id })) { await removeSavedSound({ soundId: soundEffect.id }); } else { await saveSoundEffect({ soundEffect }); } }, clearSavedSounds: async () => { try { await storageService.clearSavedSounds(); set({ savedSounds: [], savedSoundsError: null, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to clear saved sounds"; set({ savedSoundsError: errorMessage }); toast.error("Failed to clear saved sounds"); console.error("Failed to clear saved sounds:", error); } }, addSoundToTimeline: async ({ sound }) => { const audioUrl = sound.previewUrl; if (!audioUrl) { toast.error("Sound file not available"); return false; } try { const editor = EditorCore.getInstance(); const currentTime = editor.playback.getCurrentTime(); const response = await fetch(audioUrl); if (!response.ok) throw new Error(`Failed to download audio: ${response.statusText}`); const arrayBuffer = await response.arrayBuffer(); const audioContext = new AudioContext(); const buffer = await audioContext.decodeAudioData(arrayBuffer); const element = buildLibraryAudioElement({ sourceUrl: audioUrl, name: sound.name, duration: mediaTimeFromSeconds({ seconds: sound.duration }), startTime: currentTime, buffer, }); editor.timeline.insertElement({ placement: { mode: "auto", trackType: "audio" }, element, }); return true; } catch (error) { console.error("Failed to add sound to timeline:", error); toast.error( error instanceof Error ? error.message : "Failed to add sound to timeline", { id: `sound-${sound.id}` }, ); return false; } }, }));