using System.Collections.Immutable;
using System.Linq;
using GalaxyBudsClient.Generators.Utils;

namespace GalaxyBudsClient.Generators.Enums;

public static class RootSourceGenerationHelper
{
    public static string GenerateAttribute() =>
        """
        // <auto-generated/>
        namespace GalaxyBudsClient.Generated.Model.Attributes;

        [global::System.AttributeUsage(global::System.AttributeTargets.Enum)]
        internal class CompiledEnumAttribute : global::System.Attribute {}
        """;

    public static string GenerateClass(ImmutableArray<EnumToGenerate?> enumsToGenerate)
    {
        var gen = new CodeGenerator();
   
        gen.AppendLine(SourceGenerationHelper.Header);
        
        enumsToGenerate.Select(x => x?.Namespace)
            .Concat(new []{ "System", "System.Linq" })
            .Distinct()
            .Select(x => $"using {x?.Trim()};")
            .ToList()
            .ForEach(gen.AppendLine);
        
        gen.AppendLines($"""
                        namespace GalaxyBudsClient.Generated.Enums;

                        """);
        
        gen.EnterScope("internal static class CompiledEnums");
        
        // GetDescription
        gen.AppendLines("""
                        /// <summary>Returns the description or localized description by type and value.</summary>
                        /// <param name="type">Type instance of the enum</param>
                        /// <param name="value">Enum value</param>
                        /// <returns>Description, localized if possible.</returns>
                        /// <exception cref="ArgumentOutOfRangeException">Thrown when the enum type is unknown</exception>
                        """);
        gen.EnterScope("public static string GetDescriptionByType(global::System.Type type, object value)");
        gen.EnterScope($"return type.FullName switch");

        foreach (var enumToGenerate in enumsToGenerate.Cast<EnumToGenerate>())
        {
            var allAttr = enumToGenerate.Names
                .SelectMany(x => x.Value.AttributeTemplates)
                .ToList();
            var hasDesc = allAttr.Any(x => x.Name == "Description");
            var hasLocDesc = allAttr.Any(x => x.Name == "LocalizableDescription");

            var returnValue = hasLocDesc ? $"(value as {enumToGenerate.FullyQualifiedName}?)?.GetLocalizedDescription() ?? " : string.Empty;
            if(hasDesc)
                returnValue += $"(value as {enumToGenerate.FullyQualifiedName}?)?.GetDescription() ?? ";
            returnValue += "value.ToString()";
            
            gen.AppendLine($"\"{enumToGenerate.FullyQualifiedName}\" => {returnValue},"); 
        }
        
        gen.AppendLine("""
                         _ => "<unknown>"
                         """);
        
        gen.LeaveScope(" ?? \"<unknown>\";");
        gen.LeaveScope();

        // GetValuesByType
        gen.AppendLines("""
                        /// <summary>Returns all values of the specified enum type.</summary>
                        /// <param name="type">Type instance of the enum</param>
                        /// <returns>Array of enum values as a boxed object</returns>
                        /// <exception cref="ArgumentOutOfRangeException">Thrown when the enum type is unknown</exception>
                        """);
        gen.EnterScope("public static object GetValuesByType(global::System.Type type)");
        gen.EnterScope($"return type.FullName switch");

        foreach (var enumToGenerate in enumsToGenerate.Cast<EnumToGenerate>())
        {
            gen.AppendLine($"""
                            "{enumToGenerate.FullyQualifiedName}" => (object)global::{enumToGenerate.Namespace}.{enumToGenerate.ExtName}.GetValues(),
                            """);
        }

        gen.AppendLine("""
                       _ => throw new global::System.ArgumentOutOfRangeException(nameof(type), $"Unknown enum type '{type.FullName}'. Make sure to use the [CompiledEnum] attribute.")
                       """);
        gen.LeaveScope(";");
        gen.LeaveScope();

        
        // GetNamesByType
        gen.AppendLines("""
                        /// <summary>Returns all names of the specified enum type.</summary>
                        /// <param name="type">Type instance of the enum</param>
                        /// <returns>Array of enum names</returns>
                        /// <exception cref="ArgumentOutOfRangeException">Thrown when the enum type is unknown</exception>
                        """);
        gen.EnterScope("public static string[] GetNamesByType(global::System.Type type)");
        gen.EnterScope($"return type.FullName switch");

        foreach (var enumToGenerate in enumsToGenerate.Cast<EnumToGenerate>())
        {
            gen.AppendLine($"""
                            "{enumToGenerate.FullyQualifiedName}" => global::{enumToGenerate.Namespace}.{enumToGenerate.ExtName}.GetNames(),
                            """);
        }

        gen.AppendLine("""
                       _ => throw new global::System.ArgumentOutOfRangeException(nameof(type), $"Unknown enum type '{type.FullName}'. Make sure to use the [CompiledEnum] attribute.")
                       """);
        gen.LeaveScope(";");
        gen.LeaveScope();
        
        gen.LeaveScope();
        
        return gen.ToString();
    }
}