import { StallTimeoutError } from "./errors.js";
export class IdleMonitor {
    session;
    maxIdleTimeMs;
    onStallCallback;
    timer = null;
    stallError = null;
    stallCallbacks = [];
    subscribed = false;
    constructor(options) {
        this.session = options.session;
        this.maxIdleTimeMs = options.maxIdleTimeMs;
        this.onStallCallback = options.onStall;
    }
    /**
     * Start the idle monitor. Arms the timer and subscribes to session events.
     */
    start() {
        this.stallError = null;
        this.session.resetActivityTimer();
        this.subscribeToEvents();
        this.armTimer();
    }
    /**
     * Stop the idle monitor. Clears the timer.
     *
     * Event subscriptions are intentionally not removed: the IdleMonitor and
     * StreamSession share the same lifecycle (both created per execute() call),
     * so there's no leak. Keeping subscriptions allows the monitor to restart
     * on retry without re-subscribing.
     */
    stop() {
        if (this.timer !== null) {
            clearTimeout(this.timer);
            this.timer = null;
        }
    }
    /**
     * Get the stall error if one has been detected.
     */
    getStallError() {
        return this.stallError;
    }
    /**
     * Returns a promise that rejects when a stall is detected.
     */
    waitForStall() {
        if (this.stallError) {
            return Promise.reject(this.stallError);
        }
        return new Promise((_, reject) => {
            this.stallCallbacks.push(reject);
        });
    }
    subscribeToEvents() {
        if (this.subscribed) {
            return;
        }
        this.subscribed = true;
        this.session.events.on("chunk", this.handleChunk);
        this.session.events.on("retry", this.handleRetry);
        this.session.events.on("session:complete", this.handleSessionEnd);
        this.session.events.on("session:error", this.handleSessionEnd);
        this.session.events.on("session:abort", this.handleSessionEnd);
    }
    handleChunk = () => {
        this.stallError = null;
        this.stallCallbacks = [];
        this.armTimer();
    };
    handleRetry = () => {
        this.stallError = null;
        this.stallCallbacks = [];
        this.session.resetActivityTimer();
        this.armTimer();
    };
    handleSessionEnd = () => {
        this.stop();
    };
    armTimer() {
        if (this.timer !== null) {
            clearTimeout(this.timer);
        }
        this.timer = setTimeout(() => {
            this.fireStall();
        }, this.maxIdleTimeMs);
    }
    fireStall() {
        if (!this.session.isPending()) {
            return;
        }
        const idleTime = this.session.getTimeSinceLastActivity();
        this.stallError = new StallTimeoutError(idleTime, this.maxIdleTimeMs);
        try {
            this.onStallCallback?.(this.stallError);
        }
        catch {
            // Callback failure must not prevent stall detection from working
        }
        for (const callback of this.stallCallbacks) {
            callback(this.stallError);
        }
        this.stallCallbacks = [];
    }
}
//# sourceMappingURL=idle-monitor.js.map