import { TProject } from "@/types/project"; import { create } from "zustand"; import { storageService } from "@/lib/storage/storage-service"; import { toast } from "sonner"; import { useMediaStore } from "./media-store"; import { useTimelineStore } from "./timeline-store"; import { generateUUID } from "@/lib/utils"; interface ProjectStore { activeProject: TProject | null; savedProjects: TProject[]; isLoading: boolean; isInitialized: boolean; // Actions createNewProject: (name: string) => Promise; loadProject: (id: string) => Promise; saveCurrentProject: () => Promise; loadAllProjects: () => Promise; deleteProject: (id: string) => Promise; closeProject: () => void; renameProject: (projectId: string, name: string) => Promise; duplicateProject: (projectId: string) => Promise; updateProjectBackground: (backgroundColor: string) => Promise; updateBackgroundType: ( type: "color" | "blur", options?: { backgroundColor?: string; blurIntensity?: number } ) => Promise; updateProjectFps: (fps: number) => Promise; getFilteredAndSortedProjects: ( searchQuery: string, sortOption: string ) => TProject[]; } export const useProjectStore = create((set, get) => ({ activeProject: null, savedProjects: [], isLoading: true, isInitialized: false, createNewProject: async (name: string) => { const newProject: TProject = { id: generateUUID(), name, thumbnail: "", createdAt: new Date(), updatedAt: new Date(), backgroundColor: "#000000", backgroundType: "color", blurIntensity: 8, }; set({ activeProject: newProject }); try { await storageService.saveProject(newProject); // Reload all projects to update the list await get().loadAllProjects(); return newProject.id; } catch (error) { toast.error("Failed to save new project"); throw error; } }, loadProject: async (id: string) => { if (!get().isInitialized) { set({ isLoading: true }); } // Clear media and timeline immediately to prevent flickering when switching projects const mediaStore = useMediaStore.getState(); const timelineStore = useTimelineStore.getState(); mediaStore.clearAllMedia(); timelineStore.clearTimeline(); try { const project = await storageService.loadProject(id); if (project) { set({ activeProject: project }); // Load project-specific data in parallel await Promise.all([ mediaStore.loadProjectMedia(id), timelineStore.loadProjectTimeline(id), ]); } else { throw new Error(`Project with id ${id} not found`); } } catch (error) { console.error("Failed to load project:", error); throw error; // Re-throw so the editor page can handle it } finally { set({ isLoading: false }); } }, saveCurrentProject: async () => { const { activeProject } = get(); if (!activeProject) return; try { // Save project metadata and timeline data in parallel const timelineStore = useTimelineStore.getState(); await Promise.all([ storageService.saveProject(activeProject), timelineStore.saveProjectTimeline(activeProject.id), ]); await get().loadAllProjects(); // Refresh the list } catch (error) { console.error("Failed to save project:", error); } }, loadAllProjects: async () => { if (!get().isInitialized) { set({ isLoading: true }); } try { const projects = await storageService.loadAllProjects(); set({ savedProjects: projects }); } catch (error) { console.error("Failed to load projects:", error); } finally { set({ isLoading: false, isInitialized: true }); } }, deleteProject: async (id: string) => { try { // Delete project data in parallel await Promise.all([ storageService.deleteProjectMedia(id), storageService.deleteProjectTimeline(id), storageService.deleteProject(id), ]); await get().loadAllProjects(); // Refresh the list // If we deleted the active project, close it and clear data const { activeProject } = get(); if (activeProject?.id === id) { set({ activeProject: null }); const mediaStore = useMediaStore.getState(); const timelineStore = useTimelineStore.getState(); mediaStore.clearAllMedia(); timelineStore.clearTimeline(); } } catch (error) { console.error("Failed to delete project:", error); } }, closeProject: () => { set({ activeProject: null }); // Clear data from stores when closing project const mediaStore = useMediaStore.getState(); const timelineStore = useTimelineStore.getState(); mediaStore.clearAllMedia(); timelineStore.clearTimeline(); }, renameProject: async (id: string, name: string) => { const { savedProjects } = get(); // Find the project to rename const projectToRename = savedProjects.find((p) => p.id === id); if (!projectToRename) { toast.error("Project not found", { description: "Please try again", }); return; } const updatedProject = { ...projectToRename, name, updatedAt: new Date(), }; try { // Save to storage await storageService.saveProject(updatedProject); await get().loadAllProjects(); // Update activeProject if it's the same project const { activeProject } = get(); if (activeProject?.id === id) { set({ activeProject: updatedProject }); } } catch (error) { console.error("Failed to rename project:", error); toast.error("Failed to rename project", { description: error instanceof Error ? error.message : "Please try again", }); } }, duplicateProject: async (projectId: string) => { try { const project = await storageService.loadProject(projectId); if (!project) { toast.error("Project not found", { description: "Please try again", }); throw new Error("Project not found"); } const { savedProjects } = get(); // Extract the base name (remove any existing numbering) const numberMatch = project.name.match(/^\((\d+)\)\s+(.+)$/); const baseName = numberMatch ? numberMatch[2] : project.name; const existingNumbers: number[] = []; // Check for pattern "(number) baseName" in existing projects savedProjects.forEach((p) => { const match = p.name.match(/^\((\d+)\)\s+(.+)$/); if (match && match[2] === baseName) { existingNumbers.push(parseInt(match[1], 10)); } }); const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1; const newProject: TProject = { id: generateUUID(), name: `(${nextNumber}) ${baseName}`, thumbnail: project.thumbnail, createdAt: new Date(), updatedAt: new Date(), }; await storageService.saveProject(newProject); await get().loadAllProjects(); return newProject.id; } catch (error) { console.error("Failed to duplicate project:", error); toast.error("Failed to duplicate project", { description: error instanceof Error ? error.message : "Please try again", }); throw error; } }, updateProjectBackground: async (backgroundColor: string) => { const { activeProject } = get(); if (!activeProject) return; const updatedProject = { ...activeProject, backgroundColor, updatedAt: new Date(), }; try { await storageService.saveProject(updatedProject); set({ activeProject: updatedProject }); await get().loadAllProjects(); // Refresh the list } catch (error) { console.error("Failed to update project background:", error); toast.error("Failed to update background", { description: "Please try again", }); } }, updateBackgroundType: async ( type: "color" | "blur", options?: { backgroundColor?: string; blurIntensity?: number } ) => { const { activeProject } = get(); if (!activeProject) return; const updatedProject = { ...activeProject, backgroundType: type, ...(options?.backgroundColor && { backgroundColor: options.backgroundColor, }), ...(options?.blurIntensity && { blurIntensity: options.blurIntensity }), updatedAt: new Date(), }; try { await storageService.saveProject(updatedProject); set({ activeProject: updatedProject }); await get().loadAllProjects(); // Refresh the list } catch (error) { console.error("Failed to update background type:", error); toast.error("Failed to update background", { description: "Please try again", }); } }, updateProjectFps: async (fps: number) => { const { activeProject } = get(); if (!activeProject) return; const updatedProject = { ...activeProject, fps, updatedAt: new Date(), }; try { await storageService.saveProject(updatedProject); set({ activeProject: updatedProject }); await get().loadAllProjects(); // Refresh the list } catch (error) { console.error("Failed to update project FPS:", error); toast.error("Failed to update project FPS", { description: "Please try again", }); } }, getFilteredAndSortedProjects: (searchQuery: string, sortOption: string) => { const { savedProjects } = get(); // Filter projects by search query const filteredProjects = savedProjects.filter((project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()) ); // Sort filtered projects const sortedProjects = [...filteredProjects].sort((a, b) => { const [key, order] = sortOption.split("-"); if (key !== "createdAt" && key !== "name") { console.warn(`Invalid sort key: ${key}`); return 0; } const aValue = a[key]; const bValue = b[key]; if (aValue === undefined || bValue === undefined) return 0; if (order === "asc") { if (aValue < bValue) return -1; if (aValue > bValue) return 1; return 0; } else { if (aValue > bValue) return -1; if (aValue < bValue) return 1; return 0; } }); return sortedProjects; }, }));