// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0

using GFramework.Core.SourceGenerators.Abstractions.Enums;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;

namespace GFramework.Core.SourceGenerators.Enums;

/// <summary>
///     枚举扩展方法生成器，用于自动生成枚举相关的扩展方法
/// </summary>
[Generator]
public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
{
    private static string AttributeMetadataName =>
        $"{PathContests.SourceGeneratorsAbstractionsPath}.Enums.GenerateEnumExtensionsAttribute";

    /// <summary>
    ///     仅用于 Syntax 粗筛选
    /// </summary>
    protected override string AttributeShortNameWithoutSuffix => "GenerateEnumExtensions";

    /// <summary>
    ///     按元数据名称解析枚举上的 <c>GenerateEnumExtensionsAttribute</c>。
    /// </summary>
    /// <param name="compilation">当前编译上下文。</param>
    /// <param name="symbol">待检查的枚举符号。</param>
    /// <returns>匹配到的属性数据；若未标注目标属性则返回 <see langword="null" />。</returns>
    protected override AttributeData? ResolveAttribute(Compilation compilation, INamedTypeSymbol symbol)
    {
        var attrSymbol = compilation.GetTypeByMetadataName(AttributeMetadataName);

        if (attrSymbol is null)
            return null;

        return symbol.GetAttributes()
            .FirstOrDefault(a =>
                SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
    }

    /// <summary>
    ///     验证候选符号仍然是可生成扩展方法的枚举类型。
    /// </summary>
    /// <param name="context">源生成诊断上下文。</param>
    /// <param name="compilation">当前编译上下文。</param>
    /// <param name="syntax">候选枚举声明语法。</param>
    /// <param name="symbol">候选命名类型符号。</param>
    /// <param name="attr">已解析出的生成器属性。</param>
    /// <returns>当符号满足生成前置条件时返回 <see langword="true" />。</returns>
    protected override bool ValidateSymbol(SourceProductionContext context, Compilation compilation,
        EnumDeclarationSyntax syntax,
        INamedTypeSymbol symbol, AttributeData attr)
    {
        if (symbol.TypeKind == TypeKind.Enum) return true;
        var loc = syntax.Identifier.GetLocation();
        context.ReportDiagnostic(Diagnostic.Create(
            CommonDiagnostics.ClassMustBePartial,
            loc,
            symbol.Name
        ));
        return false;
    }

    /// <summary>
    ///     生成枚举扩展方法的源代码
    /// </summary>
    /// <param name="symbol">枚举类型符号</param>
    /// <param name="attr">属性数据</param>
    /// <returns>生成的源代码字符串</returns>
    protected override string Generate(INamedTypeSymbol symbol, AttributeData attr)
    {
        var ns = symbol.ContainingNamespace.IsGlobalNamespace
            ? null
            : symbol.ContainingNamespace.ToDisplayString();

        var generationOptions = GetGenerationOptions(attr);
        var enumName = symbol.Name;
        var fullEnumName = symbol.ToDisplayString();
        var members = symbol.GetMembers()
            .OfType<IFieldSymbol>()
            .Where(f => f.ConstantValue != null)
            .ToArray();

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

        sb.AppendLine(!string.IsNullOrEmpty(ns) ? $"namespace {ns}" : "namespace EnumExtensionsGenerated");

        sb.AppendLine("{");

        sb.AppendLine("    /// <summary>");
        sb.AppendLine($"    /// 为 <see cref=\"{fullEnumName}\" /> 提供自动生成的扩展方法。");
        sb.AppendLine("    /// </summary>");
        sb.AppendLine($"    public static partial class {enumName}Extensions");
        sb.AppendLine("    {");

        // 两个生成开关是彼此独立的契约，需要分别控制输出，并保持空行布局稳定，便于快照精确回归。
        var hasGeneratedMembers = false;

        if (generationOptions.GenerateIsMethods)
        {
            hasGeneratedMembers = AppendIsMethods(
                sb,
                members,
                fullEnumName);
        }

        if (generationOptions.GenerateIsInMethod)
        {
            if (hasGeneratedMembers)
            {
                sb.AppendLine();
            }

            AppendIsInMethod(
                sb,
                fullEnumName);
        }

        sb.AppendLine("    }");
        sb.AppendLine("}"); // namespace

        return sb.ToString();
    }

    /// <summary>
    ///     读取枚举扩展生成选项，并在属性未显式指定时回退到契约默认值。
    /// </summary>
    /// <param name="attribute">待分析的特性数据。</param>
    /// <returns>包含各个生成开关的选项元组。</returns>
    private static (bool GenerateIsMethods, bool GenerateIsInMethod) GetGenerationOptions(AttributeData attribute)
    {
        return (
            GetNamedBooleanArgument(
                attribute,
                nameof(GenerateEnumExtensionsAttribute.GenerateIsMethods),
                true),
            GetNamedBooleanArgument(
                attribute,
                nameof(GenerateEnumExtensionsAttribute.GenerateIsInMethod),
                true));
    }

    /// <summary>
    ///     获取生成文件的提示名称
    /// </summary>
    /// <param name="symbol">命名类型符号</param>
    /// <returns>生成文件的提示名称</returns>
    protected override string GetHintName(INamedTypeSymbol symbol)
    {
        return $"{symbol.Name}.EnumExtensions.g.cs";
    }

    /// <summary>
    ///     读取属性上的命名布尔参数，并在参数未显式提供时回退到属性契约默认值。
    /// </summary>
    /// <param name="attribute">待读取的属性数据。</param>
    /// <param name="argumentName">命名参数名称。</param>
    /// <param name="defaultValue">属性未提供该参数时使用的默认值。</param>
    /// <returns>解析得到的布尔值；若参数缺失或类型不匹配则返回 <paramref name="defaultValue" />。</returns>
    private static bool GetNamedBooleanArgument(AttributeData attribute, string argumentName, bool defaultValue)
    {
        foreach (var namedArgument in attribute.NamedArguments)
        {
            if (string.Equals(namedArgument.Key, argumentName, StringComparison.Ordinal) &&
                namedArgument.Value.Value is bool value)
            {
                return value;
            }
        }

        return defaultValue;
    }

    /// <summary>
    ///     为每个枚举成员追加单值判断扩展方法。
    /// </summary>
    /// <param name="builder">目标源码构建器。</param>
    /// <param name="members">需要生成扩展方法的枚举成员。</param>
    /// <param name="fullEnumName">枚举的完整类型名。</param>
    /// <returns>当至少生成了一个方法时返回 <see langword="true" />。</returns>
    private static bool AppendIsMethods(StringBuilder builder, IEnumerable<IFieldSymbol> members, string fullEnumName)
    {
        var hasGeneratedMembers = false;

        foreach (var memberName in members.Select(m => m.Name))
        {
            if (hasGeneratedMembers)
            {
                builder.AppendLine();
            }

            builder.AppendLine("        /// <summary>");
            builder.AppendLine(
                $"        /// 判断给定值是否为 <see cref=\"{fullEnumName}.{memberName}\" />。");
            builder.AppendLine("        /// </summary>");
            builder.AppendLine("        /// <param name=\"value\">要检查的枚举值。</param>");
            builder.AppendLine(
                $"        /// <returns>当 <paramref name=\"value\" /> 等于 <see cref=\"{fullEnumName}.{memberName}\" /> 时返回 <see langword=\"true\" />；否则返回 <see langword=\"false\" />。</returns>");
            builder.AppendLine(
                $"        public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
            hasGeneratedMembers = true;
        }

        return hasGeneratedMembers;
    }

    /// <summary>
    ///     追加用于多值匹配的 <c>IsIn</c> 扩展方法。
    /// </summary>
    /// <param name="builder">目标源码构建器。</param>
    /// <param name="fullEnumName">枚举的完整类型名。</param>
    private static void AppendIsInMethod(StringBuilder builder, string fullEnumName)
    {
        builder.AppendLine("        /// <summary>");
        builder.AppendLine("        /// 判断给定值是否属于指定候选集合。");
        builder.AppendLine("        /// </summary>");
        builder.AppendLine("        /// <param name=\"value\">要检查的枚举值。</param>");
        builder.AppendLine(
            "        /// <param name=\"values\">用于匹配的候选枚举值集合；当为 <see langword=\"null\" /> 时返回 <see langword=\"false\" />。</param>");
        builder.AppendLine(
            "        /// <returns>当 <paramref name=\"value\" /> 命中任一候选值时返回 <see langword=\"true\" />；否则返回 <see langword=\"false\" />。</returns>");
        builder.AppendLine(
            $"        public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
        builder.AppendLine("        {");
        builder.AppendLine("            if (values == null) return false;");
        builder.AppendLine("            foreach (var v in values) if (value == v) return true;");
        builder.AppendLine("            return false;");
        builder.AppendLine("        }");
    }
}
