using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers;

/// <summary>
/// Base class for property setters used by deserializers.
/// Derives from PropertyMetadataBase (not PropertyAccessorBase) to avoid unnecessary typed getter delegates.
/// Contains setter functionality and typed setter delegates.
/// Inherits GetValue() from PropertyMetadataBase for Populate/Merge operations.
/// </summary>
public abstract class PropertySetterBase : PropertyMetadataBase
{
    /// <summary>
    /// Compiled setter delegate for writing property values (boxed).
    /// </summary>
    protected readonly Action<object, object?> _setter;
    
    /// <summary>
    /// Whether this property is a collection of IId elements.
    /// </summary>
    public bool IsIIdCollection { get; }
    
    /// <summary>
    /// Element type if this property is a collection, null otherwise.
    /// </summary>
    public Type? ElementType { get; }
    
    /// <summary>
    /// The Id type of collection elements (if IsIIdCollection is true).
    /// </summary>
    public Type? ElementIdType { get; }
    
    /// <summary>
    /// Compiled getter for the Id property of collection elements.
    /// </summary>
    public Func<object, object?>? ElementIdGetter { get; }

    #region Strongly-typed setter delegate fields (eliminates runtime cast for deserialize)
    
    // Only ONE of these is set based on AccessorType
    private readonly Action<object, int>? _int32Setter;
    private readonly Action<object, long>? _int64Setter;
    private readonly Action<object, bool>? _boolSetter;
    private readonly Action<object, double>? _doubleSetter;
    private readonly Action<object, float>? _floatSetter;
    private readonly Action<object, decimal>? _decimalSetter;
    private readonly Action<object, DateTime>? _dateTimeSetter;
    private readonly Action<object, byte>? _byteSetter;
    private readonly Action<object, short>? _int16Setter;
    private readonly Action<object, ushort>? _uint16Setter;
    private readonly Action<object, uint>? _uint32Setter;
    private readonly Action<object, ulong>? _uint64Setter;
    private readonly Action<object, Guid>? _guidSetter;
    
    #endregion

    protected PropertySetterBase(
        PropertyInfo prop,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType)
        : base(prop, declaringType)
    {
        _setter = AcSerializerCommon.CreateCompiledSetter(declaringType, prop);

        // Initialize typed setter
        InitializeTypedSetter(declaringType, prop);

        // Determine collection element type. ElementType is derived from PropertyType via reflection
        // (generic argument extraction) — the trimmer cannot statically prove the DAMs requirement on
        // CreateCompiledGetter below. The element type's PublicProperties metadata is preserved
        // transitively via the consumer's [DynamicallyAccessedMembers(PublicProperties)] on the root
        // type T (Deserialize<T>): when T is preserved with PublicProperties, the trimmer keeps each
        // property's PropertyType reference, and for collection-typed properties the generic argument
        // (T's element type) is reachable. The actual ctor + property metadata of element types must
        // be rooted by the consumer or via [AcBinarySerializable] on the element types.
        ElementType = GetCollectionElementType(PropertyType);

        var isCollection = ElementType != null &&
                          ElementType != typeof(object) &&
                          typeof(IEnumerable).IsAssignableFrom(PropertyType) &&
                          !ReferenceEquals(PropertyType, StringType);

        if (isCollection && ElementType != null)
        {
            var idInfo = GetIdInfo(ElementType);
            if (idInfo.IsId)
            {
                IsIIdCollection = true;
                ElementIdType = idInfo.IdType;
                ElementIdGetter = TryCreateElementIdGetter(ElementType);
            }
        }
    }

    /// <summary>
    /// Wrapper that hides the ElementType DAMs requirement from the trimmer at this call site —
    /// the element type's reflection metadata is preserved transitively via the root type's
    /// PublicProperties annotation (see ctor comment). Suppression is intentional and bounded.
    /// </summary>
    [UnconditionalSuppressMessage("Trimming", "IL2072",
        Justification = "ElementType is derived from a [DynamicallyAccessedMembers(PublicProperties)] root via "
                      + "GetCollectionElementType(PropertyType). When the consumer's root type T is properly "
                      + "annotated, the element type's properties survive trimming via the property-type chain.")]
    [UnconditionalSuppressMessage("Trimming", "IL2075",
        Justification = "Same as IL2072 — ElementType.GetProperty(\"Id\") relies on the root-type DAMs chain.")]
    private static Func<object, object?>? TryCreateElementIdGetter(Type elementType)
    {
        var idProp = elementType.GetProperty("Id");
        return idProp != null
            ? AcSerializerCommon.CreateCompiledGetter(elementType, idProp)
            : null;
    }

    private void InitializeTypedSetter(
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType,
        PropertyInfo prop)
    {
        switch (AccessorType)
        {
            case PropertyAccessorType.Int32:
                Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateTypedSetter<int>(declaringType, prop);
                break;
            case PropertyAccessorType.Int64:
                Unsafe.AsRef(in _int64Setter) = AcSerializerCommon.CreateTypedSetter<long>(declaringType, prop);
                break;
            case PropertyAccessorType.Boolean:
                Unsafe.AsRef(in _boolSetter) = AcSerializerCommon.CreateTypedSetter<bool>(declaringType, prop);
                break;
            case PropertyAccessorType.Double:
                Unsafe.AsRef(in _doubleSetter) = AcSerializerCommon.CreateTypedSetter<double>(declaringType, prop);
                break;
            case PropertyAccessorType.Single:
                Unsafe.AsRef(in _floatSetter) = AcSerializerCommon.CreateTypedSetter<float>(declaringType, prop);
                break;
            case PropertyAccessorType.Decimal:
                Unsafe.AsRef(in _decimalSetter) = AcSerializerCommon.CreateTypedSetter<decimal>(declaringType, prop);
                break;
            case PropertyAccessorType.DateTime:
                Unsafe.AsRef(in _dateTimeSetter) = AcSerializerCommon.CreateTypedSetter<DateTime>(declaringType, prop);
                break;
            case PropertyAccessorType.Byte:
                Unsafe.AsRef(in _byteSetter) = AcSerializerCommon.CreateTypedSetter<byte>(declaringType, prop);
                break;
            case PropertyAccessorType.Int16:
                Unsafe.AsRef(in _int16Setter) = AcSerializerCommon.CreateTypedSetter<short>(declaringType, prop);
                break;
            case PropertyAccessorType.UInt16:
                Unsafe.AsRef(in _uint16Setter) = AcSerializerCommon.CreateTypedSetter<ushort>(declaringType, prop);
                break;
            case PropertyAccessorType.UInt32:
                Unsafe.AsRef(in _uint32Setter) = AcSerializerCommon.CreateTypedSetter<uint>(declaringType, prop);
                break;
            case PropertyAccessorType.UInt64:
                Unsafe.AsRef(in _uint64Setter) = AcSerializerCommon.CreateTypedSetter<ulong>(declaringType, prop);
                break;
            case PropertyAccessorType.Guid:
                Unsafe.AsRef(in _guidSetter) = AcSerializerCommon.CreateTypedSetter<Guid>(declaringType, prop);
                break;
            case PropertyAccessorType.Enum:
                Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateEnumSetter(declaringType, prop);
                break;
        }
    }

    #region Typed Setters - Direct invocation, no cast!

    /// <summary>
    /// Sets the property value on the target object (boxed).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public virtual void SetValue(object target, object? value) => _setter(target, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetInt32(object obj, int value) => _int32Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetInt64(object obj, long value) => _int64Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetBoolean(object obj, bool value) => _boolSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetDouble(object obj, double value) => _doubleSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetSingle(object obj, float value) => _floatSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetDecimal(object obj, decimal value) => _decimalSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetDateTime(object obj, DateTime value) => _dateTimeSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetByte(object obj, byte value) => _byteSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetInt16(object obj, short value) => _int16Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetUInt16(object obj, ushort value) => _uint16Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetUInt32(object obj, uint value) => _uint32Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetUInt64(object obj, ulong value) => _uint64Setter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetGuid(object obj, Guid value) => _guidSetter!(obj, value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetEnumAsInt32(object obj, int value) => _int32Setter!(obj, value);

    #endregion

    #region Dispatch helpers - centralized switch for already-boxed values

    /// <summary>
    /// Sets property from already-boxed value using typed setter for unboxing optimization.
    /// Use when value is already boxed (e.g., from ReadValue).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetValueTyped(object target, object? value)
    {
        if (value == null)
        {
            SetValue(target, null);
            return;
        }

        switch (AccessorType)
        {
            case PropertyAccessorType.Int32:
                SetInt32(target, (int)value);
                return;
            case PropertyAccessorType.Int64:
                SetInt64(target, (long)value);
                return;
            case PropertyAccessorType.Boolean:
                SetBoolean(target, (bool)value);
                return;
            case PropertyAccessorType.Double:
                SetDouble(target, (double)value);
                return;
            case PropertyAccessorType.Single:
                SetSingle(target, (float)value);
                return;
            case PropertyAccessorType.Decimal:
                SetDecimal(target, (decimal)value);
                return;
            case PropertyAccessorType.DateTime:
                SetDateTime(target, (DateTime)value);
                return;
            case PropertyAccessorType.Byte:
                SetByte(target, (byte)value);
                return;
            case PropertyAccessorType.Int16:
                SetInt16(target, (short)value);
                return;
            case PropertyAccessorType.UInt16:
                SetUInt16(target, (ushort)value);
                return;
            case PropertyAccessorType.UInt32:
                SetUInt32(target, (uint)value);
                return;
            case PropertyAccessorType.UInt64:
                SetUInt64(target, (ulong)value);
                return;
            case PropertyAccessorType.Guid:
                SetGuid(target, (Guid)value);
                return;
            case PropertyAccessorType.Enum:
                SetEnumAsInt32(target, (int)value);
                return;
            default:
                SetValue(target, value);
                return;
        }
    }

    /// <summary>
    /// Sets property to its type's default value using typed setter.
    /// Avoids boxing for value types.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetToDefault(object target)
    {
        switch (AccessorType)
        {
            case PropertyAccessorType.Int32:
                SetInt32(target, 0);
                return;
            case PropertyAccessorType.Int64:
                SetInt64(target, 0L);
                return;
            case PropertyAccessorType.Boolean:
                SetBoolean(target, false);
                return;
            case PropertyAccessorType.Double:
                SetDouble(target, 0.0);
                return;
            case PropertyAccessorType.Single:
                SetSingle(target, 0f);
                return;
            case PropertyAccessorType.Decimal:
                SetDecimal(target, 0m);
                return;
            case PropertyAccessorType.DateTime:
                SetDateTime(target, default);
                return;
            case PropertyAccessorType.Byte:
                SetByte(target, 0);
                return;
            case PropertyAccessorType.Int16:
                SetInt16(target, 0);
                return;
            case PropertyAccessorType.UInt16:
                SetUInt16(target, 0);
                return;
            case PropertyAccessorType.UInt32:
                SetUInt32(target, 0);
                return;
            case PropertyAccessorType.UInt64:
                SetUInt64(target, 0);
                return;
            case PropertyAccessorType.Guid:
                SetGuid(target, Guid.Empty);
                return;
            case PropertyAccessorType.Enum:
                SetEnumAsInt32(target, 0);
                return;
            default:
                SetValue(target, null);
                return;
        }
    }

    #endregion
}
