using System.Text;
using NodePipeline.Configuration.Abstractions.Interfaces;
using NodePipeline.Configuration.Abstractions.Models.Edit;
using NodePipeline.Configuration.Yaml.Context;
using NodePipeline.Configuration.Yaml.Exceptions;
using NodePipeline.Configuration.Yaml.Internal_Models;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace NodePipeline.Configuration.Yaml.Loader;

public class YamlEditorConfigLoader : IEditorPipelineConfigLoader
{
    private static readonly YamlPipelineConfigContext Context = new();

    public EditorPipelineConfig Load(Stream stream)
    {
        using var reader = new StreamReader(stream, Encoding.UTF8);
        var yaml = reader.ReadToEnd();

        if (string.IsNullOrWhiteSpace(yaml))
        {
            var fileName = stream is FileStream fs ? Path.GetFileName(fs.Name) : null;
            throw new YamlFileIsEmptyException(fileName);
        }

        var deserializer = new StaticDeserializerBuilder(Context)
            .WithNamingConvention(CamelCaseNamingConvention.Instance)
            .Build();

        var raw = deserializer.Deserialize<YamlEditorPipelineConfig>(yaml);
        var config = Convert(raw);
        return config;
    }

    private static EditorPipelineConfig Convert(YamlEditorPipelineConfig raw)
    {
        var nodes = new List<EditorNodeConfig>(raw.Nodes
            .Sum(x => x.Parameters.Count + x.Inputs.Count + x.Outputs.Count));
        foreach (var node in raw.Nodes)
        {
            var parameters = node.Parameters
                .Where(q => q.Value?.Value is not null)
                .ToDictionary(kv => kv.Key, kv => new EditorParameterConfig
                {
                    Name = kv.Key,
                    Label = kv.Value?.Label,
                    Description = kv.Value?.Description,
                    Value = kv.Value?.Value
                });
            var inputs = node.Inputs
                .ToDictionary(kv => kv.Key, kv => new EditorInputPort
                {
                    Name = kv.Key,
                    Label = kv.Value?.Label,
                    Source = kv.Value?.Source,
                    Description = kv.Value?.Description
                });

            var outputs = node.Outputs
                .ToDictionary(kv => kv.Key, kv => new EditorOutputPort
                {
                    Name = kv.Key,
                    Label = kv.Value?.Label,
                    Description = kv.Value?.Description
                });
            nodes.Add(new EditorNodeConfig
            {
                Id = node.Id,
                Type = node.Type,
                Title = node.Title,
                Description = node.Description,
                Parameters = parameters,
                Inputs = inputs,
                Outputs = outputs
            });
        }

        var result = new EditorPipelineConfig
        {
            Name = raw.Name,
            Description = raw.Description,
            Nodes = nodes
        };

        return result;
    }
}