using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;

namespace Ghost.Generator;

[Generator]
internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var handerCandidates = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (s, _) => s is ClassDeclarationSyntax,
                transform: GetAssetHandlerSymbol)
            .Where(symbol => symbol != null)
            .Collect();

        context.RegisterSourceOutput(handerCandidates, GenerateRegistrationCode);
    }

    private static T GetValueOrDefault<T>(IDictionary<string, TypedConstant> dictionary, string key, T defaultValue = default)
    {
        if (dictionary.TryGetValue(key, out var value))
        {
            if (value.Value is T typedValue)
            {
                return typedValue;
            }
        }

        return defaultValue;
    }

    private static ImmutableArray<TypedConstant> GetValuesOrDefault(IDictionary<string, TypedConstant> dictionary, string key)
    {
        if (dictionary.TryGetValue(key, out var value))
        {
            return value.Values;
        }

        return default;
    }

    private void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> array)
    {
        if (array.IsDefaultOrEmpty)
        {
            return;
        }

        var sb = new System.Text.StringBuilder();

        foreach (var symbol in array)
        {
            var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.Assets.CustomAssetHandlerAttribute");
            if (attribute == null)
            {
                continue;
            }

            var properties = attribute.NamedArguments.ToDictionary(kv => kv.Key, kv => kv.Value);

            var id = GetValueOrDefault(properties, "AssetTypeId", string.Empty);
            var runtimeType = GetValueOrDefault(properties, "RuntimeAssetType", 0);
            var version = GetValueOrDefault(properties, "Version", 1);
            var allowCaching = GetValueOrDefault(properties, "AllowCaching", false) ? "true" : "false";

            var extensionsTypesConstants = GetValuesOrDefault(properties, "Extensions");
            var extensions = string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()));

            sb.AppendLine("        global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(");
            sb.AppendLine($"            typeof({symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}),");
            sb.AppendLine($"            System.Guid.Parse(\"{id}\"),");
            sb.AppendLine($"            (Ghost.Engine.Streaming.AssetType){runtimeType},");
            sb.AppendLine($"            {version},");
            sb.AppendLine($"            {allowCaching},");
            sb.AppendLine($"            new string[] {{ {extensions} }});");
            sb.AppendLine();
        }

        var registerTypeName = "g_assethandler_registeration";
        var code = $@"// <auto-generated />

#if GHOST_EDITOR
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal static partial class {registerTypeName}
{{

    [global::System.Runtime.CompilerServices.ModuleInitializer]
    internal static void RegisterAssetHandlers()
    {{
{sb}
    }}
}}
#endif";

        context.AddSource($"{registerTypeName}.gen.cs", code);
    }

    private INamedTypeSymbol GetAssetHandlerSymbol(GeneratorSyntaxContext context, CancellationToken token)
    {
        var classSyntax = (ClassDeclarationSyntax)context.Node;
        if (context.SemanticModel.GetDeclaredSymbol(classSyntax) is not INamedTypeSymbol symbol)
        {
            return null;
        }

        var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetHandler");
        if (iHandlerSymbol == null)
        {
            return null;
        }

        foreach (var iface in symbol.AllInterfaces)
        {
            if (SymbolEqualityComparer.Default.Equals(iface, iHandlerSymbol))
            {
                return symbol;
            }
        }

        return null;
    }
}

[Generator]
internal class IAssetSettingsRegistrationGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var settingsCandidates = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (s, _) => s is ClassDeclarationSyntax,
                transform: GetAssetSettingsSymbol)
            .Where(symbol => symbol != null)
            .Collect();
        context.RegisterSourceOutput(settingsCandidates, GenerateRegistrationCode);
    }

    private void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> array)
    {
        if (array.IsDefaultOrEmpty)
        {
            return;
        }

        var sb = new System.Text.StringBuilder();

        foreach (var iface in array)
        {
            sb.AppendLine($"        global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
        }

        var registerTypeName = "g_iassetsettings_registeration";
        var code = $@"// <auto-generated />

#if GHOST_EDITOR
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal static partial class {registerTypeName}
{{
    [global::System.Runtime.CompilerServices.ModuleInitializer]
    internal static void RegisterIAssetSettingsTypes()
    {{
{sb}
    }}
}}
#endif";

        context.AddSource($"{registerTypeName}.gen.cs", code);
    }

    private INamedTypeSymbol GetAssetSettingsSymbol(GeneratorSyntaxContext context, CancellationToken token)
    {
        var classSyntax = (ClassDeclarationSyntax)context.Node;
        if (context.SemanticModel.GetDeclaredSymbol(classSyntax) is not INamedTypeSymbol symbol)
        {
            return null;
        }

        if (symbol.IsAbstract)
        {
            return null;
        }

        var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetSettings");
        if (iSettingsSymbol == null)
        {
            return null;
        }

        foreach (var iface in symbol.AllInterfaces)
        {
            if (SymbolEqualityComparer.Default.Equals(iface, iSettingsSymbol))
            {
                return symbol;
            }
        }

        return null;
    }
}
