"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.recoverPendingReplyDeliveryForDialog = recoverPendingReplyDeliveryForDialog;
exports.recoverPendingReplyDeliveryAfterRestart = recoverPendingReplyDeliveryAfterRestart;
const dialog_instance_registry_1 = require("../dialog-instance-registry");
const kernel_driver_1 = require("../llm/kernel-driver");
const tellask_special_1 = require("../llm/kernel-driver/tellask-special");
const log_1 = require("../log");
const persistence_1 = require("../persistence");
const log = (0, log_1.createLogger)('reply-delivery-recovery');
const inFlightReplyRecoveryByDialog = new Map();
function dispatchDrive(dialog, options) {
    if (options.humanPrompt !== undefined) {
        return (0, kernel_driver_1.driveDialogStream)(dialog, options.humanPrompt, options.waitInQue, options.driveOptions);
    }
    return (0, kernel_driver_1.driveDialogStream)(dialog, undefined, options.waitInQue, options.driveOptions);
}
async function recoverPendingReplyDeliveryForDialog(dialog) {
    const dialogKey = dialog.id.valueOf();
    const existing = inFlightReplyRecoveryByDialog.get(dialogKey);
    if (existing) {
        return await existing;
    }
    // This helper is reserved for execution-oriented flows only (restart recovery, resume, and
    // dead-sideDialog remediation). Those flows are allowed to have side effects and may legitimately
    // unblock downstream dialogs, so reply recovery keeps normal immediate drive behavior here.
    // Pure read/display flows must not call into this helper.
    const recoveryPromise = (async () => {
        const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
        const replyDelivery = latest?.replyDelivery;
        if (!replyDelivery ||
            (replyDelivery.status !== 'pending' && replyDelivery.toolResultStatus !== 'pending')) {
            return 0;
        }
        return await (0, tellask_special_1.recoverPendingReplyDelivery)({
            dlg: dialog,
            replyDelivery,
            callbacks: {
                scheduleDrive: (scheduledDialog, options) => {
                    void dispatchDrive(scheduledDialog, options);
                },
                driveDialog: async (scheduledDialog, options) => {
                    return await dispatchDrive(scheduledDialog, options);
                },
            },
        });
    })();
    inFlightReplyRecoveryByDialog.set(dialogKey, recoveryPromise);
    try {
        return await recoveryPromise;
    }
    finally {
        if (inFlightReplyRecoveryByDialog.get(dialogKey) === recoveryPromise) {
            inFlightReplyRecoveryByDialog.delete(dialogKey);
        }
    }
}
async function dialogNeedsReplyRecovery(dialogId) {
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogId, 'running');
    return (latest?.replyDelivery?.status === 'pending' ||
        latest?.replyDelivery?.toolResultStatus === 'pending');
}
async function listReplyRecoveryCandidateDialogIds() {
    const rootDialogIds = await persistence_1.DialogPersistence.listMainDialogIds('running');
    const candidates = [];
    const seen = new Set();
    for (const rootDialogId of rootDialogIds) {
        const rootKey = rootDialogId.key();
        if (!seen.has(rootKey)) {
            seen.add(rootKey);
            candidates.push(rootDialogId);
        }
        const wakeQueueTargetDialogIds = await persistence_1.DialogPersistence.loadWakeQueueTargetDialogIds(rootDialogId, 'running');
        for (const wakeQueueTargetDialogId of wakeQueueTargetDialogIds) {
            const wakeQueueTargetKey = wakeQueueTargetDialogId.key();
            if (seen.has(wakeQueueTargetKey)) {
                continue;
            }
            seen.add(wakeQueueTargetKey);
            candidates.push(wakeQueueTargetDialogId);
        }
    }
    return candidates;
}
async function recoverPendingReplyDeliveryAfterRestart() {
    const dialogIds = await listReplyRecoveryCandidateDialogIds();
    for (const dialogId of dialogIds) {
        try {
            // Startup recovery is also execution-oriented: if a persisted replyTellask* was left
            // half-finished by process crash, finish delivery before normal backend driving proceeds.
            if (!(await dialogNeedsReplyRecovery(dialogId))) {
                continue;
            }
            const mainDialog = await (0, dialog_instance_registry_1.getOrRestoreMainDialog)(dialogId.rootId, 'running');
            if (!mainDialog) {
                continue;
            }
            const dialog = dialogId.selfId === dialogId.rootId
                ? mainDialog
                : await (0, dialog_instance_registry_1.ensureDialogLoaded)(mainDialog, dialogId, 'running');
            if (!dialog) {
                continue;
            }
            await recoverPendingReplyDeliveryForDialog(dialog);
        }
        catch (err) {
            log.error('Failed to recover pending replyTellask* delivery during restart recovery', err, {
                rootId: dialogId.rootId,
                selfId: dialogId.selfId,
            });
        }
    }
}
