import { formatDateKey } from '@/common/utils/format'; import type { FoodLog, FoodRecommendation, UserProfile } from '@/types/foodlog'; import { useCallback, useEffect, useRef, useState } from 'react'; import * as Repository from './RecommendationRepository'; import { generateRecommendations, DAYS_TO_REVIEW } from './recommendationService'; type UseFoodRecommendationsResult = { ingredients: string[]; foods: FoodRecommendation[]; loading: boolean; hasGenerated: boolean; isEligibleForReport: boolean; canGenerate: boolean; generate: () => void; }; function isDateEligibleForReport(selectedDate: Date): boolean { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); const selected = new Date( selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate() ); return selected >= today && selected <= tomorrow; } function getRecentDateKeys(fromDate: Date, days: number): Set { const keys = new Set(); for (let i = 0; i < days; i++) { const d = new Date(fromDate); d.setDate(d.getDate() - i); keys.add(formatDateKey(d)); } return keys; } export function useFoodRecommendations( allLogs: FoodLog[], selectedDate: Date, profile: UserProfile | null ): UseFoodRecommendationsResult { const [ingredients, setIngredients] = useState([]); const [foods, setFoods] = useState([]); const [loading, setLoading] = useState(false); const [hasGenerated, setHasGenerated] = useState(false); const dateKey = formatDateKey(selectedDate); const profileId = profile?.id ?? 'demo'; const requestIdRef = useRef(0); const mountedRef = useRef(true); const inFlightRef = useRef(false); const isEligibleForReport = isDateEligibleForReport(selectedDate); const recentDateKeys = getRecentDateKeys(selectedDate, DAYS_TO_REVIEW); const hasRecentLogs = allLogs.some(log => recentDateKeys.has(log.date)); const canGenerate = isEligibleForReport && hasRecentLogs && !loading; useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); useEffect(() => { setIngredients([]); setFoods([]); setHasGenerated(false); const loadCached = async () => { const day = await Repository.getDay(profileId, dateKey); if (day?.recommendedIngredients?.length || day?.recommendedFoods?.length) { if (mountedRef.current) { setIngredients(day.recommendedIngredients); setFoods(day.recommendedFoods); setHasGenerated(true); } } }; loadCached(); }, [profileId, dateKey]); const generate = useCallback(async () => { if (!isEligibleForReport || !hasRecentLogs || inFlightRef.current) return; const currentRequestId = ++requestIdRef.current; const isStale = () => !mountedRef.current || requestIdRef.current !== currentRequestId; inFlightRef.current = true; setLoading(true); try { const result = await generateRecommendations(allLogs, selectedDate, profile); if (result.ingredients.length || result.foods.length) { await Repository.saveDay({ profileId, date: dateKey, ingredients: result.ingredients, foods: result.foods, }); } if (isStale()) return; setIngredients(result.ingredients); setFoods(result.foods); if (result.ingredients.length || result.foods.length) { setHasGenerated(true); } } catch (error) { console.error('Failed to generate recommendations:', error); } finally { inFlightRef.current = false; if (!isStale()) { setLoading(false); } } }, [allLogs, selectedDate, profile, profileId, dateKey, isEligibleForReport, hasRecentLogs]); return { ingredients, foods, loading, hasGenerated, isEligibleForReport, canGenerate, generate }; }