using AyCode.Core.Helpers;
using AyCode.Core.Serializers;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using static AyCode.Core.Helpers.JsonUtilities;

namespace AyCode.Core.Serializers.Jsons;

public static partial class AcJsonDeserializer
{
    /// <summary>
    /// JSON deserialization type metadata.
    /// Extends DeserializeTypeMetadataBase which provides cached IId info.
    /// </summary>
    private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase
    {
        public FrozenDictionary<string, PropertySetterInfo> PropertySettersFrozen { get; }
        public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects)

        public JsonDeserializeTypeMetadata(
            [DynamicallyAccessedMembers(RequiredMembers)] Type type)
            : base(type, HasJsonIgnoreAttribute)
        {
            // Use pre-computed WritableProperties directly - no method call overhead!
            var props = WritableProperties;

            var propertySetters = new Dictionary<string, PropertySetterInfo>(props.Length, StringComparer.OrdinalIgnoreCase);
            var propsArray = new PropertySetterInfo[props.Length];
            var index = 0;

            foreach (var prop in props)
            {
                var propInfo = new PropertySetterInfo(prop, type);
                propertySetters[prop.Name] = propInfo;
                propsArray[index++] = propInfo;
            }

            PropertiesArray = propsArray;
            PropertySettersFrozen = propertySetters.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);

            // IId info (IsIId, IdType, IdGetter) is now cached in DeserializeTypeMetadataBase
        }

        /// <summary>
        /// Try to find property by UTF-8 name using ValueTextEquals (avoids string allocation).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryGetPropertyUtf8(ref Utf8JsonReader reader, out PropertySetterInfo? propInfo)
        {
            var props = PropertiesArray;
            for (var i = 0; i < props.Length; i++)
            {
                if (reader.ValueTextEquals(props[i].NameUtf8))
                {
                    propInfo = props[i];
                    return true;
                }
            }
            propInfo = null;
            return false;
        }
    }

    /// <summary>
    /// JSON deserialization property setter.
    /// </summary>
    private sealed class PropertySetterInfo : JsonPropertySetterBase
    {
        public PropertySetterInfo(PropertyInfo prop, Type declaringType) 
            : base(prop, declaringType)
        {
        }
        
        /// <summary>
        /// Read and set value directly from Utf8JsonReader, avoiding boxing for primitives.
        /// Returns true if value was set, false if it needs fallback to SetValue.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TrySetValueDirect(object target, ref Utf8JsonReader reader)
        {
            var tokenType = reader.TokenType;
            
            // Handle null
            if (tokenType == JsonTokenType.Null)
            {
                if (IsNullable || !PropertyType.IsValueType)
                {
                    SetValue(target, null);
                    return true;
                }
                return true; // Skip null for non-nullable value types
            }
            
            // Fast path for booleans
            if (tokenType == JsonTokenType.True)
            {
                TrySetBoolean(target, true);
                return true;
            }
            if (tokenType == JsonTokenType.False)
            {
                TrySetBoolean(target, false);
                return true;
            }
            
            // Fast path for numbers - try typed setters, TrySet returns false if type doesn't match
            if (tokenType == JsonTokenType.Number)
            {
                // Use TryGet to handle scientific notation
                if (reader.TryGetInt32(out var i32) && TrySetInt32(target, i32)) return true;
                if (reader.TryGetInt64(out var i64) && TrySetInt64(target, i64)) return true;
                if (TrySetDouble(target, reader.GetDouble())) return true;
                if (TrySetDecimal(target, reader.GetDecimal())) return true;
                if (TrySetSingle(target, reader.GetSingle())) return true;
                return false; // Fallback to boxed path
            }
            
            // Fast path for strings - check type BEFORE calling reader.Get*() to avoid FormatException
            if (tokenType == JsonTokenType.String)
            {
                if (ReferenceEquals(UnderlyingType, typeof(string)))
                {
                    SetValue(target, reader.GetString());
                    return true;
                }
                // Only call reader.GetDateTime() if property is DateTime type
                if (ReferenceEquals(UnderlyingType, typeof(DateTime)))
                {
                    TrySetDateTime(target, reader.GetDateTime());
                    return true;
                }
                // Only call reader.GetGuid() if property is Guid type
                if (ReferenceEquals(UnderlyingType, typeof(Guid)))
                {
                    TrySetGuid(target, reader.GetGuid());
                    return true;
                }
                return false; // Fallback to boxed path for other string-based types
            }
            
            return false; // Complex types need standard handling
        }
    }
}
