using System;
using System.Collections.Generic;
using System.Linq;
#if NET8_0_OR_GREATER
using System.Net.Http;
using System.Reflection.Emit;

using System.Reflection;

#endif
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.IO;
using Newtonsoft.Json;

namespace Nodify.Calculator
{
    public enum logType
    {
        Information,
        Warning,
        Error
    }
    public delegate void LogMe(string message, logType logtype = logType.Information);

    public class Executor
    {
        private readonly EditorViewModel editorViewModel;
        public event LogMe OnLogMe;

        static bool isAlreadyLogged = false;
        static Dictionary<string, string> outputs = new Dictionary<string, string>();
        static Dictionary<string, dynamic> variables = new Dictionary<string, dynamic>();

        // Auth configuration populated from the Auth node
        private string _authBaseUrl = string.Empty;
        private string _authType = string.Empty;
        private string _authToken = string.Empty;
        private string _authApiKey = string.Empty;
        private string _authUsername = string.Empty;
        private string _authPassword = string.Empty;

        public Executor(EditorViewModel editorViewModel)
        {
            this.editorViewModel = editorViewModel;
        }

        public string PerformPreCheck()
        {
            string errorString = string.Empty;
            var calcModel = editorViewModel.Calculator;
            var allnodes = calcModel.Operations;
            errorString = ValidateRequiredNodes("end", allnodes);
            if (!string.IsNullOrEmpty(errorString))
            {
                OnLogMe?.Invoke(errorString, logType.Error);
                return errorString;
            }
            errorString = ValidateRequiredNodes("begin", allnodes);
            OnLogMe?.Invoke(errorString, logType.Error);
            return errorString;
        }

        public string Execute()
        {
            string errorString = string.Empty;
            try
            {
                OnLogMe?.Invoke("Starting executing the flow.");
                OnLogMe?.Invoke("Wish all the best to us :)");
                OnLogMe?.Invoke("Perorming chain check, it may take some time depending upon the number of nodes.");
                outputs.Clear();
                var calcModel = editorViewModel.Calculator;
                var allnodes = calcModel.Operations;
                var allConnections = calcModel.Connections;

                // Resolve Auth node configuration before execution
                ResolveAuthNode(allnodes);

                // Resolve GET variable nodes (they are not in the flow chain)
                ResolveGetVariableNodes(allnodes, allConnections);

                PerformChainCheck(allnodes, allConnections, false);
                PerformChainCheck(allnodes, allConnections, true);  //Execute operations
            }
            catch (Exception e)
            {
                OnLogMe?.Invoke("either your or our bad luck :(", logType.Error);
                OnLogMe?.Invoke("Error Details : ", logType.Error);
                OnLogMe?.Invoke(e.Message, logType.Error);
                OnLogMe?.Invoke(e.StackTrace, logType.Error);
                errorString = "Error occured dueing execution of flow";
            }

            return errorString;
        }

        public static string GenerateClassFromJson(string json, string className)
        {
            // Parse the JSON to understand its structure
            JToken token = JToken.Parse(json);

            if (token.Type == JTokenType.Array)
            {
                // If the JSON is an array, get the first object in the array for structure
                JArray array = (JArray)token;
                if (array.Count > 0 && array[0].Type == JTokenType.Object)
                {
                    JObject firstObject = (JObject)array[0];
                    return $"{GenerateClassFromJObject(firstObject, className)}";
                }

                return $"The JSON array does not contain valid objects.";
            }
            else if (token.Type == JTokenType.Object)
            {
                // If the JSON is an object
                JObject obj = (JObject)token;
                return GenerateClassFromJObject(obj, className);
            }
            else
            {
                return "Unsupported JSON structure.";
            }
        }

        /// <summary>
        /// Generates a class definition string from a JObject.
        /// </summary>
        /// <param name="jObject">The JObject representing the JSON object.</param>
        /// <param name="className">The name of the class to generate.</param>
        /// <returns>The class definition as a string.</returns>
        public static string GenerateClassFromJObject(JObject jObject, string className)
        {
            var sb = new StringBuilder();

            // Start class definition
            sb.AppendLine($"public class {className}");
            sb.AppendLine("{");

            // Loop through properties in the JObject and generate class fields
            foreach (var property in jObject.Properties())
            {
                string propName = property.Name;
                string propType = GetCSharpType(property.Value.Type);

                // Generate the property definition
                sb.AppendLine($"\tpublic {propType} {ToPascalCase(propName)} {{ get; set; }}");
            }

            // End class definition
            sb.AppendLine("}");

            return sb.ToString();
        }

        /// <summary>
        /// Maps a JTokenType to a C# type.
        /// </summary>
        /// <param name="jsonType">The JTokenType.</param>
        /// <returns>The corresponding C# type as a string.</returns>
        public static string GetCSharpType(JTokenType jsonType)
        {
            return jsonType switch
            {
                JTokenType.Integer => "int",
                JTokenType.Float => "double",
                JTokenType.String => "string",
                JTokenType.Boolean => "bool",
                JTokenType.Object => "object",
                JTokenType.Array => "List<object>",
                _ => "string", // Default to string for other types
            };
        }

        /// <summary>
        /// Converts a string to PascalCase for naming conventions.
        /// </summary>
        /// <param name="input">The string to convert.</param>
        /// <returns>A PascalCase version of the input string.</returns>
        public static string ToPascalCase(string input)
        {
            if (string.IsNullOrEmpty(input))
                return input;

            return char.ToUpper(input[0]) + input.Substring(1);
        }

        static Type CreateTypeFromJson(JsonElement root, string typeName)
        {

#if NET8_0_OR_GREATER
            var assemblyName = new AssemblyName("DynamicAssembly");
            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");

            var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);

            foreach (var prop in root.EnumerateObject())
            {
                Type propType = prop.Value.ValueKind switch
                {
                    JsonValueKind.String => typeof(string),
                    JsonValueKind.Number => typeof(int),
                    JsonValueKind.True or JsonValueKind.False => typeof(bool),
                    _ => typeof(object)
                };

                var fieldBuilder = typeBuilder.DefineField("_" + prop.Name, propType, FieldAttributes.Private);
                var propertyBuilder = typeBuilder.DefineProperty(prop.Name, PropertyAttributes.HasDefault, propType, null);

                // Getter
                var getter = typeBuilder.DefineMethod(
                    "get_" + prop.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    propType,
                    Type.EmptyTypes
                );
                var getterIL = getter.GetILGenerator();
                getterIL.Emit(OpCodes.Ldarg_0);
                getterIL.Emit(OpCodes.Ldfld, fieldBuilder);
                getterIL.Emit(OpCodes.Ret);
                propertyBuilder.SetGetMethod(getter);

                // Setter
                var setter = typeBuilder.DefineMethod(
                    "set_" + prop.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    null,
                    new Type[] { propType }
                );
                var setterIL = setter.GetILGenerator();
                setterIL.Emit(OpCodes.Ldarg_0);
                setterIL.Emit(OpCodes.Ldarg_1);
                setterIL.Emit(OpCodes.Stfld, fieldBuilder);
                setterIL.Emit(OpCodes.Ret);
                propertyBuilder.SetSetMethod(setter);
            }
            return typeBuilder.CreateType()!;
#endif

            return default;
        }

        private void CreateModelsFromString(string jsonString)
        {
#if NET8_0_OR_GREATER

            var jsonDoc = JsonDocument.Parse(jsonString);
            var root = jsonDoc.RootElement;

            if (root.ValueKind == JsonValueKind.Object)
            {
                ParseFromJsonelement(root);
            }
            else if(root.ValueKind == JsonValueKind.Array)
            {
                foreach (var item in root.EnumerateArray())
                {
                    if (item.ValueKind == JsonValueKind.Object)
                    {
                        ParseFromJsonelement(item);
                        break;
                    }
                }
            }
#endif

        }

        private void ParseFromJsonelement(JsonElement jsonElem)
        {
#if NET8_0_OR_GREATER
            Type dynamicType = CreateTypeFromJson(jsonElem, "DynamicPerson");

            object instance = Activator.CreateInstance(dynamicType);

            foreach (var prop in jsonElem.EnumerateObject())
            {
                PropertyInfo pi = dynamicType.GetProperty(prop.Name);
                object value = prop.Value.ValueKind switch
                {
                    JsonValueKind.String => prop.Value.GetString(),
                    JsonValueKind.Number => prop.Value.GetDouble(),
                    JsonValueKind.True => true,
                    JsonValueKind.False => false,
                    _ => null
                };
                pi.SetValue(instance, value);
            }
            Console.WriteLine("Properties of runtime class:");
            foreach (var prop in dynamicType.GetProperties())
            {
                Console.WriteLine($"{prop.Name} = {prop.GetValue(instance)}");
            }
#endif
        }

        private void StartExecution(OperationViewModel op, ICollection<ConnectionViewModel> connections)
        {
            var url = op.Title ?? "";
            if (url.ToLower().Contains("create model"))
            {
                var conByTitle = connections.Where(c => c.Input.Operation.Title.ToLower().Contains("create model")).ToList();
                if (conByTitle.Count <= 0)
                {
                    OnLogMe?.Invoke("No input found", logType.Error);
                    throw new Exception("Input connection missig for : " + url);
                }
                var flowConnection = conByTitle.Where(c=>c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
                if (flowConnection == null) 
                {
                    OnLogMe?.Invoke("No input found", logType.Error);
                    throw new Exception("Input connection missig for : " + url);
                }

                var outputNodeId = flowConnection.InputNodeId;

                string outputValue = string.Empty;
                if (outputs.TryGetValue(outputNodeId, out outputValue))
                {
                    //Convert model here
                    //CreateModelsFromString(outputValue);
                    var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
                    Directory.CreateDirectory(customModelDir);
                    string className = "Class 1";
                    className = className.Replace(" ", "");
                    var classStruct = GenerateClassFromJson(outputValue, className);
                    string flPath = Path.Join(customModelDir, $"{className}.cs");
                    File.WriteAllText(flPath, classStruct);
                    OperationInfoViewModel opv = new OperationInfoViewModel()
                    {
                        Title = className,
                        IsModelNode = true,
                        Type = OperationType.System,
                        sysOp = SystemOperations.GET_SET,
                        ClassName = className
                    };
                    this.editorViewModel.Calculator.OperationsMenu.AddNewModel(opv);
                }
            }

            if (url.ToLower().Contains("set"))
            {
                // Handle simple variable SET (e.g., "SET myVar (string)")
                if (op is SystemOperationViewModel sysVarOp && url.Contains("(") && url.Contains(")"))
                {
                    var parts = url.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                    if (parts.Length >= 2)
                    {
                        var varName = parts[1];
                        // Find the data input connection (non-triangle)
                        var inputCons = connections.Where(c => c.Input.Operation == op && c.Input.Shape != ConnectorShape.Triangle).ToList();
                        if (inputCons.Any())
                        {
                            var sourceConn = inputCons.First();
                            var sourceNodeId = sourceConn.Output.Operation.NodeId;
                            if (outputs.TryGetValue(sourceNodeId, out var sourceVal))
                            {
                                variables[varName] = sourceVal;
                                OnLogMe?.Invoke($"Variable '{varName}' SET to: {sourceVal}");
                            }
                            else
                            {
                                // Use the connector value directly
                                variables[varName] = sourceConn.Output.Value.ToString();
                                OnLogMe?.Invoke($"Variable '{varName}' SET to connector value: {sourceConn.Output.Value}");
                            }
                        }
                        else
                        {
                            // No connection — use default value from the Value input connector
                            var valueInput = op.Input.FirstOrDefault(i => i.Title == "Value");
                            if (valueInput != null)
                            {
                                variables[varName] = valueInput.Value.ToString();
                                OnLogMe?.Invoke($"Variable '{varName}' SET to default: {valueInput.Value}");
                            }
                        }
                        outputs[op.NodeId] = variables.ContainsKey(varName) ? variables[varName]?.ToString() ?? "" : "";
                    }
                    return;
                }

                var fullTitle = url;
                var classNameToSet = fullTitle.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1];
                var inputConnectionList = connections.Where(c => c.Input.Operation.Title == url).ToList();
                var inputConnection = inputConnectionList.Where(c => c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
                if (inputConnection == null)
                {
                    OnLogMe?.Invoke("No input found", logType.Error);
                    throw new Exception("Input connection missig for : " + url);
                }
                var outPutNode = inputConnection.Output;
                if (outPutNode == null) 
                {
                    OnLogMe?.Invoke("No output found", logType.Error);
                    throw new Exception("Output connection missig for : " + url);
                }
                if (outPutNode.Operation.Title.ToLower().Contains("parse json"))
                {
                    var inputNodeForParseJson = connections.Where(c => c.Input.Operation.Title == outPutNode.Title).FirstOrDefault();
                    if (inputNodeForParseJson == null)
                    {
                        OnLogMe?.Invoke("No input found", logType.Error);
                        throw new Exception("Input connection missig for : " + url);
                    }
                    var inputNodeOutput = outputs[inputNodeForParseJson.InputNodeId];
                    if (inputNodeOutput != null) 
                    {
                        var obj = JsonConvert.DeserializeObject(inputNodeOutput);
                        variables[op.NodeId] = obj;
                    }
                }
            }
            
            OnLogMe?.Invoke($"Execution started : {url}");
            if (url.ToLower() == "begin" || url.ToLower() == "end")
            {
                OnLogMe?.Invoke($"Being or End node found. skipping it");
                return;
            }
            if (url.ToLower() == "auth")
            {
                ResolveAuthNode(editorViewModel.Calculator.Operations);
                OnLogMe?.Invoke($"Auth node resolved. Base URL: {_authBaseUrl}, Auth Type: {_authType}");
                return;
            }
            if (op is SystemOperationViewModel debugSysOp && debugSysOp.SystemOperationType == SystemOperations.DEBUG)
            {
                // Find the data input connection (non-triangle)
                var debugInputCons = connections.Where(c => c.Input.Operation == op && c.Input.Shape != ConnectorShape.Triangle).ToList();
                if (debugInputCons.Any())
                {
                    var sourceConn = debugInputCons.First();
                    var sourceNodeId = sourceConn.Output.Operation.NodeId;
                    if (outputs.TryGetValue(sourceNodeId, out var debugVal))
                    {
                        OnLogMe?.Invoke($"[DEBUG] {debugVal}");
                    }
                    else if (sourceConn.Output.Value != null)
                    {
                        OnLogMe?.Invoke($"[DEBUG] {sourceConn.Output.Value}");
                    }
                    else
                    {
                        OnLogMe?.Invoke($"[DEBUG] (no value)", logType.Warning);
                    }
                }
                else
                {
                    OnLogMe?.Invoke($"[DEBUG] No input connected", logType.Warning);
                }
                return;
            }

            // Handle Function nodes — execute the inner flow
            if (op is FunctionOperationViewModel funcOp)
            {
                OnLogMe?.Invoke($"Executing function: {funcOp.FunctionName}");
                ExecuteFunction(funcOp, connections);
                return;
            }
            OnLogMe?.Invoke($"Starting Execution : {url}");
            var httpMethod = (op is APIOperationViewModel apiVm) ? apiVm.OperationType?.ToLower() ?? "get" : "get";
            var res = GetResponse(url, httpMethod);
            if (!string.IsNullOrEmpty(res))
            {
                outputs[op.NodeId] = res;

                // Auto-parse response to model if ResponseModelClassName is specified
                if (op is APIOperationViewModel apiOp && !string.IsNullOrEmpty(apiOp.ResponseModelClassName))
                {
                    var modelClassName = apiOp.ResponseModelClassName;
                    bool isList = modelClassName.StartsWith("List<") && modelClassName.EndsWith(">");
                    var innerClassName = isList ? modelClassName.Substring(5, modelClassName.Length - 6) : modelClassName;

                    // Store the parsed JSON as variable keyed by node ID for downstream SET/Split nodes
                    variables[op.NodeId] = res;
                    OnLogMe?.Invoke($"Response auto-parsed as {modelClassName}");
                }
            }
            OnLogMe?.Invoke($"Response Result : {res}");
        }

        private void ResolveAuthNode(ICollection<OperationViewModel> allNodes)
        {
            var authNode = allNodes.OfType<AuthOperationViewModel>().FirstOrDefault();
            if (authNode == null)
            {
                OnLogMe?.Invoke("No Auth node found. Using defaults.", logType.Warning);
                return;
            }

            foreach (var inp in authNode.Input)
            {
                var title = inp.Title?.Trim() ?? string.Empty;
                // For unconnected inputs the user types a value into the textbox, which is stored as Value (double).
                // But the actual text is stored in the Title's textbox or the Value textbox.
                // We read the connector's Value textbox string representation via the bound text.
                var val = GetConnectorTextValue(inp);

                switch (title)
                {
                    case "Base URL": _authBaseUrl = val; break;
                    case "Auth Type": _authType = val; break;
                    case "Token": _authToken = val; break;
                    case "API Key": _authApiKey = val; break;
                    case "Username": _authUsername = val; break;
                    case "Password": _authPassword = val; break;
                }
            }

            // Also read from the view-model properties which are bound to the text boxes in the node
            if (!string.IsNullOrWhiteSpace(authNode.BaseUrl))
                _authBaseUrl = authNode.BaseUrl;
            if (!string.IsNullOrWhiteSpace(authNode.AuthType))
                _authType = authNode.AuthType;

            OnLogMe?.Invoke($"Auth configured — Base URL: {_authBaseUrl}, Auth Type: {_authType}");
        }

        private void ResolveGetVariableNodes(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections)
        {
            // Find all GET variable nodes (not in flow chain) and populate their output
            foreach (var node in allNodes)
            {
                var title = node.Title ?? "";
                if (!title.StartsWith("GET ") || !title.Contains("(") || !title.Contains(")"))
                    continue;

                var parts = title.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 2) continue;
                var varName = parts[1];

                // Check if the variable already has a value
                if (variables.TryGetValue(varName, out var existingVal))
                {
                    outputs[node.NodeId] = existingVal?.ToString() ?? "";
                    // Set the output connector value for downstream nodes
                    var outConn = node.Output.FirstOrDefault(c => c.Shape != ConnectorShape.Triangle);
                    if (outConn != null)
                    {
                        if (double.TryParse(existingVal?.ToString(), out double dVal))
                            outConn.Value = dVal;
                    }
                    OnLogMe?.Invoke($"GET variable '{varName}' resolved to: {existingVal}");
                }
                else
                {
                    OnLogMe?.Invoke($"GET variable '{varName}' has no value yet (will resolve during execution).", logType.Warning);
                }
            }
        }

        private static string GetConnectorTextValue(ConnectorViewModel connector)
        {
            return string.Empty;
        }

        private void ExecuteFunction(FunctionOperationViewModel funcOp, ICollection<ConnectionViewModel> connections)
        {
            // Propagate outer input values into the function
            for (int i = 0; i < funcOp.InputParameters.Count; i++)
            {
                var outerInputIndex = i + 1; // skip flow triangle
                if (outerInputIndex < funcOp.Input.Count)
                {
                    var outerConnector = funcOp.Input[outerInputIndex];
                    var inputCon = connections.FirstOrDefault(c => c.Input == outerConnector);
                    if (inputCon != null)
                    {
                        var sourceNodeId = inputCon.Output.Operation.NodeId;
                        if (outputs.TryGetValue(sourceNodeId, out var val))
                        {
                            outerConnector.Value = double.TryParse(val, out var d) ? d : 0;
                        }
                    }
                }
            }

            // Trigger propagation of input values to inner Begin node
            funcOp.PropagateInputsToInner();

            // Execute the inner calculator's flow (Begin -> End)
            var innerOps = funcOp.InnerCalculator.Operations;
            var innerConns = funcOp.InnerCalculator.Connections;

            OnLogMe?.Invoke($"  [Function '{funcOp.FunctionName}'] Resolving inner GET variables...");
            ResolveGetVariableNodes(innerOps, innerConns);

            OnLogMe?.Invoke($"  [Function '{funcOp.FunctionName}'] Executing inner chain...");
            var innerStart = innerOps.FirstOrDefault(n => n.Title?.Equals("Begin", StringComparison.OrdinalIgnoreCase) == true);
            if (innerStart == null)
            {
                OnLogMe?.Invoke($"  [Function '{funcOp.FunctionName}'] No Begin node found inside function!", logType.Error);
                return;
            }

            var visited = new HashSet<string>();
            TraverseChain(innerStart, "end", innerConns, visited, true);

            // Collect outputs from the inner End node
            var innerEnd = innerOps.FirstOrDefault(n => n.Title?.Equals("End", StringComparison.OrdinalIgnoreCase) == true);
            if (innerEnd != null)
            {
                for (int i = 0; i < funcOp.OutputParameters.Count; i++)
                {
                    var endInputIndex = i + 1; // skip flow triangle
                    if (endInputIndex < innerEnd.Input.Count)
                    {
                        var endConnector = innerEnd.Input[endInputIndex];
                        var endCon = innerConns.FirstOrDefault(c => c.Input == endConnector);
                        if (endCon != null)
                        {
                            var sourceId = endCon.Output.Operation.NodeId;
                            if (outputs.TryGetValue(sourceId, out var resultVal))
                            {
                                var outerOutputIndex = i + 1; // skip flow triangle
                                if (outerOutputIndex < funcOp.Output.Count)
                                {
                                    funcOp.Output[outerOutputIndex].Value = double.TryParse(resultVal, out var dv) ? dv : 0;
                                }
                                outputs[funcOp.NodeId] = resultVal;
                                OnLogMe?.Invoke($"  [Function '{funcOp.FunctionName}'] Output '{funcOp.OutputParameters[i].Name}' = {resultVal}");
                            }
                        }
                    }
                }
            }

            OnLogMe?.Invoke($"Function '{funcOp.FunctionName}' execution completed.");
        }

        private string GetResponse(string url, string type)
        {
            string baseURL = !string.IsNullOrWhiteSpace(_authBaseUrl) ? _authBaseUrl : "https://localhost:7107";
            string responseString = string.Empty;

#if NET8_0_OR_GREATER
            using (HttpClient client = new HttpClient())
            {
                client.BaseAddress = new Uri(baseURL);

                // Apply authentication headers
                if (!string.IsNullOrWhiteSpace(_authType))
                {
                    var authTypeLower = _authType.Trim().ToLower();
                    if (authTypeLower.Contains("bearer") && !string.IsNullOrWhiteSpace(_authToken))
                    {
                        client.DefaultRequestHeaders.Authorization =
                            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _authToken);
                    }
                    else if (authTypeLower.Contains("basic") &&
                             !string.IsNullOrWhiteSpace(_authUsername))
                    {
                        var credentials = Convert.ToBase64String(
                            Encoding.UTF8.GetBytes($"{_authUsername}:{_authPassword}"));
                        client.DefaultRequestHeaders.Authorization =
                            new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
                    }
                    else if (authTypeLower.Contains("api") && !string.IsNullOrWhiteSpace(_authApiKey))
                    {
                        client.DefaultRequestHeaders.Add("X-Api-Key", _authApiKey);
                    }
                }

                if (type == "get")
                {
                    HttpResponseMessage response = client.GetAsync(url).Result;
                    response.EnsureSuccessStatusCode();
                    responseString = response.Content.ReadAsStringAsync().Result;
                }
            }
#endif

            return responseString;
        }

        private void PerformChainCheck(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections, bool isExecute)
        {
            string startNodeTitle = "begin";
            string endNodeTitle = "end";

            // Find the starting node
            var startNode = allNodes.FirstOrDefault(node => node.Title?.ToLower() == startNodeTitle);
            if (startNode == null)
            {
                OnLogMe?.Invoke("No Begin node found", logType.Error);
                throw new Exception("Begin node not found");
            }

            // Track visited nodes to prevent infinite loops or revisits
            HashSet<string> visitedNodes = new HashSet<string>();

            // Perform the DFS Traversal
            bool isValidChain = TraverseChain(startNode, endNodeTitle, connections, visitedNodes, isExecute);

            if (isValidChain)
            {
                OnLogMe?.Invoke("Found complete chain...");
                OnLogMe?.Invoke("You did it champ...");
            }
            else
            {
                OnLogMe?.Invoke("Broken Chain found!!! Please fix the chain from begin to end.", logType.Warning);
            }
        }

        private bool TraverseChain(OperationViewModel currentNode, string endNodeTitle, 
                                   ICollection<ConnectionViewModel> connections,
                                   HashSet<string> visitedNodes, bool isExecute)
        {
            OnLogMe?.Invoke($"Checking node: {currentNode.Title} , NodeId : {currentNode.NodeId}");

            // If we've reached the "end" node, the chain is valid
            if (currentNode.Title?.Equals(endNodeTitle, StringComparison.OrdinalIgnoreCase) == true)
            {
                return true;
            }

            // Detect cycles and avoid infinite recursion
            if (visitedNodes.Contains(currentNode.NodeId))
            {
                OnLogMe?.Invoke($"Cycle detected at node: {currentNode.Title}. Broken chain found!", logType.Warning);
                return false;
            }
            visitedNodes.Add(currentNode.NodeId);

            // Find all outgoing connections from the current node
            var outgoingConnections = connections.Where(conn => conn.Output?.Operation == currentNode && conn.Output.Shape == ConnectorShape.Triangle);

            if (!outgoingConnections.Any())
            {
                // Only log the first "broken chain" warning, and stop further logging on backtrack
                OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
                isAlreadyLogged = true;
                return false;
            }

            // Check all outgoing connections
            foreach (var connection in outgoingConnections)
            {
                var nextNode = connection.Input?.Operation;
                if (nextNode == null)
                {
                    // Handle null input in the connection
                    OnLogMe?.Invoke($"Broken chain detected due to null input connection from node: {currentNode.Title}", logType.Warning);
                    return false;
                }

                OnLogMe?.Invoke($"Following connection from {currentNode.Title} to {nextNode.Title}");

                if (isExecute)
                {
                    StartExecution(currentNode, connections);
                }

                // Recursively check the next node; stop on the first failure
                if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute))
                {
                    return true; // If a valid path to the "end" is found, exit
                }
            }

            if (!isAlreadyLogged)
            {
                // If no connections lead to the end, this is the broken point => log here ONLY
                OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
                isAlreadyLogged = true;
            }
            
            return false;
        }

        private string ValidateRequiredNodes(string nodeName, ICollection<OperationViewModel> allnodes)
        {
            var requireNode = allnodes.Where(c => c.Title.ToLower() == nodeName).ToList();
            if (requireNode.Count > 1)
            {
                return $"One or more {nodeName} node found. Only one allowed at a time.";
            }
            else if (requireNode.Count == 0)
            {
                return $"No {nodeName} node found. At least one {nodeName} node required system to work";
            }
            return string.Empty;
        }
    }
}
