"use strict";
/**
 * Module: dialog-display-state
 *
 * Owns the persisted/broadcast display-state projection for dialogs, plus best-effort
 * cancellation for in-flight dialog drives.
 *
 * Design constraints:
 * - `displayState` is a UI/diagnostic projection, not a business source of truth.
 * - Primary control flow must rely on underlying facts (active runs, stop requests,
 *   pending Q4H, active callee dispatches, queued prompts, persisted status, explicit
 *   interruption/death markers).
 * - The projection is persisted to latest.yaml (`DialogLatestFile.displayState`) so it survives
 *   restarts and multi-tab views can converge quickly.
 * - "Stop" is best-effort: it aborts in-flight LLM streaming and lets the driver unwind.
 * - Broadcasting is optional: when configured, display-state updates are pushed to all WS clients
 *   so multi-tab views converge without polling.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRunControlCountsSnapshotEpoch = getRunControlCountsSnapshotEpoch;
exports.setDisplayStateBroadcaster = setDisplayStateBroadcaster;
exports.isStoppedReasonResumable = isStoppedReasonResumable;
exports.isDisplayStateResumable = isDisplayStateResumable;
exports.isExecutionMarkerResumable = isExecutionMarkerResumable;
exports.isDialogLatestResumable = isDialogLatestResumable;
exports.getRunControlCountsSnapshot = getRunControlCountsSnapshot;
exports.createRunControlCountsMessage = createRunControlCountsMessage;
exports.broadcastRunControlCountsSnapshot = broadcastRunControlCountsSnapshot;
exports.hasActiveRun = hasActiveRun;
exports.getActiveRunSignal = getActiveRunSignal;
exports.createActiveRun = createActiveRun;
exports.clearActiveRun = clearActiveRun;
exports.clearMainDialogQuarantiningIfIdle = clearMainDialogQuarantiningIfIdle;
exports.markMainDialogQuarantining = markMainDialogQuarantining;
exports.clearMainDialogQuarantining = clearMainDialogQuarantining;
exports.forceStopActiveRunsForMainDialog = forceStopActiveRunsForMainDialog;
exports.getStopRequestedReason = getStopRequestedReason;
exports.loadDialogExecutionMarker = loadDialogExecutionMarker;
exports.setDialogExecutionMarker = setDialogExecutionMarker;
exports.clearDialogInterruptedExecutionMarker = clearDialogInterruptedExecutionMarker;
exports.setDialogDisplayState = setDialogDisplayState;
exports.broadcastDisplayStateMarker = broadcastDisplayStateMarker;
exports.computeIdleDisplayState = computeIdleDisplayState;
exports.refreshRunControlProjectionFromPersistenceFacts = refreshRunControlProjectionFromPersistenceFacts;
exports.reconcileDisplayStatesAfterRestart = reconcileDisplayStatesAfterRestart;
exports.requestInterruptDialog = requestInterruptDialog;
exports.requestEmergencyStopAll = requestEmergencyStopAll;
const time_1 = require("@longrun-ai/kernel/utils/time");
const crypto_1 = require("crypto");
const dialog_1 = require("./dialog");
const dialog_generation_run_1 = require("./dialog-generation-run");
const dialog_global_registry_1 = require("./dialog-global-registry");
const dialog_interruption_1 = require("./dialog-interruption");
const dialog_latest_state_1 = require("./dialog-latest-state");
const evt_registry_1 = require("./evt-registry");
const log_1 = require("./log");
const persistence_1 = require("./persistence");
const persistence_errors_1 = require("./persistence-errors");
const interjection_pause_stop_1 = require("./runtime/interjection-pause-stop");
const log = (0, log_1.createLogger)('dialog-display-state');
let broadcastToClients;
const activeRunsByDialogKey = new Map();
const quarantiningMainDialogIds = new Set();
const runControlCountsSnapshotEpoch = (0, crypto_1.randomUUID)();
let runControlCountsSnapshotSeq = 0;
let runControlCountsSnapshotQueue = Promise.resolve();
function getRunControlCountsSnapshotEpoch() {
    return runControlCountsSnapshotEpoch;
}
function setDisplayStateBroadcaster(fn) {
    broadcastToClients = fn ?? undefined;
}
function syncRunControlCountsAfterActiveRunChange(trigger, dialogId) {
    void (async () => {
        try {
            await broadcastRunControlCountsSnapshot();
        }
        catch (err) {
            log.warn('Failed to broadcast run-control counts snapshot after active-run change', err, {
                dialogId: dialogId.valueOf(),
                trigger,
            });
        }
    })();
}
function isStoppedReasonResumable(reason) {
    return (0, dialog_interruption_1.isInterruptionReasonManualResumeEligible)(reason);
}
function isDisplayStateResumable(state) {
    return state?.kind === 'stopped' && state.continueEnabled;
}
function isExecutionMarkerResumable(executionMarker) {
    return (executionMarker?.kind === 'interrupted' && isStoppedReasonResumable(executionMarker.reason));
}
function isDialogLatestResumable(latest) {
    return (latest?.displayState?.kind === 'stopped' &&
        latest.displayState.continueEnabled &&
        latest.executionMarker?.kind === 'interrupted');
}
function isSameDisplayState(left, right) {
    return JSON.stringify(left) === JSON.stringify(right);
}
function isNonIdleDisplayProjection(state) {
    return state !== undefined && state.kind !== 'idle_waiting_user';
}
function hasPendingNextStepTriggers(latest) {
    return (latest?.nextStep.triggers.length ?? 0) > 0;
}
function waitingHumanInputDisplayState(hasQ4H) {
    if (hasQ4H) {
        return { kind: 'blocked', reason: { kind: 'needs_human_input' } };
    }
    return undefined;
}
async function waitingSideDialogDisplayState(dialogId, status, activeReplyObligation) {
    if (status !== 'running' || activeReplyObligation === undefined) {
        return undefined;
    }
    const activeCallees = await persistence_1.DialogPersistence.loadActiveCallees(dialogId, status);
    const hasPendingSideDialog = activeCallees.batches.some((batch) => batch.callees.some((callee) => callee.status === 'pending'));
    if (!hasPendingSideDialog) {
        return undefined;
    }
    return { kind: 'waiting_side_dialog' };
}
function pendingReplyObligationDisplayState(activeReplyObligation) {
    if (activeReplyObligation === undefined) {
        return undefined;
    }
    return { kind: 'proceeding' };
}
async function resolveSideDialogFinalResponseClosure(args) {
    if (!args.latest) {
        return { kind: 'no_final_response' };
    }
    const finalResponseAnchor = args.latest.sideDialogFinalResponse;
    if (!finalResponseAnchor) {
        return { kind: 'no_final_response' };
    }
    const finalResponseCallId = finalResponseAnchor.callId.trim();
    const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(args.dialogId, 'running');
    if (activeReplyObligation === undefined) {
        return {
            kind: 'closed_without_active_reply_obligation',
            callId: finalResponseCallId,
        };
    }
    if (activeReplyObligation.targetCallId.trim() === finalResponseCallId) {
        return {
            kind: 'closed_with_matching_reply_obligation',
            callId: finalResponseCallId,
            activeReplyObligation,
        };
    }
    return {
        kind: 'blocked_by_different_reply_obligation',
        callId: finalResponseCallId,
        activeReplyObligation,
    };
}
async function clearMatchingFinalResponseReplyObligation(dialogId, closure) {
    if (closure.kind !== 'closed_with_matching_reply_obligation') {
        return false;
    }
    await persistence_1.DialogPersistence.setActiveTellaskReplyObligation(dialogId, undefined, 'running');
    return true;
}
async function settleFinalResponseClosureForIdleProjection(dialogId, closure) {
    switch (closure.kind) {
        case 'no_final_response':
        case 'blocked_by_different_reply_obligation':
            return false;
        case 'closed_without_active_reply_obligation':
            return true;
        case 'closed_with_matching_reply_obligation':
            await clearMatchingFinalResponseReplyObligation(dialogId, closure);
            return true;
        default: {
            const _exhaustive = closure;
            throw new Error(`Unhandled final response closure kind: ${String(_exhaustive)}`);
        }
    }
}
function classifyRunControlBucket(state) {
    if (!state)
        return 'none';
    if (state.kind === 'proceeding' || state.kind === 'proceeding_stop_requested') {
        return 'proceeding';
    }
    if (isDisplayStateResumable(state)) {
        return 'resumable';
    }
    return 'none';
}
function shouldBroadcastRunControlCounts(previous, next) {
    return classifyRunControlBucket(previous) !== classifyRunControlBucket(next);
}
async function getRunControlCountsSnapshot() {
    let proceeding = 0;
    let resumable = 0;
    const activeRunKeysByDialogKey = new Map(activeRunsByDialogKey.entries());
    const seenDialogKeys = new Set();
    const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
    for (const dialogId of dialogIds) {
        try {
            const dialogKey = dialogId.key();
            seenDialogKeys.add(dialogKey);
            if (quarantiningMainDialogIds.has(dialogId.rootId)) {
                continue;
            }
            // listAllDialogIds() is intentionally a candidate scan. Per-dialog latest reads below may
            // still quarantine one malformed dialog without invalidating the rest of the snapshot.
            const activeRun = activeRunKeysByDialogKey.get(dialogKey);
            if (activeRun) {
                proceeding++;
                continue;
            }
            let latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
            if (!latest) {
                continue;
            }
            const healedLatest = await healStaleSideDialogRunControlAfterFinalResponse({
                dialogId,
                latest,
                trigger: 'run_control_snapshot',
            });
            latest = healedLatest;
            if (latest?.generating === true) {
                proceeding++;
            }
            else if (latest?.executionMarker?.kind === 'interrupted' &&
                isStoppedReasonResumable(latest.executionMarker.reason)) {
                // Keep run-control counts aligned with actual Continue affordance:
                // - ordinary interrupted dialogs count as resumable only when no Q4H human-input wait remains
                // - legacy interjection-paused dialogs still count as resumable even if Q4H remains,
                //   because Continue only re-evaluates the original task from fresh facts
                if ((0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
                    resumable++;
                }
                else {
                    if (latest.userWait?.kind !== 'awaiting_user_answer') {
                        resumable++;
                    }
                }
            }
        }
        catch (error) {
            if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
                throw error;
            }
            log.warn('Skipping malformed dialog during run-control snapshot rebuild', error, {
                dialogId: dialogId.valueOf(),
            });
            continue;
        }
    }
    for (const [dialogKey, activeRun] of activeRunKeysByDialogKey.entries()) {
        if (!activeRun) {
            continue;
        }
        const [rootId] = dialogKey.includes('#') ? dialogKey.split('#') : [dialogKey];
        if (!rootId || quarantiningMainDialogIds.has(rootId)) {
            continue;
        }
        if (!seenDialogKeys.has(dialogKey)) {
            proceeding++;
        }
    }
    return { proceeding, resumable };
}
async function createRunControlCountsMessageUnqueued() {
    const counts = await getRunControlCountsSnapshot();
    const snapshotSeq = ++runControlCountsSnapshotSeq;
    return {
        type: 'run_control_counts_evt',
        proceeding: counts.proceeding,
        resumable: counts.resumable,
        snapshotEpoch: runControlCountsSnapshotEpoch,
        snapshotSeq,
        timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
    };
}
function createRunControlCountsMessage() {
    const nextSnapshot = runControlCountsSnapshotQueue.then(() => createRunControlCountsMessageUnqueued());
    runControlCountsSnapshotQueue = nextSnapshot.then(() => undefined, () => undefined);
    return nextSnapshot;
}
async function broadcastRunControlCountsSnapshot() {
    if (!broadcastToClients)
        return;
    broadcastToClients(await createRunControlCountsMessage());
}
function hasActiveRun(dialogId) {
    return activeRunsByDialogKey.has(dialogId.key());
}
function getActiveRunSignal(dialogId) {
    const run = activeRunsByDialogKey.get(dialogId.key());
    return run?.abortController.signal;
}
function createActiveRun(dialogId) {
    const key = dialogId.key();
    const existing = activeRunsByDialogKey.get(key);
    if (existing) {
        return existing.abortController.signal;
    }
    const run = { abortController: new AbortController() };
    activeRunsByDialogKey.set(key, run);
    syncRunControlCountsAfterActiveRunChange('create_active_run', dialogId);
    return run.abortController.signal;
}
function clearActiveRun(dialogId, options) {
    const deleted = activeRunsByDialogKey.delete(dialogId.key());
    if (!deleted) {
        clearQuarantiningMainDialogIfIdle(dialogId.rootId);
        return;
    }
    clearQuarantiningMainDialogIfIdle(dialogId.rootId);
    if (options?.notifyBackendLoop !== false) {
        dialog_global_registry_1.globalDialogRegistry.notifyActiveRunCleared(dialogId.rootId, {
            source: 'dialog_display_state_active_run_clear',
            reason: dialogId.selfId === dialogId.rootId
                ? 'root_active_run_cleared'
                : 'sideDialog_active_run_cleared',
        });
    }
    syncRunControlCountsAfterActiveRunChange('clear_active_run', dialogId);
}
function clearQuarantiningMainDialogIfIdle(rootId) {
    for (const key of activeRunsByDialogKey.keys()) {
        const [candidateRootId] = key.includes('#') ? key.split('#') : [key];
        if (candidateRootId === rootId) {
            return;
        }
    }
    quarantiningMainDialogIds.delete(rootId);
}
function clearMainDialogQuarantiningIfIdle(mainDialogId) {
    clearQuarantiningMainDialogIfIdle(mainDialogId.selfId);
}
function markMainDialogQuarantining(mainDialogId) {
    quarantiningMainDialogIds.add(mainDialogId.selfId);
}
function clearMainDialogQuarantining(mainDialogId) {
    quarantiningMainDialogIds.delete(mainDialogId.selfId);
}
async function forceStopActiveRunsForMainDialog(mainDialogId) {
    for (const key of Array.from(activeRunsByDialogKey.keys())) {
        const [rootId, selfId] = key.includes('#') ? key.split('#') : [key, key];
        if (!rootId || !selfId)
            continue;
        if (rootId !== mainDialogId.selfId)
            continue;
        const dialogId = new dialog_1.DialogID(selfId, rootId);
        const run = activeRunsByDialogKey.get(key);
        if (!run)
            continue;
        if (!run.stopRequested) {
            run.stopRequested = 'emergency_stop';
            try {
                await setDialogDisplayState(dialogId, {
                    kind: 'proceeding_stop_requested',
                    reason: 'emergency_stop',
                });
            }
            catch (error) {
                log.warn('Failed to persist stop-requested state while forcing main dialog stop', error, {
                    dialogId: dialogId.valueOf(),
                    mainDialogId: mainDialogId.valueOf(),
                });
            }
        }
        try {
            run.abortController.abort();
        }
        catch (error) {
            log.warn('Failed to abort active run while forcing main dialog stop', error, {
                dialogId: dialogId.valueOf(),
                mainDialogId: mainDialogId.valueOf(),
            });
        }
    }
}
function getStopRequestedReason(dialogId) {
    return activeRunsByDialogKey.get(dialogId.key())?.stopRequested;
}
async function loadDialogExecutionMarker(dialogId, status = 'running') {
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
    return latest?.executionMarker;
}
async function setDialogExecutionMarker(dialogId, executionMarker, status = 'running') {
    if (executionMarker?.kind === 'dead' && dialogId.selfId === dialogId.rootId) {
        log.warn('Rejecting dead executionMarker for main dialog (main dialogs must not be dead)', undefined, {
            dialogId: dialogId.valueOf(),
        });
        return;
    }
    try {
        await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
            kind: 'patch',
            patch: { executionMarker },
        }), status);
    }
    catch (err) {
        log.warn('Failed to persist dialog executionMarker', err, {
            dialogId: dialogId.valueOf(),
            rootId: dialogId.rootId,
            selfId: dialogId.selfId,
            status,
            intendedExecutionMarker: executionMarker ?? null,
        });
    }
}
async function clearDialogInterruptedExecutionMarker(dialogId, status = 'running') {
    try {
        const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
        if (latest?.executionMarker?.kind !== 'interrupted') {
            return;
        }
    }
    catch (err) {
        log.warn('Failed to inspect executionMarker before clearing interrupted marker', err, {
            dialogId: dialogId.valueOf(),
            status,
        });
        return;
    }
    await setDialogExecutionMarker(dialogId, undefined, status);
}
async function setDialogDisplayState(dialogId, displayState, status = 'running') {
    if (displayState.kind === 'dead' && dialogId.selfId === dialogId.rootId) {
        log.warn('Rejecting dead displayState for main dialog (main dialogs must not be dead)', undefined, {
            dialogId: dialogId.valueOf(),
        });
        return;
    }
    let nextDisplayState = displayState;
    let previousDisplayState;
    let previousExecutionMarker;
    // "dead" is irreversible. Once a dialog is marked dead, do not allow overwriting it with
    // another state (best-effort; races may still exist across concurrent writers).
    try {
        const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, status);
        previousDisplayState = latest?.displayState;
        previousExecutionMarker = latest?.executionMarker;
        if (dialogId.selfId !== dialogId.rootId &&
            latest &&
            latest.executionMarker &&
            latest.executionMarker.kind === 'dead' &&
            displayState.kind !== 'dead') {
            const typed = evt_registry_1.dialogEventRegistry.createTypedEvent(dialogId, {
                type: 'dlg_display_state_evt',
                displayState: latest.displayState ?? {
                    kind: 'dead',
                    reason: latest.executionMarker.reason,
                },
            });
            if (broadcastToClients) {
                broadcastToClients(typed);
            }
            return;
        }
        if (status === 'running' && displayState.kind === 'idle_waiting_user') {
            const waitingHumanInput = waitingHumanInputDisplayState(latest?.userWait?.kind === 'awaiting_user_answer');
            if (waitingHumanInput) {
                nextDisplayState = waitingHumanInput;
                log.warn('Redirecting idle displayState to waiting-human-input state', undefined, {
                    dialogId: dialogId.valueOf(),
                    rootId: dialogId.rootId,
                    selfId: dialogId.selfId,
                    status,
                    nextDisplayState,
                    previousDisplayState: latest?.displayState ?? null,
                    previousExecutionMarker: latest?.executionMarker ?? null,
                });
            }
            else if (!(await settleFinalResponseClosureForIdleProjection(dialogId, await resolveSideDialogFinalResponseClosure({
                dialogId,
                latest,
            })))) {
                const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(dialogId, status);
                const waitingSideDialog = await waitingSideDialogDisplayState(dialogId, status, activeReplyObligation);
                if (waitingSideDialog) {
                    nextDisplayState = waitingSideDialog;
                    log.warn('Redirecting idle displayState to waiting-side-dialog state', undefined, {
                        dialogId: dialogId.valueOf(),
                        rootId: dialogId.rootId,
                        selfId: dialogId.selfId,
                        status,
                        nextDisplayState,
                        targetCallId: activeReplyObligation?.targetCallId ?? null,
                        targetDialogId: activeReplyObligation?.targetDialogId.valueOf() ?? null,
                        previousDisplayState: latest?.displayState ?? null,
                        previousExecutionMarker: latest?.executionMarker ?? null,
                    });
                }
                else {
                    const pendingReplyObligation = pendingReplyObligationDisplayState(activeReplyObligation);
                    if (pendingReplyObligation) {
                        nextDisplayState = pendingReplyObligation;
                        log.warn('Redirecting idle displayState to active reply obligation state', undefined, {
                            dialogId: dialogId.valueOf(),
                            rootId: dialogId.rootId,
                            selfId: dialogId.selfId,
                            status,
                            nextDisplayState,
                            targetCallId: activeReplyObligation?.targetCallId ?? null,
                            targetDialogId: activeReplyObligation?.targetDialogId.valueOf() ?? null,
                            previousDisplayState: latest?.displayState ?? null,
                            previousExecutionMarker: latest?.executionMarker ?? null,
                            sideDialogFinalResponseCallId: latest?.sideDialogFinalResponse?.callId ?? null,
                        });
                    }
                }
            }
        }
    }
    catch (err) {
        log.warn('Failed to check existing displayState before setDialogDisplayState', err, {
            dialogId: dialogId.valueOf(),
            status,
            intendedDisplayState: displayState,
        });
        if (status === 'running' && displayState.kind === 'idle_waiting_user') {
            return;
        }
    }
    const nextExecutionMarker = nextDisplayState.kind === 'stopped'
        ? { kind: 'interrupted', reason: nextDisplayState.reason }
        : nextDisplayState.kind === 'dead'
            ? { kind: 'dead', reason: nextDisplayState.reason }
            : previousExecutionMarker?.kind === 'interrupted'
                ? undefined
                : previousExecutionMarker;
    try {
        await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
            kind: 'patch',
            patch: { displayState: nextDisplayState, executionMarker: nextExecutionMarker },
        }), status);
    }
    catch (err) {
        log.warn('Failed to persist dialog displayState', err, {
            dialogId: dialogId.valueOf(),
            rootId: dialogId.rootId,
            selfId: dialogId.selfId,
            status,
            intendedDisplayState: displayState,
            persistedDisplayState: nextDisplayState,
        });
    }
    const typed = evt_registry_1.dialogEventRegistry.createTypedEvent(dialogId, {
        type: 'dlg_display_state_evt',
        displayState: nextDisplayState,
    });
    if (broadcastToClients) {
        broadcastToClients(typed);
    }
    if (shouldBroadcastRunControlCounts(previousDisplayState, nextDisplayState)) {
        try {
            await broadcastRunControlCountsSnapshot();
        }
        catch (err) {
            log.warn('Failed to broadcast run-control counts snapshot', err, {
                dialogId: dialogId.valueOf(),
            });
        }
    }
}
function broadcastDisplayStateMarker(dialogId, marker) {
    const typed = evt_registry_1.dialogEventRegistry.createTypedEvent(dialogId, {
        type: 'dlg_display_state_marker_evt',
        kind: marker.kind,
        reason: marker.reason,
    });
    if (broadcastToClients) {
        broadcastToClients(typed);
    }
}
async function computeIdleDisplayState(dlg) {
    if (dlg.status === 'completed' || dlg.status === 'archived') {
        return { kind: 'idle_waiting_user' };
    }
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dlg.id, 'running');
    if (dlg.id.selfId !== dlg.id.rootId &&
        latest?.executionMarker &&
        latest.executionMarker.kind === 'dead') {
        return { kind: 'dead', reason: latest.executionMarker.reason };
    }
    if (latest?.executionMarker?.kind === 'interrupted' &&
        latest.executionMarker.reason.kind !== 'pending_reply_obligation') {
        return {
            kind: 'stopped',
            reason: latest.executionMarker.reason,
            continueEnabled: isStoppedReasonResumable(latest.executionMarker.reason),
        };
    }
    if (latest?.pendingRuntimePrompt) {
        return {
            kind: 'stopped',
            reason: { kind: 'pending_runtime_prompt' },
            continueEnabled: true,
        };
    }
    const waitingHumanInput = waitingHumanInputDisplayState(latest?.userWait?.kind === 'awaiting_user_answer');
    if (waitingHumanInput) {
        return waitingHumanInput;
    }
    const finalResponseClosure = await resolveSideDialogFinalResponseClosure({
        dialogId: dlg.id,
        latest,
    });
    if (await settleFinalResponseClosureForIdleProjection(dlg.id, finalResponseClosure)) {
        return { kind: 'idle_waiting_user' };
    }
    const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(dlg.id, dlg.status);
    const waitingSideDialog = await waitingSideDialogDisplayState(dlg.id, dlg.status, activeReplyObligation);
    if (waitingSideDialog) {
        return waitingSideDialog;
    }
    const pendingReplyObligation = pendingReplyObligationDisplayState(activeReplyObligation);
    if (pendingReplyObligation) {
        return pendingReplyObligation;
    }
    return { kind: 'idle_waiting_user' };
}
async function computeIdleDisplayStateFromPersistence(dialogId) {
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
    const status = latest?.status;
    if (status === 'completed' || status === 'archived') {
        return { kind: 'idle_waiting_user' };
    }
    if (dialogId.selfId !== dialogId.rootId &&
        latest &&
        latest.executionMarker &&
        latest.executionMarker.kind === 'dead') {
        return { kind: 'dead', reason: latest.executionMarker.reason };
    }
    if (latest?.executionMarker?.kind === 'interrupted' &&
        latest.executionMarker.reason.kind !== 'pending_reply_obligation') {
        return {
            kind: 'stopped',
            reason: latest.executionMarker.reason,
            continueEnabled: isStoppedReasonResumable(latest.executionMarker.reason),
        };
    }
    if (latest?.pendingRuntimePrompt) {
        return {
            kind: 'stopped',
            reason: { kind: 'pending_runtime_prompt' },
            continueEnabled: true,
        };
    }
    const waitingHumanInput = waitingHumanInputDisplayState(latest?.userWait?.kind === 'awaiting_user_answer');
    if (waitingHumanInput) {
        return waitingHumanInput;
    }
    const finalResponseClosure = await resolveSideDialogFinalResponseClosure({ dialogId, latest });
    if (await settleFinalResponseClosureForIdleProjection(dialogId, finalResponseClosure)) {
        return { kind: 'idle_waiting_user' };
    }
    const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(dialogId, 'running');
    const waitingSideDialog = await waitingSideDialogDisplayState(dialogId, 'running', activeReplyObligation);
    if (waitingSideDialog) {
        return waitingSideDialog;
    }
    const pendingReplyObligation = pendingReplyObligationDisplayState(activeReplyObligation);
    if (pendingReplyObligation) {
        return pendingReplyObligation;
    }
    return { kind: 'idle_waiting_user' };
}
async function healStaleSideDialogRunControlAfterFinalResponse(args) {
    if (args.dialogId.selfId === args.dialogId.rootId ||
        (!hasPendingNextStepTriggers(args.latest) &&
            args.latest.generating !== true &&
            args.latest.executionMarker?.kind !== 'interrupted' &&
            !isNonIdleDisplayProjection(args.latest.displayState))) {
        return args.latest;
    }
    if (args.latest.executionMarker?.kind === 'dead') {
        return args.latest;
    }
    if (args.latest.pendingRuntimePrompt) {
        return args.latest;
    }
    const finalResponseClosure = await resolveSideDialogFinalResponseClosure({
        dialogId: args.dialogId,
        latest: args.latest,
    });
    if (finalResponseClosure.kind !== 'closed_without_active_reply_obligation' &&
        finalResponseClosure.kind !== 'closed_with_matching_reply_obligation') {
        return args.latest;
    }
    if (!(await settleFinalResponseClosureForIdleProjection(args.dialogId, finalResponseClosure))) {
        return args.latest;
    }
    const clearedReplyObligation = finalResponseClosure.kind === 'closed_with_matching_reply_obligation';
    log.warn('Healing stale sideDialog run-control flags after final response anchor', undefined, {
        dialogId: args.dialogId.valueOf(),
        trigger: args.trigger,
        responseCallId: finalResponseClosure.callId,
        clearedReplyObligation,
        previousGenerating: args.latest.generating ?? null,
        previousNextStepTriggerCount: args.latest.nextStep.triggers.length,
        previousDisplayState: args.latest.displayState ?? null,
        previousExecutionMarker: args.latest.executionMarker ?? null,
    });
    await persistence_1.DialogPersistence.mutateDialogLatest(args.dialogId, () => ({
        kind: 'patch',
        patch: {
            generating: false,
            nextStep: (0, dialog_latest_state_1.createEmptyDialogNextStepState)(),
            displayState: { kind: 'idle_waiting_user' },
            executionMarker: undefined,
        },
    }));
    return await persistence_1.DialogPersistence.loadDialogLatest(args.dialogId, 'running');
}
async function refreshRunControlProjectionFromPersistenceFacts(dialogId, trigger) {
    let latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
    if (!latest) {
        return null;
    }
    if (hasActiveRun(dialogId)) {
        return latest;
    }
    const healedStaleSideDialogRunControl = await healStaleSideDialogRunControlAfterFinalResponse({
        dialogId,
        latest,
        trigger,
    });
    if (!healedStaleSideDialogRunControl) {
        return null;
    }
    latest = healedStaleSideDialogRunControl;
    if (latest.generating === true) {
        return latest;
    }
    const desired = await (async () => {
        if (dialogId.selfId !== dialogId.rootId &&
            latest.executionMarker &&
            latest.executionMarker.kind === 'dead') {
            return { kind: 'dead', reason: latest.executionMarker.reason };
        }
        if (latest.executionMarker?.kind === 'interrupted' &&
            (0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
            // WARNING:
            // This is the one place where the projection intentionally preserves legacy
            // paused-interjection stopped state ahead of the current wait facts. That is not a bug:
            // the UI may still need to show that the original task was paused even if the underlying
            // dialog is now waiting on Q4H.
            //
            // The true source-of-truth decision about what Continue should do next lives in `flow.ts`'s
            // resume path, which performs a fresh fact scan at resume time and then either restores the
            // Q4H waiting-human-input state or keeps driving immediately.
            //
            // Do not "heal" this branch away by prioritizing wait facts here; that would collapse the
            // temporary interjection UX and make repeated interjection turns revert too early.
            return {
                kind: 'stopped',
                reason: latest.executionMarker.reason,
                continueEnabled: isStoppedReasonResumable(latest.executionMarker.reason),
            };
        }
        if (latest.pendingRuntimePrompt) {
            return {
                kind: 'stopped',
                reason: { kind: 'pending_runtime_prompt' },
                continueEnabled: true,
            };
        }
        const waitingHumanInput = waitingHumanInputDisplayState(latest.userWait?.kind === 'awaiting_user_answer');
        if (waitingHumanInput) {
            return waitingHumanInput;
        }
        const finalResponseClosure = await resolveSideDialogFinalResponseClosure({ dialogId, latest });
        if (await settleFinalResponseClosureForIdleProjection(dialogId, finalResponseClosure)) {
            return { kind: 'idle_waiting_user' };
        }
        const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(dialogId, 'running');
        const waitingSideDialog = await waitingSideDialogDisplayState(dialogId, 'running', activeReplyObligation);
        if (waitingSideDialog) {
            return waitingSideDialog;
        }
        const pendingReplyObligation = pendingReplyObligationDisplayState(activeReplyObligation);
        if (pendingReplyObligation) {
            return pendingReplyObligation;
        }
        if (latest.executionMarker?.kind === 'interrupted' &&
            latest.executionMarker.reason.kind !== 'pending_reply_obligation') {
            return {
                kind: 'stopped',
                reason: latest.executionMarker.reason,
                continueEnabled: isStoppedReasonResumable(latest.executionMarker.reason),
            };
        }
        return { kind: 'idle_waiting_user' };
    })();
    const executionMarkerNeedsHealing = desired.kind === 'stopped'
        ? latest.executionMarker?.kind !== 'interrupted' ||
            JSON.stringify(latest.executionMarker.reason) !== JSON.stringify(desired.reason)
        : desired.kind === 'dead'
            ? latest.executionMarker?.kind !== 'dead' ||
                JSON.stringify(latest.executionMarker.reason) !== JSON.stringify(desired.reason)
            : latest.executionMarker?.kind === 'interrupted';
    const displayStateNeedsHealing = !isSameDisplayState(latest.displayState, desired);
    if (!displayStateNeedsHealing && !executionMarkerNeedsHealing) {
        return latest;
    }
    log.warn('Healing stale run-control projection from persistence facts', undefined, {
        dialogId: dialogId.valueOf(),
        trigger,
        previousDisplayState: latest.displayState ?? null,
        previousExecutionMarker: latest.executionMarker ?? null,
        healedDisplayState: desired,
    });
    await setDialogDisplayState(dialogId, desired);
    return await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
}
async function computeIdleDisplayStateForReconciliation(dialogId) {
    try {
        return await computeIdleDisplayStateFromPersistence(dialogId);
    }
    catch (error) {
        if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
            throw error;
        }
        log.warn('Skipping malformed dialog during display-state idle reconstruction', error, {
            dialogId: dialogId.valueOf(),
        });
        return null;
    }
}
async function reconcileDisplayStatesAfterRestart() {
    const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
    for (const dialogId of dialogIds) {
        let latest;
        try {
            latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
        }
        catch (error) {
            if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
                throw error;
            }
            log.warn('Skipping malformed dialog during display-state restart reconciliation', error, {
                dialogId: dialogId.valueOf(),
            });
            continue;
        }
        if (!latest) {
            continue;
        }
        const existing = latest.displayState;
        const existingMarker = latest.executionMarker;
        if (existingMarker && existingMarker.kind === 'dead' && dialogId.selfId !== dialogId.rootId) {
            if (latest.generating === true) {
                const displayState = latest.displayState ?? { kind: 'dead', reason: existingMarker.reason };
                try {
                    await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
                        kind: 'patch',
                        patch: {
                            generating: false,
                            displayState,
                            executionMarker: existingMarker,
                        },
                    }));
                }
                catch (err) {
                    log.warn('Failed to clear generating flag for dead dialog after restart', err, {
                        dialogId: dialogId.valueOf(),
                    });
                }
            }
            continue;
        }
        if (latest.generating === true && latest.generationRunState === undefined) {
            await persistence_1.DialogPersistence.quarantineMalformedRuntimeState(dialogId, 'running', 'restart_reconciliation_missing_generation_run_state', `Restart reconciliation refused to recover generating dialog without generationRunState (rootId=${dialogId.rootId}, selfId=${dialogId.selfId})`);
            continue;
        }
        if (dialogId.selfId !== dialogId.rootId &&
            (latest.generating === true ||
                hasPendingNextStepTriggers(latest) ||
                latest.executionMarker?.kind === 'interrupted' ||
                isNonIdleDisplayProjection(latest.displayState))) {
            const healedStaleSideDialogRunControl = await healStaleSideDialogRunControlAfterFinalResponse({
                dialogId,
                latest,
                trigger: 'restart_reconciliation',
            });
            if (!healedStaleSideDialogRunControl) {
                continue;
            }
            latest = healedStaleSideDialogRunControl;
            if (latest.generating !== true && !hasPendingNextStepTriggers(latest)) {
                continue;
            }
        }
        const recoverableGenerationRunState = (0, dialog_generation_run_1.getRecoverableGenerationRunState)(latest);
        if (recoverableGenerationRunState !== undefined) {
            try {
                await persistence_1.DialogPersistence.upsertNextStepTrigger(dialogId, {
                    triggerId: `open-generation-recovery:${dialogId.selfId}:${recoverableGenerationRunState.course}:${recoverableGenerationRunState.genseq}`,
                    kind: 'open_generation_recovery',
                    course: recoverableGenerationRunState.course,
                    genseq: recoverableGenerationRunState.genseq,
                    createdAt: recoverableGenerationRunState.openedAt,
                }, 'running');
                await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
                    kind: 'patch',
                    patch: {
                        displayState: { kind: 'proceeding' },
                        executionMarker: existingMarker?.kind === 'interrupted' &&
                            (existingMarker.reason.kind === 'pending_runtime_prompt' ||
                                existingMarker.reason.kind === 'pending_reply_obligation')
                            ? undefined
                            : existingMarker,
                    },
                }));
            }
            catch (err) {
                log.warn('Failed to preserve open-generation dialog for auto-drive after restart', err, {
                    dialogId: dialogId.valueOf(),
                });
            }
            continue;
        }
        if (latest.generating === true || hasPendingNextStepTriggers(latest)) {
            const nextIdle = await computeIdleDisplayStateForReconciliation(dialogId);
            if (!nextIdle) {
                continue;
            }
            const next = nextIdle.kind === 'blocked' ||
                nextIdle.kind === 'waiting_side_dialog' ||
                nextIdle.kind === 'stopped' ||
                nextIdle.kind === 'dead'
                ? nextIdle
                : {
                    kind: 'stopped',
                    reason: { kind: 'server_restart' },
                    continueEnabled: true,
                };
            try {
                await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
                    kind: 'patch',
                    patch: {
                        generating: false,
                        displayState: next,
                        executionMarker: next.kind === 'stopped' ? { kind: 'interrupted', reason: next.reason } : undefined,
                    },
                }));
            }
            catch (err) {
                log.warn('Failed to reconcile open-generation dialog after restart', err, {
                    dialogId: dialogId.valueOf(),
                });
            }
            continue;
        }
        if (latest.userWait?.kind === 'awaiting_user_answer' ||
            latest.pendingRuntimePrompt !== undefined ||
            latest.executionMarker?.kind === 'interrupted' ||
            isNonIdleDisplayProjection(latest.displayState)) {
            try {
                await refreshRunControlProjectionFromPersistenceFacts(dialogId, 'restart_reconciliation');
            }
            catch (err) {
                log.warn('Failed to refresh stale run-control projection after restart', err, {
                    dialogId: dialogId.valueOf(),
                });
            }
            continue;
        }
        if (!existing) {
            const inferred = await computeIdleDisplayStateForReconciliation(dialogId);
            if (!inferred) {
                continue;
            }
            try {
                await persistence_1.DialogPersistence.mutateDialogLatest(dialogId, () => ({
                    kind: 'patch',
                    patch: { displayState: inferred },
                }));
            }
            catch (err) {
                log.warn('Failed to backfill missing displayState', err, { dialogId: dialogId.valueOf() });
            }
        }
    }
}
async function requestInterruptDialog(dialogId, reason) {
    const key = dialogId.key();
    const run = activeRunsByDialogKey.get(key);
    if (!run) {
        return { applied: false };
    }
    if (run.stopRequested) {
        return { applied: false };
    }
    run.stopRequested = reason;
    await setDialogDisplayState(dialogId, { kind: 'proceeding_stop_requested', reason });
    try {
        run.abortController.abort();
    }
    catch (err) {
        log.warn('Failed to abort active run', err, { dialogId: dialogId.valueOf() });
    }
    return { applied: true };
}
async function requestEmergencyStopAll() {
    const keys = [...activeRunsByDialogKey.keys()];
    let interrupted = 0;
    for (const key of keys) {
        const [rootId, selfId] = key.includes('#') ? key.split('#') : [key, key];
        if (!rootId || !selfId)
            continue;
        const dialogId = new dialog_1.DialogID(selfId, rootId);
        const res = await requestInterruptDialog(dialogId, 'emergency_stop');
        if (res.applied)
            interrupted++;
    }
    return { interrupted };
}
