// The in-memory run registry (ADR-0011 D7). It maps a runId to a live run record (the streaming
// sink, the run status, the captured final report, and a cancel handle). It is BOUNDED on two axes:
// a cap on simultaneously-active runs (a new run is refused past the cap) and a TTL after which a
// TERMINATED run's record — including its event ring buffer — is evicted to reclaim memory (D7's
// documented retention: buffers are dropped once the run terminates and its TTL elapses). The
// registry is created via `createRunRegistry` and hung off the handler deps, never a module global,
// so each server instance (and each test) owns an isolated registry with no cross-talk.
export class ActiveRunLimitError extends Error {
    constructor(limit) {
        super(`active run limit reached (${String(limit)})`);
        this.name = "ActiveRunLimitError";
    }
}
const DEFAULT_MAX_ACTIVE_RUNS = 16;
const DEFAULT_TERMINATED_TTL_MS = 600_000;
function isTerminal(status) {
    return status !== "running";
}
function evictExpired(state) {
    const cutoff = state.now();
    for (const [runId, record] of state.records) {
        if (record.terminatedAt !== undefined && cutoff - record.terminatedAt >= state.ttlMs) {
            state.records.delete(runId);
        }
    }
}
function countActive(state) {
    let count = 0;
    for (const record of state.records.values()) {
        if (!isTerminal(record.status)) {
            count += 1;
        }
    }
    return count;
}
function registerRun(state, input) {
    evictExpired(state);
    if (countActive(state) >= state.maxActive) {
        throw new ActiveRunLimitError(state.maxActive);
    }
    const record = {
        runId: input.runId,
        fingerprint: input.fingerprint,
        modelId: input.modelId,
        sink: input.sink,
        status: "running",
        report: undefined,
        cancel: input.cancel,
        appliable: undefined,
        applyReport: undefined,
        appliedAt: undefined,
        terminatedAt: undefined,
    };
    state.records.set(input.runId, record);
    return record;
}
function completeRun(state, runId, status, report, appliable) {
    const record = state.records.get(runId);
    if (record === undefined) {
        return;
    }
    record.status = status;
    record.report = report;
    record.appliable = appliable;
    record.terminatedAt = state.now();
}
export function createRunRegistry(options = {}) {
    const state = {
        records: new Map(),
        maxActive: options.maxActiveRuns ?? DEFAULT_MAX_ACTIVE_RUNS,
        ttlMs: options.terminatedTtlMs ?? DEFAULT_TERMINATED_TTL_MS,
        now: options.now ?? Date.now,
    };
    return {
        register: (input) => registerRun(state, input),
        get: (runId) => {
            evictExpired(state);
            return state.records.get(runId);
        },
        complete: (runId, status, report, appliable) => {
            completeRun(state, runId, status, report, appliable);
        },
        snapshot: (limit) => {
            evictExpired(state);
            const records = Array.from(state.records.values());
            if (limit === undefined || limit >= records.length) {
                return records;
            }
            return records.slice(Math.max(0, records.length - limit));
        },
        activeCount: () => {
            evictExpired(state);
            return countActive(state);
        },
        size: () => {
            evictExpired(state);
            return state.records.size;
        },
    };
}
