/**
 * Generic fixed-capacity ring buffer (circular buffer).
 *
 * All writes are O(1). When the buffer is full the oldest entry is evicted
 * (FIFO); eviction is observable through `evictedCount` and the optional
 * `onEvict` callback. Reading all entries is O(n) where n ≤ capacity.
 *
 * This is the canonical ring-buffer utility for the SDK. Prefer this over
 * ad-hoc implementations when bounded-memory queues are needed.
 */
/**
 * A generic ring buffer with a fixed capacity.
 *
 * Entries are stored in insertion order. When the buffer is full the oldest
 * entry is overwritten (FIFO eviction). All push operations are O(1).
 */
export class RingBuffer {
    _buf;
    _onEvict;
    _head = 0; // next-write slot
    _count = 0; // current occupancy (≤ capacity)
    _evictedCount = 0;
    capacity;
    constructor(capacity, options = {}) {
        if (capacity < 1)
            throw new RangeError(`RingBuffer capacity must be >= 1, got ${capacity}`);
        this.capacity = capacity;
        this._onEvict = options.onEvict;
        this._buf = new Array(capacity).fill(undefined);
    }
    /** Number of entries currently stored (≤ capacity). */
    get size() {
        return this._count;
    }
    /** True when the buffer holds at least one entry. */
    get isEmpty() {
        return this._count === 0;
    }
    /** Number of entries evicted since construction or the last `clear()`. */
    get evictedCount() {
        return this._evictedCount;
    }
    /**
     * Push an entry into the buffer.
     *
     * If the buffer is full the oldest entry is evicted to make room and
     * `evictedCount` is incremented.
     * Always O(1).
     */
    push(item) {
        if (this._count === this.capacity) {
            const evicted = this._buf[this._head];
            this._evictedCount++;
            this._onEvict?.(evicted);
        }
        this._buf[this._head] = item;
        this._head = (this._head + 1) % this.capacity;
        if (this._count < this.capacity)
            this._count++;
    }
    /**
     * Return all entries in insertion order (oldest → newest).
     *
     * Allocates a new array of length `size` per call.
     */
    toArray() {
        if (this._count === 0)
            return [];
        const result = new Array(this._count);
        const oldest = this._count < this.capacity ? 0 : this._head;
        for (let i = 0; i < this._count; i++) {
            result[i] = this._buf[(oldest + i) % this.capacity];
        }
        return result;
    }
    /**
     * Return the N most recent entries in insertion order (oldest → newest).
     *
     * If `n >= size` returns the same result as `toArray()`.
     */
    takeLast(n) {
        if (n <= 0)
            return [];
        const all = this.toArray();
        return n >= all.length ? all : all.slice(all.length - n);
    }
    /**
     * Return the N most recent entries in reverse insertion order (newest → oldest).
     *
     * Useful for "latest first" display without an extra `.reverse()` call.
     */
    takeLastReversed(n) {
        if (n <= 0)
            return [];
        const count = Math.min(n, this._count);
        const result = new Array(count);
        for (let i = 0; i < count; i++) {
            // Walk backwards from head-1 (most recent write)
            const idx = (this._head - 1 - i + this.capacity) % this.capacity;
            result[i] = this._buf[idx];
        }
        return result;
    }
    /** Remove all entries and reset internal state. Capacity is preserved. */
    clear() {
        this._buf.fill(undefined);
        this._head = 0;
        this._count = 0;
        this._evictedCount = 0;
    }
}
