using System;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace ZB.MOM.WW.CBDD.SourceGenerators.Helpers
{
    public static class AttributeHelper
    {
        /// <summary>
        /// Determines whether a property should be ignored during mapping.
        /// </summary>
        /// <param name="property">The property symbol to inspect.</param>
        /// <returns><see langword="true"/> when the property has an ignore attribute; otherwise, <see langword="false"/>.</returns>
        public static bool ShouldIgnore(IPropertySymbol property)
        {
            return HasAttribute(property, "BsonIgnore") ||
                   HasAttribute(property, "JsonIgnore") ||
                   HasAttribute(property, "NotMapped");
        }

        /// <summary>
        /// Determines whether a property is marked as a key.
        /// </summary>
        /// <param name="property">The property symbol to inspect.</param>
        /// <returns><see langword="true"/> when the property has a key attribute; otherwise, <see langword="false"/>.</returns>
        public static bool IsKey(IPropertySymbol property)
        {
            return HasAttribute(property, "Key") ||
                   HasAttribute(property, "BsonId");
        }

        /// <summary>
        /// Gets the first constructor argument value for the specified attribute as a string.
        /// </summary>
        /// <param name="symbol">The symbol to inspect.</param>
        /// <param name="attributeName">The attribute name to match.</param>
        /// <returns>The attribute value if found; otherwise, <see langword="null"/>.</returns>
        public static string? GetAttributeStringValue(ISymbol symbol, string attributeName)
        {
            var attr = GetAttribute(symbol, attributeName);
            if (attr != null && attr.ConstructorArguments.Length > 0)
            {
                return attr.ConstructorArguments[0].Value?.ToString();
            }

            return null;
        }

        /// <summary>
        /// Gets the first constructor argument value for the specified attribute as an integer.
        /// </summary>
        /// <param name="symbol">The symbol to inspect.</param>
        /// <param name="attributeName">The attribute name to match.</param>
        /// <returns>The attribute value if found; otherwise, <see langword="null"/>.</returns>
        public static int? GetAttributeIntValue(ISymbol symbol, string attributeName)
        {
            var attr = GetAttribute(symbol, attributeName);
            if (attr != null && attr.ConstructorArguments.Length > 0)
            {
                if (attr.ConstructorArguments[0].Value is int val) return val;
            }

            return null;
        }

        /// <summary>
        /// Gets the first constructor argument value for the specified attribute as a double.
        /// </summary>
        /// <param name="symbol">The symbol to inspect.</param>
        /// <param name="attributeName">The attribute name to match.</param>
        /// <returns>The attribute value if found; otherwise, <see langword="null"/>.</returns>
        public static double? GetAttributeDoubleValue(ISymbol symbol, string attributeName)
        {
            var attr = GetAttribute(symbol, attributeName);
            if (attr != null && attr.ConstructorArguments.Length > 0)
            {
                if (attr.ConstructorArguments[0].Value is double val) return val;
                if (attr.ConstructorArguments[0].Value is float fval) return (double)fval;
                if (attr.ConstructorArguments[0].Value is int ival) return (double)ival;
            }

            return null;
        }

        /// <summary>
        /// Gets a named argument value from an attribute.
        /// </summary>
        /// <param name="attr">The attribute data.</param>
        /// <param name="name">The named argument key.</param>
        /// <returns>The named argument value if present; otherwise, <see langword="null"/>.</returns>
        public static string? GetNamedArgumentValue(AttributeData attr, string name)
        {
            return attr.NamedArguments.FirstOrDefault(a => a.Key == name).Value.Value?.ToString();
        }

        /// <summary>
        /// Gets the first attribute that matches the specified name.
        /// </summary>
        /// <param name="symbol">The symbol to inspect.</param>
        /// <param name="attributeName">The attribute name to match.</param>
        /// <returns>The matching attribute data if found; otherwise, <see langword="null"/>.</returns>
        public static AttributeData? GetAttribute(ISymbol symbol, string attributeName)
        {
            return symbol.GetAttributes().FirstOrDefault(a =>
                a.AttributeClass != null &&
                (a.AttributeClass.Name == attributeName ||
                 a.AttributeClass.Name == attributeName + "Attribute"));
        }

        /// <summary>
        /// Determines whether a symbol has the specified attribute.
        /// </summary>
        /// <param name="symbol">The symbol to inspect.</param>
        /// <param name="attributeName">The attribute name to match.</param>
        /// <returns><see langword="true"/> when a matching attribute exists; otherwise, <see langword="false"/>.</returns>
        public static bool HasAttribute(ISymbol symbol, string attributeName)
        {
            return GetAttribute(symbol, attributeName) != null;
        }
    }
}
