using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using AyCode.Core.Serializers.Binaries;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers;

/// <summary>
/// Enum for typed property accessor dispatch.
/// </summary>
public enum PropertyAccessorType : byte
{
    Object = 0,
    Int32,
    Int64,
    Boolean,
    Double,
    Single,
    Decimal,
    DateTime,
    Byte,
    Int16,
    UInt16,
    UInt32,
    UInt64,
    Guid,
    Enum,
    String
}

/// <summary>
/// Base class containing common property metadata shared by serializers and deserializers.
/// Contains the dynamic getter used by both serialize (for reading) and deserialize (for Populate/Merge).
/// </summary>
public abstract class PropertyMetadataBase
{
    /// <summary>
    /// Property name.
    /// </summary>
    public string Name { get; }
    
    /// <summary>
    /// Pre-encoded UTF8 bytes of property name for fast matching.
    /// </summary>
    public byte[] NameUtf8 { get; }
    
    /// <summary>
    /// The property type (may be nullable).
    /// </summary>
    public Type PropertyType { get; }
    
    /// <summary>
    /// The underlying type (unwrapped from Nullable if applicable).
    /// </summary>
    public Type UnderlyingType { get; }
    
    /// <summary>
    /// Cached TypeCode for fast primitive type dispatch.
    /// </summary>
    public TypeCode PropertyTypeCode { get; }
    
    /// <summary>
    /// Whether the property type is nullable.
    /// </summary>
    public bool IsNullable { get; }
    
    /// <summary>
    /// The declaring type of this property.
    /// </summary>
    public Type DeclaringType { get; }
    
    /// <summary>
    /// True if this property needs recursive scanning (not primitive/string).
    /// Pre-computed to avoid IsPrimitiveOrStringFast() calls in hot path.
    /// </summary>
    public bool IsComplexType { get; }

    /// <summary>
    /// FNV-1a hash of property name. Deterministic across processes.
    /// Used for property matching in UseMetadata mode.
    /// </summary>
    public int PropertyNameHash { get; }
    
    /// <summary>
    /// The accessor type for fast typed getter/setter dispatch.
    /// </summary>
    public PropertyAccessorType AccessorType { get; }

    /// <summary>
    /// Compiled getter delegate for reading property values (boxed).
    /// Used by serialize (for reading values) and deserialize (for Populate/Merge to get existing references).
    /// </summary>
    protected readonly Func<object, object?> _dynamicGetter;

    protected PropertyMetadataBase(
        PropertyInfo prop,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType)
    {
        Name = prop.Name;
        // Single-pass UTF-8 encode via the string-overload — encode + exact-size byte[] allocation in
        // one BCL call. Faster than the two-pass (GetByteCount + Utf8.FromUtf16) AND faster than
        // worst-case-buffer + Utf8.FromUtf16 + trim/copy patterns: no ArrayPool rent overhead, no
        // extra copy, the BCL's internal encoder uses the known string length to size the output array
        // exactly without a separate counting pass.
        NameUtf8 = System.Text.Encoding.UTF8.GetBytes(prop.Name);
        DeclaringType = declaringType;
        PropertyType = prop.PropertyType;

        var underlying = Nullable.GetUnderlyingType(PropertyType);
        IsNullable = underlying != null;
        UnderlyingType = underlying ?? PropertyType;
        PropertyTypeCode = Type.GetTypeCode(UnderlyingType);

        // Pre-compute: is this a complex type that needs recursive handling?
        IsComplexType = !IsPrimitiveOrStringFast(PropertyType);

        PropertyNameHash = FnvHash.ComputeString(Name);

        AccessorType = DetermineAccessorType(PropertyType);

       _dynamicGetter = AcSerializerCommon.CreateCompiledGetter(declaringType, prop);
    }
    
    /// <summary>
    /// Gets the property value from the target object (boxed).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public object? GetValue(object obj) => _dynamicGetter(obj);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected static PropertyAccessorType DetermineAccessorType(Type propType)
    {
        var underlying = Nullable.GetUnderlyingType(propType);
        if (underlying != null)
            return PropertyAccessorType.Object;

        if (propType.IsEnum)
            return PropertyAccessorType.Enum;

        if (ReferenceEquals(propType, GuidType))
            return PropertyAccessorType.Guid;

        return Type.GetTypeCode(propType) switch
        {
            TypeCode.Int32 => PropertyAccessorType.Int32,
            TypeCode.Int64 => PropertyAccessorType.Int64,
            TypeCode.Boolean => PropertyAccessorType.Boolean,
            TypeCode.Double => PropertyAccessorType.Double,
            TypeCode.Single => PropertyAccessorType.Single,
            TypeCode.Decimal => PropertyAccessorType.Decimal,
            TypeCode.DateTime => PropertyAccessorType.DateTime,
            TypeCode.Byte => PropertyAccessorType.Byte,
            TypeCode.Int16 => PropertyAccessorType.Int16,
            TypeCode.UInt16 => PropertyAccessorType.UInt16,
            TypeCode.UInt32 => PropertyAccessorType.UInt32,
            TypeCode.UInt64 => PropertyAccessorType.UInt64,
            TypeCode.String => PropertyAccessorType.String,
            _ => PropertyAccessorType.Object
        };
    }
}
