using System.Linq;
using System.Text;
using NodePipeline.Abstractions.Interfaces.Nodes;
using NodePipeline.Engine.CodeGeneration.Abstractions;
using NodePipeline.Engine.CodeGeneration.Abstractions.Models;

namespace NodePipeline.Engine.CodeGeneration;

internal static class NodeFactoryGenerator
{
    public static string Generate(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("using System.Globalization;");
        sb.AppendLine("using NodePipeline.Abstractions;");
        sb.AppendLine("using NodePipeline.Abstractions.Exceptions.NodeFactory;");
        sb.AppendLine("using NodePipeline.Abstractions.Interfaces;");
        sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Nodes;");
        sb.AppendLine("using NodePipeline.Configuration.Abstractions.Models.Execute;");
        sb.AppendLine("using NodePipeline.Engine.Abstractions;");
        sb.AppendLine("using NodePipeline.Engine.Abstractions.Validation;");
        sb.AppendLine("using NodePipeline.Engine.NodeFields;");
        sb.AppendLine("using NodePipeline.Engine.Utils;");
        sb.AppendLine();
        sb.AppendLine("// ReSharper disable CheckNamespace");
        sb.AppendLine("// ReSharper disable ConvertTypeCheckPatternToNullCheck");
        sb.AppendLine("// ReSharper disable HeuristicUnreachableCode");
        sb.AppendLine("// ReSharper disable RedundantCast");
        sb.AppendLine("// ReSharper disable RedundantNameQualifier");
        sb.AppendLine("// ReSharper disable ReturnTypeCanBeNotNullable\n");
        sb.AppendLine("// ReSharper disable UnusedMember.Local");
        sb.AppendLine("// ReSharper disable UnusedParameter.Local");
        sb.AppendLine();
        sb.AppendLine("#nullable enable");
        sb.AppendLine("#pragma warning disable CS0162 // Unreachable code detected");
        sb.AppendLine();
        sb.AppendLine("namespace NodePipeline.Engine.Generated;");
        sb.AppendLine();
        sb.AppendLine("public sealed class GeneratedNodeFactory : INodeFactory");
        sb.AppendLine("{");

        sb.AppendLine("    public Dictionary<string, Func<INode>> NodeFactories { get; set; } = new();");
        sb.AppendLine(
            "    public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();");
        sb.AppendLine();

        sb.AppendLine(BuildCreateNodeMethod(model));
        sb.AppendLine(BuildInitializeNodeFieldsMethod(model));
        sb.AppendLine(BuildInitializeNodeFieldsSpecificMethods(model));
        sb.AppendLine(BuildGetParameterCodesMethod(model));
        sb.AppendLine(BuildParameterCodesForMethods(model));
        sb.AppendLine(BuildReadParameterValueMethod(model));
        sb.AppendLine(BuildReadParameterValueForMethods(model));
        sb.AppendLine(BuildGetNodePortsMethod(model));
        sb.AppendLine(BuildGetNodePortsSpecificMethods(model));
        sb.AppendLine(BuildGetNodeOutputsMethod(model));
        sb.AppendLine(BuildGetNodeOutputsSpecificMethods(model));
        sb.AppendLine(BuildConnectPortsMethod(model));
        sb.AppendLine(BuildConnectPortsSpecificMethods(model));
        sb.AppendLine(BuildConnectPortsStructGenericMethod());
        sb.AppendLine(BuildConnectPortsClassGenericMethod());
        sb.AppendLine(BuildParseParameterMethod(model));
        sb.AppendLine(BuildGetParameterDefaultValueMethod(model));
        sb.AppendLine(BuildGetParameterDefaultValueSpecificMethods(model));
        sb.AppendLine(BuildSetNodeParametersValuesMethod(model));
        sb.AppendLine(BuildSetNodeParametersValuesSpecificMethods(model));
        sb.AppendLine(BuildGetNodeTypeMethod(model));

        sb.AppendLine(
            "    private record PortConnectionInfo(bool Connected, object? OutputFieldObj, string FromPortName, string FromNodeType, string FromNodeId);");
        sb.AppendLine("}");
        sb.AppendLine("#pragma warning restore CS0162 // Unreachable code detected");
        sb.AppendLine("#nullable restore");
        return sb.ToString();
    }

    private static string BuildCreateNodeMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        //TODO: add method description
        sb.AppendLine("    public INode CreateNode(string pipelineId, NodeConfig config)");
        sb.AppendLine("    {");
        sb.AppendLine("        INode? node;");
        sb.AppendLine("        if (NodeFactories.TryGetValue(config.Type, out var f))");
        sb.AppendLine("        {");
        sb.AppendLine("            node = f();");
        sb.AppendLine("            InitializeNodeFields(pipelineId, node, config.Id);");
        sb.AppendLine("            return node;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        switch (config.Type)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
            if (n.HasParameterlessConstructor)
            {
                sb.AppendLine($"                node = new {n.TypeNameFull}();");
                sb.AppendLine("                break;");
            }
            else
            {
                sb.AppendLine(
                    "                throw new NodeCanNotBeInitializedWithoutFactoryException(pipelineId, config.Type, config.Id);");
            }
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, config.Type, config.Id);");
        sb.AppendLine("        }");
        sb.AppendLine("        InitializeNodeFields(pipelineId, node, config.Id);");
        sb.AppendLine(
            "        return node ?? throw new NodeWasNotInitializedException(pipelineId, config.Type, config.Id);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string GetSpecificInitializeNodeFieldsMethodName(NodeDescriptor node)
    {
        return $"Initialize__{node.TypeNameShortSanitized}__NodeFields";
    }

    private static string BuildInitializeNodeFieldsMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private void InitializeNodeFields(string pipelineId, INode node, string nodeId)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (node)");
        sb.AppendLine("        {");
        var i = 1;
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            case {n.TypeNameFull} t{i}:");
            sb.AppendLine($"                {GetSpecificInitializeNodeFieldsMethodName(n)}(t{i}, nodeId);");
            sb.AppendLine("                break;");
            i++;
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildInitializeNodeFieldsSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private static void {GetSpecificInitializeNodeFieldsMethodName(n)}({n.TypeNameFull} node, string nodeId)");
            sb.AppendLine("    {");
            foreach (var f in n.Fields)
            {
                var dir = f.Direction switch
                {
                    FieldDirection.Input => "FieldDirection.Input",
                    FieldDirection.Output => "FieldDirection.Output",
                    _ => "FieldDirection.Parameter"
                };

                var nodeFieldType = string.Concat(f.ValueType, f.IsNullableValueType ? "?" : string.Empty);
                sb.AppendLine(
                    $"        node.{f.PropertyName} = new NodePipeline.Abstractions.Models.NodeField<{nodeFieldType}>()");
                sb.AppendLine("        {");
                sb.AppendLine($"            Name = \"{f.FieldName}\",");
                sb.AppendLine(
                    $"            Code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{f.FieldName}\", {dir}),");
                sb.AppendLine($"            Direction = {dir},");
                sb.AppendLine("            // Description intentionally omitted for runtime");
                sb.AppendLine("        };");
            }

            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string GetSpecificParameterCodesMethodName(NodeDescriptor node)
    {
        return $"GetParameterCodesFor__{node.TypeNameShortSanitized}__Node";
    }

    private static string BuildGetParameterCodesMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("    public IEnumerable<string> GetParameterCodes(string nodeType)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (nodeType)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
            sb.AppendLine($"                return {GetSpecificParameterCodesMethodName(n)}();");
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeTypeNotFoundException(nodeType);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildParameterCodesForMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"    private IEnumerable<string> {GetSpecificParameterCodesMethodName(n)}()");
            sb.AppendLine("    {");
            var nodeParameters = n.Fields
                .Where(f => f.Direction == FieldDirection.Parameter)
                .ToList();
            sb.Append("        return");
            if (nodeParameters.Count == 0)
            {
                sb.AppendLine(" [];");
            }
            else
            {
                sb.AppendLine("        [");
                foreach (var f in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
                    sb.AppendLine($"            \"{f.FieldName}\",");
                sb.AppendLine("        ];");
            }

            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string GetSpecificReadParameterValueMethodName(NodeDescriptor node)
    {
        return $"ReadParameterValueFrom__{node.TypeNameShortSanitized}__Node";
    }

    private static string BuildReadParameterValueMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    public object? ReadParameterValue(string pipelineId, string nodeId, string nodeType, string parameterName, string? valueString)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (nodeType)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
            sb.AppendLine(
                $"                return {GetSpecificReadParameterValueMethodName(n)}(pipelineId, nodeType, nodeId, parameterName, valueString);");
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeTypeNotFoundException(nodeType);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildReadParameterValueForMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private static object? {GetSpecificReadParameterValueMethodName(n)}(string pipelineId, string nodeType, string nodeId, string parameterName, string? valueString)");
            sb.AppendLine("    {");
            sb.AppendLine("        switch (parameterName)");
            sb.AppendLine("        {");
            foreach (var p in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
            {
                sb.AppendLine($"            case \"{p.FieldName}\":");
                sb.AppendLine($"                return ParseParameter<{p.ValueType}>(valueString);");
            }

            sb.AppendLine("            default:");
            sb.AppendLine(
                "                throw new NodeParameterNotFoundException(pipelineId, parameterName, nodeType, nodeId);");
            sb.AppendLine("        }");
            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string GetSpecificNodePortsMethodName(NodeDescriptor node)
    {
        return $"Get__{node.TypeNameShortSanitized}__NodePorts";
    }

    private static string BuildGetNodePortsMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    public Dictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, HashSet<string> nodeTypes, Dictionary<string, IEnumerable<string>> nodeList)");
        sb.AppendLine("    {");
        sb.AppendLine("        var result = new Dictionary<NodePortKey, NodePortInfo>();");
        sb.AppendLine("        foreach (var nodeType in nodeTypes)");
        sb.AppendLine("        {");
        sb.AppendLine("            if (!nodeList.TryGetValue(nodeType, out var ids)) continue;");
        sb.AppendLine("            foreach (var nodeId in ids)");
        sb.AppendLine("            {");
        sb.AppendLine("                var ports = GetNodePorts(pipelineId, nodeType, nodeId);");
        sb.AppendLine("                foreach (var kv in ports) result.Add(kv.Key, kv.Value);");
        sb.AppendLine("            }");
        sb.AppendLine("        }");
        sb.AppendLine("        return result;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    public IEnumerable<NodePortInfo> GetNodeInputPorts(string pipelineId, string nodeType, string nodeId)");
        sb.AppendLine("    {");
        sb.AppendLine("        var ports = GetNodePorts(pipelineId, nodeType, nodeId);");
        sb.AppendLine("        foreach (var kv in ports) if (kv.Key.Input) yield return kv.Value;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    private Dictionary<NodePortKey, NodePortInfo> GetNodePorts(string pipelineId, string nodeType, string nodeId)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (nodeType)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
            sb.AppendLine(
                $"            {NodeGeneratorHelper.GetNodeNameCaseString(n)} return {GetSpecificNodePortsMethodName(n)}(nodeId);");
        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, nodeType, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BoolToString(bool value)
    {
        return value.ToString().ToLower();
    }

    private static string BuildGetNodePortsSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private static Dictionary<NodePortKey, NodePortInfo> {GetSpecificNodePortsMethodName(n)}(string nodeId)");
            sb.AppendLine("    {");
            sb.AppendLine("        return new Dictionary<NodePortKey, NodePortInfo>()");
            sb.AppendLine("        {");
            foreach (var f in n.Fields.Where(f => f.Direction is FieldDirection.Input or FieldDirection.Output))
            {
                var input = f.Direction == FieldDirection.Input ? "true" : "false";
                sb.AppendLine("            {");
                sb.AppendLine($"                new NodePortKey(nodeId, \"{f.FieldName}\", {input}),");
                sb.AppendLine(
                    $"                new NodePortInfo(\"{f.FieldName}\", typeof({f.ValueType.TrimEnd('?')}), {BoolToString(f.IsNullableValueType)}, {BoolToString(f.Metadata?.IsRequired == true)}, {BoolToString(f.Metadata?.DisallowNullableOutput == true)}, {BoolToString(f.Direction == FieldDirection.Input)})");
                sb.AppendLine("            },");
            }

            sb.AppendLine("        };");
            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string GetGetNodeOutputsSpecificMethodName(NodeDescriptor node)
    {
        return $"Get__{node.TypeNameShortSanitized}__NodeOutputs";
    }

    private static string BuildGetNodeOutputsMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private IReadOnlyDictionary<string, object> GetNodeOutputs(string pipelineId, string nodeId, INode node)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (node)");
        sb.AppendLine("        {");
        var i = 1;
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            case {n.TypeNameFull} t{i}:");
            sb.AppendLine($"                return {GetGetNodeOutputsSpecificMethodName(n)}(nodeId, t{i});");
            i++;
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        sb.AppendLine();
        return sb.ToString();
    }

    private static string BuildGetNodeOutputsSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private IReadOnlyDictionary<string, object> {GetGetNodeOutputsSpecificMethodName(n)}(string nodeId, {n.TypeNameFull} node)");
            sb.AppendLine("    {");
            var nodeOutputs = n.Fields
                .Where(f => f.Direction == FieldDirection.Output)
                .ToList();
            sb.Append("        return new Dictionary<string, object>");
            if (nodeOutputs.Count == 0)
            {
                sb.AppendLine("();");
            }
            else
            {
                sb.AppendLine();
                sb.AppendLine("        {");
                foreach (var p in nodeOutputs)
                    sb.AppendLine(
                        $"            {{ NodeFieldCodeGenerator.GenerateFieldCode(nodeId, \"{p.FieldName}\", FieldDirection.Output), node.{p.PropertyName} }},");
                sb.AppendLine("        };");
            }

            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string GetConnectPortsMethodName(NodeDescriptor node)
    {
        return $"Connect__{node.TypeNameShortSanitized}__NodePorts";
    }

    private static string BuildConnectPortsMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    public void ConnectPorts<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes) where TNode : INode");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (node)");
        sb.AppendLine("        {");
        var i = 1;
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            case {n.TypeNameFull} t{i}:");
            sb.AppendLine(
                $"                {GetConnectPortsMethodName(n)}(pipelineId, t{i}, nodeId, inputs, createdNodes);");
            sb.AppendLine("                break;");
            i++;
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildConnectPortsSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private PortConnectionInfo? TryConnectPorts<T>(string pipelineId, string nodeId, string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs, string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool isRequired = false) where T : notnull");
        sb.AppendLine("    {");
        sb.AppendLine("        if (!inputs.TryGetValue(inputKey, out var connection))");
        sb.AppendLine(
            "            return !isRequired ? null : throw new InputPortHasNoRequiredConnectionException(pipelineId, inputKey, nodeId, nodeType);");
        sb.AppendLine("        var fromNodeId = connection.NodeId;");
        sb.AppendLine("        var fromPortName = connection.OutputName;");
        sb.AppendLine(
            "        if (nodeInput == null) throw new InputPortNotFoundException(pipelineId, inputKey, nodeId, nodeType);");
        sb.AppendLine(
            "        if (!createdNodes.TryGetValue(fromNodeId, out var fromNode)) throw new OutputPortNodeNotFoundException(pipelineId, fromNodeId, nodeId);");
        sb.AppendLine("        var outputs = GetNodeOutputs(pipelineId, fromNodeId, fromNode);");
        sb.AppendLine(
            "        var outputFieldKey = NodeFieldCodeGenerator.GenerateFieldCode(fromNodeId, fromPortName, FieldDirection.Output);");
        sb.AppendLine(
            "        if (!outputs.TryGetValue(outputFieldKey, out var outputFieldObj)) throw new OutputPortNotFoundException(pipelineId, fromPortName, fromNodeId, nodeId);");
        sb.AppendLine("        var fromNodeType = GetNodeType(pipelineId, fromNodeId, fromNode);");
        sb.AppendLine("        if (outputFieldObj is INodeField<T> outStrict && nodeInput is INodeField<T> inStrict)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectStatic(outStrict, inStrict);");
        sb.AppendLine(
            "            return new PortConnectionInfo(true, outputFieldObj, fromPortName, fromNodeType, fromNodeId);");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        return new PortConnectionInfo(false, outputFieldObj, fromPortName, fromNodeType, fromNodeId);");
        sb.AppendLine("    }");
        sb.AppendLine();

        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private void {GetConnectPortsMethodName(n)}(string pipelineId, {n.TypeNameFull} node, string nodeId, Dictionary<string, InputSource> inputs, IReadOnlyDictionary<string, INode> createdNodes)");
            sb.AppendLine("    {");
            foreach (var input in n.Fields.Where(f => f.Direction == FieldDirection.Input))
            {
                var methodName = input.IsValueReferenceType ? "ConnectPortsClass" : "ConnectPortsStruct";
                sb.AppendLine(
                    $"        {methodName}(pipelineId, nodeId, \"{n.TypeNameShort}\", node.{input.PropertyName}, nameof(node.{input.PropertyName}), inputs, \"{input.FieldName}\", createdNodes, disallowNullableOutput: {BoolToString(input.Metadata?.DisallowNullableOutput == true)}, isRequired: true);");
            }

            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    private static string BuildParseParameterMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private static T? ParseParameter<T>(string? valueString)");
        sb.AppendLine("    {");
        sb.AppendLine("        var type = typeof(T);");
        sb.AppendLine(
            "        if (type == typeof(string)) return valueString is not null ? (T)(object)valueString : default;");
        sb.AppendLine("        if (string.IsNullOrEmpty(valueString)) return default;");
        sb.AppendLine("        if (type == typeof(int)) return (T)(object)int.Parse(valueString);");
        sb.AppendLine(
            "        if (type == typeof(double)) return (T)(object)double.Parse(valueString, CultureInfo.InvariantCulture);");
        sb.AppendLine("        if (type == typeof(bool)) return (T)(object)bool.Parse(valueString);");
        var knownEnums = model.Nodes.SelectMany(q => q.Fields
                .Where(x => x.Direction == FieldDirection.Parameter && x.IsValueEnum))
            .Select(q => q.ValueType)
            .Distinct();
        foreach (var enumType in knownEnums)
            sb.AppendLine(
                $"        if (type == typeof({enumType})) return (T)(object)Enum.Parse<{enumType}>(valueString);");
        sb.AppendLine("        throw new NotSupportedException(type.Name);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string GetGetParameterDefaultValueMethodName(NodeDescriptor node)
    {
        return $"GetParameterDefaultValueFor__{node.TypeNameShortSanitized}__Node";
    }

    private static string BuildGetParameterDefaultValueMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("    public object? GetParameterDefaultValue(string nodeType, string parameterName)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (nodeType)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            {NodeGeneratorHelper.GetNodeNameCaseString(n)}");
            sb.AppendLine($"                return {GetGetParameterDefaultValueMethodName(n)}(parameterName);");
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeParameterNotFoundException(parameterName, nodeType);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildGetParameterDefaultValueSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        const string tab = "                ";
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private static object? {GetGetParameterDefaultValueMethodName(n)}(string parameterName)");
            sb.AppendLine("    {");
            sb.AppendLine("        switch (parameterName)");
            sb.AppendLine("        {");
            foreach (var p in n.Fields.Where(f => f.Direction == FieldDirection.Parameter))
            {
                sb.AppendLine($"            case \"{p.FieldName}\":");
                var parameterType = string.Concat(p.ValueType, p.IsNullableValueType ? "?" : string.Empty);
                sb.AppendLine(p.Metadata?.DefaultValue is null
                    ? $"                return GetDefaultValue<{parameterType}>();"
                    : GetDefaultValueCode(p, parameterType, tab));
            }

            sb.AppendLine("            default:");
            sb.AppendLine($"                throw new NodeParameterNotFoundException(parameterName, \"{n.Type}\");");
            sb.AppendLine("        }");
            sb.AppendLine("    }");
            sb.AppendLine();
        }

        sb.AppendLine("    private static T? GetDefaultValue<T>()");
        sb.AppendLine("    {");
        sb.AppendLine("        var type = typeof(T);");
        sb.AppendLine("        if (type == typeof(string)) return (T)(object)string.Empty;");
        sb.AppendLine("        if (type == typeof(int)) return (T)(object)0;");
        sb.AppendLine("        if (type == typeof(double)) return (T)(object)0d;");
        sb.AppendLine("        if (type == typeof(decimal)) return (T)(object)0m;");
        sb.AppendLine("        if (type == typeof(bool)) return (T)(object)false;");
        sb.AppendLine("        if (type == typeof(Guid)) return (T)(object)Guid.Empty;");
        sb.AppendLine("        if (type.IsEnum) return default;");
        sb.AppendLine("        throw new NotSupportedException(type.Name);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string GetDefaultValueCode(NodeFieldDescriptor parameterDescriptor, string parameterType,
        string tabs)
    {
        var sb = new StringBuilder();
        if (parameterDescriptor.Metadata?.DefaultValue != null)
        {
            var defaultValue = parameterDescriptor.Metadata.DefaultValue;
            sb.Append(tabs);
            switch (defaultValue)
            {
                case string strVal:
                    sb.Append($"return \"{strVal}\";");
                    break;
                case char c:
                    sb.Append($"return '{c}';");
                    break;
                case bool b:
                    sb.Append($"return {b.ToString().ToLowerInvariant()};");
                    break;
                case int or long or short or byte or uint or ulong or ushort or sbyte:
                    sb.Append($"return {defaultValue};");
                    break;
                case float f:
                    sb.Append($"return {f}f;");
                    break;
                case double d:
                    sb.Append($"return {d}d;");
                    break;
                case decimal dec:
                    sb.Append($"return {dec}m;");
                    break;
                case null:
                    sb.Append("return null;");
                    break;
                default:
                    sb.Append(parameterDescriptor.Metadata?.CanDefaultValueBeInitialized == true
                        ? $"return new {parameterType}();"
                        : $"throw new NotSupportedException(\"Cannot initialize default value for type {parameterType}\");");
                    break;
            }
        }
        else
        {
            sb.Append($"return GetDefaultValue<{parameterType}>();");
        }

        return sb.ToString();
    }

    private static string GetSetNodeParametersValuesMethodName(NodeDescriptor node)
    {
        return $"Set__{node.TypeNameShortSanitized}__NodeParametersValues";
    }

    private static string BuildSetNodeParametersValuesMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    public void SetNodeParametersValues<TNode>(string pipelineId, TNode node, string nodeId, Dictionary<string, object?> parameters) where TNode : INode");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (node)");
        sb.AppendLine("        {");
        var i = 1;
        foreach (var n in model.Nodes)
        {
            sb.AppendLine($"            case {n.TypeNameFull} t{i}:");
            sb.AppendLine($"                {GetSetNodeParametersValuesMethodName(n)}(t{i}, nodeId, parameters);");
            sb.AppendLine("                break;");
            i++;
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildSetNodeParametersValuesSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        foreach (var n in model.Nodes)
        {
            sb.AppendLine(
                $"    private static void {GetSetNodeParametersValuesMethodName(n)}({n.TypeNameFull} node, string nodeId, Dictionary<string, object?> parameters)");
            sb.AppendLine("    {");
            var i = 1;
            foreach (var p in n.Fields.Where(q => q.Direction == FieldDirection.Parameter))
            {
                sb.AppendLine(
                    $"        if (parameters.TryGetValue(\"{p.FieldName}\", out var val{i}) && val{i} is {p.ValueType} typedVal{i})");
                sb.AppendLine($"            node.{p.PropertyName}.Value = typedVal{i};");
                i++;
            }

            sb.AppendLine("    }");
            sb.AppendLine();
        }

        return sb.ToString();
    }


    private static string BuildConnectPortsStructGenericMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private void ConnectPortsStruct<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,");
        sb.AppendLine(
            "        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)");
        sb.AppendLine("        where T : struct");
        sb.AppendLine("    {");
        sb.AppendLine(
            "        var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);");
        sb.AppendLine("        if (portConnectionInfo is null || portConnectionInfo.Connected) return;");
        sb.AppendLine(
            "        if (typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectRuntimeFromNullableStruct(outputField, nodeInput!,");
        sb.AppendLine(
            "                nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectRuntimeBothNullable(outNullable, inNullable);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)");
        sb.AppendLine("        {");
        sb.AppendLine("            if (disallowNullableOutput)");
        sb.AppendLine("            {");
        sb.AppendLine(
            "                throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);");
        sb.AppendLine("            }");
        sb.AppendLine(
            "            PortConnector.ConnectRuntimeFromNullableStruct(outNToStrict, nodeInput, currentNodeId: nodeId,");
        sb.AppendLine(
            "                inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectRuntimeToNullableStruct(outStrictToN, inNullable2);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine("        throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,");
        sb.AppendLine(
            "            portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildConnectPortsClassGenericMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private void ConnectPortsClass<T>(string pipelineId, string nodeId,string nodeType, INodeField<T>? nodeInput, string nodeInputName, Dictionary<string, InputSource> inputs,");
        sb.AppendLine(
            "        string inputKey, IReadOnlyDictionary<string, INode> createdNodes, bool disallowNullableOutput = false, bool isRequired = false)");
        sb.AppendLine("        where T : class");
        sb.AppendLine("    {");
        sb.AppendLine(
            "        var portConnectionInfo = TryConnectPorts(pipelineId, nodeId, nodeType, nodeInput, nodeInputName, inputs, inputKey, createdNodes, isRequired);");
        sb.AppendLine("        if (portConnectionInfo is null || portConnectionInfo.Connected) return;");
        sb.AppendLine(
            "        if (!typeof(T).IsValueType && portConnectionInfo.OutputFieldObj is INodeField<T?> outputField)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectRuntimeFromNullableClass(outputField, nodeInput!,");
        sb.AppendLine(
            "                nodeId, inputKey, portConnectionInfo.FromNodeId, portConnectionInfo.FromPortName);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNullable && nodeInput is INodeField<T?> inNullable)");
        sb.AppendLine("        {");
        sb.AppendLine("            PortConnector.ConnectRuntimeBothNullable(outNullable, (INodeField<T?>)inNullable);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T?> outNToStrict && nodeInput != null)");
        sb.AppendLine("        {");
        sb.AppendLine("            if (disallowNullableOutput)");
        sb.AppendLine("            {");
        sb.AppendLine(
            "                throw new NonNullableInputRestrictsConnectionToNullableOutputException(pipelineId, nodeId, nodeType, inputKey, portConnectionInfo.FromPortName, portConnectionInfo.FromNodeId);");
        sb.AppendLine("            }");
        sb.AppendLine(
            "            PortConnector.ConnectRuntimeFromNullableClass(outNToStrict, nodeInput, currentNodeId: nodeId,");
        sb.AppendLine(
            "                inputPortName: inputKey, sourceNodeId: portConnectionInfo.FromNodeId, outputPortName: portConnectionInfo.FromPortName);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine(
            "        if (portConnectionInfo.OutputFieldObj is INodeField<T> outStrictToN && nodeInput is INodeField<T?> inNullable2)");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            PortConnector.ConnectRuntimeToNullableClass(outStrictToN, (INodeField<T?>)inNullable2);");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine("        throw new PortsTypeMismatchException(pipelineId, inputKey, nodeType, nodeId,");
        sb.AppendLine(
            "            portConnectionInfo.FromPortName, portConnectionInfo.FromNodeType, portConnectionInfo.FromNodeId);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildGetNodeTypeMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private static string GetNodeType(string pipelineId, string nodeId, INode node)");
        sb.AppendLine("    {");
        sb.AppendLine("        switch (node)");
        sb.AppendLine("        {");
        foreach (var n in model.Nodes)
        {
            var nodeName = NodeGeneratorHelper.GetNodeName(n);
            sb.AppendLine($"            case {n.TypeNameFull}:");
            sb.AppendLine($"                return {nodeName};");
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new NodeNotFoundException(pipelineId, node.GetType().Name, nodeId);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        sb.AppendLine();
        return sb.ToString();
    }
}