using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// Binary output that writes directly to an IBufferWriter (e.g. SignalR pipe, network stream).
///
/// Two usage modes:
/// 1. Context mode: Initialize/Grow/Flush — all write methods live in BinarySerializationContext.
/// 2. Standalone mode: direct write methods (WriteByte, WriteVarUInt, etc.) for use outside
///    the serialization pipeline (e.g. AcBinaryHubProtocol frame headers).
///
/// Uses a cached chunk pattern: acquires a large chunk once via GetMemory, extracts the backing
/// array, and writes into it with direct indexing. Only calls Advance + GetMemory when the chunk fills up.
///
/// Call <see cref="Flush()"/> after all standalone writes to commit any pending bytes.
/// </summary>
public struct BufferWriterBinaryOutput : IBinaryOutputBase
{
    private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

    private readonly IBufferWriter<byte> _writer;
    private readonly int _chunkSize;
    private int _committedBytes;       // total bytes Advanced to writer so far
    private int _currentChunkStart;    // _position value at start of current chunk
    private bool _ownedBuffer;         // true if current buffer is from ArrayPool (fallback path)

    // Standalone mode buffer state (used by direct write methods)
    private byte[] _buffer = null!;
    private int _position;
    private int _bufferEnd;

    public BufferWriterBinaryOutput(IBufferWriter<byte> writer, int chunkSize = 65536)
    {
        _writer = writer;
        _chunkSize = chunkSize;
        // Initialize standalone buffer for direct write usage
        _committedBytes = 0;
        AcquireChunk(_chunkSize, out _buffer, out _position, out _bufferEnd);
        _currentChunkStart = _position;
    }

    /// <summary>
    /// Provides the initial buffer from the IBufferWriter.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Initialize(out byte[] buffer, out int position, out int bufferEnd)
    {
        _committedBytes = 0;
        AcquireChunk(_chunkSize, out buffer, out position, out bufferEnd);
        _currentChunkStart = position;
    }

    /// <summary>
    /// Called when the context's buffer is full. Commits current chunk to the IBufferWriter
    /// and acquires a new chunk.
    /// </summary>
    [MethodImpl(MethodImplOptions.NoInlining)]
    public void Grow(ref byte[] buffer, ref int position, ref int bufferEnd, int needed)
    {
        // Commit bytes written in current chunk
        var bytesInChunk = position - _currentChunkStart;
        if (bytesInChunk > 0)
        {
            if (_ownedBuffer)
            {
                FlushOwnedBuffer(buffer, bytesInChunk);
            }
            else
            {
                _writer.Advance(bytesInChunk);
            }
            _committedBytes += bytesInChunk;
        }

        // Acquire new chunk
        AcquireChunk(Math.Max(needed, _chunkSize), out buffer, out position, out bufferEnd);
        _currentChunkStart = position;
    }

    /// <summary>
    /// Returns total bytes written: committed + pending in current chunk.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int GetTotalPosition(int currentPosition)
        => _committedBytes + (currentPosition - _currentChunkStart);

    /// <summary>
    /// Commits any pending bytes to the underlying IBufferWriter.
    /// Must be called after all writes are complete.
    /// Takes the context's current buffer state.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Flush(byte[] buffer, int position)
    {
        var bytesInChunk = position - _currentChunkStart;
        if (bytesInChunk > 0)
        {
            if (_ownedBuffer)
            {
                FlushOwnedBuffer(buffer, bytesInChunk);
            }
            else
            {
                _writer.Advance(bytesInChunk);
            }
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void FlushOwnedBuffer(byte[] buffer, int bytesInChunk)
    {
        // Copy from our owned array to the writer, then return to pool
        var span = _writer.GetSpan(bytesInChunk);
        buffer.AsSpan(_currentChunkStart, bytesInChunk).CopyTo(span);
        _writer.Advance(bytesInChunk);
        ArrayPool<byte>.Shared.Return(buffer);
        _ownedBuffer = false;
    }

    private void AcquireChunk(int requestSize, out byte[] buffer, out int position, out int bufferEnd)
    {
        // Use GetMemory so we can extract the backing array via TryGetArray
        var actualRequest = Math.Max(requestSize, _chunkSize);
        var memory = _writer.GetMemory(actualRequest);

        if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment) && segment.Array != null)
        {
            buffer = segment.Array;
            position = segment.Offset;
            bufferEnd = segment.Offset + segment.Count;
            _ownedBuffer = false;
        }
        else
        {
            // Fallback for non-array-backed IBufferWriter (native memory).
            // Rent our own buffer; FlushOwnedBuffer copies to writer on next Grow/Flush.
            var owned = ArrayPool<byte>.Shared.Rent(actualRequest);
            buffer = owned;
            position = 0;
            bufferEnd = owned.Length;
            _ownedBuffer = true;
        }
    }

    /// <summary>
    /// No-op for BufferWriterBinaryOutput — chunks are owned by IBufferWriter, not us.
    /// </summary>
    public void Reset() { }

    #region Standalone Write Methods — for direct usage outside serialization pipeline (e.g. AcBinaryHubProtocol)

    /// <summary>
    /// Total bytes written in standalone mode (committed + pending in current chunk).
    /// </summary>
    public int Position
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => _committedBytes + (_position - _currentChunkStart);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void StandaloneEnsureCapacity(int additionalBytes)
    {
        if (_position + additionalBytes > _bufferEnd)
            Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteByte(byte value)
    {
        if (_position >= _bufferEnd)
            Grow(ref _buffer, ref _position, ref _bufferEnd, 1);
        _buffer[_position++] = value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteBytes(ReadOnlySpan<byte> data)
    {
        StandaloneEnsureCapacity(data.Length);
        data.CopyTo(_buffer.AsSpan(_position));
        _position += data.Length;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteRaw<T>(T value) where T : unmanaged
    {
        var size = Unsafe.SizeOf<T>();
        StandaloneEnsureCapacity(size);
        Unsafe.WriteUnaligned(ref _buffer[_position], value);
        _position += size;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteVarUInt(uint value)
    {
        if (value < 0x80)
        {
            if (_position >= _bufferEnd)
                Grow(ref _buffer, ref _position, ref _bufferEnd, 1);
            _buffer[_position++] = (byte)value;
            return;
        }
        StandaloneEnsureCapacity(5);
        WriteVarUIntMultiByteUnsafe(value);
    }

    /// <summary>Writes VarUInt without bounds check. Caller must ensure sufficient space.</summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void WriteVarUIntUnsafe(uint value)
    {
        if (value < 0x80)
        {
            _buffer[_position++] = (byte)value;
            return;
        }
        WriteVarUIntMultiByteUnsafe(value);
    }

    /// <summary>
    /// Prefix-tier VarUInt encoding (UTF-8-style). Compact: 2/3/4-byte tiers packed into a single
    /// <see cref="Unsafe.WriteUnaligned{T}"/>&lt;<see cref="uint"/>&gt; store via
    /// <see cref="BitOperations.Log2"/>-derived tier; 5-byte tier uses one byte + one uint32 store.
    /// Caller must ensure ≥5 bytes of buffer space. See
    /// <see cref="AcBinarySerializer.BinarySerializationContext{TOutput}.WriteVarUInt"/> for the
    /// full tier table.
    /// </summary>
    [MethodImpl(MethodImplOptions.NoInlining)]
    private void WriteVarUIntMultiByteUnsafe(uint value)
    {
        // Writes EXACTLY `tier` bytes — no overrun. See the context-side
        // WriteVarUIntMultiByteUnsafe for the rationale (Unsafe.WriteUnaligned<uint> 4-byte
        // store would corrupt callers that use savedPos-rewind-then-prefix patterns like
        // WriteStringUtf8).

        _buffer[_position + 1] = (byte)value;
        if (value < 0x4000)
        {
            // 2-byte tier
            _buffer[_position] = (byte)(0x80 | (value >> 8));
            _position += 2;
            return;
        }

        _buffer[_position + 2] = (byte)(value >> 8);
        if (value < 0x200000)
        {
            // 3-byte tier
            _buffer[_position] = (byte)(0xC0 | (value >> 16));
            _position += 3;
            return;
        }

        _buffer[_position + 3] = (byte)(value >> 16);
        if (value < 0x10000000)
        {
            // 4-byte tier
            _buffer[_position] = (byte)(0xE0 | (value >> 24));
            _position += 4;
            return;
        }

        // 5-byte tier: 1111xxxx (low nibble unused) + 4B LE (full uint32)
        _buffer[_position] = 0xF0;
        _buffer[_position + 4] = (byte)(value >> 24);
        _position += 5;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int VarUIntSize(uint value)
    {
        if (value < 0x80) return 1;
        if (value < 0x4000) return 2;
        if (value < 0x200000) return 3;
        if (value < 0x10000000) return 4;
        return 5;
    }

    public void WriteStringUtf8(string value)
    {
        var charLength = value.Length;

        // Pre-allocate VarUInt + ASCII body BEFORE savedPosition — if Grow happens,
        // it fires here, before the save. savedPosition is always in the current chunk.
        StandaloneEnsureCapacity(VarUIntSize((uint)charLength) + charLength);
        var savedPosition = _position;

        WriteVarUIntUnsafe((uint)charLength);
        if (Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, charLength), out _) == OperationStatus.Done)
        {
            _position += charLength;
            return;
        }

        // Non-ASCII fallback: safe rewind (no Grow happened since pre-allocate)
        _position = savedPosition;
        var byteCount = Utf8NoBom.GetByteCount(value);
        StandaloneEnsureCapacity(VarUIntSize((uint)byteCount) + byteCount);
        WriteVarUIntUnsafe((uint)byteCount);
        Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
        _position += byteCount;
    }

    /// <summary>
    /// Commits any pending bytes in standalone mode to the underlying IBufferWriter.
    /// Call after all standalone writes are complete.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Flush()
    {
        Flush(_buffer, _position);
    }

    /// <summary>
    /// Commits pending bytes and invalidates the current chunk so that the underlying
    /// IBufferWriter can be used directly (e.g. by AcBinarySerializer).
    /// The next standalone write will re-acquire a fresh chunk via Grow.
    /// </summary>
    public void FlushAndReset()
    {
        var bytesInChunk = _position - _currentChunkStart;
        if (bytesInChunk > 0)
        {
            if (_ownedBuffer)
                FlushOwnedBuffer(_buffer, bytesInChunk);
            else
                _writer.Advance(bytesInChunk);
            _committedBytes += bytesInChunk;
        }
        // Invalidate chunk — next write triggers Grow → AcquireChunk
        _position = _bufferEnd;
        _currentChunkStart = _bufferEnd;
    }

    #endregion
}
