using System;
using System.Collections.Generic;
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 NodeValidatorGenerator
{
    private const string NullString = "null";

    private static readonly TypeCode[] NumericTypeCodes =
    [
        TypeCode.Byte, TypeCode.SByte,
        TypeCode.Int16, TypeCode.UInt16,
        TypeCode.Int32, TypeCode.UInt32,
        TypeCode.Int64, TypeCode.UInt64,
        TypeCode.Single, TypeCode.Double,
        TypeCode.Decimal
    ];


    public static string Generate(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine("// ReSharper disable RedundantUsingDirective");
        sb.AppendLine("using System.Globalization;");
        sb.AppendLine("using NodePipeline.Abstractions;");
        sb.AppendLine("using NodePipeline.Abstractions.Exceptions.Validation;");
        sb.AppendLine("using NodePipeline.Abstractions.Interfaces;");
        sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Nodes;");
        sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Validation;");
        sb.AppendLine("using NodePipeline.Abstractions.Models.Validation;");
        sb.AppendLine("using NodePipeline.Abstractions.Validators;");
        sb.AppendLine("using NodePipeline.Configuration.Abstractions.Models.Execute;");
        sb.AppendLine("using NodePipeline.Engine.Abstractions;");
        sb.AppendLine("using NodePipeline.Engine.NodeFields;");
        sb.AppendLine("using NodePipeline.Engine.Utils;");
        sb.AppendLine("using NodePipeline.Engine.Abstractions.Validation;");
        sb.AppendLine();
        sb.AppendLine("// ReSharper disable CheckNamespace");
        sb.AppendLine("// ReSharper disable RedundantNameQualifier");
        sb.AppendLine("// ReSharper disable UnusedMember.Local");
        sb.AppendLine("// ReSharper disable UnusedParameter.Local");
        sb.AppendLine("// ReSharper disable UnusedVariable");
        sb.AppendLine("#nullable enable");
        sb.AppendLine();
        sb.AppendLine("namespace NodePipeline.Engine.Generated;");
        sb.AppendLine();
        sb.AppendLine("public sealed class GeneratedNodeValidator : IPipelineNodeValidator");
        sb.AppendLine("{");
        sb.AppendLine("    private Dictionary<NodePortKey, NodePortInfo>? _allPorts;");
        sb.AppendLine("    public Dictionary<Type, INodeValidator> NodeValidators { get; set; } = [];");
        sb.AppendLine("    public Dictionary<Type, INodeFieldValidator> NodeFieldValidators { get; set; } = [];");
        sb.AppendLine(
            "    public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();");
        sb.AppendLine();

        sb.AppendLine(BuildValidateNodeMethod(model));

        sb.AppendLine(BuildValidateNodeSpecificMethods(model));

        sb.AppendLine(BuildGetAllPortsMethod());
        sb.AppendLine(BuildGetNodeFieldValidatorMethod());
        sb.AppendLine(BuildValidateNodePortsMethod());
        sb.AppendLine(BuildValidateInputPortMethod());
        sb.AppendLine(BuildGetNodeInputKeyMethod());
        sb.AppendLine(BuildGetNodeValidatorMethod());
        sb.AppendLine(BuildCheckRequiredNodeParameterMethod());
        sb.AppendLine(BuildSuccessNodeValidationResultMethod());
        sb.AppendLine(BuildGetErrorValidationResultMethod());
        sb.AppendLine(BuildSuccessNodeFieldValidationResultMethod());
        sb.AppendLine(BuildUpdateValidationResultMethod());


        sb.AppendLine("}");
        sb.AppendLine("#nullable restore");
        return sb.ToString();
    }

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

    private static string BuildValidateNodeMethod(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    public NodeValidationResult ValidateNode(string pipelineId, INodeFactory nodeFactory, NodeConfig nodeConfig, List<NodeConfig> allNodes, Dictionary<string, object?> parameters, CultureInfo cultureInfo)");
        sb.AppendLine("    {");
        sb.AppendLine("        var node = nodeFactory.CreateNode(pipelineId, nodeConfig);");
        sb.AppendLine("            nodeFactory.SetNodeParametersValues(pipelineId, node, nodeConfig.Id, parameters);");
        sb.AppendLine("        var nodeConnections = allNodes.SelectMany(q => q.Inputs");
        sb.AppendLine(
            "                .Select(x => new KeyValuePair<string, InputSource>(GetNodeInputKey(q.Id, x.Key), x.Value))");
        sb.AppendLine("            ).ToDictionary(q => q.Key, q => q.Value);");
        sb.AppendLine("        var ports = GetAllPorts(pipelineId, nodeFactory, allNodes);");
        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 {GetSpecificValidateNodeMethodName(n)}(pipelineId, nodeFactory, t{i}, nodeConfig.Id, nodeConnections, ports, cultureInfo);");
            i++;
        }

        sb.AppendLine("            default:");
        sb.AppendLine("                throw new UnknownNodeTypeException(nodeConfig.Type, nodeConfig.Id);");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildValidateNodeSpecificMethods(NodeModelBuilder.NodesModel model)
    {
        var sb = new StringBuilder();
        const string tab = "        ";
        foreach (var n in model.Nodes)
        {
            var parameterValidatorsCode = CreateParameterValidatorsMethods(n, tab, out var parameterValidatorsCount);

            var nodeParameters = n.Fields
                .Where(f => f.Direction == FieldDirection.Parameter)
                .ToList();

            sb.AppendLine(
                $"    private NodeValidationResult {GetSpecificValidateNodeMethodName(n)}(string pipelineId, INodeFactory nodeFactory, {n.TypeNameFull} node, string nodeId, IReadOnlyDictionary<string, InputSource> nodePortConnections, IReadOnlyDictionary<NodePortKey, NodePortInfo> ports, CultureInfo cultureInfo)");
            sb.AppendLine("    {");

            if (parameterValidatorsCount > 0)
                sb.AppendLine($"{tab}var validationResult = ValidationResult.Valid;");

            sb.Append($"{tab}var fieldResults = new Dictionary<string, List<NodeFieldValidationResult>>");
            if (nodeParameters.Count > 0)
            {
                sb.AppendLine();
                sb.AppendLine(CreateFieldResultsContentCode(nodeParameters, "            "));
            }
            else
            {
                sb.AppendLine("();");
                sb.AppendLine();
            }

            if (parameterValidatorsCount > 0) sb.AppendLine(parameterValidatorsCode);

            var nodeName = NodeGeneratorHelper.GetNodeName(n);
            sb.AppendLine(
                $"{tab}var portsValidationResult = ValidateNodePorts(pipelineId, nodeFactory, {nodeName}, nodeId, nodePortConnections, ports, out var portResults, cultureInfo);");

            sb.Append($"{tab}var isValid = ");
            if (parameterValidatorsCount > 0) sb.Append("validationResult != ValidationResult.HasErrors && ");
            sb.AppendLine("portsValidationResult != ValidationResult.HasErrors;");

            if (parameterValidatorsCount > 0)
                sb.AppendLine($"{tab}UpdateValidationResult(portsValidationResult, ref validationResult);");
            sb.AppendLine($"{tab}return !isValid");

            sb.Append("            ? GetErrorValidationResult(");
            sb.Append(parameterValidatorsCount > 0 ? "validationResult" : "ValidationResult.HasErrors");
            sb.AppendLine(", nodeId, fieldResults, portResults, cultureInfo)");

            var nodeValidator = n.CustomValidators.FirstOrDefault();
            if (nodeValidator == null)
            {
                sb.Append("            : SuccessNodeValidationResult(");
                if (parameterValidatorsCount > 0) sb.Append("validationResult == ValidationResult.HasWarnings || ");
                sb.AppendLine("portsValidationResult == ValidationResult.HasWarnings, fieldResults, portResults);");
            }
            else
            {
                sb.AppendLine($"            : GetNodeValidator(typeof({nodeValidator.ValidatorType})).Validate(");
                sb.AppendLine("                new Dictionary<string, object>");
                sb.AppendLine("                {");
                foreach (var p in nodeParameters)
                    sb.AppendLine($"                    {{ \"{p.FieldName}\", node.{p.PropertyName}.Value }},");
                sb.AppendLine("                }, fieldResults, portResults);");
            }

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

        return sb.ToString();
    }

    private static string CreateParameterValidatorsMethods(NodeDescriptor n, string tab,
        out int parameterValidatorsCount)
    {
        var sb = new StringBuilder();
        var currentValidatorNumber = 1;
        var nodeParameters = n.Fields.Where(f => f.Direction == FieldDirection.Parameter)
            .ToList();

        foreach (var p in nodeParameters)
        {
            if (HasParameterValueLengthValidator(p, out var minLength, out var maxLength))
            {
                EnsureParameterValueIsString(p.FieldName, n.Type, p.ValueType);
                var validatorCallText =
                    $"new StringLengthValidator(PipelineLocalizationProvider, {minLength}, {maxLength})";
                sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab));
            }


            if (HasParameterNumberBoundValidator(p, out var minValue, out var maxValue))
            {
                EnsureParameterValueIsNumber(p.FieldName, n.Type, p.ValueType);
                var validatorCallText =
                    $"new NumberRangeValidator<{p.ValueType}>(PipelineLocalizationProvider, {minValue}, {maxValue})";
                sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab));
            }

            if (p.Metadata?.IsRequired == true)
                sb.AppendLine(AddRequiredValidatorCode(p, ref currentValidatorNumber, tab));

            if (p.Metadata == null) continue;

            foreach (var validator in p.Metadata.CustomValidators)
            {
                var validatorCallText = validator.CanBeInitialized
                    ? $"{validator.ValidatorType}()"
                    : $"GetNodeFieldValidator<{p.ValueType}>(typeof({validator.ValidatorType}))";
                sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab));
            }
        }

        parameterValidatorsCount = currentValidatorNumber - 1;
        return sb.ToString();
    }

    private static string CreateFieldResultsContentCode(List<NodeFieldDescriptor> nodeParameters, string tab)
    {
        var sb = new StringBuilder();

        sb.AppendLine($"{tab}{{");
        foreach (var p in nodeParameters) sb.AppendLine($"            {{ \"{p.FieldName}\", [] }},");
        sb.AppendLine($"{tab}}};");
        return sb.ToString();
    }

    private static string GetValidatorString(NodeFieldDescriptor parameter, string validatorCallText,
        ref int currentValidatorNumber, string tab)
    {
        var sb = new StringBuilder();
        currentValidatorNumber++;

        sb.AppendLine(
            $"{tab}var r{currentValidatorNumber} = {validatorCallText}.Validate(node.{parameter.PropertyName});");
        sb.AppendLine($"{tab}fieldResults[\"{parameter.FieldName}\"].Add(r{currentValidatorNumber});");
        sb.AppendLine($"{tab}UpdateValidationResult(r{currentValidatorNumber}.Result, ref validationResult);");

        return sb.ToString();
    }

    private static bool HasParameterValueLengthValidator(NodeFieldDescriptor parameter, out string minLength,
        out string maxLength)
    {
        if (parameter.Metadata is null ||
            (parameter.Metadata?.StringMaxLength is null && parameter.Metadata?.StringMinLength is null))
        {
            minLength = NumberToString(null);
            maxLength = NumberToString(null);
            return false;
        }

        minLength = NumberToString(parameter.Metadata.StringMinLength);
        maxLength = NumberToString(parameter.Metadata.StringMaxLength);
        return true;
    }

    private static bool HasParameterNumberBoundValidator(NodeFieldDescriptor parameter, out string minValue,
        out string maxValue)
    {
        if (parameter.Metadata is null ||
            (parameter.Metadata?.NumberMinBound is null && parameter.Metadata?.NumberMaxBound is null))
        {
            minValue = NumberToString(null);
            maxValue = NumberToString(null);
            return false;
        }

        minValue = NumberToString(parameter.Metadata.NumberMinBound);
        maxValue = NumberToString(parameter.Metadata.NumberMaxBound);
        return true;
    }

    private static string NumberToString(int? value)
    {
        return value?.ToString() ?? NullString;
    }

    private static string NumberToString(decimal? value)
    {
        return value?.ToString() ?? NullString;
    }

    private static string AddRequiredValidatorCode(NodeFieldDescriptor parameter, ref int currentValidatorNumber,
        string tab)
    {
        var sb = new StringBuilder();
        currentValidatorNumber++;

        sb.AppendLine(
            $"{tab}var r{currentValidatorNumber} = CheckRequiredNodeParameter<{parameter.ValueType}>(nodeId, node.{parameter.PropertyName}, \"{parameter.FieldName}\", cultureInfo);");
        sb.AppendLine($"{tab}fieldResults[\"{parameter.FieldName}\"].Add(r{currentValidatorNumber});");
        sb.AppendLine($"{tab}UpdateValidationResult(r{currentValidatorNumber}.Result, ref validationResult);");

        return sb.ToString();
    }


    private static string BuildGetAllPortsMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private IReadOnlyDictionary<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, INodeFactory nodeFactory, List<NodeConfig> allNodes)");
        sb.AppendLine("    {");
        sb.AppendLine("        var nodeTypes = allNodes.Select(q => q.Type)");
        sb.AppendLine("            .Distinct()");
        sb.AppendLine("            .ToHashSet();");
        sb.AppendLine();
        sb.AppendLine("        var nodeList = allNodes");
        sb.AppendLine("            .GroupBy(q => q.Type,");
        sb.AppendLine("                (type, g) => new { Type = type, NodeIds = g.Select(q => q.Id) })");
        sb.AppendLine("            .ToDictionary(q => q.Type, q => q.NodeIds);");
        sb.AppendLine("        _allPorts ??= nodeFactory.GetAllPorts(pipelineId, nodeTypes, nodeList);");
        sb.AppendLine("        return _allPorts.AsReadOnly();");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildGetNodeFieldValidatorMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private INodeFieldValidator<T> GetNodeFieldValidator<T>(Type validatorType)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (!NodeFieldValidators.TryGetValue(validatorType, out var validator)");
        sb.AppendLine("            || validator is not INodeFieldValidator<T> genericValidator)");
        sb.AppendLine(
            "            throw new UserDefinedNodeFieldValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);");
        sb.AppendLine("        return genericValidator;");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildValidateNodePortsMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private ValidationResult ValidateNodePorts(string pipelineId, INodeFactory nodeFactory, string nodeType, string nodeId,");
        sb.AppendLine(
            "        IReadOnlyDictionary<string, InputSource> nodePortConnections, IReadOnlyDictionary<NodePortKey, NodePortInfo> ports,");
        sb.AppendLine(
            "        out Dictionary<string, List<NodeFieldValidationResult>> inputPortValidationResults, CultureInfo cultureInfo)");
        sb.AppendLine("    {");
        sb.AppendLine("        var result = ValidationResult.Valid;");
        sb.AppendLine(
            "        inputPortValidationResults = new Dictionary<string, List<NodeFieldValidationResult>>();");
        sb.AppendLine();
        sb.AppendLine("        var inputPorts = nodeFactory.GetNodeInputPorts(pipelineId, nodeType, nodeId);");
        sb.AppendLine("        foreach (var inputPort in inputPorts)");
        sb.AppendLine("        {");
        sb.AppendLine("            var code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, inputPort.Name,");
        sb.AppendLine("                inputPort.Input ? FieldDirection.Input : FieldDirection.Output);");
        sb.AppendLine("            inputPortValidationResults[code] = [];");
        sb.AppendLine(
            "            var hasConnection = nodePortConnections.TryGetValue(GetNodeInputKey(nodeId, inputPort.Name), out var connection);");
        sb.AppendLine("            if (inputPort.Required && !hasConnection)");
        sb.AppendLine("            {");

        sb.AppendLine(
            "                inputPortValidationResults[code].Add(new NodeFieldValidationResult(ValidationResult.HasErrors,");
        sb.AppendLine(
            "                    PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredInputPortHasNoConnectionError, cultureInfo, inputPort.Name, nodeId)));");
        //TODO: add error code
        sb.AppendLine("                UpdateValidationResult(ValidationResult.HasErrors, ref result);");
        sb.AppendLine("                continue;");
        sb.AppendLine("            }");
        sb.AppendLine("            if (!hasConnection || connection == null) continue;");
        sb.AppendLine();
        sb.AppendLine(
            "            var portValidationResult = ValidateInputPort(nodeId, inputPort, connection, ports, cultureInfo);");
        sb.AppendLine("            inputPortValidationResults[code].Add(portValidationResult);");
        sb.AppendLine("            UpdateValidationResult(portValidationResult.Result, ref result);");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return result;");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildGetNodeInputKeyMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private static string GetNodeInputKey(string nodeId, string inputPortName)");
        sb.AppendLine("    {");
        sb.AppendLine("        return string.Concat(nodeId, \"_\", inputPortName);");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildValidateInputPortMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private NodeFieldValidationResult ValidateInputPort(string nodeId, NodePortInfo inputPort, InputSource connection,");
        sb.AppendLine("        IReadOnlyDictionary<NodePortKey, NodePortInfo> ports, CultureInfo cultureInfo)");
        sb.AppendLine("    {");
        sb.AppendLine(
            "        if (!ports.TryGetValue(new NodePortKey(connection.NodeId, connection.OutputName, false), out var outputPort))");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider");
        sb.AppendLine(
            "                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.OutputPortNotFoundError, cultureInfo, connection.OutputName,");
        sb.AppendLine("                    connection.NodeId, inputPort.Name, nodeId));");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        if (inputPort.Type != outputPort.Type)");
        sb.AppendLine("        {");

        sb.AppendLine(
            "            return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider");
        sb.AppendLine(
            "                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.PortsTypeMismatchError, cultureInfo,");
        sb.AppendLine(
            "                    inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        if (inputPort.IsNullable == outputPort.IsNullable)");
        sb.AppendLine("        {");
        sb.AppendLine("            return SuccessNodeFieldValidationResult();");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        if (!inputPort.IsNullable && outputPort.IsNullable)");
        sb.AppendLine("        {");

        sb.AppendLine("            return new NodeFieldValidationResult(");
        sb.AppendLine("                ValidationResult.HasWarnings, PipelineLocalizationProvider");
        sb.AppendLine(
            "                    .GetLocalizedString(Constants.ValidationResults.ErrorMessages.NullableAndNonNullablePortsConnectionWarning, cultureInfo,");
        sb.AppendLine(
            "                        inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return SuccessNodeFieldValidationResult();");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildGetNodeValidatorMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private INodeValidator GetNodeValidator(Type validatorType)");
        sb.AppendLine("    {");

        sb.AppendLine("        if (!NodeValidators.TryGetValue(validatorType, out var validator))");
        sb.AppendLine(
            "            throw new UserDefinedNodeValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);");
        sb.AppendLine("        return validator;");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildCheckRequiredNodeParameterMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private NodeFieldValidationResult CheckRequiredNodeParameter<T>(string nodeId, NodePipeline.Abstractions.Models.NodeField<T> nodeField, string nodeParameterName, CultureInfo cultureInfo)");
        sb.AppendLine("    {");
        sb.AppendLine("        return nodeField.Value is not null");
        sb.AppendLine("            ? new NodeFieldValidationResult(ValidationResult.Valid, null)");
        sb.AppendLine(
            "            : new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider");
        sb.AppendLine(
            "                .GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredNodeParameterValidationError, cultureInfo, nodeParameterName, nodeId));");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static string BuildSuccessNodeValidationResultMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private static NodeValidationResult SuccessNodeValidationResult(bool hasWarnings, Dictionary<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> portResults) =>");
        sb.AppendLine("        new(hasWarnings ? ValidationResult.HasWarnings : ValidationResult.Valid, string.Empty,");
        sb.AppendLine("            fieldResults.AsReadOnly(), portResults.AsReadOnly());");
        return sb.ToString();
    }

    private static string BuildGetErrorValidationResultMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private NodeValidationResult GetErrorValidationResult(ValidationResult validationResult, string nodeId, Dictionary<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> portResults, CultureInfo cultureInfo) =>");
        sb.AppendLine(
            "        new(validationResult, PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.NodeValidationError, cultureInfo, nodeId), fieldResults.AsReadOnly(), portResults.AsReadOnly());");
        return sb.ToString();
    }

    private static string BuildSuccessNodeFieldValidationResultMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine("    private static NodeFieldValidationResult SuccessNodeFieldValidationResult() =>");
        sb.AppendLine("        new NodeFieldValidationResult(ValidationResult.Valid, null);");
        return sb.ToString();
    }

    private static string BuildUpdateValidationResultMethod()
    {
        var sb = new StringBuilder();
        sb.AppendLine(
            "    private static void UpdateValidationResult(ValidationResult validationResult, ref ValidationResult parametersValidationResult)");
        sb.AppendLine("    {");
        sb.AppendLine(
            "        if (validationResult == parametersValidationResult || validationResult == ValidationResult.Valid) return;");
        sb.AppendLine();
        sb.AppendLine("        parametersValidationResult = validationResult switch");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            ValidationResult.HasWarnings when parametersValidationResult == ValidationResult.Valid => ValidationResult.HasWarnings,");
        sb.AppendLine(
            "            ValidationResult.HasErrors when parametersValidationResult != ValidationResult.HasErrors => ValidationResult.HasErrors,");
        sb.AppendLine("            _ => parametersValidationResult");
        sb.AppendLine("        };");
        sb.AppendLine("    }");
        return sb.ToString();
    }

    private static void EnsureParameterValueIsString(string fieldName, string nodeName, string parameterValueType)
    {
        var shortTypeName = parameterValueType.Split('.').Last();

        var isString = shortTypeName.Equals("string", StringComparison.OrdinalIgnoreCase)
                       || shortTypeName.Equals("String", StringComparison.OrdinalIgnoreCase);
        if (isString) return;
        ThrowInvalidTypeException(fieldName, nodeName, "string", parameterValueType);
    }

    private static void EnsureParameterValueIsNumber(string fieldName, string nodeName, string parameterValueType)
    {
        var normalized = NormalizeTypeName(parameterValueType);

        var type = Type.GetType(normalized, false);
        var typeCode = Type.GetTypeCode(type);

        if (NumericTypeCodes.Contains(typeCode)) return;

        var expectedType = $"number ({string.Join(", ", NumericTypeCodes)})";
        ThrowInvalidTypeException(fieldName, nodeName, expectedType, parameterValueType);
    }

    private static string NormalizeTypeName(string typeName)
    {
        if (string.IsNullOrWhiteSpace(typeName)) return typeName;
        typeName = typeName.Replace("global::", "").TrimEnd('?');

        return typeName switch
        {
            "byte" => "System.Byte",
            "sbyte" => "System.SByte",
            "short" => "System.Int16",
            "ushort" => "System.UInt16",
            "int" => "System.Int32",
            "uint" => "System.UInt32",
            "long" => "System.Int64",
            "ulong" => "System.UInt64",
            "float" => "System.Single",
            "double" => "System.Double",
            "decimal" => "System.Decimal",
            _ => typeName
        };
    }


    private static void ThrowInvalidTypeException(string fieldName, string nodeName, string expectedType,
        string realType)
    {
        throw new Exception(
            $"Parameter [{fieldName}] ([{nodeName}] node) value has [{realType}] type but [{expectedType}] is expected.");
    }
}