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.Runtime.Intrinsics.X86;
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 = Utf8Transcoder.CountUtf8Chars(src);

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

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