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

namespace QCEDL.Analyzer;

[Generator]
internal sealed class EnumExtensionsGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var enumDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: static (s, _) => s is EnumDeclarationSyntax,
                transform: static (ctx, _) => (EnumDecl: (EnumDeclarationSyntax)ctx.Node,
                                               Model: ctx.SemanticModel))
            .Where(static t => t.Model.GetDeclaredSymbol(t.EnumDecl, CancellationToken.None) is not null);

        var allEnums = enumDeclarations.Collect();

        context.RegisterSourceOutput(allEnums, (spc, enums) =>
        {
            foreach (var (enumDecl, model) in enums)
            {
                var enumSymbol = (INamedTypeSymbol)model.GetDeclaredSymbol(enumDecl)!;
                GenerateExtensionForEnum(enumSymbol, spc);
            }
        });
    }

    private static void GenerateExtensionForEnum(INamedTypeSymbol enumSymbol, SourceProductionContext spc)
    {
        var enumName = enumSymbol.Name;
        var @namespace = enumSymbol.ContainingNamespace.IsGlobalNamespace
            ? null
            : enumSymbol.ContainingNamespace.ToDisplayString();

        var sb = new StringBuilder();
        _ = sb.AppendLine("// <auto-generated />")
            .AppendLine("using System;")
            .AppendLine();

        if (!string.IsNullOrEmpty(@namespace))
        {
            _ = sb.AppendLine($"namespace {@namespace};")
                .AppendLine();
        }

        _ = sb.AppendLine("internal static class " + enumName + "Extensions")
                .AppendLine("{");

        AddToStringFast(sb, enumSymbol);
        AddToSnakeCaseUpper(sb, enumSymbol);

        _ = sb.AppendLine("}");

        var hintName = $"{enumSymbol.Name}_ToStringFast.g.cs";
        spc.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8));
    }

    private static void AddToStringFast(StringBuilder sb, INamedTypeSymbol enumSymbol)
    {
        var enumName = enumSymbol.Name;
        _ = sb.AppendLine($"    internal static string ToStringFast(this {enumName} value)")
            .AppendLine("    {")
            .AppendLine("        switch (value)")
            .AppendLine("        {");

        foreach (var member in enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(f => f.ConstantValue is not null))
        {
            var memberName = member.Name;

            var literal = member
                .GetAttributes()
                .FirstOrDefault(attr => attr.AttributeClass?.Name == "XmlEnumAttribute" ||
                                        attr.AttributeClass?.ToDisplayString() == "XmlAttribute")
                ?.NamedArguments
                .FirstOrDefault(kv => kv.Key == "Name")
                .Value.Value is not string mapped
                ? memberName
                : mapped.Replace("\\", "\\\\").Replace("\"", "\\\"");
            _ = sb.AppendLine($"                case {enumName}.{memberName}: return \"{literal}\";");
        }

        _ = sb.AppendLine("                default: return value.ToString();")
            .AppendLine("            }")
            .AppendLine("        }")
            .AppendLine();
    }

    private static void AddToSnakeCaseUpper(StringBuilder sb, INamedTypeSymbol enumSymbol)
    {
        var enumName = enumSymbol.Name;

        _ = sb.AppendLine()
            .AppendLine($"    internal static string ToStringSnakeCaseUpper(this {enumName} value)")
            .AppendLine("    {")
            .AppendLine("        switch (value)")
            .AppendLine("        {");

        foreach (var member in enumSymbol.GetMembers().OfType<IFieldSymbol>().Where(f => f.ConstantValue is not null))
        {
            var memberName = member.Name;
            var snake = SnakeCaseUpper(memberName);
            _ = sb.AppendLine($"            case {enumName}.{memberName}: return \"{snake}\";");
        }

        _ = sb.AppendLine("            default: return value.ToString();")
            .AppendLine("        }")
            .AppendLine("    }")
            .AppendLine();
    }


    private static string SnakeCaseUpper(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            return s;
        }

        var sb = new StringBuilder();
        _ = sb.Append(char.ToUpperInvariant(s[0]));
        for (var i = 1; i < s.Length; i++)
        {
            var c = s[i];
            var prev = s[i - 1];
            if ((char.IsUpper(c) && c != '_')
                || (char.IsDigit(c) && !char.IsDigit(prev))
                || (char.IsLetter(prev) && char.IsDigit(c)))
            {
                _ = sb.Append('_');
            }
            _ = sb.Append(char.ToUpperInvariant(c));
        }
        return sb.ToString();
    }
}
