import { randomUUID } from 'node:crypto';
import { SESSION_LEASE_REFRESH_INTERVAL_MS } from '../../../core/chat/engine/sessions/leases/index.js';
/**
 * Owns control-plane run coordination for session-scoped long-running work.
 *
 * Core chat services own persisted conversation semantics. This service owns
 * process-local runtime coordination: in-flight runs, abort controllers,
 * approval resolvers, and the async-start vs wait-for-result split.
 */
export class ControlPlaneSessionRunService {
    pendingApprovals = new Map();
    inFlightRuns = new Map();
    start(input) {
        const key = ControlPlaneSessionRunService.addressKey(input.address);
        if (this.inFlightRuns.has(key)) {
            throw new Error('A run is already in progress for this session.');
        }
        const run = {
            runId: `session-run-${randomUUID()}`,
            acceptedAt: new Date().toISOString(),
            controller: new AbortController(),
        };
        let accepted = false;
        let acceptanceError;
        let stopHeartbeat;
        const result = Promise.resolve()
            .then(() => {
            if (!accepted) {
                throw acceptanceError ?? new Error(`Accepted run never started: ${run.runId}`);
            }
            return input.execute(run);
        })
            .catch(async (error) => {
            if (accepted) {
                await input.onError?.(error, run);
            }
            throw error;
        })
            .finally(() => {
            stopHeartbeat?.();
            this.pendingApprovals.delete(key);
            this.inFlightRuns.delete(key);
            if (accepted) {
                void Promise.resolve(input.onSettled?.(run)).catch(() => undefined);
            }
        });
        this.inFlightRuns.set(key, { ...run, result });
        result.catch(() => undefined);
        try {
            input.onAccepted?.(run);
            stopHeartbeat = this.startHeartbeat(run, input.onHeartbeat);
            accepted = true;
        }
        catch (error) {
            acceptanceError = error;
            this.pendingApprovals.delete(key);
            this.inFlightRuns.delete(key);
            throw error;
        }
        return {
            accepted: true,
            workspaceId: input.address.workspaceId,
            sessionId: input.address.sessionId,
            runId: run.runId,
            acceptedAt: run.acceptedAt,
        };
    }
    async startAndWait(input) {
        const accepted = this.start(input);
        if (!('accepted' in accepted)) {
            throw new Error(`Expected accepted run for ${input.address.sessionId}.`);
        }
        return await this.requireRun(input.address, accepted.runId).result;
    }
    isRunning(address) {
        return this.inFlightRuns.has(ControlPlaneSessionRunService.addressKey(address));
    }
    cancelRun(address) {
        const key = ControlPlaneSessionRunService.addressKey(address);
        const run = this.inFlightRuns.get(key);
        if (!run) {
            return false;
        }
        run.controller.abort();
        const pending = this.pendingApprovals.get(key);
        if (pending) {
            this.pendingApprovals.delete(key);
            pending.resolve({
                type: 'deny',
                reason: 'Cancelled by user',
            });
        }
        return true;
    }
    getPendingApproval(address) {
        return this.pendingApprovals.get(ControlPlaneSessionRunService.addressKey(address))?.approval;
    }
    storePendingApproval(address, pending) {
        this.pendingApprovals.set(ControlPlaneSessionRunService.addressKey(address), pending);
    }
    clearPendingApproval(address) {
        this.pendingApprovals.delete(ControlPlaneSessionRunService.addressKey(address));
    }
    resolvePendingApproval(address, decision) {
        const key = ControlPlaneSessionRunService.addressKey(address);
        const pending = this.pendingApprovals.get(key);
        if (!pending) {
            return false;
        }
        this.pendingApprovals.delete(key);
        pending.resolve(decision);
        return true;
    }
    requireRun(address, runId) {
        const run = this.inFlightRuns.get(ControlPlaneSessionRunService.addressKey(address));
        if (!run || run.runId !== runId) {
            throw new Error(`Accepted run is no longer active: ${runId}`);
        }
        return run;
    }
    static addressKey(address) {
        return `${address.workspaceId}:${address.sessionId}`;
    }
    startHeartbeat(run, onHeartbeat) {
        if (!onHeartbeat) {
            return undefined;
        }
        let refreshing = false;
        const timer = setInterval(() => {
            if (refreshing || run.controller.signal.aborted) {
                return;
            }
            refreshing = true;
            Promise.resolve()
                .then(() => onHeartbeat(run))
                .catch(() => {
                run.controller.abort();
            })
                .finally(() => {
                refreshing = false;
            });
        }, SESSION_LEASE_REFRESH_INTERVAL_MS);
        return () => {
            clearInterval(timer);
        };
    }
}
//# sourceMappingURL=session-run-service.js.map