using FrameworkBLL.EIPConversation.EIPHandlers.EIPEngine.FlowEngine; using FrameworkDAL.DTO.EIPConversation; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPPlayground { public class EIPPlaygroundBLL : IEIPPlaygroundBLL { private readonly IEIPFlowEngine _flowEngine; private readonly ILogger _logger; public EIPPlaygroundBLL(IEIPFlowEngine flowEngine, ILogger logger) { _flowEngine = flowEngine ?? throw new ArgumentNullException(nameof(flowEngine)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task RunAsync( PlaygroundRunRequest request, LoginDTO login, CancellationToken ct = default) { try { // ── 1. Parse and normalize the flow definition ──────────────────────── var flowDef = ParseAndNormalizeFlow(request.FlowJson); // ── 2. Restore session state ────────────────────────────────────────── var sessionState = RestoreSession(request.SessionStateJson); // ── 3. Build execution context ──────────────────────────────────────── var context = new EIPExecutionContextDTO { ChannelType = EIPChannelType.PostMan, // playground uses Postman (no real delivery) Channel = "POSTMAN", OriginalMessage = request.Message ?? string.Empty, NormalizedMessage = (request.Message ?? string.Empty).Trim(), FlowCode = flowDef.FlowCode, CurrentStepCode = sessionState.CurrentStepCode, Variables = sessionState.Variables, UserIdentifier = "playground", TenantId = login.ClientId, LoginDTO = login, PhaseTrace = new List() }; // ── 4. Execute the flow engine with the raw definition ──────────────── var result = await _flowEngine .ExecuteFlowFromDefinitionAsync(context, flowDef, login, ct) .ConfigureAwait(false); // ── 5. Update context step after execution ──────────────────────────── context.CurrentStepCode = result.CurrentStepCode; context.NextStepCode = result.NextStepCode; // ── 6. Serialize updated session state ──────────────────────────────── var updatedSession = result.IsCompleted ? "{}" : SerializeSession(new PlaygroundSessionState { CurrentStepCode = result.NextStepCode ?? result.CurrentStepCode, Variables = context.Variables }); return new PlaygroundRunResponse { ResponseMessage = result.ResponseMessage, CurrentStepCode = result.CurrentStepCode, NextStepCode = result.NextStepCode, IsCompleted = result.IsCompleted, PhaseTrace = context.PhaseTrace ?? new List(), SessionStateJson = updatedSession }; } catch (Exception ex) { _logger.LogError(ex, "EIPPlaygroundBLL.RunAsync failed | Message={Message}", request.Message); return new PlaygroundRunResponse { ErrorMessage = ex.Message, IsCompleted = false, SessionStateJson = "{}" }; } } // ── Helpers ─────────────────────────────────────────────────────────────────── private EIPFlowDefinitionDTO ParseAndNormalizeFlow(string flowJson) { if (string.IsNullOrWhiteSpace(flowJson)) throw new ArgumentException("Flow JSON is required."); EIPFlowDefinitionDTO flowDef; try { flowDef = JsonConvert.DeserializeObject(flowJson) ?? throw new InvalidOperationException("Flow JSON deserialized to null."); } catch (Exception ex) { throw new InvalidOperationException($"Invalid flow JSON: {ex.Message}", ex); } if (string.IsNullOrWhiteSpace(flowDef.StartStepCode)) throw new InvalidOperationException("Flow JSON must include 'StartStepCode'."); if (flowDef.Steps == null || flowDef.Steps.Count == 0) throw new InvalidOperationException("Flow JSON must include at least one step in 'Steps'."); // Normalize steps — same logic as EIPFlowRepository foreach (var (stepCode, step) in flowDef.Steps) { if (step == null) throw new InvalidOperationException($"Step definition is null for key '{stepCode}'."); if (string.IsNullOrWhiteSpace(step.StepType)) throw new InvalidOperationException($"StepType is required for step '{stepCode}'."); step.StepType = step.StepType.Trim().ToUpperInvariant(); step.DecisionMap ??= new Dictionary(); step.Metadata ??= new Dictionary(); step.Actions ??= new List(); } if (!flowDef.Steps.ContainsKey(flowDef.StartStepCode)) throw new InvalidOperationException( $"StartStepCode '{flowDef.StartStepCode}' not found in Steps."); if (string.IsNullOrWhiteSpace(flowDef.FlowCode)) flowDef.FlowCode = "PLAYGROUND"; return flowDef; } private static PlaygroundSessionState RestoreSession(string? sessionStateJson) { if (string.IsNullOrWhiteSpace(sessionStateJson) || sessionStateJson == "{}") return new PlaygroundSessionState(); try { return JsonConvert.DeserializeObject(sessionStateJson) ?? new PlaygroundSessionState(); } catch { return new PlaygroundSessionState(); } } private static string SerializeSession(PlaygroundSessionState state) { try { return JsonConvert.SerializeObject(state); } catch { return "{}"; } } // Thin session state round-tripped as JSON between turns private class PlaygroundSessionState { public string? CurrentStepCode { get; set; } public Dictionary Variables { get; set; } = new(); } } }