using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Terminal.Gui.Analyzers.Internal.Attributes;
using Terminal.Gui.Analyzers.Internal.Constants;

namespace Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;

/// <summary>
///     Incremental code generator for enums decorated with <see cref="GenerateEnumExtensionMethodsAttribute"/>.
/// </summary>
[SuppressMessage ("CodeQuality", "IDE0079", Justification = "Suppressions here are intentional and the warnings they disable are just noise.")]
[Generator (LanguageNames.CSharp)]
public sealed class EnumExtensionMethodsIncrementalGenerator : IIncrementalGenerator
{
    private const string ExtensionsForEnumTypeAttributeFullyQualifiedName = $"{Strings.AnalyzersAttributesNamespace}.{ExtensionsForEnumTypeAttributeName}";
    private const string ExtensionsForEnumTypeAttributeName = "ExtensionsForEnumTypeAttribute";
    private const string GeneratorAttributeFullyQualifiedName = $"{Strings.AnalyzersAttributesNamespace}.{GeneratorAttributeName}";
    private const string GeneratorAttributeName = nameof (GenerateEnumExtensionMethodsAttribute);

    /// <summary>Fully-qualified symbol name format without the "global::" prefix.</summary>
    private static readonly SymbolDisplayFormat _fullyQualifiedSymbolDisplayFormatWithoutGlobal =
        SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle (SymbolDisplayGlobalNamespaceStyle.Omitted);

    /// <inheritdoc/>
    /// <remarks>
    ///     <para>
    ///         Basically, this method is called once by the compiler, and is responsible for wiring up
    ///         everything important about how source generation works.
    ///     </para>
    ///     <para>
    ///         See in-line comments for specifics of what's going on.
    ///     </para>
    ///     <para>
    ///         Note that <paramref name="context"/> is everything in the compilation,
    ///         except for code generated by this generator or generators which have not yet executed.<br/>
    ///         The methods registered to perform generation get called on-demand by the host (the IDE,
    ///         compiler, etc), sometimes as often as every single keystroke.
    ///     </para>
    /// </remarks>
    public void Initialize (IncrementalGeneratorInitializationContext context)
    {
        // Write out namespaces that may be used later. Harmless to declare them now and will avoid
        // additional processing and potential omissions later on.
        context.RegisterPostInitializationOutput (GenerateDummyNamespaces);

        // This executes the delegate given to it immediately after Roslyn gets all set up.
        // 
        // As written, this will result in the GenerateEnumExtensionMethodsAttribute code
        // being added to the environment, so that it can be used without having to actually
        // be declared explicitly in the target project.
        // This is important, as it guarantees the type will exist and also guarantees it is
        // defined exactly as the generator expects it to be defined.
        context.RegisterPostInitializationOutput (GenerateAttributeSources);

        // Next up, we define our pipeline.
        // To do so, we create one or more IncrementalValuesProvider<T> objects, each of which
        // defines on stage of analysis or generation as needed.
        //
        // Critically, these must be as fast and efficient as reasonably possible because,
        // once the pipeline is registered, this stuff can get called A LOT.
        //
        // Note that declaring these doesn't really do much of anything unless they are given to the
        // RegisterSourceOutput method at the end of this method.
        //
        // The delegates are not actually evaluated right here. That is triggered by changes being
        // made to the source code.

        // This provider grabs attributes that pass our filter and then creates lightweight
        // metadata objects to be used in the final code generation step.
        // It also preemptively removes any nulls from the collection before handing things off
        // to the code generation logic.
        IncrementalValuesProvider<EnumExtensionMethodsGenerationInfo?> enumGenerationInfos =
            context
                .SyntaxProvider

                // This method is a highly-optimized (and highly-recommended) filter on the incoming
                // code elements that only bothers to present code that is annotated with the specified
                // attribute, by its fully-qualified name, as a string, which is the first parameter.
                //
                // Two delegates are passed to it, in the second and third parameters.
                //
                // The second parameter is a filter predicate taking each SyntaxNode that passes the
                // name filter above, and then refines that result.
                //
                // It is critical that the filter predicate be as simple and fast as possible, as it
                // will be called a ton, triggered by keystrokes or anything else that modifies code
                // in or even related to (in either direction) the pre-filtered code.
                // It should collect metadata only and not actually generate any code.
                // It must return a boolean indicating whether the supplied SyntaxNode should be
                // given to the transform delegate at all.
                // 
                // The third parameter is the "transform" delegate.
                // That one only runs when code is changed that passed both the attribute name filter
                // and the filter predicate in the second parameter.
                // It will be called for everything that passes both of those, so it can still happen
                // a lot, but should at least be pretty close.
                // In our case, it should be 100% accurate, since we're using OUR attribute, which can
                // only be applied to enum types in the first place.
                //
                // That delegate is responsible for creating some sort of lightweight data structure
                // which can later be used to generate the actual source code for output.
                //
                // THIS DELEGATE DOES NOT GENERATE CODE!
                // However, it does need to return instances of the metadata class in use that are either
                // null or complete enough to generate meaningful code from, later on.
                //
                // We then filter out any that were null with the .Where call at the end, so that we don't
                // know or care about them when it's time to generate code.
                //
                // While the syntax of that .Where call is the same as LINQ, that is actually a
                // highly-optimized implementation specifically for this use.
                .ForAttributeWithMetadataName (
                                               GeneratorAttributeFullyQualifiedName,
                                               IsPotentiallyInterestingDeclaration,
                                               GatherMetadataForCodeGeneration
                                              )
                .WithTrackingName ("CollectEnumMetadata")
                .Where (static eInfo => eInfo is { });

        // Finally, we wire up any IncrementalValuesProvider<T> instances above to the appropriate
        // delegate that takes the SourceProductionContext that is current at run-time and an instance of
        // our metadata type and takes appropriate action.
        // Typically that means generating code from that metadata and adding it to the compilation via
        // the received context object.
        //
        // As with everything else , the delegate will be invoked once for each item that passed
        // all of the filters above, so we get to write that method from the perspective of a single
        // enum type declaration.

        context.RegisterSourceOutput (enumGenerationInfos, GenerateSourceFromGenerationInfo);
    }

    private static EnumExtensionMethodsGenerationInfo? GatherMetadataForCodeGeneration (
        GeneratorAttributeSyntaxContext context,
        CancellationToken cancellationToken
    )
    {
        var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
        cancellationToken.ThrowIfCancellationRequested ();

        // If it's not an enum symbol, we don't care.
        // EnumUnderlyingType is null for non-enums, so this validates it's an enum declaration.
        if (context.TargetSymbol is not INamedTypeSymbol { EnumUnderlyingType: { } } namedSymbol)
        {
            return null;
        }

        INamespaceSymbol? enumNamespaceSymbol = namedSymbol.ContainingNamespace;

        if (enumNamespaceSymbol is null or { IsGlobalNamespace: true })
        {
            // Explicitly choosing not to support enums in the global namespace.
            // The corresponding analyzer will report this.
            return null;
        }

        string enumName = namedSymbol.Name;

        string enumNamespace = enumNamespaceSymbol.ToDisplayString (_fullyQualifiedSymbolDisplayFormatWithoutGlobal);

        TypeCode enumTypeCode = namedSymbol.EnumUnderlyingType.Name switch
                                {
                                    "UInt32" => TypeCode.UInt32,
                                    "Int32" => TypeCode.Int32,
                                    _ => TypeCode.Empty
                                };

        EnumExtensionMethodsGenerationInfo info = new (
                                                       enumNamespace,
                                                       enumName,
                                                       enumTypeCode
                                                      );

        if (!info.TryConfigure (namedSymbol, cts.Token))
        {
            cts.Cancel ();
            cts.Token.ThrowIfCancellationRequested ();
        }

        return info;
    }


    private static void GenerateAttributeSources (IncrementalGeneratorPostInitializationContext postInitializationContext)
    {
        postInitializationContext
            .AddSource (
                        $"{nameof (IExtensionsForEnumTypeAttributes)}.g.cs",
                        SourceText.From (
                                         $$"""
                                           // ReSharper disable All
                                           {{Strings.Templates.AutoGeneratedCommentBlock}}
                                           using System;

                                           namespace {{Strings.AnalyzersAttributesNamespace}};

                                           /// <summary>
                                           ///     Interface to simplify general enumeration of constructed generic types for
                                           ///     <see cref="ExtensionsForEnumTypeAttribute{TEnum}"/>
                                           /// </summary>
                                           {{Strings.Templates.AttributesForGeneratedInterfaces}}
                                           public interface IExtensionsForEnumTypeAttributes
                                           {
                                               System.Type EnumType { get; }
                                           }

                                           """,
                                         Encoding.UTF8));

        postInitializationContext
            .AddSource (
                        $"{nameof (AssemblyExtendedEnumTypeAttribute)}.g.cs",
                        SourceText.From (
                                         $$"""
                                           // ReSharper disable All
                                           #nullable enable
                                           {{Strings.Templates.AutoGeneratedCommentBlock}}

                                           namespace {{Strings.AnalyzersAttributesNamespace}};

                                           /// <summary>Assembly attribute declaring a known pairing of an <see langword="enum" /> type to an extension class.</summary>
                                           /// <remarks>This attribute should only be written by internal source generators for Terminal.Gui. No other usage of any kind is supported.</remarks>
                                           {{Strings.Templates.AttributesForGeneratedTypes}}
                                           [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple = true)]
                                           public sealed class {{nameof(AssemblyExtendedEnumTypeAttribute)}} : System.Attribute
                                           {
                                               /// <summary>Creates a new instance of <see cref="AssemblyExtendedEnumTypeAttribute" /> from the provided parameters.</summary>
                                               /// <param name="enumType">The <see cref="System.Type" /> of an <see langword="enum" /> decorated with a <see cref="GenerateEnumExtensionMethodsAttribute" />.</param>
                                               /// <param name="extensionClass">The <see cref="System.Type" /> of the <see langword="class" /> decorated with an <see cref="ExtensionsForEnumTypeAttribute{TEnum}" /> referring to the same type as <paramref name="enumType" />.</param>
                                               public AssemblyExtendedEnumTypeAttribute (System.Type enumType, System.Type extensionClass)
                                               {
                                                   EnumType = enumType;
                                                   ExtensionClass = extensionClass;
                                               }
                                               /// <summary>An <see langword="enum" /> type that has been extended by Terminal.Gui source generators.</summary>
                                               public System.Type EnumType { get; init; }
                                               /// <summary>A class containing extension methods for <see cref="EnumType"/>.</summary>
                                               public System.Type ExtensionClass { get; init; }
                                               /// <inheritdoc />
                                               public override string ToString () => $"{EnumType.Name},{ExtensionClass.Name}";
                                           }

                                           """,
                                         Encoding.UTF8));

        postInitializationContext
            .AddSource (
                        $"{GeneratorAttributeFullyQualifiedName}.g.cs",
                        SourceText.From (
                                         $$"""
                                           {{Strings.Templates.StandardHeader}}
                                           
                                           namespace {{Strings.AnalyzersAttributesNamespace}};

                                           /// <summary>
                                           ///     Used to enable source generation of a common set of extension methods for enum types.
                                           /// </summary>
                                           {{Strings.Templates.AttributesForGeneratedTypes}}
                                           [{{Strings.DotnetNames.Types.AttributeUsageAttribute}} ({{Strings.DotnetNames.Types.AttributeTargets}}.Enum)]
                                           public sealed class {{GeneratorAttributeName}} : {{Strings.DotnetNames.Types.Attribute}}
                                           {
                                               /// <summary>
                                               ///     The name of the generated static class.
                                               /// </summary>
                                               /// <remarks>
                                               ///     If unspecified, null, empty, or only whitespace, defaults to the name of the enum plus "Extensions".<br/>
                                               ///     No other validation is performed, so illegal values will simply result in compiler errors.
                                               ///     <para>
                                               ///         Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
                                               ///     </para>
                                               /// </remarks>
                                               public string? ClassName { get; set; }
                                           
                                               /// <summary>
                                               ///     The namespace in which to place the generated static class containing the extension methods.
                                               /// </summary>
                                               /// <remarks>
                                               ///     If unspecified, null, empty, or only whitespace, defaults to the namespace of the enum.<br/>
                                               ///     No other validation is performed, so illegal values will simply result in compiler errors.
                                               ///     <para>
                                               ///         Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
                                               ///     </para>
                                               /// </remarks>
                                               public string? ClassNamespace { get; set; }
                                           
                                               /// <summary>
                                               ///     Whether to generate a fast, zero-allocation, non-boxing, and reflection-free alternative to the built-in
                                               ///     <see cref="Enum.HasFlag"/> method.
                                               /// </summary>
                                               /// <remarks>
                                               ///     <para>
                                               ///         Default: false
                                               ///     </para>
                                               ///     <para>
                                               ///         If the enum is not decorated with <see cref="Flags"/>, this option has no effect.
                                               ///     </para>
                                               ///     <para>
                                               ///         If multiple members have the same value, the first member with that value will be used and subsequent members
                                               ///         with the same value will be skipped.
                                               ///     </para>
                                               ///     <para>
                                               ///         Overloads taking the enum type itself as well as the underlying type of the enum will be generated, enabling
                                               ///         avoidance of implicit or explicit cast overhead.
                                               ///     </para>
                                               ///     <para>
                                               ///         Explicitly specifying a default value is unnecessary and will result in unnecessary processing.
                                               ///     </para>
                                               /// </remarks>
                                               public bool FastHasFlags { get; set; }
                                           
                                               /// <summary>
                                               ///     Whether to generate a fast, zero-allocation, and reflection-free alternative to the built-in
                                               ///     <see cref="Enum.IsDefined"/> method,
                                               ///     using a switch expression as a hard-coded reverse mapping of numeric values to explicitly-named members.
                                               /// </summary>
                                               /// <remarks>
                                               ///     <para>
                                               ///         Default: true
                                               ///     </para>
                                               ///     <para>
                                               ///         If multiple members have the same value, the first member with that value will be used and subsequent members
                                               ///         with the same value will be skipped.
                                               ///     </para>
                                               ///     <para>
                                               ///         As with <see cref="Enum.IsDefined"/> the source generator only considers explicitly-named members.<br/>
                                               ///         Generation of values which represent valid bitwise combinations of members of enums decorated with
                                               ///         <see cref="Flags"/> is not affected by this property.
                                               ///     </para>
                                               /// </remarks>
                                               public bool FastIsDefined { get; init; } = true;
                                           
                                               /// <summary>
                                               ///     Gets a <see langword="bool"/> value indicating if this <see cref="GenerateEnumExtensionMethodsAttribute"/> instance
                                               ///     contains default values only. See <see href="#remarks">remarks</see> of this method or documentation on properties of this type for details.
                                               /// </summary>
                                               /// <returns>
                                               ///     A <see langword="bool"/> value indicating if all property values are default for this
                                               ///     <see cref="GenerateEnumExtensionMethodsAttribute"/> instance.
                                               /// </returns>
                                               /// <remarks>
                                               ///     Default values that will result in a <see langword="true"/> return value are:<br/>
                                               ///     <see cref="FastIsDefined"/> &amp;&amp; !<see cref="FastHasFlags"/> &amp;&amp; <see cref="ClassName"/>
                                               ///     <see langword="is"/> <see langword="null"/> &amp;&amp; <see cref="ClassNamespace"/> <see langword="is"/>
                                               ///     <see langword="null"/>
                                               /// </remarks>
                                               public override bool IsDefaultAttribute ()
                                               {
                                                   return FastIsDefined
                                                          && !FastHasFlags
                                                          && ClassName is null
                                                          && ClassNamespace is null;
                                               }
                                           }
                                           
                                           """,
                                         Encoding.UTF8));

        postInitializationContext
            .AddSource (
                        $"{ExtensionsForEnumTypeAttributeFullyQualifiedName}.g.cs",
                        SourceText.From (
                                         $$"""
                                           // ReSharper disable RedundantNameQualifier
                                           // ReSharper disable RedundantNullableDirective
                                           // ReSharper disable UnusedType.Global
                                           {{Strings.Templates.AutoGeneratedCommentBlock}}
                                           #nullable enable

                                           namespace {{Strings.AnalyzersAttributesNamespace}};

                                           /// <summary>
                                           ///     Attribute written by the source generator for enum extension classes, for easier analysis and reflection.
                                           /// </summary>
                                           /// <remarks>
                                           ///     Properties are just convenient shortcuts to properties of <typeparamref name="TEnum"/>.
                                           /// </remarks>
                                           {{Strings.Templates.AttributesForGeneratedTypes}}
                                           [System.AttributeUsageAttribute (System.AttributeTargets.Class | System.AttributeTargets.Interface)]
                                           public sealed class {{ExtensionsForEnumTypeAttributeName}}<TEnum>: System.Attribute, IExtensionsForEnumTypeAttributes where TEnum : struct, Enum
                                           {
                                               /// <summary>
                                               ///     The namespace-qualified name of <typeparamref name="TEnum"/>.
                                               /// </summary>
                                               public string EnumFullName => EnumType.FullName!;
                                           
                                               /// <summary>
                                               ///     The unqualified name of <typeparamref name="TEnum"/>.
                                               /// </summary>
                                               public string EnumName => EnumType.Name;
                                           
                                               /// <summary>
                                               ///     The namespace containing <typeparamref name="TEnum"/>.
                                               /// </summary>
                                               public string EnumNamespace => EnumType.Namespace!;
                                           
                                               /// <summary>
                                               ///     The <see cref="Type"/> given by <see langword="typeof"/>(<typeparamref name="TEnum"/>).
                                               /// </summary>
                                               public Type EnumType => typeof (TEnum);
                                           }
                                           
                                           """,
                                         Encoding.UTF8));
    }

    [SuppressMessage ("Roslynator", "RCS1267", Justification = "Intentionally used so that Spans are used.")]
    private static void GenerateDummyNamespaces (IncrementalGeneratorPostInitializationContext postInitializeContext)
    {
        postInitializeContext.AddSource (
                                         string.Concat (Strings.InternalAnalyzersNamespace, "Namespaces.g.cs"),
                                         SourceText.From (Strings.Templates.DummyNamespaceDeclarations, Encoding.UTF8));
    }

    private static void GenerateSourceFromGenerationInfo (SourceProductionContext context, EnumExtensionMethodsGenerationInfo? enumInfo)
    {
        // Just in case we still made it this far with a null...
        if (enumInfo is not { })
        {
            return;
        }

        CodeWriter writer = new (enumInfo);

        context.AddSource ($"{enumInfo.FullyQualifiedClassName}.g.cs", writer.GenerateSourceText ());
    }

    /// <summary>
    ///     Returns true if <paramref name="syntaxNode"/> is an EnumDeclarationSyntax
    ///     whose parent is a NamespaceDeclarationSyntax, FileScopedNamespaceDeclarationSyntax, or a
    ///     (Class|Struct)DeclarationSyntax.<br/>
    ///     Additional filtering is performed in later stages.
    /// </summary>
    private static bool IsPotentiallyInterestingDeclaration (SyntaxNode syntaxNode, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested ();

        return syntaxNode is
               {
                   RawKind: 8858, //(int)SyntaxKind.EnumDeclaration,
                   Parent.RawKind: 8845 //(int)SyntaxKind.FileScopedNamespaceDeclaration
                                   or 8842 //(int)SyntaxKind.NamespaceDeclaration
                                   or 8855 //(int)SyntaxKind.ClassDeclaration
                                   or 8856 //(int)SyntaxKind.StructDeclaration
                                   or 9068 //(int)SyntaxKind.RecordStructDeclaration
                                   or 9063 //(int)SyntaxKind.RecordDeclaration
               };
    }
}
