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

namespace Ghost.Generator
{
    // TODO: this should be per assembly, not global
    [Generator]
    public class ComponentRegistrationGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var componentCandidates = context.SyntaxProvider
                .CreateSyntaxProvider(
                    predicate: (s, _) => s is StructDeclarationSyntax,
                    transform: GetComponentSymbol)
                .Where(symbol => symbol != null)
                .Collect();

            context.RegisterSourceOutput(componentCandidates, GenerateRegistrationCode);
        }

        // Extraction Logic for Components
        private static INamedTypeSymbol GetComponentSymbol(GeneratorSyntaxContext ctx, CancellationToken _)
        {
            var structSyntax = (StructDeclarationSyntax)ctx.Node;
            if (ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is not INamedTypeSymbol symbol)
            {
                return null;
            }

            var iComponentSymbol = ctx.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Entities.IComponent");
            if (iComponentSymbol == null)
            {
                return null;
            }

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

            return null;
        }

        // The Generation Logic (Stateless)
        private static void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> components)
        {
            if (components.IsDefaultOrEmpty)
            {
                return;
            }

            var sb = new StringBuilder();

            foreach (var symbol in components.Distinct(SymbolEqualityComparer.Default))
            {
                if (symbol is null)
                {
                    continue;
                }

                sb.AppendLine($@"        global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();");
            }

            var typeName = $"g_component_registration";
            var code = $@"// <auto-generated/>

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal static partial class {typeName}
{{
    [global::System.Runtime.CompilerServices.ModuleInitializer]
    internal static void RegisterIComponentTypes()
    {{
{sb}
    }}
}}";

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