import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/constants/app_theme.dart'; import '../../core/models/recipe.dart'; import '../../core/providers.dart'; class RecipeEditScreen extends ConsumerStatefulWidget { final Recipe? recipe; const RecipeEditScreen({super.key, this.recipe}); @override ConsumerState createState() => _RecipeEditScreenState(); } class _RecipeEditScreenState extends ConsumerState { final _formKey = GlobalKey(); late TextEditingController _titleController; late TextEditingController _descController; late TextEditingController _servingsController; late TextEditingController _prepTimeController; late TextEditingController _cookTimeController; late TextEditingController _sourceUrlController; late TextEditingController _imageUrlController; late TextEditingController _ingredientsController; late TextEditingController _instructionsController; bool _isSaving = false; bool _isPolishing = false; @override void initState() { super.initState(); final r = widget.recipe; _titleController = TextEditingController(text: r?.title ?? ''); _descController = TextEditingController(text: r?.description ?? ''); _servingsController = TextEditingController(text: r?.servings ?? ''); _prepTimeController = TextEditingController(text: r?.prepTime ?? ''); _cookTimeController = TextEditingController(text: r?.cookTime ?? ''); _sourceUrlController = TextEditingController(text: r?.sourceUrl ?? ''); _imageUrlController = TextEditingController(text: r?.image ?? ''); _ingredientsController = TextEditingController(text: r?.ingredients.join('\n') ?? ''); _instructionsController = TextEditingController(text: r?.instructions.join('\n') ?? ''); _prepTimeController.addListener(_updateTimes); _cookTimeController.addListener(_updateTimes); } void _updateTimes() { setState(() {}); } @override void dispose() { _titleController.dispose(); _descController.dispose(); _servingsController.dispose(); _prepTimeController.dispose(); _cookTimeController.dispose(); _sourceUrlController.dispose(); _imageUrlController.dispose(); _ingredientsController.dispose(); _instructionsController.dispose(); super.dispose(); } String get _calculatedTotal { return RecipeTimeUtils.calculateTotalTime( prepTime: _prepTimeController.text.trim(), cookTime: _cookTimeController.text.trim(), totalTime: widget.recipe?.totalTime, ); } Future _handleAiPolish() async { final title = _titleController.text.trim(); final rawIng = _ingredientsController.text.trim(); final rawInst = _instructionsController.text.trim(); if (rawIng.isEmpty && rawInst.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Please add ingredients or instructions first to polish with AI.')), ); return; } setState(() => _isPolishing = true); try { final api = ref.read(apiClientProvider); final result = await api.aiPolishRecipe( title: title, ingredients: rawIng.split('\n').map((s) => s.trim()).where((s) => s.isNotEmpty).toList(), instructions: rawInst.split('\n').map((s) => s.trim()).where((s) => s.isNotEmpty).toList(), ); if (mounted) { if (result['title'] != null && result['title'].toString().isNotEmpty) { _titleController.text = result['title'].toString(); } if (result['description'] != null && result['description'].toString().isNotEmpty) { _descController.text = result['description'].toString(); } if (result['servings'] != null && result['servings'].toString().isNotEmpty) { _servingsController.text = result['servings'].toString(); } if (result['prepTime'] != null && result['prepTime'].toString().isNotEmpty) { _prepTimeController.text = result['prepTime'].toString(); } if (result['cookTime'] != null && result['cookTime'].toString().isNotEmpty) { _cookTimeController.text = result['cookTime'].toString(); } if (result['ingredients'] is List) { final list = (result['ingredients'] as List).map((e) => e.toString()).toList(); _ingredientsController.text = list.join('\n'); } if (result['instructions'] is List) { final list = (result['instructions'] as List).map((e) => e.toString()).toList(); _instructionsController.text = list.join('\n'); } ScaffoldMessenger.of(context).showSnackBar( const SnackBar( backgroundColor: AppTheme.secondary, content: Row( children: [ Icon(Icons.auto_awesome, color: Colors.white, size: 20), SizedBox(width: 10), Text('Recipe polished and organized by Gemini AI!'), ], ), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: AppTheme.danger, content: Text('AI Polish failed: $e'), ), ); } } finally { if (mounted) setState(() => _isPolishing = false); } } Future _handleSave() async { if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); final isNew = widget.recipe == null; final prep = _prepTimeController.text.trim(); final cook = _cookTimeController.text.trim(); final total = RecipeTimeUtils.calculateTotalTime( prepTime: prep, cookTime: cook, totalTime: widget.recipe?.totalTime, ); final ingList = _ingredientsController.text .split('\n') .map((s) => s.trim()) .where((s) => s.isNotEmpty) .toList(); final instList = _instructionsController.text .split('\n') .map((s) => s.trim()) .where((s) => s.isNotEmpty) .toList(); final updated = Recipe( id: widget.recipe?.id, title: _titleController.text.trim(), description: _descController.text.trim(), servings: _servingsController.text.trim(), prepTime: prep, cookTime: cook, totalTime: total, sourceUrl: _sourceUrlController.text.trim(), image: _imageUrlController.text.trim().isNotEmpty ? _imageUrlController.text.trim() : widget.recipe?.image, images: widget.recipe?.images ?? [], ingredients: ingList, instructions: instList, shareToken: widget.recipe?.shareToken, ); try { await ref.read(recipeListProvider.notifier).addOrUpdate(updated); if (mounted) { Navigator.pop(context, true); ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: AppTheme.secondary, content: Text(isNew ? 'Created "${updated.title}"' : 'Updated "${updated.title}"'), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: AppTheme.danger, content: Text('Failed to save recipe: $e'), ), ); } } finally { if (mounted) setState(() => _isSaving = false); } } @override Widget build(BuildContext context) { final isNew = widget.recipe == null; return Scaffold( appBar: AppBar( title: Text(isNew ? 'New Recipe' : 'Edit Recipe'), actions: [ if (_isPolishing) const Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.secondary))), ) else TextButton.icon( onPressed: _handleAiPolish, icon: const Icon(Icons.auto_awesome, color: AppTheme.secondary, size: 18), label: const Text('AI Polish', style: TextStyle(color: AppTheme.secondary, fontWeight: FontWeight.w700)), ), const SizedBox(width: 4), Padding( padding: const EdgeInsets.only(right: 12), child: _isSaving ? const Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primary))) : ElevatedButton( onPressed: _handleSave, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), minimumSize: const Size(60, 36), ), child: const Text('Save'), ), ), ], ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Title Field TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: 'Recipe Title *', hintText: 'e.g. Creamy Tuscan Garlic Chicken', prefixIcon: Icon(Icons.restaurant_rounded), ), validator: (val) => (val == null || val.trim().isEmpty) ? 'Please enter a recipe title' : null, ), const SizedBox(height: 14), // Description Field TextFormField( controller: _descController, maxLines: 2, decoration: const InputDecoration( labelText: 'Description', hintText: 'A short overview or tasting notes', prefixIcon: Icon(Icons.description_outlined), ), ), const SizedBox(height: 14), // Servings, Prep Time, Cook Time Row Row( children: [ Expanded( child: TextFormField( controller: _servingsController, decoration: const InputDecoration( labelText: 'Servings', hintText: 'e.g. 4', isDense: true, ), ), ), const SizedBox(width: 10), Expanded( child: TextFormField( controller: _prepTimeController, decoration: const InputDecoration( labelText: 'Prep Time', hintText: '15 mins', isDense: true, ), ), ), const SizedBox(width: 10), Expanded( child: TextFormField( controller: _cookTimeController, decoration: const InputDecoration( labelText: 'Cook Time', hintText: '30 mins', isDense: true, ), ), ), ], ), // Total Time live preview badge if (_calculatedTotal.isNotEmpty) ...[ const SizedBox(height: 8), Row( children: [ const Icon(Icons.timer_rounded, size: 14, color: AppTheme.primary), const SizedBox(width: 6), Text( 'Calculated Total Time: $_calculatedTotal', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: AppTheme.primary), ), ], ), ], const SizedBox(height: 14), // Source URL TextFormField( controller: _sourceUrlController, decoration: const InputDecoration( labelText: 'Source URL', hintText: 'https://...', prefixIcon: Icon(Icons.link_rounded), isDense: true, ), ), const SizedBox(height: 14), // Image URL TextFormField( controller: _imageUrlController, decoration: const InputDecoration( labelText: 'Cover Image URL', hintText: 'https://...', prefixIcon: Icon(Icons.image_outlined), isDense: true, ), ), const SizedBox(height: 20), // Ingredients Section Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Ingredients', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: AppTheme.primary)), Text( 'One per line', style: TextStyle(fontSize: 12, color: AppTheme.textMuted.withOpacity(0.8)), ), ], ), const SizedBox(height: 8), TextFormField( controller: _ingredientsController, maxLines: 8, decoration: const InputDecoration( hintText: '2 chicken breasts, diced\n1 cup heavy cream\n2 cloves garlic, minced\n1 cup baby spinach', alignLabelWithHint: true, ), ), const SizedBox(height: 20), // Instructions Section Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Instructions', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: AppTheme.primary)), Text( 'One step per line', style: TextStyle(fontSize: 12, color: AppTheme.textMuted.withOpacity(0.8)), ), ], ), const SizedBox(height: 8), TextFormField( controller: _instructionsController, maxLines: 8, decoration: const InputDecoration( hintText: 'Heat olive oil in a skillet over medium heat.\nAdd garlic and cook until fragrant.\nPour in heavy cream and simmer until thickened.', alignLabelWithHint: true, ), ), const SizedBox(height: 32), ], ), ), ); } }