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

/**
 * Registry that maps `type_id ↔ SimAction subclass`, with a small per-type
 * object pool to recycle action instances (avoids GC pressure on the hot path).
 *
 * Wire format: `type_id` is a `uint8`. The cap of 256 distinct action types is
 * deliberate — if you need more, your design probably wants a generic
 * `MutateComponentAction` rather than a bespoke action per case.
 *
 * Registration is order-sensitive: `type_id` is assigned in registration order
 * starting from 0. To keep `type_id` stable across versions, register actions
 * in a fixed order at engine init.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class SimActionRegistry {
    #classes_by_id;
    #pools;


    constructor() {
        /**
         * @type {Function[]}
         * @private
         */
        this.#classes_by_id = [];

        /**
         * Per-type instance pool. `pools[type_id]` is an array of available SimAction instances.
         * @type {SimAction[][]}
         * @private
         */
        this.#pools = [];

        /**
         * Maximum pool size per type. Beyond this, released instances are dropped.
         * @type {number}
         */
        this.max_pool_size = 64;
    }

    /**
     * Register a SimAction subclass and assign it a stable `type_id`.
     * Returns the assigned id.
     *
     * @param {Function} klass
     * @returns {number} assigned type_id
     */
    register(klass) {
        assert.isFunction(klass, 'klass');
        assert.notEqual(klass.type_id, undefined, "klass must inherit static type_id from SimAction");

        const type_id = this.#classes_by_id.length;
        if (type_id > 0xFF) {
            throw new Error(`SimActionRegistry: at most 256 action types are supported, tried to register ${type_id + 1}`);
        }

        klass.type_id = type_id;
        this.#classes_by_id[type_id] = klass;
        this.#pools[type_id] = [];

        return type_id;
    }

    /**
     * Look up an action class by type_id. Returns undefined if unknown.
     *
     * @param {number} type_id
     * @returns {Function|undefined}
     */
    klass_for(type_id) {
        assert.isNonNegativeInteger(type_id, 'type_id');
        return this.#classes_by_id[type_id];
    }

    /**
     * Acquire a recycled instance of the given action class, or create a fresh
     * one if the pool is empty. The returned instance has had `reset()` called.
     *
     * @param {Function|{type_id:number}} klass
     * @returns {SimAction}
     */
    acquire(klass) {
        assert.isFunction(klass, 'klass');
        const pool = this.#pools[klass.type_id];

        if (pool === undefined) {
            throw new Error(`SimActionRegistry.acquire: ${klass.name} is not registered`);
        }

        if (pool.length > 0) {
            const instance = pool.pop();
            instance.reset();
            return instance;
        }

        return new klass();
    }

    /**
     * Release an instance back to its pool. Drops on the floor if the pool is full.
     *
     * @param {SimAction} instance
     */
    release(instance) {
        assert.ok(instance && instance.isSimAction, 'release: instance must be a SimAction');
        const type_id = instance.constructor.type_id;
        const pool = this.#pools[type_id];

        if (pool === undefined) {
            return; // unregistered — let it GC
        }

        if (pool.length < this.max_pool_size) {
            pool.push(instance);
        }
    }

    /**
     * Number of registered action types.
     * @returns {number}
     */
    type_count() {
        return this.#classes_by_id.length;
    }
}
