/**
 * DeferredQuestionRegistry
 *
 * In-process bridge between the SDK V2 `canUseTool` callback (which
 * expects a Promise) and the asynchronous, DB-backed answer that may
 * arrive from a different process (CLI, web). Each register() returns a
 * Promise that resolves on answer, rejects on cancel, or rejects with a
 * typed timeout error after a hard deadline.
 *
 * Implementation notes (spec 093, task 16):
 *  - One internal `Map<id, Entry>` keyed by question id; ids are unique.
 *  - register() throws synchronously on duplicate id so SDK callbacks
 *    can't accidentally overwrite an existing awaiter.
 *  - resolve/reject/timeout always run a single cleanup helper that
 *    clears the timer AND removes the map entry, so no timer leaks.
 *  - cancelAll matches by scope: appId is required (NFR-7), featureId
 *    and agentRunId narrow further. A scope with only appId rejects
 *    every awaiter under that app.
 *  - Settling a non-existent id is a no-op (e.g. an answer arrives for
 *    a question whose registration was never created in this process).
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { injectable } from 'tsyringe';
import { AgentQuestionCancelledError, AgentQuestionTimeoutError, DEFAULT_DEFERRED_QUESTION_TIMEOUT_MS, } from '../../../../application/ports/output/agents/agent-question-service.interface.js';
let DeferredQuestionRegistry = class DeferredQuestionRegistry {
    entries = new Map();
    register(id, scope, timeoutMs) {
        if (this.entries.has(id)) {
            throw new Error(`DeferredQuestionRegistry: question id "${id}" is already registered`);
        }
        const effectiveTimeout = timeoutMs ?? DEFAULT_DEFERRED_QUESTION_TIMEOUT_MS;
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                this.cleanup(id);
                reject(new AgentQuestionTimeoutError(id, effectiveTimeout));
            }, effectiveTimeout);
            this.entries.set(id, {
                resolve: (answer) => {
                    this.cleanup(id);
                    resolve(answer);
                },
                reject: (err) => {
                    this.cleanup(id);
                    reject(err);
                },
                timer,
                scope,
            });
        });
    }
    resolve(id, answer) {
        const entry = this.entries.get(id);
        if (!entry)
            return;
        entry.resolve(answer);
    }
    reject(id, reason) {
        const entry = this.entries.get(id);
        if (!entry)
            return;
        entry.reject(new AgentQuestionCancelledError(id, reason));
    }
    cancelAll(scope, reason) {
        let cancelled = 0;
        for (const [id, entry] of this.entries) {
            if (!matchesScope(entry.scope, scope))
                continue;
            entry.reject(new AgentQuestionCancelledError(id, reason));
            cancelled++;
        }
        return cancelled;
    }
    has(id) {
        return this.entries.has(id);
    }
    cleanup(id) {
        const entry = this.entries.get(id);
        if (!entry)
            return;
        clearTimeout(entry.timer);
        this.entries.delete(id);
    }
};
DeferredQuestionRegistry = __decorate([
    injectable()
], DeferredQuestionRegistry);
export { DeferredQuestionRegistry };
function matchesScope(entry, target) {
    if (entry.appId !== target.appId)
        return false;
    if (target.featureId !== undefined && entry.featureId !== target.featureId)
        return false;
    if (target.agentRunId !== undefined && entry.agentRunId !== target.agentRunId)
        return false;
    return true;
}
