import { create } from "zustand"; import { persist } from "zustand/middleware"; import { browseAll, browseCategory, searchAll, searchStickers as searchStickersFromProviders, } from "@/stickers"; import type { StickerBrowseResult, StickerSearchResult } from "@/stickers"; import { STICKER_CATEGORIES } from "@/stickers/categories"; import type { StickerCategory } from "@/stickers/types"; import { registerDefaultStickerProviders } from "@/stickers/providers"; import { stickersRegistry } from "@/stickers/registry"; import { parseStickerId } from "@/stickers/sticker-id"; const MAX_RECENT_STICKERS = 50; let browseRequestVersion = 0; function isValidStickerId(value: unknown): value is string { if (typeof value !== "string") { return false; } try { const parsed = parseStickerId({ stickerId: value }); return stickersRegistry.has(parsed.providerId); } catch { return false; } } function sanitizeRecentStickers({ recentStickers, }: { recentStickers: unknown; }): string[] { registerDefaultStickerProviders({}); if (!Array.isArray(recentStickers)) { return []; } const sanitized: string[] = []; for (const stickerId of recentStickers) { if (!isValidStickerId(stickerId)) { continue; } if (sanitized.includes(stickerId)) { continue; } sanitized.push(stickerId); if (sanitized.length >= MAX_RECENT_STICKERS) { break; } } return sanitized; } type ViewMode = "search" | "browse"; interface StickersStore { searchQuery: string; selectedCategory: StickerCategory; viewMode: ViewMode; searchResults: StickerSearchResult | null; browseContent: StickerBrowseResult | null; recentStickers: string[]; isSearching: boolean; isBrowsing: boolean; setSearchQuery: ({ query }: { query: string }) => void; setSelectedCategory: ({ category }: { category: StickerCategory }) => void; searchStickers: ({ query }: { query: string }) => Promise; browseStickers: () => Promise; addToRecentStickers: ({ stickerId }: { stickerId: string }) => void; clearRecentStickers: () => void; } export const useStickersStore = create()( persist( (set, get) => ({ searchQuery: "", selectedCategory: "all", viewMode: "browse", searchResults: null, browseContent: null, recentStickers: [], isSearching: false, isBrowsing: false, setSearchQuery: ({ query }) => set({ searchQuery: query }), setSelectedCategory: ({ category }) => { set({ selectedCategory: category in STICKER_CATEGORIES ? category : "all", browseContent: null, }); const query = get().searchQuery.trim(); if (query) { void get().searchStickers({ query }); return; } void get().browseStickers(); }, searchStickers: async ({ query }: { query: string }) => { const trimmedQuery = query.trim(); if (!trimmedQuery) { set({ searchResults: null, viewMode: "browse" }); await get().browseStickers(); return; } const category = get().selectedCategory; const selectedCategory = category in STICKER_CATEGORIES ? category : "all"; set({ isSearching: true, viewMode: "search" }); try { if (selectedCategory === "all") { const browseContent = await searchAll({ query: trimmedQuery }); set({ browseContent, searchResults: null }); } else { const results = await searchStickersFromProviders({ query: trimmedQuery, category: selectedCategory, limit: 100, }); set({ searchResults: results }); } } catch (error) { console.error("Search failed:", error); set({ searchResults: null }); } finally { set({ isSearching: false }); } }, browseStickers: async () => { const version = ++browseRequestVersion; const category = get().selectedCategory; const selectedCategory = category in STICKER_CATEGORIES ? category : "all"; set({ isBrowsing: true, viewMode: "browse" }); try { const browseContent = selectedCategory === "all" ? await browseAll({ recentStickers: get().recentStickers, }) : await browseCategory({ category: selectedCategory, }); if (version !== browseRequestVersion) return; set({ browseContent }); } catch (error) { if (version !== browseRequestVersion) return; console.error("Browse failed:", error); set({ browseContent: null }); } finally { if (version === browseRequestVersion) { set({ isBrowsing: false }); } } }, addToRecentStickers: ({ stickerId }: { stickerId: string }) => { const sanitizedStickerIds = sanitizeRecentStickers({ recentStickers: [stickerId], }); if (sanitizedStickerIds.length === 0) { return; } set((state) => { const recent = [ sanitizedStickerIds[0], ...state.recentStickers.filter((s) => s !== sanitizedStickerIds[0]), ]; return { recentStickers: recent.slice(0, MAX_RECENT_STICKERS), }; }); if (get().viewMode === "browse" && get().selectedCategory === "all") { void get().browseStickers(); } }, clearRecentStickers: () => { set({ recentStickers: [] }); if (get().viewMode === "browse" && get().selectedCategory === "all") { void get().browseStickers(); } }, }), { name: "stickers-settings", version: 1, migrate: (persistedState) => { if ( typeof persistedState === "object" && persistedState !== null && "selectedCategory" in persistedState ) { const typedState = persistedState as { selectedCategory?: string; recentStickers?: string[]; }; const category = typedState.selectedCategory ?? "all"; return { ...typedState, selectedCategory: category in STICKER_CATEGORIES ? (category as StickerCategory) : "all", recentStickers: sanitizeRecentStickers({ recentStickers: typedState.recentStickers ?? [], }), }; } return persistedState; }, partialize: (state) => ({ selectedCategory: state.selectedCategory, recentStickers: state.recentStickers, }), }, ), );