using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

namespace AyCode.Core.Serializers.Jsons;

public static partial class AcJsonDeserializer
{
    private static class JsonDeserializationContextPool
    {
        private static readonly ConcurrentQueue<DeserializationContext> Pool = new();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static DeserializationContext Get(in AcJsonSerializerOptions options)
        {
            if (Pool.TryDequeue(out var context))
            {
                context.Reset(options);
                return context;
            }
            return new DeserializationContext(options);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Return(DeserializationContext context, in AcJsonSerializerOptions options)
        {
            if (Pool.Count < options.MaxContextPoolSize)
            {
                context.Clear(options);
                Pool.Enqueue(context);
            }
        }
    }

    private sealed class DeferredReference(int refId, Type targetType)
    {
        public int RefId { get; } = refId;
        public Type TargetType { get; } = targetType;
    }

    private readonly struct PropertyToResolve(object target, PropertySetterInfo property, int refId)
    {
        public readonly object Target = target;
        public readonly PropertySetterInfo Property = property;
        public readonly int RefId = refId;
    }

    private sealed class DeserializationContext : AcSerializerContextBase<JsonDeserializeTypeMetadata, AcJsonSerializerOptions>
    {
        // Use shared reference tracker from AcSerializerCommon
        private readonly AcSerializerCommon.DeserializationReferenceTracker _refTracker = new();
        private List<PropertyToResolve>? _propertiesToResolve;
        
        public bool IsMergeMode { get; set; }
        
        /// <summary>
        /// Chain reference tracker for maintaining object identity across chain operations.
        /// Only set when in chain mode (CreateDeserializeChain).
        /// </summary>
        public AcSerializerCommon.ChainReferenceTracker? ChainTracker { get; set; }
        
        /// <summary>
        /// Returns true if in chain mode (ChainTracker is set).
        /// </summary>
        public bool IsChainMode => ChainTracker != null;

        /// <summary>
        /// Factory for creating JsonDeserializeTypeMetadata instances.
        /// </summary>
        protected override MetadataFactoryDelegate MetadataFactory
            => static ([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] t)
                => new JsonDeserializeTypeMetadata(t);

        public DeserializationContext(in AcJsonSerializerOptions options)
        {
            Reset(options);
        }

        public override void Reset(AcJsonSerializerOptions options)
        {
            // IMPORTANT: base.Reset sets Options first
            base.Reset(options);
            
            IsMergeMode = false;
            ChainTracker = null;
            _refTracker.Reset();
        }

        public void Clear(AcJsonSerializerOptions options)
        {
            _refTracker.Reset();
            _propertiesToResolve?.Clear();
            ChainTracker = null;

            Reset(options);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void RegisterObject(int id, object obj)
        {
            if (ReferenceHandling == ReferenceHandlingMode.None) return;
            _refTracker.RegisterObject(id, obj);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryGetReferencedObject(int id, out object? obj)
            => _refTracker.TryGetReferencedObject(id, out obj);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void AddPropertyToResolve(object target, PropertySetterInfo property, int refId)
        {
            _propertiesToResolve ??= new List<PropertyToResolve>(4);
            _propertiesToResolve.Add(new PropertyToResolve(target, property, refId));
        }

        public void ResolveReferences()
        {
            if (_propertiesToResolve == null) return;
            
            foreach (ref readonly var ptr in System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_propertiesToResolve))
            {
                if (_refTracker.TryGetReferencedObject(ptr.RefId, out var refObj))
                    ptr.Property.SetValue(ptr.Target, refObj);
            }
        }
    }
}
