using Fitness_core_BE.Helpers; using System.Text.Json; namespace Fitness_core_BE.AI { public class MealPlanOrchestrator { private readonly IAiClient _aiClient; private readonly ModificationGuard _guard; public MealPlanOrchestrator( IAiClient aiClient, ModificationGuard guard) { _aiClient = aiClient; _guard = guard; } public async Task GeneratePlanAsync(object payload) { var data = DeserializePayload.Deserialize(payload); decimal bmi = DataExtractor.GetRequiredDecimal(data, "bmi"); string country = DataExtractor.GetRequiredString(data, "country"); string province = DataExtractor.GetRequiredString(data, "province"); string goal = DataExtractor.GetRequiredString(data, "goal"); decimal budget = DataExtractor.GetRequiredDecimal(data, "budgetPerWeek"); int mealsPerDay = DataExtractor.GetRequiredInt(data, "mealsPerDay"); bool isVegetarian = DataExtractor.GetRequiredBool(data, "isVegetarian"); // Basic validation if (bmi < 10 || bmi > 60) throw new Exception("Invalid BMI"); var systemPrompt = @" You are a certified nutritionist. Your response must be STRICTLY a JSON object. Output format rules: 1. The first character of your response MUST be { 2. The last character MUST be } 3. Do not include ``` or any markdown formatting 4. Do not include any explanation text 5. Do not include comments 6. Do not prefix with ""Here is"" 7. Do not suffix with any text Return ONLY valid JSON. Structure: { name: string, description: string, mealItems: [ { name: string, description: string, calories: number, protein: number, carbohydrates: number, fats: number, servingSize: string, mealItems: [ { food: string, quantity: string } ], preparationNotes: string } ] } "; var jsonResponse = await _aiClient.GenerateJsonAsync( systemPrompt, data ); // Validate JSON before returning try { var parsed = JsonSerializer.Deserialize(jsonResponse); if (parsed == null) throw new Exception("Invalid AI JSON"); } catch { throw new Exception("AI returned invalid JSON structure"); } return JsonSerializer.Deserialize(jsonResponse)!; } public async Task UpdatePlanAsync(object payload) { var data = DeserializePayload.Deserialize(payload); if (!data.ContainsKey("currentPlan")) throw new Exception("Current plan missing"); string modification = DataExtractor.GetRequiredString(data, "modification"); _guard.Validate(modification); var systemPrompt = @" You are a professional nutrition AI. You will receive: 1) The current meal plan in valid JSON format. 2) A user modification request in free text. Your task: - Carefully analyze the modification request. - Modify ONLY the specific elements explicitly requested by the user. - Do NOT change anything else. VERY IMPORTANT RULES: - Do NOT change the plan name unless the user explicitly asks. - Do NOT change the number of meals unless requested. - Do NOT modify calories, protein, carbs, fats unless the request requires it. - Do NOT rewrite descriptions unless necessary for the modification. - Do NOT change serving sizes unless required. - Do NOT add or remove meals unless explicitly requested. - Do NOT modify ingredients that are unrelated to the request. - Preserve every unchanged value exactly as provided. Scope control: If the request only affects one ingredient, update only that ingredient. If the request does not affect the plan, return it unchanged. Preserve the exact internal structure of the meal plan. Do NOT rename fields. Do NOT remove required properties. Do NOT change the JSON schema. Return your response strictly in this JSON format: { ""reason"": string, ""modified"": boolean, ""currentPlan"": { ""name"": string, ""description"": string, ""mealItems"": [ { ""name"": string, ""description"": string, ""calories"": number, ""protein"": number, ""carbohydrates"": number, ""fats"": number, ""servingSize"": string, ""mealItems"": [ { ""food"": string, ""quantity"": string } ], ""preparationNotes"": string } ] }, ""updatedPlan"": { ""name"": string, ""description"": string, ""mealItems"": [ { ""name"": string, ""description"": string, ""calories"": number, ""protein"": number, ""carbohydrates"": number, ""fats"": number, ""servingSize"": string, ""mealItems"": [ { ""food"": string, ""quantity"": string } ], ""preparationNotes"": string } ] } } STRICT OUTPUT RULES: - Return ONLY valid raw JSON. - No markdown. - No explanations. - First character must be {. - Last character must be }. - No trailing commas. - No extra commentary. If modification cannot be applied: - Set ""modified"": false - Keep currentPlan and updatedPlan identical. - Explain reason clearly. "; var jsonResponse = await _aiClient.GenerateJsonAsync( systemPrompt, data ); try { var parsed = JsonSerializer.Deserialize(jsonResponse); if (parsed == null) throw new Exception("Invalid JSON"); } catch { throw new Exception("AI returned invalid JSON"); } return JsonSerializer.Deserialize(jsonResponse)!; } } }