using System;
using System.Buffers;
using System.Runtime.CompilerServices;

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// High-performance binary output backed by a byte[] from ArrayPool.
/// Only handles buffer lifecycle (Initialize, Grow, result extraction).
/// All write methods live in BinarySerializationContext for zero virtual dispatch.
///
/// This is the fastest output path — use when the result is needed as byte[]/Span.
/// </summary>
public struct ArrayBinaryOutput : IBinaryOutputBase, IDisposable
{
    private const int DefaultMinBufferSize = 256;
    private const int MaxKeepBufferSize = 32 * 1024; // 32KB — below this, keep for reuse

    private readonly int _initialCapacity;
    private byte[]? _rentedBuffer;

    public ArrayBinaryOutput(int initialCapacity = 65535)
    {
        _initialCapacity = Math.Max(initialCapacity, DefaultMinBufferSize);
        _rentedBuffer = ArrayPool<byte>.Shared.Rent(_initialCapacity);
    }

    /// <summary>
    /// Provides the initial buffer. Position starts at 0, bufferEnd = buffer.Length.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Initialize(out byte[] buffer, out int position, out int bufferEnd)
    {
        buffer = _rentedBuffer;
        position = 0;
        bufferEnd = _rentedBuffer.Length;
    }

    /// <summary>
    /// Grows the buffer: rents a bigger one from ArrayPool, copies old data, returns old to pool.
    /// Position is unchanged (data was copied to the same offset in the new buffer).
    /// </summary>
    [MethodImpl(MethodImplOptions.NoInlining)]
    public void Grow(ref byte[] buffer, ref int position, ref int bufferEnd, int needed)
    {
        var required = position + needed;
        var newSize = Math.Max(buffer.Length * 2, required);
        var newBuffer = ArrayPool<byte>.Shared.Rent(newSize);
        buffer.AsSpan(0, position).CopyTo(newBuffer);
        ArrayPool<byte>.Shared.Return(buffer);
        buffer = newBuffer;
        bufferEnd = newBuffer.Length;
        _rentedBuffer = newBuffer;
    }

    /// <summary>
    /// For ArrayBinaryOutput, position IS the total (single contiguous buffer).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int GetTotalPosition(int currentPosition) => currentPosition;

    #region Result Extraction — receive buffer/position from context

    //TODO: miért nem static a AsSpan?
    /// <summary>Returns the written data as a ReadOnlySpan without allocation.</summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlySpan<byte> AsSpan(byte[] buffer, int position) => buffer.AsSpan(0, position);

    //TODO: miért nem static a ToArray? Miért nem valami static common osztályban van?
    /// <summary>Copies the written data to a new exactly-sized array.</summary>
    public byte[] ToArray(byte[] buffer, int position)
    {
        var result = GC.AllocateUninitializedArray<byte>(position);
        buffer.AsSpan(0, position).CopyTo(result);
        return result;
    }

    ///// <summary>Copies the written data to an IBufferWriter (single memcpy).</summary>
    //public void WriteTo(IBufferWriter<byte> writer, byte[] buffer, int position)
    //{
    //    var span = writer.GetSpan(position);
    //    buffer.AsSpan(0, position).CopyTo(span);
    //    writer.Advance(position);
    //}

    //TODO: miért nem static a DetachResult?
    /// <summary>
    /// Detaches the internal buffer as a BinarySerializationResult and allocates a fresh buffer.
    /// The caller owns the returned result and must dispose it to return the buffer to the pool.
    /// </summary>
    public AcBinarySerializer.BinarySerializationResult DetachResult(byte[] buffer, int position)
    {
        var resultBuffer = buffer;
        var resultLength = position;

        //_rentedBuffer = ArrayPool<byte>.Shared.Rent(Math.Max(resultBuffer.Length / 2, _initialCapacity));

        return new AcBinarySerializer.BinarySerializationResult(resultBuffer, resultLength, pooled: true);
    }

    /// <summary>
    /// Resets for reuse when context is returned to pool.
    /// Small buffers (≤ MaxKeepBufferSize): keep as-is (faster than pool round-trip).
    /// Large buffers: return to pool, rent a halved one to avoid memory bloat.
    /// </summary>
    public void Reset()
    {
        if (_rentedBuffer == null) return;

        // Small buffer: keep as-is
        if (_rentedBuffer.Length <= MaxKeepBufferSize) return;

        // Large buffer: return to pool, rent half size
        var nextCapacity = Math.Max(_rentedBuffer.Length / 2, _initialCapacity);
        ArrayPool<byte>.Shared.Return(_rentedBuffer);

        _rentedBuffer = nextCapacity == _initialCapacity ? null : ArrayPool<byte>.Shared.Rent(nextCapacity);
    }

    #endregion

    #region IDisposable

    public void Dispose()
    {
        if (_rentedBuffer != null)
        {
            ArrayPool<byte>.Shared.Return(_rentedBuffer);
            _rentedBuffer = null!;
        }
    }

    #endregion
}
