import { AsyncLocalStorage } from "node:async_hooks";
/**
 * Linearizable admission boundary for externally-produced workflow-stage traffic.
 * JavaScript execution between the state check and state transition is synchronous:
 * an enqueue that wins belongs to the stage, while close makes every later enqueue
 * use the external route. Stable keys make either outcome exactly-once.
 */
export class WorkflowStageAdmissionBoundary {
    constructor(drainAdmittedWork = async () => { }) {
        this.open = true;
        this.completed = new Set();
        this.inFlight = new Map();
        this.pending = new Set();
        this.invocationContext = new AsyncLocalStorage();
        this.drainAdmittedWork = drainAdmittedWork;
    }
    admit(key, deliver, routeLate) {
        if (key !== undefined) {
            if (this.completed.has(key))
                return { decision: "duplicate", completion: Promise.resolve() };
            const inFlight = this.inFlight.get(key);
            if (inFlight) {
                return { decision: "duplicate", completion: this.invocationContext.getStore() === key ? Promise.resolve() : inFlight };
            }
        }
        const decision = this.open ? "admitted" : "late";
        let completion;
        if (key === undefined) {
            completion = this.invoke(this.open ? deliver : routeLate);
        }
        else {
            let resolveCompletion;
            let rejectCompletion;
            completion = new Promise((resolve, reject) => { resolveCompletion = resolve; rejectCompletion = reject; });
            void completion.catch(() => { });
            this.inFlight.set(key, completion);
            const delivery = this.invocationContext.run(key, () => this.invoke(this.open ? deliver : routeLate));
            void delivery.then(resolveCompletion, rejectCompletion);
            void completion.then(() => {
                if (this.inFlight.get(key) !== completion)
                    return;
                this.inFlight.delete(key);
                this.completed.add(key);
            }, () => {
                if (this.inFlight.get(key) === completion)
                    this.inFlight.delete(key);
            });
        }
        if (decision === "admitted")
            this.trackAdmittedWork(completion);
        return { decision, completion };
    }
    trackAdmittedWork(completion) {
        this.pending.add(completion);
        void completion.then(() => this.pending.delete(completion), () => this.pending.delete(completion));
    }
    isOpen() {
        return this.open;
    }
    seal() {
        this.open = false;
    }
    close() {
        this.seal();
        this.closePromise ??= this.finishClose();
        return this.closePromise;
    }
    async finishClose() {
        await Promise.allSettled([...this.pending]);
        await this.drainAdmittedWork();
    }
    invoke(callback) {
        try {
            return Promise.resolve(callback());
        }
        catch (error) {
            return Promise.reject(error);
        }
    }
}
//# sourceMappingURL=workflow-stage-admission.js.map