/**
 * Bulkhead worker pool — bounded concurrency with FIFO queue and metrics.
 *
 * Sprint-468 (command-center:sprint-468:bulkhead).
 *
 * createBulkhead returns a typed worker-pool object with:
 *   - run(fn)   — enqueue a zero-argument task; resolves/rejects per task
 *   - metrics() — live snapshot: active, queued, rejected, completed
 *
 * This is the orchestration-layer variant of resilience/bulkhead.ts.
 * The key difference is the factory pattern (one pool, many tasks) vs. the
 * resilience variant that wraps a single fixed function. Use this when the
 * pool itself is a resource to manage (e.g. DB connection pool, LLM slots).
 *
 * Queue discipline: FIFO. Tasks enter the queue in submission order and
 * acquire a worker slot in the same order.
 *
 * Fast-reject: when `rejectOnFull` is true (default) and the queue is at
 * `maxQueue` capacity, new submissions throw `BulkheadQueueFullError`
 * synchronously (before awaiting). When `rejectOnFull` is false, callers
 * block indefinitely until a slot opens — useful for back-pressure scenarios.
 */
export class BulkheadQueueFullError extends Error {
    constructor(active, queued, maxConcurrent, maxQueue) {
        super(`Bulkhead full — active=${active}/${maxConcurrent}, queued=${queued}/${maxQueue}`);
        this.name = "BulkheadQueueFullError";
    }
}
/**
 * Create a typed bulkhead worker pool.
 *
 * @example
 * ```ts
 * const pool = createBulkhead<string>({ maxConcurrent: 3, maxQueue: 10 });
 * const result = await pool.run(() => fetchSomething());
 * console.log(pool.metrics()); // { active: 0, queued: 0, rejected: 0, completed: 1 }
 * ```
 */
export function createBulkhead(opts) {
    const maxConcurrent = opts.maxConcurrent;
    const maxQueue = opts.maxQueue ?? maxConcurrent * 10;
    const rejectOnFull = opts.rejectOnFull !== false; // default true
    let active = 0;
    let rejected = 0;
    let completed = 0;
    const queue = [];
    function tryDequeue() {
        if (active >= maxConcurrent)
            return;
        const next = queue.shift();
        if (next) {
            active += 1;
            next();
        }
    }
    function acquire() {
        if (active < maxConcurrent) {
            active += 1;
            return Promise.resolve();
        }
        if (rejectOnFull && queue.length >= maxQueue) {
            rejected += 1;
            return Promise.reject(new BulkheadQueueFullError(active, queue.length, maxConcurrent, maxQueue));
        }
        // Either not rejectOnFull, or queue has room — enqueue.
        return new Promise((resolve, reject) => {
            if (queue.length >= maxQueue) {
                // Reached here only when rejectOnFull=false but queue just filled
                // concurrently before we could enqueue. Reject as safety valve.
                rejected += 1;
                reject(new BulkheadQueueFullError(active, queue.length, maxConcurrent, maxQueue));
                return;
            }
            queue.push(() => {
                resolve();
            });
        });
    }
    function release() {
        active -= 1;
        completed += 1;
        tryDequeue();
    }
    return {
        async run(fn) {
            await acquire();
            try {
                return await fn();
            }
            finally {
                release();
            }
        },
        metrics() {
            return { active, queued: queue.length, rejected, completed };
        },
    };
}
//# sourceMappingURL=bulkhead.js.map