using InnovEnergy.Lib.SrcGen.Trees;
using InnovEnergy.Lib.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Sawmill;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;

namespace InnovEnergy.Lib.SrcGen;



public static class NestProperties
{
    private static readonly SyntaxToken PublicKeyword = Token(SyntaxKind.PublicKeyword);
    
    public static async Task Generate(Project project, Compilation compilation)
    {
        var syntaxTrees = await project
                               .Documents
                               .Where(d => d.Project == project)
                               .Where(d => d.SupportsSyntaxTree)
                               .Select(async d => await d.GetSyntaxRootAsync())
                               .WhenAll();

        syntaxTrees.NotNull()
                   .SelectMany(c => c.GetNestPropertiesAttributes())
                   .ForEach(GenerateFile);

        String GenerateFile(AttributeSyntax attribute)
        {
            var typeName        = attribute.ArgumentList!.Arguments.Single().ToString().Replace("\"", "");
            var type            = attribute.GetAncestor<TypeDeclarationSyntax>();
            var nameSpace       = attribute.GetAncestor<NamespaceDeclarationSyntax>();
            var filePath        = type.SyntaxTree.FilePath;
            var generatedPath   = filePath.GetGeneratedPath(nameof(NestProperties));

            var recordProperties = type
                                  .Members
                                  .OfType<PropertyDeclarationSyntax>()
                                  .Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword)))
                                  .ToList();
            
            var propNames = recordProperties.Select(m => m.Identifier.ValueText);

            var prefixTree = PrefixTree.Create(propNames);

            prefixTree.WriteLine();
            Console.WriteLine("$$$$$$$$$$$$$");


            var wrappedTypeName = type.Identifier.ValueText;

            var records = new List<RecordDeclarationSyntax>();
            
            prefixTree.Fold<Tree<String>, MemberDeclarationSyntax>((memory, tree) =>
            {
                 var path = tree
                           .Ancestors
                           .Prepend(tree)
                           .Aggregate("", (a, b) => b.Node + a);
                
                if (tree.IsLeaf)
                {
                    var prop = recordProperties.First(p => p.Identifier.ValueText == path);

                    var writable = prop
                                  .DescendantNodes()
                                  .OfType<AccessorDeclarationSyntax>()
                                  .Any(d => d.IsKind(SetAccessorDeclaration)  
                                        && !d.DescendantTokens().Any(t => t.IsKind(PrivateKeyword)));
                    
                    return CreateAccessorProperty
                    (
                        prop.Type,
                        tree.Node,
                        path,
                        writable
                    );
                }
                else
                {
                    var children = memory.ToArray();

                    var rec = CreateWrapperRecord(typeName + path, wrappedTypeName, children);
                    records.Add(rec);
                    
                    return CreateWrapperProperty(IdentifierName(typeName + path), Identifier(tree.Node));    
                }
            });

            //x.NormalizeWhitespace().GetText().WriteLine();

            records.Reverse();
            records.ForEach(r => r.NormalizeWhitespace().WriteLine("\n"));
            

            
            Console.WriteLine($"Generating {generatedPath}");

            
  
            
            
            return "";

     

        }
        
    }

    private static CompilationUnitSyntax CreateCompilationUnit(NameSyntax nameSpaceName, params MemberDeclarationSyntax[] members)
    {
        var nameSpace = NamespaceDeclaration(nameSpaceName)
                       .WithMembers(members.Apply(List));

        return CompilationUnit().WithMembers(nameSpace.Apply(SingletonList<MemberDeclarationSyntax>));
    }

    private static RecordDeclarationSyntax CreateWrapperRecord(String structName,
                                                               String wrappedTypeName, 
                               IEnumerable<MemberDeclarationSyntax> properties)
    {
        const String self = "Self";
        
        var wrappedType = IdentifierName(wrappedTypeName);
        var recId       = Identifier(structName);
        var modifiers   = TokenList(Token(SyntaxKind.PublicKeyword), Token(ReadOnlyKeyword));




        //properties.Prepend<MemberDeclarationSyntax>(selfProperty).Prepend(constructor).Apply(List);
        
        // var structMembers = new MemberDeclarationSyntax[] { selfProperty, constructor }
        //                    .Apply(List);
        
        var recMembers = properties.Apply(List);


        var recParams = self
                       .Apply(Identifier)
                       .Apply(Parameter)
                       .WithType(wrappedType)
                       .Apply(SingletonSeparatedList)
                       .Apply(ParameterList);
        
        return RecordDeclaration(Token(RecordKeyword), recId)
              .WithModifiers(modifiers)
              .WithClassOrStructKeyword(Token(StructKeyword))
              .WithParameterList(recParams)
              .WithModifiers(modifiers)
              .WithOpenBraceToken(Token(OpenBraceToken))
              .WithMembers(recMembers)
              .WithCloseBraceToken(Token(CloseBraceToken));
        
        //return /*StructDeclaration(structId)*/
              

    }


    private static PropertyDeclarationSyntax CreateAccessorProperty(TypeSyntax propertyTypeName, 
                                                                        String propertyName, 
                                                                        String parentPropertyName,
                                                                       Boolean writeable)
    {
        const String self = "Self";
        const String value = "value";
        
        var propertyModifier = SyntaxKind
                              .PublicKeyword
                              .Apply(Token)
                              .Apply(TokenList);

        var semicolonToken = Token(SemicolonToken);

        var accessExpression = MemberAccessExpression
        (
            SimpleMemberAccessExpression,
            IdentifierName(self),
            IdentifierName(parentPropertyName)
        );

        var getterBody = ArrowExpressionClause(accessExpression);
        
        var getter = GetAccessorDeclaration
                    .Apply(AccessorDeclaration)
                    .WithExpressionBody(getterBody)
                    .WithSemicolonToken(semicolonToken);

        var setterBody = AssignmentExpression(SimpleAssignmentExpression, accessExpression, IdentifierName(value))
                        .Apply(ArrowExpressionClause);
        
        var setter = SetAccessorDeclaration
                    .Apply(AccessorDeclaration)
                    .WithExpressionBody(setterBody)
                    .WithSemicolonToken(semicolonToken);

        var accessors = writeable 
                      ? new[] { getter, setter } 
                      : new[] { getter };
        
        var accessorsList = accessors
                           .Apply(List)
                           .Apply(AccessorList);

        var property = PropertyDeclaration
        (
            type      : propertyTypeName,
            identifier: Identifier(propertyName)
        );
        
        return property
              .WithModifiers(propertyModifier)
              .WithAccessorList(accessorsList);
    }



    private static PropertyDeclarationSyntax CreateWrapperProperty(TypeSyntax type, SyntaxToken identifier)
    {
        var modifiers = SyntaxKind
                       .PublicKeyword
                       .Apply(Token)
                       .Apply(TokenList);

        var self = IdentifierName("Self")
                  .Apply(Argument)
                  .Apply(SingletonSeparatedList)
                  .Apply(ArgumentList);

        var body = ObjectCreationExpression(type)
                  .WithArgumentList(self)
                  .Apply(ArrowExpressionClause);
        
        return PropertyDeclaration(type, identifier)
              .WithModifiers(modifiers)
              .WithExpressionBody(body)
              .WithSemicolonToken(Token(SemicolonToken));
    }
}