import { useMemo, useState } from 'react'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { router } from 'expo-router'; import { useTranslation } from 'react-i18next'; import { commitRecipes, createRecipeSession, generateRecipeDrafts, regenerateRecipePart, removeRecipeDraft, } from '@/src/api/recipe-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 type { CommitRecipeResult, GeneratedRecipeDraft, RecipeGeneratorConstraints } from '@/src/types/generator'; import { MealCategory } from '@/src/types/recipe'; import { extractApiError } from '@/src/utils/apiError'; import { ALL_CATEGORIES, categoryLabel } from '@/src/utils/recipe'; type Step = 'constraints' | 'review' | 'done'; export default function RecipeGeneratorScreen() { const { t } = useTranslation(); const { colors } = useAppearance(); const [step, setStep] = useState('constraints'); const [sessionId, setSessionId] = useState(null); const [drafts, setDrafts] = useState([]); const [selectedIds, setSelectedIds] = useState>(new Set()); const [savedRecipes, setSavedRecipes] = useState([]); const [recipeCount, setRecipeCount] = useState('3'); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const [constraints, setConstraints] = useState({ prompt: '', mealCategory: MealCategory.Breakfast, targetCalories: null, calorieToleranceKcal: 10, maxPrepTimeMinutes: 30, cuisineStyle: '', dietStyle: '', exclusions: '', }); const calorieTolerance = constraints.calorieToleranceKcal ?? 10; function isWithinCalorieTarget(calories?: number | null): boolean { if (!constraints.targetCalories || calories == null) return true; return Math.abs(calories - constraints.targetCalories) <= calorieTolerance; } const allSelected = useMemo( () => drafts.length > 0 && drafts.every((d) => selectedIds.has(d.draftId)), [drafts, selectedIds], ); function syncDrafts(next: GeneratedRecipeDraft[]) { setDrafts(next); setSelectedIds(new Set(next.map((d) => d.draftId))); } async function runGenerate() { setError(null); setPending(true); const count = Math.min(8, Math.max(1, Number(recipeCount) || 3)); try { let session; if (sessionId) { session = await generateRecipeDrafts(sessionId, count); } else { const created = await createRecipeSession(constraints); setSessionId(created.id); session = await generateRecipeDrafts(created.id, count); } syncDrafts(session.drafts ?? []); setStep('review'); } catch (e) { setError(extractApiError(e, t('generators.recipe.error'))); } finally { setPending(false); } } async function regen(draftId: string, mode: 'ingredients' | 'steps' | 'full') { if (!sessionId) return; setPending(true); setError(null); try { const session = await regenerateRecipePart(sessionId, draftId, mode, constraints.prompt); syncDrafts(session.drafts ?? []); } catch (e) { setError(extractApiError(e, t('generators.recipe.error'))); } finally { setPending(false); } } async function removeDraft(draftId: string) { if (!sessionId) return; setPending(true); setError(null); try { const session = await removeRecipeDraft(sessionId, draftId); const next = session.drafts ?? []; syncDrafts(next); if (next.length === 0) setStep('constraints'); } catch (e) { setError(extractApiError(e, t('generators.recipe.error'))); } finally { setPending(false); } } async function saveSelected() { if (!sessionId || selectedIds.size === 0) return; setPending(true); setError(null); try { const result = await commitRecipes(sessionId, [...selectedIds]); setSavedRecipes(result.saved); const remaining = drafts.filter((d) => !selectedIds.has(d.draftId)); if (remaining.length === 0) setStep('done'); else syncDrafts(remaining); } catch (e) { setError(extractApiError(e, t('generators.recipe.error'))); } finally { setPending(false); } } function toggleSelected(draftId: string) { setSelectedIds((prev) => { const next = new Set(prev); if (next.has(draftId)) next.delete(draftId); else next.add(draftId); return next; }); } function toggleAll() { setSelectedIds(allSelected ? new Set() : new Set(drafts.map((d) => d.draftId))); } return ( {error ? ( {error} ) : null} {step === 'constraints' ? ( {t('generators.recipe.constraintsTitle')} setConstraints({ ...constraints, prompt: v })} /> setConstraints({ ...constraints, targetCalories: v.trim() ? Number(v) || null : null, }) } /> {t('generators.recipe.targetCaloriesHint')} {t('common.category')} {ALL_CATEGORIES.map((cat) => ( setConstraints({ ...constraints, mealCategory: cat })} style={[ styles.chip, { backgroundColor: constraints.mealCategory === cat ? colors.brand : colors.surface, borderColor: constraints.mealCategory === cat ? colors.brand : colors.border, }, ]} > {categoryLabel(cat, t)} ))} setConstraints({ ...constraints, maxPrepTimeMinutes: Number(v) || undefined }) } /> setConstraints({ ...constraints, exclusions: v })} />