import { assert } from "../../../core/assert.js";

/**
 * Per-peer "last acknowledged frame" tracker.
 *
 * The replicator sends actions for frames `[Baseline.last_acked(peer) + 1, current]`
 * to each peer. When the peer's ack arrives (via the {@link Channel}), it
 * advances the baseline.
 *
 * Frame numbers are absolute uint32. At 60 Hz, 2^32 frames = 2.27 years —
 * we can ignore wraparound for practical sessions and use plain `>` comparison.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Baseline {
    #last_acked;


    constructor() {
        /**
         * peer_id -> last acked frame number
         * @type {Map<number, number>}
         * @private
         */
        this.#last_acked = new Map();
    }

    /**
     * Record that `peer_id` has acknowledged through `frame`. Monotonic: out-of-order
     * acks (for an older frame than already recorded) are ignored.
     *
     * @param {number} peer_id
     * @param {number} frame
     */
    set_acked(peer_id, frame) {
        assert.isNonNegativeInteger(peer_id, 'peer_id');
        assert.isNonNegativeInteger(frame, 'frame');
        const current = this.#last_acked.get(peer_id);
        if (current === undefined || frame > current) {
            this.#last_acked.set(peer_id, frame);
        }
    }

    /**
     * Returns the most recent acknowledged frame for `peer_id`, or -1 if none.
     *
     * @param {number} peer_id
     * @returns {number}
     */
    last_acked(peer_id) {
        assert.isNonNegativeInteger(peer_id, 'peer_id');
        const v = this.#last_acked.get(peer_id);
        return v === undefined ? -1 : v;
    }

    /**
     * Forget tracking for `peer_id` (e.g. on disconnect).
     *
     * @param {number} peer_id
     */
    forget(peer_id) {
        assert.isNonNegativeInteger(peer_id, 'peer_id');
        this.#last_acked.delete(peer_id);
    }

    /**
     * Number of tracked peers.
     * @returns {number}
     */
    peer_count() {
        return this.#last_acked.size;
    }

    /**
     * Iterate (peer_id, last_acked_frame) pairs.
     * @param {function(number, number): void} fn
     */
    for_each(fn) {
        assert.isFunction(fn, 'fn');
        this.#last_acked.forEach((frame, peer_id) => fn(peer_id, frame));
    }
}
