import { DayRepository, FoodLogRepository } from '@/add-log/repository'; import { formatDateKey } from '@/common/utils/format'; import { useProfile } from '@/profile/ProfileProvider'; import { useDatabase, useQuery } from '@/remote/database'; import { schedulePhotoUpload } from '@/add-log/photo/uploadService'; import type { FoodLog, FoodLogPred, FoodLogStatus } from '@/types/foodlog'; import type { Nutrition } from '@/types/nutrition'; import * as Crypto from 'expo-crypto'; import { useCallback, useMemo } from 'react'; export type ModelPrediction = { provider: string; predictedFoodName: string; ingredients: string[]; novaClass?: number; nutrition?: Nutrition; }; export type AddLogInput = { id?: string; foodName: string; ingredients?: string[]; novaClass?: number; nutrition?: Nutrition; photoUri?: string; eatenAt: Date; modelPredictions?: ModelPrediction[]; status?: FoodLogStatus; }; export function useFoodLogs(filterDate?: Date) { const db = useDatabase(); const { profile } = useProfile(); const profileId = profile?.id ?? 'demo'; const dateStr = filterDate ? formatDateKey(filterDate) : null; const foodLogRepo = useMemo(() => new FoodLogRepository(db), [db]); const dayRepo = useMemo(() => new DayRepository(db), [db]); const { data: rows } = useQuery( dateStr ? `SELECT fl.*, flp.photo_uri FROM food_logs fl LEFT JOIN food_log_photos flp ON fl.id = flp.id WHERE fl.profile_id = ? AND date(fl.eaten_time) = ? AND fl.deleted_at IS NULL ORDER BY fl.eaten_time DESC` : `SELECT fl.*, flp.photo_uri FROM food_logs fl LEFT JOIN food_log_photos flp ON fl.id = flp.id WHERE fl.profile_id = ? AND fl.deleted_at IS NULL ORDER BY fl.eaten_time DESC`, dateStr ? [profileId, dateStr] : [profileId] ); const logs = useMemo( () => (rows ?? []).map(r => FoodLogRepository.mapRowToFoodLog(r as Record)), [rows] ); const addLogs = useCallback( async (items: AddLogInput[]): Promise<{ successIds: string[]; failedIndices: number[] }> => { const successIds: string[] = []; const failedIndices: number[] = []; if (!profileId) return { successIds, failedIndices }; for (let i = 0; i < items.length; i++) { const item = items[i]; const logId = item.id ?? Crypto.randomUUID(); try { const dayId = await dayRepo.getOrCreateDay(profileId, item.eatenAt); await foodLogRepo.create({ id: logId, profileId, dayId, foodName: item.foodName, ingredients: item.ingredients ?? [], novaClass: item.novaClass, nutrition: item.nutrition, photoUri: item.photoUri, boundingBox: null, eatenAt: item.eatenAt, status: item.status ?? 'completed', predictions: item.modelPredictions, }); successIds.push(logId); } catch (error) { console.error('Failed to add log:', error); failedIndices.push(i); } } if (successIds.length > 0) { schedulePhotoUpload(); } return { successIds, failedIndices }; }, [foodLogRepo, dayRepo, profileId] ); const updateLog = useCallback( async (id: string, updates: { foodName?: string }) => { await foodLogRepo.update(id, updates); }, [foodLogRepo] ); const updateLogNutrition = useCallback( async (id: string, novaClass: number | undefined, nutrition: Nutrition | undefined) => { await foodLogRepo.updateNutrition(id, novaClass, nutrition); }, [foodLogRepo] ); const deleteLog = useCallback( async (id: string) => { await foodLogRepo.delete(id); }, [foodLogRepo] ); const getLogById = useCallback( (id: string): FoodLog | undefined => logs.find(log => log.id === id), [logs] ); const getPredictions = useCallback( async (foodLogId: string): Promise => { return await foodLogRepo.getPredictions(foodLogId); }, [foodLogRepo] ); return { logs, addLogs, updateLog, updateLogNutrition, deleteLog, getLogById, getPredictions, }; }