using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;

namespace AyCode.Core.Serializers.Binaries;

public static partial class AcBinaryDeserializer
{
    /// <summary>
    /// Pool for BinaryDeserializationContext instances.
    /// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
    /// </summary>
    private static class DeserializationContextPool<TInput> where TInput : struct, IBinaryInputBase
    {
        private static readonly ConcurrentQueue<BinaryDeserializationContext<TInput>> Pool = new();

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static BinaryDeserializationContext<TInput> Get(AcBinarySerializerOptions options)
        {
            if (Pool.TryDequeue(out var ctx))
            {
                ctx.Reset(options);
                return ctx;
            }

            var newCtx = new BinaryDeserializationContext<TInput>();
            newCtx.Reset(options);
            return newCtx;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Return(BinaryDeserializationContext<TInput> ctx)
        {
            if (Pool.Count < ctx.Options.MaxContextPoolSize)
            {
                ctx.Clear();
                Pool.Enqueue(ctx);
            }
        }
    }

    /// <summary>
    /// Binary deserialization context. Sealed class for pool reuse.
    /// Holds all state: buffer, position, caches, options, metadata, interning.
    /// Buffer state and read methods are directly in the context (via partial Read.cs)
    /// for zero-indirection hot-path access — mirrors the serializer pattern.
    /// TInput handles buffer lifecycle (Initialize/AdvanceSegment) — mirrors BinarySerializationContext&lt;TOutput&gt;.
    /// </summary>
    internal sealed partial class BinaryDeserializationContext<TInput>
        : AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
        where TInput : struct, IBinaryInputBase
    {
        /// <summary>
        /// Input target — handles only Initialize (startup) and AdvanceSegment (cold path).
        /// </summary>
        public TInput Input;

        // Marker-based interning: sequential cache (no footer needed)
        private object?[]? _internCache;

        public bool HasMetadata;
        public bool IsMergeMode;
        public bool RemoveOrphanedItems;
        public bool FastWire;

        // Options-derived properties
        public byte MinStringInternLength;

        /// <summary>
        /// Chain reference tracker for maintaining object identity across chain operations.
        /// Only set when in chain mode (CreateDeserializeChain).
        /// </summary>
        public AcSerializerCommon.ChainReferenceTracker? ChainTracker;

        /// <summary>
        /// Returns true if in chain mode (ChainTracker is set).
        /// </summary>
        public bool IsChainMode => ChainTracker != null;

        // Pooled arrays - reused across deserializations, zero steady-state allocation
        private int[]? _pooledDupData;
        private object?[]? _pooledInternCache;
        private int _pooledDupDataLength;
        private int _pooledInternCacheLength;
        private int _lastInternCacheUsed;      // how many slots were used (for targeted Clear)

        // Small arrays: keep across calls. Large arrays: return to pool in Clear().
        private const int SmallArrayThreshold = 256;

        // String cache - for WASM optimization
        private Dictionary<int, string>? _stringCache;

        // Intern cache index counter
        private int _nextCacheIndex;

        // Removed: _linearizedBuffer was used by InitFromSpan (eliminated — all paths now use byte[] directly)

        /// <summary>
        /// Inline metadata entries flat array.
        /// </summary>
        private MetadataEntry[]? _metadataEntries;
        private int _metadataEntryCount;

        /// <summary>
        /// Runtime polymorphic slot counter. Indices 0..RuntimeSlotCount-1 stored in _wrapperSlots.
        /// Overflow (index >= RuntimeSlotCount) stored in _polyOverflow.
        /// </summary>
        internal int _nextRuntimeSlot;

        /// <summary>
        /// Overflow array for polymorphic types beyond RuntimeSlotCount.
        /// Only allocated when >RuntimeSlotCount distinct poly types (very rare).
        /// Indexed by (polyIndex - RuntimeSlotCount).
        /// </summary>
        private TypeMetadataWrapper<BinaryDeserializeTypeMetadata>[]? _polyOverflow;

        /// <summary>
        /// A metadata entry for the deserializer.
        /// </summary>
        internal struct MetadataEntry
        {
            public int PropNameHash;
            public int[] PropertyHashes;
        }

        /// <summary>
        /// Factory for creating BinaryDeserializeTypeMetadata instances.
        /// </summary>
        /// <remarks>
        /// The lambda's <c>t</c> parameter explicitly declares DAMs to match the
        /// <see cref="MetadataFactoryDelegate"/> signature — DAMs do not auto-propagate from the
        /// delegate type to lambda parameters.
        /// </remarks>
        protected override MetadataFactoryDelegate MetadataFactory
            => static ([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] t)
                => new BinaryDeserializeTypeMetadata(t);

        public BinaryDeserializationContext()
        {
            InitializeWrapperSlots(Volatile.Read(ref AcBinarySerializer.s_nextWrapperSlot));
        }

        /// <summary>
        /// Initializes the context with the given TInput.
        /// Calls Input.Initialize to set up buffer/position/length.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void InitInput(TInput input)
        {
            Input = input;
            Input.Initialize(out _buffer, out _position, out _bufferLength);

            _useStringCaching = Options.UseStringCaching;
            _maxCachedStringLength = Options.MaxCachedStringLength;

            if (_useStringCaching) GetOrCreateStringCache();

            _internCache = null;
            HasMetadata = false;
            IsMergeMode = false;
            RemoveOrphanedItems = false;

            FastWire = Options.WireMode == WireMode.Fast;
            MinStringInternLength = Options.MinStringInternLength;

            ChainTracker = null;
        }

        // Removed: InitFromSpan — all deserialization now goes through byte[] directly (zero-copy).

        #region Header

        public void ReadHeader()
        {
            if (_position == 0 && IsAtEnd)
            {
                throw new AcBinaryDeserializationException("Binary payload is too short to contain a header.");
            }

            var version = ReadByte();
            if (version != AcBinarySerializerOptions.FormatVersion)
            {
                throw new AcBinaryDeserializationException(
                    $"Unsupported binary format version '{version}'. Expected '{AcBinarySerializerOptions.FormatVersion}'.",
                    _position - 1);
            }

            var marker = ReadByte();
            var hasPropertyTable = false;

            if (marker == BinaryTypeCode.MetadataHeader)
            {
                hasPropertyTable = true;
                Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
            }
            else if (marker == BinaryTypeCode.NoMetadataHeader)
            {
                Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
            }
            else if ((marker & 0xF0) == BinaryTypeCode.HeaderFlagsBase)
            {
                var flags = (byte)(marker & 0x0F);
                hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
                var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
                var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
                Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
                    : hasOnlyId ? ReferenceHandlingMode.OnlyId
                    : ReferenceHandlingMode.None;

                var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
                if (hasCacheCount)
                {
                    var cacheCount = (int)ReadVarUInt();
                    if (cacheCount > 0)
                    {
                        _internCache = RentInternCache(cacheCount);
                        SetInternCacheUsed(cacheCount);
                    }
                }
            }
            else
            {
                throw new AcBinaryDeserializationException(
                    $"Unsupported binary header marker '{marker}'.",
                    _position - 1);
            }

            HasMetadata = hasPropertyTable;
        }

        #endregion

        #region String Interning

        /// <summary>
        /// Next intern cache index to assign when registering interned values.
        /// </summary>
        internal ref int NextCacheIndexRef
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => ref _nextCacheIndex;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void RegisterNextInternedValue(object value)
        {
            _internCache![_nextCacheIndex++] = value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void RegisterInternedValueAt(int cacheIndex, object value)
        {
            _internCache![cacheIndex] = value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string GetInternedString(int cacheIndex)
        {
            var result = _internCache![cacheIndex];

            #if DEBUG
            if (result == null)
                throw new AcBinaryDeserializationException($"Interned string at cache index '{cacheIndex}' was not populated.", _position);
            #endif

            return (string)result!;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public object GetInternedObject(int cacheIndex)
        {
            var result = _internCache![cacheIndex];
            if (result == null)
            {
                throw new AcBinaryDeserializationException(
                    $"Interned object at cache index '{cacheIndex}' was not populated.",
                    _position);
            }

            return result;
        }

        #endregion

        #region Pooled Arrays & Caches (merged from ContextClass)

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Dictionary<int, string> GetOrCreateStringCache()
        {
            return _stringCache ??= new Dictionary<int, string>(128);
        }

        // Removed: RentLinearizedBuffer — was only used by InitFromSpan (eliminated).

        public int[] RentDupData(int minLength)
        {
            if (_pooledDupData != null && _pooledDupDataLength >= minLength)
                return _pooledDupData;

            if (_pooledDupData != null)
                ArrayPool<int>.Shared.Return(_pooledDupData);

            _pooledDupData = ArrayPool<int>.Shared.Rent(minLength);
            _pooledDupDataLength = _pooledDupData.Length;
            return _pooledDupData;
        }

        public object?[] RentInternCache(int minLength)
        {
            if (_pooledInternCache != null && _pooledInternCacheLength >= minLength)
            {
                if (_lastInternCacheUsed > 0)
                {
                    Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
                    _lastInternCacheUsed = 0;
                }
                return _pooledInternCache;
            }

            if (_pooledInternCache != null)
                ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);

            _pooledInternCache = ArrayPool<object?>.Shared.Rent(minLength);
            _pooledInternCacheLength = _pooledInternCache.Length;
            _lastInternCacheUsed = 0;
            return _pooledInternCache;
        }

        public void SetInternCacheUsed(int count)
        {
            _lastInternCacheUsed = count;
        }

        #endregion

        #region Inline Metadata

        public void RegisterInlineMetadata(int propNameHash, int[] propertyHashes)
        {
            if (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length)
            {
                var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8);
                var newArray = new MetadataEntry[newSize];
                if (_metadataEntries != null)
                    Array.Copy(_metadataEntries, newArray, _metadataEntryCount);
                _metadataEntries = newArray;
            }

            _metadataEntries[_metadataEntryCount++] = new MetadataEntry
            {
                PropNameHash = propNameHash,
                PropertyHashes = propertyHashes
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int[]? FindSourceHashes(int propNameHash)
        {
            for (var i = 0; i < _metadataEntryCount; i++)
            {
                if (_metadataEntries![i].PropNameHash == propNameHash)
                    return _metadataEntries[i].PropertyHashes;
            }
            return null;
        }

        #endregion

        #region Polymorphic Wrapper Cache

        /// <summary>
        /// Registers a wrapper in the polymorphic cache (called by ReadObjectWithTypeName/RefFirst).
        /// Indices 0..RuntimeSlotCount-1 → _wrapperSlots (fast path, ~1-2ns).
        /// Indices RuntimeSlotCount+ → _polyOverflow (still O(1) array access).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void RegisterPolymorphicWrapper(TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper)
        {
            var slot = _nextRuntimeSlot++;
            if (slot < AcBinarySerializer.RuntimeSlotCount)
            {
                SetWrapperSlot(slot, wrapper);
            }
            else
            {
                RegisterPolymorphicWrapperOverflow(wrapper, slot - AcBinarySerializer.RuntimeSlotCount);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void RegisterPolymorphicWrapperOverflow(TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int overflowIndex)
        {
            if (_polyOverflow == null || overflowIndex >= _polyOverflow.Length)
            {
                var newSize = _polyOverflow == null ? 4 : _polyOverflow.Length * 2;
                var newArray = new TypeMetadataWrapper<BinaryDeserializeTypeMetadata>[newSize];
                if (_polyOverflow != null)
                    Array.Copy(_polyOverflow, newArray, overflowIndex);
                _polyOverflow = newArray;
            }
            _polyOverflow[overflowIndex] = wrapper;
        }

        /// <summary>
        /// Gets a previously registered polymorphic wrapper by index (called by ReadObjectWithTypeIndex/RefFirst).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal TypeMetadataWrapper<BinaryDeserializeTypeMetadata> GetPolymorphicWrapper(int index)
        {
            if (index < AcBinarySerializer.RuntimeSlotCount)
                return GetWrapperSlot(index)!;
            return _polyOverflow![index - AcBinarySerializer.RuntimeSlotCount]!;
        }

        #endregion

        #region Reset & Clear

        public override void Reset(AcBinarySerializerOptions options)
        {
            base.Reset(options);
        }

        public override void Clear()
        {
            base.Clear();

            _metadataEntryCount = 0;
            _nextCacheIndex = 0;

            // Clear runtime FixObj slots to prevent stale wrapper reuse on pool return.
            // Slot-to-type mapping changes between sessions (slot 0 may be TestOrder in session A
            // but TestGenericAttribute in session B). Without clearing, ReadObjectFromSlot
            // would reuse the stale wrapper → wrong type → stream misalignment.
            if (_nextRuntimeSlot > 0)
            {
                ClearWrapperSlots(Math.Min(_nextRuntimeSlot, AcBinarySerializer.RuntimeSlotCount));
            }

            _nextRuntimeSlot = 0;

            // String cache: clear content but keep dictionary allocated for reuse
            _stringCache?.Clear();

            // Intern cache: clear GC roots, return large arrays to pool
            if (_pooledInternCache != null)
            {
                if (_pooledInternCacheLength > SmallArrayThreshold)
                {
                    ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
                    _pooledInternCache = null;
                    _pooledInternCacheLength = 0;
                }
                else if (_lastInternCacheUsed > 0)
                {
                    Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
                }
                _lastInternCacheUsed = 0;
            }

            // Dup data: no GC roots (int[]), return large arrays to pool
            if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold)
            {
                ArrayPool<int>.Shared.Return(_pooledDupData);
                _pooledDupData = null;
                _pooledDupDataLength = 0;
            }

            // Removed: _linearizedBuffer cleanup — field eliminated with InitFromSpan.
        }

        #endregion
    }
}
