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

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// Binary-specific property accessor.
/// Inherits typed getters from PropertyAccessorBase.
/// Adds Binary-specific properties: PropertyIndex, IsStringInternProperty.
/// </summary>
public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase
{
    /// <summary>
    /// Deterministic property index based on alphabetical ordering of property names.
    /// This is computed once during metadata creation and is consistent across all platforms.
    /// Used for fast serialization without dictionary lookup.
    /// </summary>
    public int PropertyIndex { get; internal set; } = -1;

    /// <summary>
    /// Index into the PropertyTypeWrappers array on TypeMetadataWrapper.
    /// Only set for complex (non-primitive, non-string) properties. -1 for primitives and strings.
    /// Used to pre-cache TypeMetadataWrapper per property type, eliminating GetWrapper dictionary lookups.
    /// </summary>
    public int ComplexPropertyIndex { get; internal set; } = -1;
    public bool IsStringCollectionProperty { get; }

    /// <summary>
    /// True when declared property type is System.Object.
    /// Used to trigger ObjectWithTypeName marker (68) so the deserializer
    /// can resolve the concrete runtime type.
    /// </summary>
    public bool IsObjectDeclaredType { get; }

    /// <summary>
    /// True when declared property type is a non-sealed reference type (not string).
    /// Polymorphism is possible: runtime type may differ from declared type.
    /// Covers object, interface, abstract, and non-sealed class properties.
    /// When true, serializer checks GetType() != PropertyType and writes polymorphic prefix.
    /// When false (sealed, value type, string): 0 overhead, no check needed.
    /// </summary>
    public bool IsPolymorphicCandidate { get; }

    /// <summary>
    /// Cached [AcStringIntern] attribute value for this property.
    /// null = no attribute (follow global StringInterningMode)
    /// true = [AcStringIntern(true)] � force intern
    /// false = [AcStringIntern(false)] � force skip
    /// </summary>
    private readonly byte _interningFlags;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool UseStringPropertyInterning(int internBit)
    {
        return (_interningFlags & internBit) != 0;
    }
    /// <summary>
    /// Object getter for property filter context.
    /// </summary>
    public Func<object, object?> DynamicGetter => _dynamicGetter;

    /// <summary>
    /// Pre-computed expected type code for markerless serialization (UseMetadata=false).
    /// When set, the serializer skips writing the type marker byte and the deserializer
    /// uses this value instead of reading from the stream.
    /// null = marker must be written/read (bool, enum, string, object, collection, nullable value types).
    /// Non-null = markerless (non-nullable value types: int, long, double, float, decimal, Guid, DateTime, byte, short, ushort, uint, ulong).
    /// </summary>
    public byte? ExpectedTypeCode { get; }

    protected BinaryPropertyAccessorBase(
        PropertyInfo prop,
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type declaringType,
        bool enableInternString)
        : base(prop, declaringType)
    {
        IsStringCollectionProperty = IsStringCollection(prop.PropertyType);
        IsObjectDeclaredType = prop.PropertyType == typeof(object);
        IsPolymorphicCandidate = !prop.PropertyType.IsSealed
            && !prop.PropertyType.IsValueType
            && prop.PropertyType != typeof(string);

        // All typed getters are initialized in PropertyAccessorBase
        if (enableInternString && (AccessorType == PropertyAccessorType.String || IsStringCollectionProperty))
        {
            // Cache [AcStringIntern] attribute (inherit: true to check base class properties)
            var internAttr = prop.GetCustomAttribute<AcStringInternAttribute>(inherit: true);

            var stringInternAttributeValue = internAttr?.Enabled;

            byte flags = 0;
            if (stringInternAttributeValue == true) flags |= (1 << (int)StringInterningMode.Attribute);
            if (stringInternAttributeValue != false) flags |= (1 << (int)StringInterningMode.All);
            _interningFlags = flags;
        }

        ExpectedTypeCode = ComputeExpectedTypeCode(AccessorType);
    }

    /// <summary>
    /// Checks if the property type is a collection with string elements (string[], List&lt;string&gt;, etc.).
    /// </summary>
    private static bool IsStringCollection(Type propertyType)
    {
        if (propertyType.IsArray)
            return propertyType.GetElementType() == typeof(string);

        if (propertyType.IsGenericType)
        {
            var args = propertyType.GetGenericArguments();
            return args.Length == 1 && args[0] == typeof(string);
        }

        return false;
    }

    /// <summary>
    /// Maps AccessorType to the BinaryTypeCode that would normally be written as marker.
    /// Returns null for types that always need a stream marker (string, object/nullable).
    /// </summary>
    private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch
    {
        PropertyAccessorType.Int32 => BinaryTypeCode.Int32,
        PropertyAccessorType.Int64 => BinaryTypeCode.Int64,
        PropertyAccessorType.Double => BinaryTypeCode.Float64,
        PropertyAccessorType.Single => BinaryTypeCode.Float32,
        PropertyAccessorType.Decimal => BinaryTypeCode.Decimal,
        PropertyAccessorType.DateTime => BinaryTypeCode.DateTime,
        PropertyAccessorType.Guid => BinaryTypeCode.Guid,
        PropertyAccessorType.Byte => BinaryTypeCode.UInt8,
        PropertyAccessorType.Int16 => BinaryTypeCode.Int16,
        PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16,
        PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32,
        PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64,
        PropertyAccessorType.Boolean => BinaryTypeCode.True,
        PropertyAccessorType.Enum => BinaryTypeCode.Enum,
        _ => null // String, Object � always write marker to stream
    };
}
