using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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;
                // Trusted-single-segment fast path — JIT folds the constant: for ArrayBinaryInput
                // the branch becomes `if (true) return true;` and the TryAdvanceSegment call below is
                // dead-code-eliminated. For multi-segment / streaming inputs, the call is preserved.
                if (TInput.IsTrustedSingleSegment) return true;
                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()
        {
            // Routes through EnsureAvailable to leverage the trusted-single-segment JIT-eliminate guard.
            // ArrayBinaryInput: EnsureAvailable body fully eliminated → just `_buffer[_position++]`.
            // Multi-segment / streaming: bounds-check + TryAdvanceSegment kept (cross-segment safe).
            EnsureAvailable(1);
            return _buffer[_position++];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte PeekByte()
        {
            // Same trusted-single-segment fast path as ReadByte (no _position advance).
            EnsureAvailable(1);
            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;
        }

        /// <summary>
        /// H2Q6 helper — reads 2 bytes as little-endian <c>ushort</c> (low byte = first byte, high byte = second).
        /// Used by <c>StringSmall</c> / <c>StringInternFirstSmall</c> readers to grab <c>charLen:8 | utf8Len:8</c>
        /// in a single 2-byte aligned-load + EnsureAvailable.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ushort ReadTwoBytesUnsafe()
        {
            EnsureAvailable(2);
            var value = Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
            _position += 2;
            return value;
        }

        /// <summary>
        /// Reads a 4-byte unsigned integer (little-endian on Intel/AMD, native-endian elsewhere — wire format
        /// is little-endian by convention; on big-endian hosts this would need <c>BinaryPrimitives.ReverseEndianness</c>).
        /// Used by <c>StringBig</c> reader to grab <c>charLen:32</c> and <c>utf8Len:32</c>.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public uint ReadUInt32Unsafe()
        {
            EnsureAvailable(4);
            var value = Unsafe.ReadUnaligned<uint>(ref _buffer[_position]);
            _position += 4;
            return value;
        }

        /// <summary>
        /// Reads a 4-byte signed integer (little-endian on Intel/AMD, native-endian elsewhere).
        /// Symmetric with <c>Unsafe.WriteUnaligned&lt;int&gt;</c> on the writer side. Used by FastWire
        /// <c>StringSmall</c> reader to grab <c>charLen:int32</c>.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int ReadInt32Unsafe()
        {
            EnsureAvailable(4);
            var value = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
            _position += 4;
            return value;
        }

        /// <summary>
        /// Reads an 8-byte unsigned integer (little-endian on Intel/AMD, native-endian elsewhere).
        /// Used by H2Q6 <c>StringBig</c> reader to grab packed <c>charLen:32 | utf8Len:32</c> in a single load.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ulong ReadUInt64Unsafe()
        {
            EnsureAvailable(8);
            var value = Unsafe.ReadUnaligned<ulong>(ref _buffer[_position]);
            _position += 8;
            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();
            return (int)(raw >> 1) ^ -(int)(raw & 1);
        }

        /// <summary>
        /// Prefix-tier VarUInt decode (UTF-8-style). Wire-size identical to legacy LEB128 across all
        /// 5 tiers (7/14/21/28/32 bit); decode is loop-less — the first-byte prefix determines total
        /// size in O(1), and each subsequent byte is read incrementally (no continuation-loop, no
        /// per-byte shift cascade).
        /// <para>Tier table (first-byte pattern → total bytes → value range):</para>
        /// <list type="bullet">
        ///   <item><c>0xxxxxxx</c> → 1 byte → <c>0..127</c></item>
        ///   <item><c>10xxxxxx</c> → 2 byte → <c>128..16 383</c></item>
        ///   <item><c>110xxxxx</c> → 3 byte → <c>16 384..2 097 151</c></item>
        ///   <item><c>1110xxxx</c> → 4 byte → <c>2 097 152..268 435 455</c></item>
        ///   <item><c>1111xxxx</c> → 5 byte → <c>268 435 456..4 294 967 295</c> (prefix nibble unused)</item>
        /// </list>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public uint ReadVarUInt()
        {
            //if (FastWire) { return ReadRaw<uint>(); }

            // ReadByte() routes through EnsureAvailable(1) — ArrayBinaryInput JIT-eliminates the
            // bounds-check, multi-segment / AsyncPipeReaderInput advances cross-segment as needed.
            // All tiers are cross-segment safe without explicit segment-handling here.

            // 1-byte tier (0..127) — ~70% of cases (the most common one for small ids, counts, indices).
            var b0 = ReadByte();
            if (b0 < 0x80) return b0;

            // 2-byte tier (128..16 383) — 10xxxxxx + 1B raw.
            var b1 = ReadByte();
            if (b0 < 0xC0) return ((uint)(b0 & 0x3F) << 8) | b1;

            // 3-byte tier (16 384..2 097 151) — 110xxxxx + 2B LE.
            var b2 = ReadByte();
            if (b0 < 0xE0) return ((uint)(b0 & 0x1F) << 16) | ((uint)b2 << 8) | b1;

            // 4 / 5-byte tiers (rare — value ≥ 2 097 152) — handed off to non-inlined slow path
            // with already-read bytes passed as args (no re-read).
            return ReadVarUIntSlow(b0, b1, b2);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private uint ReadVarUIntSlow(byte b0, byte b1, byte b2)
        {
            // 4-byte tier (2 097 152 .268 435 455) — 1110xxxx + 3B LE.
            var b3 = ReadByte();
            if (b0 < 0xF0) return ((uint)(b0 & 0x0F) << 24) | ((uint)b3 << 16) | ((uint)b2 << 8) | b1;

            // 5-byte tier (268 435 456..4 294 967 295) — 1111xxxx + 4B LE (prefix nibble unused).
            var b4 = ReadByte();
            return ((uint)b4 << 24) | ((uint)b3 << 16) | ((uint)b2 << 8) | b1;
        }

        [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 [];
            }

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

        /// <summary>
        /// Reads a UTF-16 raw string of <paramref name="charLength"/> chars (FastWire mode body).
        /// Wire body is <c>charLength * 2</c> raw bytes (LE on Intel/AMD, native-endian elsewhere) — zero-decode
        /// memcpy via <see cref="MemoryMarshal.Cast{TFrom, TTo}(System.Span{TFrom})"/>.
        /// <para>Caller MUST be on the FastWire path. The companion <see cref="ReadStringUtf8"/> is
        /// for Compact/UTF-8 wire only — the two paths are statically separate (no FastWire-runtime-check
        /// inside this method).</para>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadStringUtf16(int charLength)
        {
            if (charLength == 0) return string.Empty;

            var byteLen = charLength * 2;
            EnsureAvailable(byteLen);

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

            _position += byteLen;
            return value;
        }

        /// <summary>
        /// FastWire markerless string read — int32 sentinel header. Self-contained: handles all three
        /// states (null / empty / content) via int32 dispatch. <c>-1</c> = null, <c>0</c> = empty,
        /// <c>N &gt; 0</c> = content (followed by N×2 UTF-16 raw bytes).
        /// <para>Hot-path-first: positive length (content) is the common case, branch-prediction-favored.
        /// Companion writer is <see cref="BinarySerializationContext{TOutput}.WriteStringUtf16Markerless"/>.</para>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string? ReadStringUtf16Markerless()
        {
            var len = ReadInt32Unsafe();
            if (len > 0) return ReadStringUtf16(len);
            if (len == 0) return string.Empty;
            return null;  // len < 0 (sentinel -1)
        }

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

            EnsureAvailable(length);

            // WASM optimization: cache short strings to reduce allocations.
            // `false &&` prefix dead-codes the branch — currently unused workload-wide;
            // remove the prefix to re-enable when WASM string-cache benchmarking resumes.
            if (false && _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).
            // `false &&` prefix dead-codes the branch — currently unused workload-wide.
            if (false && _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 = Encoding.UTF8.GetCharCount(src);

            return string.Create(charCount, (Buffer: _buffer, Pos: pos, Len: byteLength), static (chars, state) =>
            {
                System.Text.Unicode.Utf8.ToUtf16(state.Buffer.AsSpan(state.Pos, state.Len), chars, out _, out _, replaceInvalidSequences: false);
            });
        }

        /// <summary>
        /// H2Q6 1-pass UTF-8 string read — both <paramref name="charLength"/> and <paramref name="byteLength"/>
        /// come from the wire (StringSmall/Medium/Big tier headers), eliminating the
        /// <see cref="Utf8Transcoder.CountUtf8Chars"/> Pass 1.
        /// </summary>
        /// <remarks>
        /// Single method (no dispatcher/core split): the V4N4 split attempt did not pay off — the AOT
        /// did NOT inline the dispatcher despite `[AggressiveInlining]` (disasm 15:12 confirmed both
        /// dispatcher AND core body remained as call-targets), so the only effect was +1 call instruction
        /// per decode (Small Deser regression +16.6 pp). Reverted to single method — `string.Create`
        /// callback uses a cached static lambda (delegate caching confirmed by `test static; jne skip ctor`
        /// pattern in disasm).
        ///
        /// <para>Compact mode only — FastWire mode never emits H2Q6 tier markers (its
        /// <see cref="ReadStringUtf8"/> path handles UTF-16 raw memcpy).</para>
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadStringUtf8WithCharLen(int charLength, int byteLength)
        {
            if (byteLength == 0) return string.Empty;

            EnsureAvailable(byteLength);

            // WASM string-cache fast path — if cached, byte-cmp validates and returns the canonical instance.
            // `false &&` prefix dead-codes the branch — currently unused workload-wide.
            if (false && _useStringCaching && byteLength <= _maxCachedStringLength)
            {
                return ReadStringUtf8Cached(byteLength);
            }

            var pos = _position;
            _position += byteLength;

            return string.Create(charLength, (Buffer: _buffer, Pos: pos, Len: byteLength), static (chars, state) =>
            {
                System.Text.Unicode.Utf8.ToUtf16(state.Buffer.AsSpan(state.Pos, state.Len), chars, out _, out _, replaceInvalidSequences: false);
            });
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        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 && Ascii.Equals(slice, cached))
                {
                    _position += length;
                    return cached;
                }
            }

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

        /// <summary>
        /// Legacy compact StringSmall reader retained only for backward-compat payloads.
        /// </summary>
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal string ReadStringSmallCompactLegacy()
        {
            System.Diagnostics.Debug.Assert(!FastWire, "ReadStringSmallCompactLegacy called with FastWire=true.");

            // H2Q6 StringSmall body: [charLen:8][utf8Len:8][UTF-8 bytes]
            var header = ReadTwoBytesUnsafe();
            var charLength = (byte)header;
            var byteLength = (byte)(header >> 8);
            return ReadStringUtf8WithCharLen(charLength, byteLength);
        }

        /// <summary>
        /// StringUtf16 reader — FastWire-mode-only body: wire <c>[charLen:int32 LE][UTF-16 raw bytes]</c>
        /// after the (mode-shared) marker has been consumed. Engaged only on the runtime
        /// <see cref="TypeReaderTable{TInput}"/> path when <c>FastWire==true</c> and the declared target
        /// type is NOT <c>string</c> (the <c>string</c>-typed FastWire short-circuit in <see cref="AcBinaryDeserializer.ReadValue{TInput}"/>
        /// bypasses the marker entirely via <c>ReadStringUtf16Markerless</c>).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadStringUtf16FastWire()
        {
            // StringUtf16 (=91) payload — fix-int charLen (matches MemPack WriteUtf16 shape).
            var charLenF = ReadInt32Unsafe();
            return ReadStringUtf16(charLenF);
        }

        /// <summary>
        /// Unified reader for marker <see cref="BinaryTypeCode.StringUtf16"/> (91).
        /// FastWire path reads UTF-16 payload; non-FastWire path keeps legacy compact payload compatibility.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadStringUtf16Marker()
        {
            return FastWire ? ReadStringUtf16FastWire() : ReadStringSmallCompactLegacy();
        }

        /// <summary>
        /// H2Q6 StringInternFirstSmall reader: wire <c>[cacheIdx:VarUInt][charLen:8][utf8Len:8][bytes]</c>
        /// after the marker has been consumed. Registers the decoded string in the intern cache and returns it.
        /// Single source of wire-decode for this marker — shared by the runtime <c>TypeReaderTable</c>
        /// dispatch, the cross-type populate path, and the SGen-emitted string-property switch.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadAndRegisterInternedStringSmall()
        {
            // First interning marker proves payload uses string interning → plain String entries
            // appear only once, so _stringCache would never hit on them.
            DisableStringCaching();
            var cacheIndex = (int)ReadVarUInt();
            var header = ReadTwoBytesUnsafe();
            var charLength = (byte)header;
            var byteLength = (byte)(header >> 8);
            if (byteLength == 0)
            {
                RegisterInternedValueAt(cacheIndex, string.Empty);
                return string.Empty;
            }
            var str = ReadStringUtf8WithCharLen(charLength, byteLength);
            RegisterInternedValueAt(cacheIndex, str);
            return str;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadUniversalFixStr(byte marker)
        {
            ReadUniversalFixStrHeader(marker, out var charLength, out var excess);
            return ReadStringByUnsignedExcess(charLength, excess);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadUniversalLongString()
        {
            ReadUniversalLongStringHeader(out var charLength, out var excess);
            return ReadStringByUnsignedExcess(charLength, excess);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void ReadUniversalFixStrHeader(byte marker, out int charLength, out uint excess)
        {
            charLength = BinaryTypeCode.DecodeFixStrLength(marker);
            excess = ReadByte();
        }

        /// <summary>
        /// Reads the long-form string header — single <see cref="BinaryTypeCode.String"/> marker followed by
        /// prefix-tier VarUInt charLength (offset by <see cref="BinaryTypeCode.FixStrCount"/>) and a 1/2/4-byte
        /// unsigned excess slot whose width is derived from charLength (see <see cref="BinaryTypeCode.GetUniversalStringExcessSlotSize"/>).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void ReadUniversalLongStringHeader(out int charLength, out uint excess)
        {
            charLength = (int)ReadVarUInt() + BinaryTypeCode.FixStrCount;
            var slotSize = BinaryTypeCode.GetUniversalStringExcessSlotSize(charLength);

            if (slotSize == 1) excess = ReadByte();
            else if (slotSize == 2) excess = ReadUInt16Unsafe();
            else excess = ReadVarUInt32Unchecked();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private string ReadStringByUnsignedExcess(int charLength, uint excess)
        {
            if (charLength == 0)
            {
                if (excess == 0) return string.Empty;
                throw new AcBinaryDeserializationException($"Invalid string header: charLength=0 requires excess=0, got {excess}.", _position);
            }

            if (excess == 0)
            {
                return ReadAsciiBytesAsString(charLength);
            }

#if DEBUG
            if (excess > (uint)(int.MaxValue - charLength))
                throw new AcBinaryDeserializationException($"Invalid string header: byteLength overflow (charLength={charLength}, excess={excess}).", _position);
#endif

            var byteLength = charLength + (int)excess;
            return ReadStringUtf8WithCharLen(charLength, byteLength);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private uint ReadVarUInt32Unchecked()
        {
            return ReadUInt32Unsafe();
        }

        /// <summary>
        /// H2Q6 StringInternFirstMedium reader: wire <c>[cacheIdx:VarUInt][charLen:16 LE][utf8Len:16 LE][bytes]</c>.
        /// Registers the decoded string in the intern cache and returns it. (Big tier never engages on the
        /// interning path — see <see cref="BinaryTypeCode"/> H2Q6 layout comment.) Shared by runtime
        /// dispatch + SGen-emit (same rationale as <see cref="ReadAndRegisterInternedStringSmall"/>).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal string ReadAndRegisterInternedStringMedium()
        {
            DisableStringCaching();
            var cacheIndex = (int)ReadVarUInt();
            // Pack charLen:16 | utf8Len:16 read in a single uint load
            var packed = ReadUInt32Unsafe();
            var charLength = (ushort)packed;
            var byteLength = (ushort)(packed >> 16);
            if (byteLength == 0)
            {
                RegisterInternedValueAt(cacheIndex, string.Empty);
                return string.Empty;
            }
            var str = ReadStringUtf8WithCharLen(charLength, byteLength);
            RegisterInternedValueAt(cacheIndex, str);
            return str;
        }

        /// <summary>
        /// ACCORE-BIN-T-K9M3 — property-level string marker dispatch for all **non-interning** markers
        /// (FixStrAscii, StringAscii, StringSmall/Medium/Big, Null, StringEmpty). The 3 interning markers
        /// (StringInterned, StringInternFirst{Small,Medium}) are handled by the companion
        /// <see cref="TryReadStringColdPath"/>, which the SGen-emit calls via <c>||</c> ONLY for
        /// interning-enabled types — non-interning types omit the cold call entirely.
        /// <para><b>Size discipline:</b> each UTF-8 tier case decodes only the header (charLen + utf8Len)
        /// into locals; the heavy decode runs at a SINGLE post-switch site per family —
        /// <see cref="ReadStringUtf8WithCharLen"/> for the UTF-8 tiers, <see cref="ReadAsciiBytesAsString"/>
        /// for ASCII (discriminated by the <c>byteLength &lt; 0</c> sentinel). Adding a tier costs one tiny
        /// header decode, not a duplicated decode body — so the 7-marker body still fits AggressiveInlining.</para>
        /// <para>Caller reads the marker byte + handles FastWire on a separate branch (markerless decode);
        /// by the time <paramref name="tc"/> reaches here FastWire is guaranteed false. Returns <c>true</c>
        /// if a non-interning marker matched (<paramref name="value"/> set — incl. deliberate <c>null</c> on
        /// <see cref="BinaryTypeCode.Null"/>); <c>false</c> for an interning marker / PropertySkip / unknown.</para>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal bool TryReadStringProperty(byte tc, out string? value)
        {
            // Hot-path invariant: property marker paths remain mode-aware for legacy payload compatibility.
            value = null;
            int charLength;
            uint excess;
            switch (tc)
            {
                case BinaryTypeCode.StringUtf16:
                    value = ReadStringUtf16Marker();
                    return true;
                case BinaryTypeCode.String:
                    ReadUniversalLongStringHeader(out charLength, out excess);
                    break;
                case BinaryTypeCode.Null:
                    return true;
                case BinaryTypeCode.StringEmpty:
                    value = string.Empty;

                    return true;
                default:
                    // FixStr (short universal string): marker carries char length.
                    if (BinaryTypeCode.IsFixStr(tc))
                    {
                        ReadUniversalFixStrHeader(tc, out charLength, out excess);
                        break;
                    }

                    // Interning marker, PropertySkip, or unknown — caller continues via short-circuit ||
                    // to TryReadStringColdPath (interning types) or leaves the property at default.
                    return false;
            }

            value = ReadStringByUnsignedExcess(charLength, excess);
            return true;
        }

        /// <summary>
        /// Interning-marker companion to <see cref="TryReadStringProperty"/> — dispatches the 3 interning
        /// markers only (StringInterned, StringInternFirstSmall, StringInternFirstMedium). Every other
        /// string marker (FixStr, String, StringUtf16, Null, StringEmpty) is handled
        /// by <see cref="TryReadStringProperty"/>; this method is emitted into generated readers ONLY for
        /// types whose string-interning feature flag is enabled — non-interning types skip it entirely
        /// (the writer never produces interning markers for them, so <see cref="TryReadStringProperty"/>
        /// alone is total).
        /// <para>Returns <c>true</c> if an interning marker matched; <c>false</c> for
        /// <see cref="BinaryTypeCode.PropertySkip"/> or an unknown / corrupted value (caller leaves the
        /// property untouched — the safer behaviour for wire corruption).</para>
        /// <para><see cref="MethodImplOptions.AggressiveOptimization"/> forces Tier-1 direct compilation —
        /// the caller pays one method-call cost only when the wire actually carries an interning marker.</para>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        internal bool TryReadStringColdPath(byte tc, out string? value)
        {
            switch (tc)
            {
                case BinaryTypeCode.StringInterned: value = GetInternedString((int)ReadVarUInt()); return true;
                case BinaryTypeCode.StringInternFirstSmall: value = ReadAndRegisterInternedStringSmall(); return true;
                case BinaryTypeCode.StringInternFirstMedium: value = ReadAndRegisterInternedStringMedium(); return true;
            }

            // PropertySkip OR unknown marker — caller leaves the property at default value
            // (safer than the previous silent null-assignment on unknown).
            value = null;
            return false;
        }

        /// <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

        /// <summary>
        /// Ensures <paramref name="length"/> bytes are readable from the current <c>_buffer[_position]</c>
        /// position, advancing to the next segment via <see cref="IBinaryInputBase.TryAdvanceSegment"/> when
        /// the current buffer is exhausted.
        /// <para><b>JIT specialization fast-path</b>: when <c>TInput.IsTrustedSingleSegment</c> is the
        /// constant <c>true</c> (e.g. <see cref="ArrayBinaryInput"/>), the entire method body is eliminated
        /// at JIT time — bounds-check + segment-advance both vanish. Per-read overhead drops to ~0 ns.
        /// Trade-off: corrupt-wire detection downgrades from <see cref="AcBinaryDeserializationException"/>
        /// to a generic <see cref="System.IndexOutOfRangeException"/>; acceptable for trusted byte[] inputs
        /// where the buffer is already validated by the caller.</para>
        /// <para>For non-trusted inputs (<see cref="SequenceBinaryInput"/>, AsyncPipeReaderInputAdapter), the
        /// guard's <c>if (false) return;</c> form is dead-code-eliminated — the bounds-check and segment-advance
        /// keep their original behaviour with zero added overhead.</para>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void EnsureAvailable(int length)
        {
            // Trusted-single-segment fast path — JIT folds the constant per TInput specialization:
            //   ArrayBinaryInput      → if (true)  return;  → method body entirely eliminated
            //   SequenceBinaryInput   → if (false) return;  → guard eliminated, bounds-check kept
            //   AsyncPipeReaderInput  → if (false) return;  → guard eliminated, bounds-check kept

            if (TInput.IsTrustedSingleSegment || _position <= _bufferLength - length) return;

            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);
        }
    }
}
