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

namespace Nebula.SourceGenerators;

[Generator]
public class DependencyAutoGenerator : IIncrementalGenerator
{
    private static readonly string ConstructGeneratorAttributeName = "Nebula.Shared.Attributes.ConstructGeneratorAttribute";
    private static readonly string GeneratePropertyAttributeName = "Nebula.Shared.Attributes.GeneratePropertyAttribute";

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var provider = context.SyntaxProvider
            .CreateSyntaxProvider(
                (s, _) => s is ClassDeclarationSyntax,
                (ctx, _) => SourceHelper.GetClassDeclarationForSourceGen(ctx,ConstructGeneratorAttributeName))
            .Where(t => t.reportAttributeFound)
            .Select((t, _) => t.Item1);

        context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()),
            (ctx, t) => GenerateCode(ctx, t.Left, t.Right));
    }

    private void GenerateCode(SourceProductionContext context, Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classDeclarations)
    {
        foreach (var classDeclarationSyntax in classDeclarations)
        {
            var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
            if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
                continue;

            var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
            var className = classDeclarationSyntax.Identifier.Text;
            
            var propertiesGenerated = GetProperties(classSymbol).ToList();
            
            var constr = propertiesGenerated.Select(a => $"{a.Type.ToDisplayString()} g{a.Name}");
            var body = propertiesGenerated.Select(a => $"this.{a.Name} = g{a.Name};");
            
            var propertiesGeneratedC = GetProperties(classSymbol)
                .Where(a=> 
                    SourceHelper.HasAttribute(a,"Nebula.Shared.Attributes.DesignConstructAttribute"))
                .ToList();
            
            var bodyC = propertiesGeneratedC.Select(a => $"this.{a.Name} = new {a.Type.ToDisplayString()}();");
            
            var code = $@"// <auto-generated/>
namespace {namespaceName};

partial class {className}
{{
    
    public {className}(){{
        {string.Join("\n\t\t", bodyC)}
        InitialiseInDesignMode();
    }}
    public {className}(
        {string.Join(",\n\t\t", constr)}
    ) : base(){{
        {string.Join("\n\t\t", body)}
        Initialise();
    }}
}}
            ";

            // Add the source code to the compilation.
            context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
        }
    }

    private static IEnumerable<IPropertySymbol> GetProperties(INamedTypeSymbol classSymbol)
    {
        return  classSymbol.GetMembers().OfType<IPropertySymbol>().Where(a =>
            SourceHelper.HasAttribute(a, GeneratePropertyAttributeName));
    }
}