import React, { createContext, useContext, useState, useCallback, useEffect, useRef, } from "react"; import { useUser, useAuth } from "@clerk/clerk-expo"; import { fitnessGoalsService, type FitnessGoal, type CreateGoalData, } from "../services/fitnessGoals"; import log from "../utils/logger"; interface FitnessGoalsContextValue { goals: FitnessGoal[]; loading: boolean; error: Error | null; refetchGoals: () => Promise; createGoal: (goalData: CreateGoalData) => Promise; updateGoal: ( id: string, updates: Partial, ) => Promise; completeGoal: (id: string) => Promise; deleteGoal: (id: string) => Promise; clearCache: () => void; } const FitnessGoalsContext = createContext( undefined, ); export function FitnessGoalsProvider({ children, }: { children: React.ReactNode; }) { const { user } = useUser(); const { getToken } = useAuth(); const [goals, setGoals] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [lastFetchTime, setLastFetchTime] = useState(0); const fetchInProgress = useRef(false); // Cache goals for 30 seconds to avoid duplicate calls const CACHE_DURATION = 30000; // 30 seconds const refetchGoals = useCallback(async () => { if (!user?.id) return; // Prevent concurrent fetches if (fetchInProgress.current) { log.debug("Fetch already in progress, skipping duplicate call"); return; } // Check if we have recent cached data const now = Date.now(); if (goals.length > 0 && now - lastFetchTime < CACHE_DURATION) { log.debug("Using cached fitness goals", { count: goals.length, age: now - lastFetchTime, cacheRemaining: CACHE_DURATION - (now - lastFetchTime), }); return; } try { fetchInProgress.current = true; setLoading(true); setError(null); log.debug("Fetching fresh fitness goals", { userId: user.id }); const token = await getToken(); const loadedGoals = await fitnessGoalsService.getGoals(user.id, token); setGoals(loadedGoals); setLastFetchTime(now); log.debug("Fitness goals fetched and cached", { count: loadedGoals.length, }); } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); log.error("Failed to fetch fitness goals", error); setError(error); } finally { setLoading(false); fetchInProgress.current = false; } }, [user?.id, getToken, goals.length, lastFetchTime]); const createGoal = useCallback( async (goalData: CreateGoalData): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); const newGoal = await fitnessGoalsService.createGoal(goalData, token); // Optimistically update cache setGoals((prev) => [...prev, newGoal]); setLastFetchTime(Date.now()); log.debug("Goal created and added to cache", { goalId: newGoal.id }); return newGoal; }, [user?.id, getToken], ); const updateGoal = useCallback( async (id: string, updates: Partial): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); const updatedGoal = await fitnessGoalsService.updateGoal( id, updates, token, ); // Optimistically update cache setGoals((prev) => prev.map((goal) => (goal.id === id ? updatedGoal : goal)), ); setLastFetchTime(Date.now()); log.debug("Goal updated in cache", { goalId: id }); return updatedGoal; }, [user?.id, getToken], ); const completeGoal = useCallback( async (id: string): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); const completedGoal = await fitnessGoalsService.completeGoal(id, token); // Optimistically update cache setGoals((prev) => prev.map((goal) => (goal.id === id ? completedGoal : goal)), ); setLastFetchTime(Date.now()); log.debug("Goal completed in cache", { goalId: id }); return completedGoal; }, [user?.id, getToken], ); const deleteGoal = useCallback( async (id: string): Promise => { if (!user?.id) throw new Error("User not authenticated"); const token = await getToken(); await fitnessGoalsService.deleteGoal(id, token); // Optimistically update cache setGoals((prev) => prev.filter((goal) => goal.id !== id)); setLastFetchTime(Date.now()); log.debug("Goal deleted from cache", { goalId: id }); }, [user?.id, getToken], ); const clearCache = useCallback(() => { setGoals([]); setLoading(false); setLastFetchTime(0); setError(null); fetchInProgress.current = false; log.debug("Fitness goals cache cleared"); }, []); useEffect(() => { clearCache(); if (user?.id) { log.debug("Fitness goals cache reset for user", { userId: user.id }); } else { log.debug("Fitness goals cache reset on sign-out"); } }, [user?.id, clearCache]); return ( {children} ); } export function useFitnessGoals() { const context = useContext(FitnessGoalsContext); if (context === undefined) { throw new Error( "useFitnessGoals must be used within a FitnessGoalsProvider", ); } return context; }