import { useEffect, useMemo, useState } from 'react'; import { Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { router } from 'expo-router'; import { useTranslation } from 'react-i18next'; import { acceptDietMacros, commitDietPlan, createDietSession, fetchDietProfile, generateDietPlan, proposeDietMacros, regenerateDietMeal, saveDietProfile, toggleDietMealLock, } from '@/src/api/diet-generator.api'; import { Button } from '@/src/components/Button'; import { Input } from '@/src/components/Input'; import { Screen } from '@/src/components/Screen'; import { useAppearance } from '@/src/context/AppearanceContext'; import { DEFAULT_DIET_PROFILE, type DietGeneratorDraftMeal, type DietGeneratorSession, type DietUserProfile, } from '@/src/types/generator'; import { MealCategory } from '@/src/types/recipe'; import { ApiError, extractApiError } from '@/src/utils/apiError'; import { categoryLabel } from '@/src/utils/recipe'; type Step = 'profile' | 'macros' | 'plan' | 'done'; function formatDraftMealMacros(meal: DietGeneratorDraftMeal): string { const parts = [`${meal.calories ?? '?'} kcal`]; if (meal.proteinG != null) parts.push(`P ${meal.proteinG}g`); if (meal.fatG != null) parts.push(`F ${meal.fatG}g`); if (meal.carbsG != null) parts.push(`C ${meal.carbsG}g`); return parts.join(' · '); } export default function DietGeneratorScreen() { const { t } = useTranslation(); const { colors } = useAppearance(); const [step, setStep] = useState('profile'); const [profile, setProfile] = useState({ ...DEFAULT_DIET_PROFILE }); const [sessionId, setSessionId] = useState(null); const [session, setSession] = useState(null); const [macros, setMacros] = useState({ calories: 2000, proteinG: 150, fatG: 65, carbsG: 200, rationale: '', }); const [commitResult, setCommitResult] = useState(null); const [error, setError] = useState(null); const [info, setInfo] = useState(null); const [loading, setLoading] = useState(false); const [previewMeal, setPreviewMeal] = useState(null); useEffect(() => { void fetchDietProfile().then((p) => { if (p) setProfile({ ...DEFAULT_DIET_PROFILE, ...p }); }); }, []); const mealsByDate = useMemo(() => { const map = new Map(); for (const meal of session?.draftMeals ?? []) { const list = map.get(meal.planDate) ?? []; list.push(meal); map.set(meal.planDate, list); } return [...map.entries()] .sort(([a], [b]) => a.localeCompare(b)) .map(([date, meals]) => ({ date, meals, summary: session?.daySummaries.find((d) => d.planDate === date), })); }, [session]); async function startSession() { setError(null); setLoading(true); try { await saveDietProfile(profile); const created = await createDietSession({ planDayCount: 7, calorieToleranceKcal: 10 }); setSessionId(created.id); if (created.proposedMacros) applyMacros(created); const refined = await proposeDietMacros(created.id); if (refined.proposedMacros) applyMacros(refined); setStep('macros'); } catch (e) { setError(extractApiError(e, t('generators.diet.error'))); } finally { setLoading(false); } } async function acceptAndGenerate() { if (!sessionId) return; setError(null); setLoading(true); try { await acceptDietMacros(sessionId, macros); const plan = await generateDietPlan(sessionId); setSession(plan); setStep('plan'); } catch (e) { setError(extractApiError(e, t('generators.diet.error'))); } finally { setLoading(false); } } async function toggleLock(mealId: number, isLocked: boolean) { if (!sessionId) return; try { const updated = await toggleDietMealLock(sessionId, mealId, isLocked); setSession(updated); } catch (e) { setError(extractApiError(e, t('generators.diet.error'))); } } async function regenMeal(planDate: string, mealCategory: number) { if (!sessionId) return; try { const updated = await regenerateDietMeal(sessionId, planDate, mealCategory); setSession(updated); setError(null); setInfo(null); } catch (e) { if (e instanceof ApiError && e.status === 400) { setInfo(t('generators.diet.noAlternativeMeals')); setError(null); } else { setError(extractApiError(e, t('generators.diet.error'))); setInfo(null); } } } async function commitPlan() { if (!sessionId) return; setLoading(true); try { const result = await commitDietPlan(sessionId); setCommitResult(`${result.planStartDate} → ${result.planEndDate}`); setStep('done'); } catch (e) { setError(extractApiError(e, t('generators.diet.error'))); } finally { setLoading(false); } } function applyMacros(s: DietGeneratorSession) { if (!s.proposedMacros) return; setMacros({ calories: s.proposedMacros.calories, proteinG: s.proposedMacros.proteinG, fatG: s.proposedMacros.fatG, carbsG: s.proposedMacros.carbsG, rationale: s.proposedMacros.rationale ?? '', }); } function reset() { setStep('profile'); setSessionId(null); setSession(null); setCommitResult(null); setError(null); setInfo(null); } return ( {info ? ( {info} ) : null} {error ? ( {error} ) : null} {step === 'profile' ? ( {t('generators.diet.profileTitle')} setProfile({ ...profile, heightCm: Number(v) || 0 })} /> setProfile({ ...profile, weightKg: Number(v) || 0 })} /> setProfile({ ...profile, age: Number(v) || 0 })} /> {t('generators.diet.sex')} {(['male', 'female'] as const).map((sex) => ( setProfile({ ...profile, sex })} style={[ styles.chip, { backgroundColor: profile.sex === sex ? colors.brand : colors.surface, borderColor: profile.sex === sex ? colors.brand : colors.border, }, ]} > {t(`generators.diet.${sex === 'male' ? 'male' : 'female'}`)} ))} {t('generators.diet.activity')} {(['sedentary', 'light', 'moderate', 'active', 'very_active'] as const).map((level) => ( setProfile({ ...profile, activityLevel: level })} style={[ styles.chip, { backgroundColor: profile.activityLevel === level ? colors.brand : colors.surface, borderColor: profile.activityLevel === level ? colors.brand : colors.border, }, ]} > {t(`generators.diet.${level === 'very_active' ? 'veryActive' : level}`)} ))} {t('generators.diet.goal')} {(['lose_weight', 'maintain', 'gain_muscle', 'recomp'] as const).map((goal) => ( setProfile({ ...profile, goal })} style={[ styles.chip, { backgroundColor: profile.goal === goal ? colors.brand : colors.surface, borderColor: profile.goal === goal ? colors.brand : colors.border, }, ]} > {t( `generators.diet.${ goal === 'lose_weight' ? 'loseWeight' : goal === 'gain_muscle' ? 'gainMuscle' : goal }`, )} ))} setProfile({ ...profile, allergiesNotes: v })} />