import { useRef, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; /** * Profile data structure returned from the database */ export interface CachedProfile { user_id: string; username: string; display_name?: string; avatar_url?: string; } /** * Hook for managing user profile caching * * Uses ref-based storage to avoid triggering re-renders while providing * efficient caching for user profile lookups during moderation. * * @example * ```tsx * const profileCache = useProfileCache(); * * // Get cached profile * const profile = profileCache.getCached(userId); * * // Bulk fetch and cache profiles * const profiles = await profileCache.bulkFetch([id1, id2, id3]); * * // Check if profile exists in cache * if (profileCache.has(userId)) { * const profile = profileCache.getCached(userId); * } * * // Clear cache * profileCache.clear(); * ``` */ export function useProfileCache() { // Use ref to prevent re-renders on cache updates const cacheRef = useRef>(new Map()); /** * Get a cached profile by user ID */ const getCached = useCallback((userId: string): CachedProfile | undefined => { return cacheRef.current.get(userId); }, []); /** * Check if a profile is cached */ const has = useCallback((userId: string): boolean => { return cacheRef.current.has(userId); }, []); /** * Set a cached profile */ const setCached = useCallback((userId: string, profile: CachedProfile): void => { cacheRef.current.set(userId, profile); }, []); /** * Get uncached user IDs from a list */ const getUncachedIds = useCallback((userIds: string[]): string[] => { return userIds.filter(id => !cacheRef.current.has(id)); }, []); /** * Bulk fetch user profiles from the database and cache them * Only fetches profiles that aren't already cached * * @param userIds - Array of user IDs to fetch * @returns Array of fetched profiles */ const bulkFetch = useCallback(async (userIds: string[]): Promise => { if (userIds.length === 0) return []; // Filter to only uncached IDs const uncachedIds = getUncachedIds(userIds); if (uncachedIds.length === 0) { // All profiles are cached, return them return userIds.map(id => getCached(id)).filter((p): p is CachedProfile => !!p); } try { const { data, error } = await supabase .from('profiles') .select('user_id, username, display_name, avatar_url') .in('user_id', uncachedIds); if (error) { console.error('Error fetching profiles:', error); return []; } // Cache the fetched profiles if (data) { data.forEach((profile: CachedProfile) => { setCached(profile.user_id, profile); }); } return data || []; } catch (error) { console.error('Failed to bulk fetch profiles:', error); return []; } }, [getCached, setCached, getUncachedIds]); /** * Fetch and return profiles for a list of user IDs * Returns a Map for easy lookup * * @param userIds - Array of user IDs to fetch * @returns Map of userId -> profile */ const fetchAsMap = useCallback(async (userIds: string[]): Promise> => { const profiles = await bulkFetch(userIds); return new Map(profiles.map(p => [p.user_id, p])); }, [bulkFetch]); /** * Fetch profiles for submitters and reviewers from submissions * Automatically extracts user IDs and reviewer IDs from submission data * * @param submissions - Array of submissions with user_id and reviewer_id * @returns Map of userId -> profile for all users involved */ const fetchForSubmissions = useCallback(async (submissions: any[]): Promise> => { const userIds = submissions.map(s => s.user_id).filter(Boolean); const reviewerIds = submissions.map(s => s.reviewer_id).filter((id): id is string => !!id); const allUserIds = [...new Set([...userIds, ...reviewerIds])]; return await fetchAsMap(allUserIds); }, [fetchAsMap]); /** * Get a display name for a user (display_name or username) * Returns 'Unknown User' if not found in cache */ const getDisplayName = useCallback((userId: string): string => { const profile = getCached(userId); if (!profile) return 'Unknown User'; return profile.display_name || profile.username || 'Unknown User'; }, [getCached]); /** * Invalidate (remove) a specific profile from cache */ const invalidate = useCallback((userId: string): void => { cacheRef.current.delete(userId); }, []); /** * Clear all cached profiles */ const clear = useCallback((): void => { cacheRef.current.clear(); }, []); /** * Get cache size */ const getSize = useCallback((): number => { return cacheRef.current.size; }, []); /** * Get all cached profile user IDs */ const getAllCachedIds = useCallback((): string[] => { return Array.from(cacheRef.current.keys()); }, []); /** * Get direct access to cache ref (for advanced use cases) * Use with caution - prefer using the provided methods */ const getCacheRef = useCallback(() => cacheRef.current, []); return { getCached, has, setCached, getUncachedIds, bulkFetch, fetchAsMap, fetchForSubmissions, getDisplayName, invalidate, clear, getSize, getAllCachedIds, getCacheRef, }; }