using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace InnovEnergy.Lib.SrcGen;

public static class SyntaxUtils
{
    // const String DebugFile = "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/Debug.txt";
    //
    // static SyntaxUtils() => File.WriteAllText(DebugFile, "");

    public static Boolean HasExtendsAttribute(this TypeDeclarationSyntax td)
    {
        return td
              .GetAttributes()
              .Select(a => a.Name)
              .OfType<GenericNameSyntax>()
              .Any(n => n.Identifier.ValueText == "Extends");
    }

 
    public static T GetAncestor<T>(this SyntaxNode sn) where T: SyntaxNode
    {
        return sn
              .Ancestors()
              .OfType<T>()
              .First();
    }
   
    
    public static IEnumerable<AttributeSyntax> GetGenerateAttributes(this SyntaxNode sn)
    {
        return sn
              .DescendantNodes()
              .Where(n => n is AttributeSyntax { Name : GenericNameSyntax { Identifier.ValueText: "Generate" } })
              .OfType<AttributeSyntax>();
    }
    
        
    public static IEnumerable<AttributeSyntax> GetNestPropertiesAttributes(this SyntaxNode sn)
    {
        return sn
              .DescendantNodes()
              .Where(n => n is AttributeSyntax { Name : IdentifierNameSyntax { Identifier.ValueText: "NestProperties" } })
              .OfType<AttributeSyntax>();
    }
    
    
  
    
    public static IEnumerable<AttributeSyntax> GetAttributes(this MemberDeclarationSyntax member)
    {
        return member
              .AttributeLists
              .SelectMany(al => al.Attributes);
    }

    // public static T Debug<T>(this T t, String? title = null)
    // {
    //     var prefix = title is null ? "" : title + ": ";
    //     var contents = t is null ? "<null>" : t.ToString();
    //
    //     File.AppendAllText(DebugFile, prefix + contents + '\n');
    //     return t;
    // }

    public static IEnumerable<AttributeSyntax> GetAttributes(this SyntaxNode node, String attributeName)
    {
        return node
              .DescendantTokens()
              .Where(st => st.ValueText == attributeName)
              .Where(st => st.IsKind(SyntaxKind.IdentifierToken))
              .Select(st => st.Parent?.Parent)
              .OfType<AttributeSyntax>();
    }

    public static IEnumerable<T> ChildNodes<T>(this SyntaxNode node) where T : SyntaxNode
    {
        return node.ChildNodes().OfType<T>();
    }
    
    public static IEnumerable<T> DescendantNodes<T>(this SyntaxNode node) where T : SyntaxNode
    {
        return node.DescendantNodes().OfType<T>();
    }

    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
    public static T ReplaceIdentifiers<T>(this T root, String idToReplace, String replacementId) where T: SyntaxNode
    {
        var newIdentifier = SyntaxFactory.Identifier(replacementId);

        return root
              .EditNodes()
              .OfType<IdentifierNameSyntax>()
              .Where(n => n.Identifier.ValueText == idToReplace)
              .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n))
              
              .EditNodes()
              .OfType<ConstructorDeclarationSyntax>()
              .Where(n => n.Identifier.ValueText == idToReplace)
              .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n))
              
              .EditNodes()
              .OfType<TypeDeclarationSyntax>()
              .Where(n => n.Identifier.ValueText == idToReplace)
              .Replace(n => n.WithIdentifier(newIdentifier.WithTrailingTrivia(NewLine)));
    }

    
    private static readonly SyntaxTrivia NewLine    = SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n");
    private static readonly SyntaxTrivia WhiteSpace = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ");
    
    private static readonly SyntaxToken PartialKeyword = SyntaxFactory
                                                        .Token(SyntaxKind.PartialKeyword)
                                                        .WithTrailingTrivia(WhiteSpace);

    public static CompilationUnitSyntax AddPartialModifier(this CompilationUnitSyntax baseFile)
    {
        return baseFile
              .EditNodes()
              .OfType<StructDeclarationSyntax>()
              .Where(s => !s.Modifiers.Contains(PartialKeyword))
              .Replace(s => s.AddModifiers(PartialKeyword));
    }


    public static T GetDefinition<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode
    {
        return compilation
              .GetDefinitions(baseTypeDeclaration)
              .OfType<T>()
              .Single();
    }
    
    public static IEnumerable<T> GetDefinitions<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode
    {
        return compilation
              .GetDefinitions(baseTypeDeclaration)
              .OfType<T>();
    }
    
    public static IEnumerable<SyntaxNode> GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration)
    {
        return compilation
              .GetSemanticModel(baseTypeDeclaration.SyntaxTree)
              .GetTypeInfo(baseTypeDeclaration)
              .Type?
              .DeclaringSyntaxReferences
              .Select(r => r.GetSyntax()) 
               ?? Enumerable.Empty<SyntaxNode>();
    }
    
    public static IEnumerable<TypeSyntax> GetGenericArguments(this AttributeSyntax attribute) => attribute.Name switch
    {
        GenericNameSyntax gns => gns.TypeArgumentList.Arguments,
        _                     => Enumerable.Empty<TypeSyntax>()
    };

    public static TypeSyntax GetGenericArgument(this AttributeSyntax attribute) => attribute.GetGenericArguments().Single();
    
    public static CompilationUnitSyntax RemoveNotImplemented(this CompilationUnitSyntax baseFile)
    {
        return baseFile
              .EditNodes()
              .OfType<IdentifierNameSyntax>()
              .Where(i => i.Identifier.ValueText == nameof(NotImplementedException))
              .HasAncestor<ThrowExpressionSyntax>()
              .GetAncestor<MemberDeclarationSyntax>()
              .Remove();
    }

    public static String GetGeneratedPath(this String path, String postfix = "generated")
    {
        var ext  = Path.GetExtension(path);
        var name = Path.GetFileNameWithoutExtension(path);
        var dir  = Path.GetDirectoryName(path);

        return $"{dir}/{name}.{postfix}{ext}";
    }
}