"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.driveQueuedDialogsOnce = driveQueuedDialogsOnce;
exports.runBackendDriver = runBackendDriver;
const dialog_1 = require("../../dialog");
const dialog_display_state_1 = require("../../dialog-display-state");
const dialog_drive_work_1 = require("../../dialog-drive-work");
const dialog_generation_run_1 = require("../../dialog-generation-run");
const dialog_global_registry_1 = require("../../dialog-global-registry");
const dialog_instance_registry_1 = require("../../dialog-instance-registry");
const dialog_interruption_1 = require("../../dialog-interruption");
const log_1 = require("../../log");
const persistence_1 = require("../../persistence");
const persistence_errors_1 = require("../../persistence-errors");
const engine_1 = require("./engine");
function formatDriveError(error) {
    if (error instanceof Error) {
        return {
            name: error.name,
            message: error.message,
            ...(typeof error.stack === 'string' && error.stack.trim() !== ''
                ? { stack: error.stack }
                : {}),
        };
    }
    return { message: String(error) };
}
function formatDriveTriggerForLog(trigger) {
    return {
        action: trigger.action,
        rootId: trigger.rootId,
        entryFound: trigger.entryFound,
        previousDriveQueued: trigger.previousDriveQueued,
        nextDriveQueued: trigger.nextDriveQueued,
        source: trigger.source,
        reason: trigger.reason,
        emittedAtMs: trigger.emittedAtMs,
    };
}
function wakeQueueHasEntriesForDialog(entries, dialogId) {
    return entries.some((entry) => entry.targetDialogId === dialogId.selfId);
}
function wakeQueueHasRootRuntimeWake(entries, dialogId) {
    if (dialogId.selfId !== dialogId.rootId) {
        return false;
    }
    return entries.some((entry) => entry.kind === 'root_runtime_wake' && entry.targetDialogId === dialogId.selfId);
}
async function removeOpenGenerationRecoveryTriggers(dialog) {
    await persistence_1.DialogPersistence.removeNextStepTriggers(dialog.id, (trigger) => trigger.kind === 'open_generation_recovery', dialog.status);
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
    if (latest) {
        await persistence_1.DialogPersistence.syncWakeQueueForDialogLatest(dialog.id, latest, dialog.status);
    }
    else {
        await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
    }
}
async function claimOpenGenerationRecoveryForBackendLoop(args) {
    if (!args.latest) {
        return { status: 'not_applicable' };
    }
    if ((0, dialog_generation_run_1.getRecoverableGenerationRunState)(args.latest) !== undefined) {
        return { status: 'claimed' };
    }
    const hasRecoveryTrigger = args.latest.nextStep.triggers.some((trigger) => trigger.kind === 'open_generation_recovery');
    if (!hasRecoveryTrigger) {
        return { status: 'not_applicable' };
    }
    await removeOpenGenerationRecoveryTriggers(args.dialog);
    return {
        status: 'stale',
        latestAfterCleanup: await persistence_1.DialogPersistence.loadDialogLatest(args.dialog.id, args.dialog.status),
    };
}
async function listLiveDialogsWithDurableDriveWork() {
    const liveDialogs = dialog_global_registry_1.globalDialogRegistry.consumeQueuedMainDialogs();
    const queued = [];
    for (const mainDialog of liveDialogs) {
        let wakeQueueEntries;
        try {
            wakeQueueEntries = await persistence_1.DialogPersistence.loadWakeQueueEntries(mainDialog.id, mainDialog.status);
        }
        catch (error) {
            log_1.log.error('Backend driver skipped root because wake queue could not be loaded', error, {
                rootId: mainDialog.id.rootId,
                selfId: mainDialog.id.selfId,
            });
            continue;
        }
        const wakeQueueTargetDialogIds = [
            ...new Set(wakeQueueEntries
                .map((entry) => entry.targetDialogId)
                .filter((selfId) => selfId !== mainDialog.id.rootId)),
        ].map((selfId) => new dialog_1.DialogID(selfId, mainDialog.id.rootId));
        const candidateDialogs = [mainDialog];
        let hadCandidateInspectionError = false;
        let hadStalledCandidateForRoot = false;
        for (const wakeQueueTargetDialogId of wakeQueueTargetDialogIds) {
            let wakeQueueTargetDialog;
            try {
                wakeQueueTargetDialog = await (0, dialog_instance_registry_1.ensureDialogLoaded)(mainDialog, wakeQueueTargetDialogId, mainDialog.status);
            }
            catch (error) {
                hadCandidateInspectionError = true;
                log_1.log.error('Backend driver failed to restore Wake Queue target side dialog', error, {
                    rootId: mainDialog.id.rootId,
                    selfId: wakeQueueTargetDialogId.selfId,
                });
                continue;
            }
            if (wakeQueueTargetDialog !== undefined) {
                candidateDialogs.push(wakeQueueTargetDialog);
            }
            else {
                log_1.log.warn('Backend driver could not restore Wake Queue target side dialog; dropping wake queue entries', undefined, {
                    rootId: mainDialog.id.rootId,
                    selfId: wakeQueueTargetDialogId.selfId,
                });
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(wakeQueueTargetDialogId, mainDialog.status);
            }
        }
        let hasQueuedCandidateForRoot = false;
        for (const dialog of candidateDialogs) {
            let latest;
            try {
                latest = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
            }
            catch (error) {
                if (!(0, persistence_errors_1.findDomindsPersistenceFileError)(error)) {
                    hadCandidateInspectionError = true;
                    log_1.log.error('Backend driver failed to inspect dialog latest snapshot', error, {
                        rootId: dialog.id.rootId,
                        selfId: dialog.id.selfId,
                    });
                    continue;
                }
                log_1.log.warn('Backend driver skipped malformed dialog latest snapshot', error, {
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                });
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
                continue;
            }
            if (!latest) {
                continue;
            }
            await persistence_1.DialogPersistence.syncWakeQueueForDialogLatest(dialog.id, latest, dialog.status);
            const hasRootRuntimeWake = wakeQueueHasRootRuntimeWake(wakeQueueEntries, dialog.id);
            if (!(0, dialog_drive_work_1.hasDurableDriveWork)(latest) && !hasRootRuntimeWake) {
                continue;
            }
            const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latest, dialog.id.selfId === dialog.id.rootId ? wakeQueueEntries : []);
            if (latest.backendDriveStall?.durableWorkFingerprint === durableWorkFingerprint) {
                hadStalledCandidateForRoot = true;
                log_1.log.warn('Backend driver skipped stalled durable work pending new facts', undefined, {
                    dialogId: dialog.id.valueOf(),
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                    stallRecordId: latest.backendDriveStall.recordId,
                });
                continue;
            }
            hasQueuedCandidateForRoot = true;
            queued.push({
                rootDialog: mainDialog,
                dialog,
            });
        }
        if (!hasQueuedCandidateForRoot && !hadCandidateInspectionError && !hadStalledCandidateForRoot) {
            dialog_global_registry_1.globalDialogRegistry.clearRootDriveQueue(mainDialog.id.rootId, {
                source: 'kernel_driver_backend_loop',
                reason: 'no_durable_drive_work',
            });
            await persistence_1.DialogPersistence.removeRootRuntimeWake(mainDialog.id, mainDialog.status);
        }
    }
    return queued;
}
async function driveQueuedDialogsOnce() {
    const dialogsToDrive = await listLiveDialogsWithDurableDriveWork();
    for (const { rootDialog, dialog } of dialogsToDrive) {
        try {
            let latestForDrive = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
            let wakeQueueEntriesForRoot = await persistence_1.DialogPersistence.loadWakeQueueEntries(rootDialog.id, rootDialog.status);
            const hasRootRuntimeWake = wakeQueueHasRootRuntimeWake(wakeQueueEntriesForRoot, dialog.id);
            if (!(0, dialog_drive_work_1.hasDurableDriveWork)(latestForDrive) && !hasRootRuntimeWake) {
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
                continue;
            }
            let openGenerationRecoveryClaim = await claimOpenGenerationRecoveryForBackendLoop({
                dialog,
                latest: latestForDrive,
            });
            if (openGenerationRecoveryClaim.status === 'stale') {
                latestForDrive = openGenerationRecoveryClaim.latestAfterCleanup;
                if (!latestForDrive || !(0, dialog_drive_work_1.hasDurableDriveWork)(latestForDrive)) {
                    if (dialog.id.selfId === dialog.id.rootId) {
                        dialog_global_registry_1.globalDialogRegistry.clearRootDriveQueue(dialog.id.rootId, {
                            source: 'kernel_driver_backend_loop',
                            reason: 'stale_open_generation_recovery',
                        });
                        await persistence_1.DialogPersistence.removeRootRuntimeWake(dialog.id, dialog.status);
                    }
                    await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
                    continue;
                }
                openGenerationRecoveryClaim = await claimOpenGenerationRecoveryForBackendLoop({
                    dialog,
                    latest: latestForDrive,
                });
            }
            const currentResumeInProgressGeneration = openGenerationRecoveryClaim.status === 'claimed';
            const currentHasBackendDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestForDrive);
            wakeQueueEntriesForRoot = await persistence_1.DialogPersistence.loadWakeQueueEntries(rootDialog.id, rootDialog.status);
            const currentHasRootRuntimeWake = wakeQueueHasRootRuntimeWake(wakeQueueEntriesForRoot, dialog.id);
            const executionMarker = latestForDrive?.executionMarker;
            const stopRequested = (0, dialog_display_state_1.getStopRequestedReason)(dialog.id);
            const interruptedBlockedWithoutExplicitResume = (0, dialog_interruption_1.isInterruptedDialogBlockedWithoutExplicitResume)(executionMarker, false);
            if (interruptedBlockedWithoutExplicitResume || stopRequested !== undefined) {
                if (dialog.id.selfId === dialog.id.rootId) {
                    dialog_global_registry_1.globalDialogRegistry.clearRootDriveQueue(dialog.id.rootId, {
                        source: 'kernel_driver_backend_loop',
                        reason: interruptedBlockedWithoutExplicitResume
                            ? 'execution_marker_blocked:interrupted'
                            : `stop_requested:${stopRequested}`,
                    });
                }
                if (dialog.id.selfId === dialog.id.rootId) {
                    await persistence_1.DialogPersistence.removeRootRuntimeWake(dialog.id, dialog.status);
                }
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
                continue;
            }
            if ((0, dialog_display_state_1.hasActiveRun)(dialog.id)) {
                log_1.log.debug('Backend driver deferred queued dialog drive because dialog already has an active run', undefined, {
                    dialogId: dialog.id.valueOf(),
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                });
                dialog_global_registry_1.globalDialogRegistry.noteActiveRunBlockedQueuedDrive(dialog.id.rootId);
                continue;
            }
            if (!currentHasBackendDurableWork && !currentHasRootRuntimeWake) {
                if (dialog.id.selfId === dialog.id.rootId) {
                    dialog_global_registry_1.globalDialogRegistry.clearRootDriveQueue(dialog.id.rootId, {
                        source: 'kernel_driver_backend_loop',
                        reason: 'missing_durable_drive_work',
                    });
                }
                if (dialog.id.selfId === dialog.id.rootId) {
                    await persistence_1.DialogPersistence.removeRootRuntimeWake(dialog.id, dialog.status);
                }
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
                continue;
            }
            if (!currentHasBackendDurableWork ||
                (!currentResumeInProgressGeneration && !(await dialog.canDrive()))) {
                continue;
            }
            await (0, engine_1.driveDialogStream)(dialog, undefined, true, {
                source: 'kernel_driver_backend_loop',
                reason: dialog.id.selfId === dialog.id.rootId ? 'root_runtime_wake' : 'wake_queue',
                ...(currentResumeInProgressGeneration ? { resumeInProgressGeneration: true } : {}),
            });
            const status = await dialog.getSuspensionStatus();
            const latestAfterDrive = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
            if (latestAfterDrive) {
                await persistence_1.DialogPersistence.syncWakeQueueForDialogLatest(dialog.id, latestAfterDrive, dialog.status);
            }
            const stillHasDurableWork = (0, dialog_drive_work_1.hasDurableDriveWork)(latestAfterDrive);
            const shouldStayQueued = dialog.hasQueuedPrompt() || !status.canDrive || stillHasDurableWork;
            if (shouldStayQueued) {
                const canRetryImmediately = dialog.hasQueuedPrompt() || (status.canDrive && stillHasDurableWork);
                if (canRetryImmediately) {
                    dialog_global_registry_1.globalDialogRegistry.queueRootDrive(rootDialog.id.rootId, {
                        source: 'kernel_driver_backend_loop',
                        reason: dialog.hasQueuedPrompt()
                            ? 'post_drive_queued_prompt_pending'
                            : 'post_drive_durable_work_pending',
                    });
                    if (dialog.id.selfId === dialog.id.rootId) {
                        await persistence_1.DialogPersistence.upsertRootRuntimeWake(dialog.id, dialog.hasQueuedPrompt()
                            ? 'post_drive_queued_prompt_pending'
                            : 'post_drive_durable_work_pending', dialog.status);
                    }
                }
                else {
                    log_1.log.debug('Backend driver left durable work parked until the blocking state changes', undefined, {
                        dialogId: dialog.id.valueOf(),
                        rootId: dialog.id.rootId,
                        selfId: dialog.id.selfId,
                        waitingQ4H: status.q4h,
                        backgroundCalleeDialogs: status.backgroundCalleeDialogs,
                        stillHasDurableWork,
                    });
                }
            }
            else {
                if (dialog.id.selfId === dialog.id.rootId) {
                    dialog_global_registry_1.globalDialogRegistry.clearRootDriveQueue(dialog.id.rootId, {
                        source: 'kernel_driver_backend_loop',
                        reason: 'post_drive_idle',
                    });
                    await persistence_1.DialogPersistence.removeRootRuntimeWake(dialog.id, dialog.status);
                }
                await persistence_1.DialogPersistence.removeWakeQueueEntriesForDialog(dialog.id, dialog.status);
            }
            const lastTrigger = dialog_global_registry_1.globalDialogRegistry.getLastDriveTrigger(dialog.id.rootId);
            const lastTriggerAgeMs = lastTrigger !== undefined ? Math.max(0, Date.now() - lastTrigger.emittedAtMs) : undefined;
            if (status.backgroundCalleeDialogs) {
                log_1.log.debug(`Dialog ${dialog.id.valueOf()} has background callee dialogs`, undefined, {
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                    waitingQ4H: status.q4h,
                    backgroundCalleeDialogs: status.backgroundCalleeDialogs,
                    hasQueuedPrompt: dialog.hasQueuedPrompt(),
                    lastDriveTrigger: lastTrigger
                        ? {
                            action: lastTrigger.action,
                            source: lastTrigger.source,
                            reason: lastTrigger.reason,
                            emittedAtMs: lastTrigger.emittedAtMs,
                            ageMs: lastTriggerAgeMs,
                            entryFound: lastTrigger.entryFound,
                            previousDriveQueued: lastTrigger.previousDriveQueued,
                            nextDriveQueued: lastTrigger.nextDriveQueued,
                        }
                        : null,
                });
            }
            if (status.q4h) {
                log_1.log.debug(`Dialog ${dialog.id.valueOf()} awaiting Q4H answer`, undefined, {
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                    waitingQ4H: status.q4h,
                    backgroundCalleeDialogs: status.backgroundCalleeDialogs,
                    hasQueuedPrompt: dialog.hasQueuedPrompt(),
                    lastDriveTrigger: lastTrigger
                        ? {
                            action: lastTrigger.action,
                            source: lastTrigger.source,
                            reason: lastTrigger.reason,
                            emittedAtMs: lastTrigger.emittedAtMs,
                            ageMs: lastTriggerAgeMs,
                            entryFound: lastTrigger.entryFound,
                            previousDriveQueued: lastTrigger.previousDriveQueued,
                            nextDriveQueued: lastTrigger.nextDriveQueued,
                        }
                        : null,
                });
            }
        }
        catch (err) {
            log_1.log.error(`Error driving dialog ${dialog.id.valueOf()}:`, err, undefined, {
                dialogId: dialog.id.valueOf(),
                rootId: dialog.id.rootId,
                selfId: dialog.id.selfId,
            });
            try {
                const latestAfterError = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
                if (latestAfterError) {
                    await persistence_1.DialogPersistence.syncWakeQueueForDialogLatest(dialog.id, latestAfterError, dialog.status);
                }
                const rootId = new dialog_1.DialogID(dialog.id.rootId);
                const rootHasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(rootId, dialog.status);
                const wakeQueueEntries = await persistence_1.DialogPersistence.loadWakeQueueEntries(rootId, dialog.status);
                if (rootHasPendingNextStepTriggers || wakeQueueEntries.length > 0) {
                    const durableWorkFingerprint = persistence_1.DialogPersistence.buildBackendDriveDurableWorkFingerprint(latestAfterError, dialog.id.selfId === dialog.id.rootId ? wakeQueueEntries : []);
                    const record = await persistence_1.DialogPersistence.appendBackendDriveStallRecord(dialog.id, {
                        dialogId: dialog.id.valueOf(),
                        rootId: dialog.id.rootId,
                        selfId: dialog.id.selfId,
                        status: dialog.status,
                        reason: 'backend_drive_error',
                        durableWorkFingerprint,
                        latestSummary: latestAfterError === null
                            ? null
                            : {
                                currentCourse: latestAfterError.currentCourse,
                                status: latestAfterError.status,
                                generating: latestAfterError.generating ?? false,
                                displayState: latestAfterError.displayState ?? null,
                                executionMarker: latestAfterError.executionMarker ?? null,
                                generationRunState: latestAfterError.generationRunState ?? null,
                                nextStepTriggerCount: latestAfterError.nextStep.triggers.length,
                                pendingRuntimePromptMsgId: latestAfterError.pendingRuntimePrompt?.msgId ?? null,
                                replyDelivery: latestAfterError.replyDelivery ?? null,
                                userWait: latestAfterError.userWait ?? null,
                                sideDialogFinalResponse: latestAfterError.sideDialogFinalResponse ?? null,
                            },
                        error: formatDriveError(err),
                        context: {
                            rootHasPendingNextStepTriggers,
                            wakeQueueEntryCount: wakeQueueEntries.length,
                        },
                    }, dialog.status);
                    log_1.log.warn('Backend driver persisted stalled durable work after drive error', undefined, {
                        dialogId: dialog.id.valueOf(),
                        rootId: dialog.id.rootId,
                        selfId: dialog.id.selfId,
                        stallRecordId: record.recordId,
                        rootHasPendingNextStepTriggers,
                        wakeQueueEntryCount: wakeQueueEntries.length,
                    });
                }
            }
            catch (stallErr) {
                log_1.log.error('Failed to persist backend drive stall after drive error', stallErr, {
                    dialogId: dialog.id.valueOf(),
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                });
            }
        }
    }
}
function isBackendDriverAborted(options) {
    return options?.abortSignal?.aborted === true;
}
function runBackendDriver(options) {
    return (async () => {
        const abortListener = () => {
            dialog_global_registry_1.globalDialogRegistry.queueRootDrive('__kernel_driver_abort__', {
                source: 'kernel_driver_backend_loop',
                reason: 'abort_signal',
            });
        };
        options?.abortSignal?.addEventListener('abort', abortListener, { once: true });
        try {
            while (!isBackendDriverAborted(options)) {
                try {
                    await driveQueuedDialogsOnce();
                    if (isBackendDriverAborted(options)) {
                        break;
                    }
                    const trigger = await dialog_global_registry_1.globalDialogRegistry.waitForDriveTrigger();
                    log_1.log.debug('Backend driver woke from drive trigger event', undefined, {
                        trigger: formatDriveTriggerForLog(trigger),
                    });
                }
                catch (loopErr) {
                    if (isBackendDriverAborted(options)) {
                        break;
                    }
                    log_1.log.error('Error in backend driver loop:', loopErr);
                    await new Promise((resolve) => setTimeout(resolve, 1000));
                }
            }
        }
        finally {
            options?.abortSignal?.removeEventListener('abort', abortListener);
        }
    })();
}
