import React, { createContext, useContext, useState, useCallback, useEffect, useRef, } from "react"; import { useUser, useAuth } from "@clerk/clerk-expo"; import { getRecommendations, generateRecommendation, generateSelfRecommendation, type Recommendation, type GenerateRecommendationRequest, } from "../api/recommendations"; import log from "../utils/logger"; interface RecommendationsContextValue { recommendations: Recommendation[]; loading: boolean; error: Error | null; refetchRecommendations: () => Promise; generateNewRecommendation: ( data: GenerateRecommendationRequest, ) => Promise; generateSelfPlan: () => Promise; clearCache: () => void; } const RecommendationsContext = createContext< RecommendationsContextValue | undefined >(undefined); export function RecommendationsProvider({ children, }: { children: React.ReactNode; }) { const { user } = useUser(); const { getToken } = useAuth(); const [recommendations, setRecommendations] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [lastFetchTime, setLastFetchTime] = useState(0); const fetchInProgress = useRef(false); // Cache recommendations for 30 seconds const CACHE_DURATION = 30000; // 30 seconds const refetchRecommendations = useCallback(async () => { if (!user?.id) return; // Prevent concurrent fetches if (fetchInProgress.current) { log.debug("Recommendations fetch already in progress, skipping"); return; } // Check if we have recent cached data const now = Date.now(); if (recommendations.length > 0 && now - lastFetchTime < CACHE_DURATION) { log.debug("Using cached recommendations", { count: recommendations.length, age: now - lastFetchTime, cacheRemaining: CACHE_DURATION - (now - lastFetchTime), }); return; } try { fetchInProgress.current = true; setLoading(true); setError(null); log.debug("Fetching fresh recommendations", { userId: user.id }); const token = await getToken(); const data = await getRecommendations(user.id, token); setRecommendations(data); setLastFetchTime(now); log.debug("Recommendations fetched and cached", { count: data.length }); } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); log.error("Failed to fetch recommendations", error); setError(error); } finally { setLoading(false); fetchInProgress.current = false; } }, [user?.id, getToken, recommendations.length, lastFetchTime]); const generateNewRecommendation = useCallback( async (data: GenerateRecommendationRequest): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); const newRecommendation = await generateRecommendation(data, token); // Optimistically update cache setRecommendations((prev) => [...prev, newRecommendation]); setLastFetchTime(Date.now()); log.debug("Recommendation generated and added to cache", { recommendationId: newRecommendation.id, }); return newRecommendation; }, [user?.id, getToken], ); const generateSelfPlan = useCallback(async (): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); const recommendation = await generateSelfRecommendation(token); setRecommendations((prev) => [recommendation, ...prev]); setLastFetchTime(Date.now()); return recommendation; }, [user?.id, getToken]); const clearCache = useCallback(() => { setRecommendations([]); setLoading(false); setLastFetchTime(0); setError(null); fetchInProgress.current = false; log.debug("Recommendations cache cleared"); }, []); useEffect(() => { clearCache(); if (user?.id) { log.debug("Recommendations cache reset for user", { userId: user.id }); } else { log.debug("Recommendations cache reset on sign-out"); } }, [user?.id, clearCache]); return ( {children} ); } export function useRecommendations() { const context = useContext(RecommendationsContext); if (context === undefined) { throw new Error( "useRecommendations must be used within a RecommendationsProvider", ); } return context; }