using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Text;

namespace AyCode.Core.Serializers.Binaries;

public static partial class AcBinaryDeserializer
{
    internal sealed partial class BinaryDeserializationContext<TInput>
    {
        private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

        #region Buffer State � owned by context for zero virtual dispatch

        internal byte[] _buffer = null!;
        internal int _bufferLength;
        internal int _position;

        #endregion

        // String caching state � needed for WASM optimization
        // The cache dictionary is owned by context (pooled), passed in at init time.
        private bool _useStringCaching;
        private int _maxCachedStringLength;

        public bool IsAtEnd
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                if (_position < _bufferLength) return false;
                // Multi-segment: try advancing to next segment before declaring end.
                // ArrayBinaryInput: TryAdvanceSegment => false (JIT eliminates, same as before).
                return !Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, 1);
            }
        }

        public int Position
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => _position;
        }

        #region Core Read Methods

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte ReadByte()
        {
            if (_position >= _bufferLength)
            {
                // Multi-segment: try advancing to next segment before giving up.
                // ArrayBinaryInput: TryAdvanceSegment => false (JIT eliminates this branch).
                if (!Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, 1))
                    throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
            }

            return _buffer[_position++];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte PeekByte()
        {
            if (_position >= _bufferLength)
            {
                // Multi-segment: try advancing to next segment before giving up.
                if (!Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, 1))
                    throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
            }

            return _buffer[_position];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Skip(int count)
        {
            EnsureAvailable(count);
            _position += count;
        }

        #endregion

        #region Fixed-Width Reads

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public short ReadInt16Unsafe()
        {
            EnsureAvailable(2);
            var value = Unsafe.ReadUnaligned<short>(ref _buffer[_position]);
            _position += 2;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ushort ReadUInt16Unsafe()
        {
            EnsureAvailable(2);
            var value = Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
            _position += 2;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public char ReadCharUnsafe()
        {
            EnsureAvailable(2);
            var value = (char)Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
            _position += 2;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float ReadSingleUnsafe()
        {
            EnsureAvailable(4);
            var bits = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
            _position += 4;
            return BitConverter.Int32BitsToSingle(bits);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public double ReadDoubleUnsafe()
        {
            EnsureAvailable(8);
            var bits = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
            _position += 8;
            return BitConverter.Int64BitsToDouble(bits);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public decimal ReadDecimalUnsafe()
        {
            EnsureAvailable(16);
            var span = _buffer.AsSpan(_position, 16);
            var ints = MemoryMarshal.Cast<byte, int>(span);
            var lo = ints[0];
            var mid = ints[1];
            var hi = ints[2];
            var flags = ints[3];
            var isNegative = (flags & unchecked((int)0x80000000)) != 0;
            var scale = (byte)((flags >> 16) & 0x7F);
            LogDecimalDrift(scale);
            _position += 16;
            return new decimal(lo, mid, hi, isNegative, scale);
        }

        [Conditional("DEBUG")]
        private void LogDecimalDrift(byte scale)
        {
            if (scale <= 28) return;
            var hex = BitConverter.ToString(_buffer, _position, Math.Min(16, _bufferLength - _position));
            throw new AcBinaryDeserializationException(
                $"[DECIMAL_DRIFT] scale={scale}, pos={_position}, bufLen={_bufferLength}, " +
                $"bufArray={_buffer.Length}, hex={hex}", _position);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public DateTime ReadDateTimeUnsafe()
        {
            EnsureAvailable(9);
            var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
            var kind = (DateTimeKind)_buffer[_position + 8];
            _position += 9;
            return new DateTime(ticks, kind);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public DateTimeOffset ReadDateTimeOffsetUnsafe()
        {
            EnsureAvailable(10);
            var utcTicks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
            var offsetMinutes = Unsafe.ReadUnaligned<short>(ref _buffer[_position + 8]);
            _position += 10;
            var utcValue = new DateTime(utcTicks, DateTimeKind.Utc);
            return new DateTimeOffset(utcValue).ToOffset(TimeSpan.FromMinutes(offsetMinutes));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TimeSpan ReadTimeSpanUnsafe()
        {
            EnsureAvailable(8);
            var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
            _position += 8;
            return new TimeSpan(ticks);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Guid ReadGuidUnsafe()
        {
            EnsureAvailable(16);
            var value = new Guid(_buffer.AsSpan(_position, 16));
            _position += 16;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public T ReadRaw<T>() where T : unmanaged
        {
            var size = Unsafe.SizeOf<T>();
            EnsureAvailable(size);
            var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
            _position += size;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int ReadInt32Raw()
        {
            EnsureAvailable(4);
            var value = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
            _position += 4;
            return value;
        }

        #endregion

        #region VarInt Reading

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int ReadVarInt()
        {
            //if (FastWire) { return ReadRaw<int>(); }

            var raw = ReadVarUInt();
            var value = (int)(raw >> 1) ^ -(int)(raw & 1);
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public uint ReadVarUInt()
        {
            //if (FastWire) { return ReadRaw<uint>(); }

            // Multi-segment safety: ensure at least 1 byte before direct buffer access.
            // ArrayBinaryInput: TryAdvanceSegment => false (JIT eliminates this branch).
            if (_position >= _bufferLength)
            {
                if (!Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, 1))
                    throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
            }

            // Fast path: single byte (0-127) - ~70% of cases
            var b0 = _buffer[_position];
            if ((b0 & 0x80) == 0)
            {
                _position++;
                return b0;
            }

            // Fast path: two bytes (128-16383) - ~25% of cases
            if (_position + 1 < _bufferLength)
            {
                var b1 = _buffer[_position + 1];
                if ((b1 & 0x80) == 0)
                {
                    _position += 2;
                    return (uint)(b0 & 0x7F) | ((uint)b1 << 7);
                }
            }

            // Slow path: 3+ bytes or cross-segment boundary — uses ReadByte() per byte
            return ReadVarUIntSlow();
        }

        private uint ReadVarUIntSlow()
        {
            uint value = 0;
            var shift = 0;
            while (true)
            {
                var b = ReadByte();
                value |= (uint)(b & 0x7F) << shift;
                if ((b & 0x80) == 0)
                {
                    break;
                }

                shift += 7;
                if (shift > 35)
                {
                    throw new AcBinaryDeserializationException("Invalid VarUInt encoding.", _position);
                }
            }

            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public long ReadVarLong()
        {
            //if (FastWire) { return ReadRaw<long>(); }

            var raw = ReadVarULong();
            var value = (long)(raw >> 1) ^ -((long)raw & 1);
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ulong ReadVarULong()
        {
            //if (FastWire) { return ReadRaw<ulong>(); }

            ulong value = 0;
            var shift = 0;
            while (true)
            {
                var b = ReadByte();
                value |= (ulong)(b & 0x7F) << shift;
                if ((b & 0x80) == 0)
                {
                    break;
                }

                shift += 7;
                if (shift > 70)
                {
                    throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position);
                }
            }

            return value;
        }

        #endregion

        /// <summary>
        /// Called on first StringInternFirst marker � disables _stringCache because
        /// interned strings are resolved via _internCache and plain strings appear only once.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void DisableStringCaching()
        {
            _useStringCaching = false;
        }

        #region Bytes & String Reading

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] ReadBytes(int length)
        {
            if (length == 0)
            {
                return Array.Empty<byte>();
            }

            EnsureAvailable(length);
            var result = GC.AllocateUninitializedArray<byte>(length);
            _buffer.AsSpan(_position, length).CopyTo(result);
            _position += length;
            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadStringUtf8(int length)
        {
            if (length == 0)
            {
                return string.Empty;
            }

            // FastWire: length is char count, data is UTF-16 (2 bytes per char)
            if (FastWire)
            {
                var byteLen = length * 2;
                EnsureAvailable(byteLen);

                var chars = MemoryMarshal.Cast<byte, char>(_buffer.AsSpan(_position, byteLen));
                var value = new string(chars);

                _position += byteLen;
                return value;
            }

            EnsureAvailable(length);

            // WASM optimization: cache short strings to reduce allocations
            if (_useStringCaching && length <= _maxCachedStringLength)
            {
                return ReadStringUtf8Cached(length);
            }

            // BASELINE TEMP: ASCII fast path disabled — every string takes the custom UTF-8 decoder.
            // Used to measure custom decoder performance in isolation, without ASCII-fast-path-vs-decoder
            // dispatch interference. Re-enable once decoder optimization is benchmarked and verified.
            //
            //// ASCII fast path: short strings (≤128 bytes) with all ASCII bytes
            //// use string.Create + direct byte→char widening, avoiding UTF8Encoding overhead.
            //if (length <= 128 && System.Text.Ascii.IsValid(_buffer.AsSpan(_position, length)))
            //{
            //    var pos = _position;
            //    _position += length;
            //    return string.Create(length, (Buffer: _buffer, Start: pos), static (chars, state) =>
            //    {
            //        var src = state.Buffer.AsSpan(state.Start, chars.Length);
            //        for (var i = 0; i < chars.Length; i++)
            //            chars[i] = (char)src[i];
            //    });
            //}

            // All strings — custom UTF-8 decoder.
            // Beats Encoding.UTF8.GetString by skipping the virtual-dispatch + encoder-fallback
            // overhead the BCL adds for arbitrary inputs. Two passes (count + decode) over the
            // bytes — both passes are tight scalar loops the JIT can auto-vectorize for the
            // common 1-byte (ASCII) branch, with predictable branches for 2/3-byte sequences
            // (Latin extended, Cyrillic, Greek, CJK BMP). 4-byte sequences (supplementary plane:
            // emoji, rare CJK ext) decode to a UTF-16 surrogate pair.
            //
            // The bytes are guaranteed valid UTF-8 because we wrote them via Encoding.UTF8.GetBytes
            // — no validation needed beyond the bounds checks Span indexing already provides.
            // If a wire payload is corrupt, an IndexOutOfRangeException surfaces at the
            // continuation-byte read, which the calling deserializer propagates as a
            // deserialization failure (same exception class as the BCL path's malformed-input
            // handling).
            return DecodeUtf8(length);
        }

        /// <summary>
        /// Reads <paramref name="byteLength"/> ASCII bytes from the wire and widens them to a UTF-16
        /// string. Caller MUST guarantee the payload is pure ASCII — typically by dispatching on a
        /// <c>FixStrAscii</c> / <c>StringAscii</c> marker (the marker IS the ASCII-validity contract).
        /// </summary>
        /// <remarks>
        /// Skips the UTF-8 decoder entirely — every byte maps 1:1 to a char via simple widening.
        /// Uses <see cref="Encoding.Latin1"/>.<c>GetString</c> for the widen — Latin1 is byte→char
        /// 1:1 (codepoints 0..255), and ASCII (0..127) is a strict subset, so for marker-validated
        /// ASCII payloads the result is identical to a hand-rolled <c>(char)b</c> widen but uses
        /// the BCL's SIMD-accelerated implementation (single-shot allocation + memcpy-class widen).
        ///
        /// Beats a <c>string.Create</c> + scalar callback widen by avoiding the lambda-state passing
        /// and JIT-trust on auto-vectorization across the callback boundary.
        ///
        /// FastWire mode never emits ASCII markers — they're a Compact-mode-only optimization. If
        /// FastWire encounters one (cross-mode wire mismatch), the read still works but the FastWire
        /// raw-memcpy fast path doesn't apply.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadAsciiBytesAsString(int byteLength)
        {
            if (byteLength == 0) return string.Empty;

            EnsureAvailable(byteLength);

            // Cached short-string path (WASM optimization) — leverages full-content hash + Ascii.Equals
            // verification (which is a no-op fast path on ASCII content).
            if (_useStringCaching && byteLength <= _maxCachedStringLength)
            {
                return ReadStringUtf8Cached(byteLength);
            }

            var pos = _position;
            _position += byteLength;
            return Encoding.Latin1.GetString(_buffer, pos, byteLength);
        }

        /// <summary>
        /// Custom UTF-8 → UTF-16 string decoder.
        /// </summary>
        /// <remarks>
        /// Two-pass over bytes (count + decode) with zero intermediate allocation:
        /// • Pass 1 — <see cref="CountUtf8Chars"/>: counts UTF-16 chars produced (scalar, JIT-vectorizable).
        /// • Pass 2 — <see cref="DecodeUtf8SinglePass"/> inside <see cref="string.Create{TState}"/> callback:
        ///   decodes directly into the newly-allocated string's char buffer. No memcpy, no temp buffer,
        ///   no <c>ArrayPool</c> rent.
        ///
        /// Beats <see cref="System.Text.Encoding.UTF8"/>.GetString by:
        /// 1. Skipping virtual-dispatch + encoder-fallback overhead the BCL adds for arbitrary inputs.
        /// 2. Multi-byte branches via direct bit-extract — no overlong/surrogate range checks.
        /// 3. Vector256 ASCII prefix bulk widen (32 bytes/iter while all-ASCII) inside Pass 2.
        /// 4. DWORD ASCII batch (4 bytes/iter when ASCII-aligned) inside Pass 2's scalar loop.
        ///
        /// The bytes are guaranteed valid UTF-8 because the writer used <c>Encoding.UTF8.GetBytes</c>.
        /// If a wire payload is corrupt (incomplete multi-byte sequence), an
        /// <see cref="IndexOutOfRangeException"/> surfaces at the continuation-byte read,
        /// which the calling deserializer propagates as a deserialization failure.
        /// </remarks>
        [MethodImpl(MethodImplOptions.NoInlining)]  // cold path; keep ReadStringUtf8 caller small
        private string DecodeUtf8(int byteLength)
        {
            var pos = _position;
            _position += byteLength;
            var src = _buffer.AsSpan(pos, byteLength);
            var charCount = CountUtf8Chars(src);

            return string.Create(charCount, (Buffer: _buffer, Pos: pos, Len: byteLength), static (chars, state) =>
            {
                DecodeUtf8SinglePass(state.Buffer.AsSpan(state.Pos, state.Len), chars);
            });
        }

        /// <summary>
        /// Counts UTF-16 chars produced by decoding the given UTF-8 byte span.
        /// </summary>
        /// <remarks>
        /// Vectorized via Vector256 (32 bytes/iter) using two bit-pattern checks:
        /// • Non-continuation bytes (NOT 10xxxxxx, mask 0xC0 ≠ 0x80): each contributes 1 char.
        /// • 4-byte start bytes (11110xxx, mask 0xF8 == 0xF0): each contributes an EXTRA char (surrogate pair).
        ///
        /// SIMD per-block result: <c>(32 - popcount(continuationMask)) + popcount(fourByteStartMask)</c>.
        /// Scalar tail handles the remaining &lt;32 bytes.
        ///
        /// Char-count rules:
        /// • Continuation bytes (10xxxxxx, 0x80–0xBF) — produce no char, skip.
        /// • All other start bytes (0xxxxxxx, 110xxxxx, 1110xxxx) — produce 1 char each.
        /// • 4-byte start bytes (11110xxx, 0xF0–0xF7) — produce 2 chars (UTF-16 surrogate pair).
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int CountUtf8Chars(ReadOnlySpan<byte> bytes)
        {
            var count = 0;
            var i = 0;
            ref var bytesRef = ref MemoryMarshal.GetReference(bytes);

            // SIMD path: 32 bytes/iter via Vector256
            if (Vector256.IsHardwareAccelerated && bytes.Length >= 32)
            {
                var contMask = Vector256.Create((byte)0xC0);
                var contValue = Vector256.Create((byte)0x80);
                var fourByteMask = Vector256.Create((byte)0xF8);
                var fourByteValue = Vector256.Create((byte)0xF0);

                do
                {
                    var v = Vector256.LoadUnsafe(ref bytesRef, (uint)i);

                    // Non-continuation count: 32 - popcount(continuation byte mask)
                    var contMatches = Vector256.Equals(v & contMask, contValue);
                    var contBits = contMatches.ExtractMostSignificantBits();
                    count += 32 - System.Numerics.BitOperations.PopCount(contBits);

                    // 4-byte start count: popcount(fourByte start byte mask)
                    var fourByteMatches = Vector256.Equals(v & fourByteMask, fourByteValue);
                    var fourByteBits = fourByteMatches.ExtractMostSignificantBits();
                    count += System.Numerics.BitOperations.PopCount(fourByteBits);

                    i += 32;
                } while (bytes.Length - i >= 32);
            }

            // Scalar tail (and fallback for non-SIMD hardware)
            for (; i < bytes.Length; i++)
            {
                var b = Unsafe.Add(ref bytesRef, i);
                if ((b & 0xC0) != 0x80) count++;       // non-continuation byte
                if ((b & 0xF8) == 0xF0) count++;        // 4-byte start: extra char for surrogate pair
            }
            return count;
        }

        /// <summary>
        /// Single-pass UTF-8 → UTF-16 decoder. Returns the actual char count written to <paramref name="dst"/>.
        /// </summary>
        /// <remarks>
        /// Layered approach for maximum throughput across mixed content:
        /// • <b>Phase 1 — Vector256 ASCII prefix bulk widen:</b> 32 bytes/iter while all top bits are zero.
        ///   Uses <see cref="Vector256.Widen(Vector256{byte})"/> to produce two Vector256&lt;ushort&gt; lanes
        ///   = 32 chars per iteration. Breaks on first non-ASCII byte found in the loaded vector.
        /// • <b>Phase 2 — DWORD ASCII batch:</b> when ≥4 bytes remain, read as <c>uint</c>, test
        ///   <c>(dword &amp; 0x80808080u) == 0</c>; on hit, widen 4 chars in 4 instructions and continue.
        /// • <b>Phase 3 — Scalar multi-byte branch:</b> 1-byte (ASCII single), 2-byte (Latin extended,
        ///   Cyrillic, Greek, Hebrew, Arabic), 3-byte (CJK BMP), 4-byte (supplementary plane → surrogate pair).
        ///   Direct bit-extract, no validation — input is trusted.
        ///
        /// JIT compiles the switch into a jump table for predictable dispatch on mixed content.
        /// Hungarian text typical pattern: ASCII run (Phase 1/2 widening) → 2-byte char (Phase 3
        /// case &lt; 0xE0) → ASCII run → 2-byte char → ... — each phase optimal for its segment.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int DecodeUtf8SinglePass(ReadOnlySpan<byte> src, Span<char> dst)
        {
            int srcIdx = 0, dstIdx = 0;
            ref byte srcRef = ref MemoryMarshal.GetReference(src);
            ref ushort dstRef = ref Unsafe.As<char, ushort>(ref MemoryMarshal.GetReference(dst));

            // Phase 1 — Vector256 ASCII prefix bulk widen (32 bytes/iter)
            if (Vector256.IsHardwareAccelerated)
            {
                while (src.Length - srcIdx >= Vector256<byte>.Count)
                {
                    var v = Vector256.LoadUnsafe(ref srcRef, (uint)srcIdx);
                    // ASCII detect: any high bit set among the 32 bytes?
                    if (v.ExtractMostSignificantBits() != 0) break;

                    // Widen 32 bytes → 2 × Vector256<ushort> (32 chars total)
                    var (lower, upper) = Vector256.Widen(v);
                    lower.StoreUnsafe(ref dstRef, (uint)dstIdx);
                    upper.StoreUnsafe(ref dstRef, (uint)(dstIdx + Vector128<ushort>.Count));
                    srcIdx += Vector256<byte>.Count;
                    dstIdx += Vector256<byte>.Count;  // 32 bytes → 32 chars
                }
            }

            // Phase 2/3 — scalar loop with DWORD ASCII batch
            while (srcIdx < src.Length)
            {
                // DWORD ASCII batch: 4 ASCII bytes → 4 chars per iter
                if (src.Length - srcIdx >= 4)
                {
                    var dword = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref srcRef, srcIdx));
                    if ((dword & 0x80808080u) == 0)
                    {
                        Unsafe.Add(ref dstRef, dstIdx)     = (byte)dword;
                        Unsafe.Add(ref dstRef, dstIdx + 1) = (byte)(dword >> 8);
                        Unsafe.Add(ref dstRef, dstIdx + 2) = (byte)(dword >> 16);
                        Unsafe.Add(ref dstRef, dstIdx + 3) = (byte)(dword >> 24);
                        srcIdx += 4;
                        dstIdx += 4;
                        continue;
                    }
                }

                // Scalar multi-byte branch (jump-table compile via switch)
                var b0 = Unsafe.Add(ref srcRef, srcIdx);
                switch (b0)
                {
                    case < 0x80:
                        // 1-byte ASCII (U+0000–U+007F)
                        Unsafe.Add(ref dstRef, dstIdx++) = b0;
                        srcIdx += 1;
                        break;
                    case < 0xE0:
                    {
                        // 2-byte: 110xxxxx 10xxxxxx → U+0080–U+07FF
                        // Latin extended, Cyrillic, Greek, Hebrew, Arabic.
                        var b1 = Unsafe.Add(ref srcRef, srcIdx + 1);
                        Unsafe.Add(ref dstRef, dstIdx++) = (ushort)(((b0 & 0x1F) << 6) | (b1 & 0x3F));
                        srcIdx += 2;
                        break;
                    }
                    case < 0xF0:
                    {
                        // 3-byte: 1110xxxx 10xxxxxx 10xxxxxx → U+0800–U+FFFF
                        // CJK BMP, various other scripts.
                        var b1 = Unsafe.Add(ref srcRef, srcIdx + 1);
                        var b2 = Unsafe.Add(ref srcRef, srcIdx + 2);
                        Unsafe.Add(ref dstRef, dstIdx++) = (ushort)(((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F));
                        srcIdx += 3;
                        break;
                    }
                    default:
                    {
                        // 4-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx → U+10000–U+10FFFF
                        // Supplementary plane (emoji, rare CJK ext) → UTF-16 surrogate pair.
                        var b1 = Unsafe.Add(ref srcRef, srcIdx + 1);
                        var b2 = Unsafe.Add(ref srcRef, srcIdx + 2);
                        var b3 = Unsafe.Add(ref srcRef, srcIdx + 3);
                        var codepoint = ((b0 & 0x07) << 18) | ((b1 & 0x3F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F);
                        codepoint -= 0x10000;
                        Unsafe.Add(ref dstRef, dstIdx)     = (ushort)(0xD800 | (codepoint >> 10));
                        Unsafe.Add(ref dstRef, dstIdx + 1) = (ushort)(0xDC00 | (codepoint & 0x3FF));
                        dstIdx += 2;
                        srcIdx += 4;
                        break;
                    }
                }
            }

            return dstIdx;
        }

        private string ReadStringUtf8Cached(int length)
        {
            var slice = _buffer.AsSpan(_position, length);
            var hash = ComputeStringHashFull(slice);

            if (_stringCache!.TryGetValue(hash, out var cached))
            {
                if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice))
                {
                    _position += length;
                    return cached;
                }
            }

            var value = Utf8NoBom.GetString(slice);
            _stringCache[hash] = value;
            _position += length;
            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
        {
            return Ascii.Equals(utf8Bytes, cached);
        }

        /// <summary>
        /// Full-content hash for string caching.
        /// CRITICAL: DO NOT SIMPLIFY � prevents hash collisions for similar property names.
        /// See BinaryDeserializationContext for full history.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int ComputeStringHashFull(ReadOnlySpan<byte> data)
        {
            if (data.Length <= 32)
            {
                var hash = new HashCode();
                hash.AddBytes(data);
                return hash.ToHashCode();
            }

            var h = new HashCode();
            h.Add(data.Length);
            h.AddBytes(data.Slice(0, 8));
            h.AddBytes(data.Slice(data.Length - 8, 8));
            h.AddBytes(data.Slice(data.Length / 2 - 4, 8));
            return h.ToHashCode();
        }

        #endregion

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void EnsureAvailable(int length)
        {
            if (_position > _bufferLength - length)
            {
                if (!Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, length))
                    throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
                AssertGuarantee(length);
            }
        }

        [Conditional("DEBUG")]
        private void AssertGuarantee(int needed)
        {
            if (_bufferLength - _position < needed)
                throw new AcBinaryDeserializationException(
                    $"[GUARANTEE_VIOLATED] TryAdvanceSegment returned true but available={_bufferLength - _position} < needed={needed}, " +
                    $"pos={_position}, bufLen={_bufferLength}, bufArray={_buffer.Length}", _position);
        }
    }
}
