using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratedMappings;

[Generator]
#pragma warning disable RS1036
public sealed class MappingRegistryGenerator : IIncrementalGenerator
#pragma warning restore RS1036
{
    private const string GENERATED_NAMESPACE = "AIStudio.Tools.PluginSystem.Assistants.Icons";
    private const string ROOT_TYPE_NAME = "MudBlazor.Icons";
    private static readonly string[] ALLOWED_GROUP_PATHS = ["Material.Filled", "Material.Outlined"];

    private static readonly DiagnosticDescriptor ROOT_TYPE_MISSING = new(
        id: "MBI001",
        title: "MudBlazor icon root type was not found",
        messageFormat: "The generator could not find '{0}' in the current compilation references. No icon registry was generated.",
        category: "SourceGeneration",
        DiagnosticSeverity.Info,
        isEnabledByDefault: true);

    private static readonly DiagnosticDescriptor NO_ICONS_FOUND = new(
        id: "MBI002",
        title: "No MudBlazor icons were discovered",
        messageFormat: "The generator found '{0}', but no nested icon constants were discovered below it",
        category: "SourceGeneration",
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true);

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterSourceOutput(context.CompilationProvider, Generate);
    }

    private static void Generate(SourceProductionContext context, Compilation compilation)
    {
        var rootType = compilation.GetTypeByMetadataName(ROOT_TYPE_NAME);
        if (rootType is null)
        {
            context.ReportDiagnostic(Diagnostic.Create(ROOT_TYPE_MISSING, Location.None, ROOT_TYPE_NAME));
            return;
        }

        var icons = new List<IconDefinition>();
        CollectIcons(rootType, [], icons);

        if (icons.Count == 0)
        {
            context.ReportDiagnostic(Diagnostic.Create(NO_ICONS_FOUND, Location.None, ROOT_TYPE_NAME));
            return;
        }

        var source = RenderSource(icons);
        context.AddSource("MudBlazorIconRegistry.g.cs", SourceText.From(source, Encoding.UTF8));
    }

    private static void CollectIcons(INamedTypeSymbol currentType, List<string> path, List<IconDefinition> icons)
    {
        foreach (var nestedType in currentType.GetTypeMembers().OrderBy(static t => t.Name, StringComparer.Ordinal))
        {
            path.Add(nestedType.Name);
            CollectIcons(nestedType, path, icons);
            path.RemoveAt(path.Count - 1);
        }

        foreach (var field in currentType.GetMembers().OfType<IFieldSymbol>().OrderBy(static f => f.Name, StringComparer.Ordinal))
        {
            if (!field.IsConst || field.Type.SpecialType != SpecialType.System_String || field.ConstantValue is not string svg)
                continue;

            if (path.Count == 0)
                continue;

            var groupPath = string.Join(".", path);
            if (!ALLOWED_GROUP_PATHS.Contains(groupPath, StringComparer.Ordinal))
                continue;

            icons.Add(new IconDefinition(
                $"Icons.{groupPath}.{field.Name}",
                svg));
        }
    }

    private static string RenderSource(IReadOnlyList<IconDefinition> icons)
    {
        var builder = new StringBuilder();

        builder.AppendLine("// <auto-generated />");
        builder.AppendLine("#nullable enable");
        builder.AppendLine("using System;");
        builder.AppendLine("using System.Collections.Generic;");
        builder.AppendLine();
        builder.Append("namespace ").Append(GENERATED_NAMESPACE).AppendLine(";");
        builder.AppendLine();
        builder.AppendLine("public static class MudBlazorIconRegistry");
        builder.AppendLine("{");
        builder.AppendLine("    public static readonly IReadOnlyDictionary<string, string> SvgByIdentifier = new Dictionary<string, string>(StringComparer.Ordinal)");
        builder.AppendLine("    {");

        foreach (var icon in icons)
        {
            builder.Append("        [")
                .Append(ToLiteral(icon.QualifiedName))
                .Append("] = ")
                .Append(ToLiteral(icon.Svg))
                .AppendLine(",");
        }

        builder.AppendLine("    };");
        builder.AppendLine();
        builder.AppendLine("    public static bool TryGetSvg(string identifier, out string svg)");
        builder.AppendLine("    {");
        builder.AppendLine("        return SvgByIdentifier.TryGetValue(identifier, out svg!);");
        builder.AppendLine("    }");
        builder.AppendLine("}");

        return builder.ToString();
    }

    private static string ToLiteral(string value)
    {
        return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(value, quote: true);
    }

    private sealed class IconDefinition(string qualifiedName, string svg)
    {
        public string QualifiedName { get; } = qualifiedName;
        
        public string Svg { get; } = svg;
    }
}