"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cancelIdleReminderWake = cancelIdleReminderWake;
exports.maybeStartIdleReminderWake = maybeStartIdleReminderWake;
const id_1 = require("@longrun-ai/kernel/utils/id");
const time_1 = require("@longrun-ai/kernel/utils/time");
const dialog_display_state_1 = require("../../dialog-display-state");
const log_1 = require("../../log");
const persistence_1 = require("../../persistence");
const driver_messages_1 = require("../../runtime/driver-messages");
const work_language_1 = require("../../runtime/work-language");
const shared_reminders_1 = require("../../shared-reminders");
const tool_1 = require("../../tool");
const IDLE_WAKE_AGGREGATION_WINDOW_MS = 500;
const idleReminderWakeTasks = new Map();
function normalizeWakeEvents(result) {
    if (result === null)
        return [];
    if (Array.isArray(result)) {
        return result.map(assertWakeEventShape);
    }
    const event = result;
    return [assertWakeEventShape(event)];
}
function assertWakeEventShape(event) {
    if (event.eventId.trim() === '') {
        throw new Error('idle reminder wake invariant violation: empty eventId');
    }
    if (event.reminderId.trim() === '') {
        throw new Error(`idle reminder wake invariant violation: empty reminderId (${event.eventId})`);
    }
    if (event.content.trim() === '') {
        throw new Error(`idle reminder wake invariant violation: empty content (${event.eventId})`);
    }
    if (!event.content.startsWith('【系统提示】') && !event.content.startsWith('[System notice]')) {
        throw new Error(`idle reminder wake invariant violation: wake event content must start with a system notice prefix (${event.eventId})`);
    }
    return event;
}
function sleep(ms, signal) {
    return new Promise((resolve) => {
        if (signal?.aborted) {
            resolve();
            return;
        }
        let timeout;
        let settled = false;
        const finish = () => {
            if (settled)
                return;
            settled = true;
            clearTimeout(timeout);
            signal?.removeEventListener('abort', onAbort);
            resolve();
        };
        const onAbort = () => {
            finish();
        };
        timeout = setTimeout(finish, ms);
        signal?.addEventListener('abort', onAbort, { once: true });
    });
}
function isCurrentTask(dialogId, task) {
    return idleReminderWakeTasks.get(dialogId.key()) === task;
}
function isAbortLikeError(error) {
    return error instanceof Error && (error.name === 'AbortError' || error.message === 'aborted');
}
function stripSystemNoticePrefix(content, prefix) {
    const normalized = content.trim();
    if (normalized.startsWith(prefix)) {
        return normalized.slice(prefix.length).trim();
    }
    return normalized;
}
function buildAggregatedWakePromptContent(events) {
    if (events.length === 1) {
        const only = events[0];
        if (!only) {
            throw new Error('idle reminder wake invariant violation: missing first event');
        }
        return only.content;
    }
    const language = (0, work_language_1.getWorkLanguage)();
    const prefix = (0, driver_messages_1.formatSystemNoticePrefix)(language);
    const body = events
        .map((event, index) => {
        const eventText = stripSystemNoticePrefix(event.content, prefix);
        return `${String(index + 1)}. ${eventText}`;
    })
        .join('\n\n');
    // Business scenario: several reminder/environment wakes arrived while the dialog was idle.
    // The model needs the events as state updates, but they are not a new human request and should
    // not trigger a standalone acknowledgement. Say the product meaning plainly instead of exposing
    // internal "runtime event" wording.
    return language === 'zh'
        ? `${prefix}
以下是对话空闲期间 Dominds 收到的环境事件。这些事件不是新的用户指令。

${body}

请结合当前任务上下文继续推进；若这些事件不影响当前工作，不要发送占位式确认。`
        : `${prefix}
The following environment events reached Dominds while the dialog was idle. These events are not new user instructions.

${body}

Continue according to the current task context; if these events do not affect the work, do not send a placeholder acknowledgement.`;
}
async function loadCurrentWakeEvents(dialog, events) {
    const targets = await dialog.listVisibleReminderTargets();
    const targetsByReminderId = new Map();
    for (const target of targets) {
        const existing = targetsByReminderId.get(target.reminder.id);
        if (existing) {
            existing.push(target);
            continue;
        }
        targetsByReminderId.set(target.reminder.id, [target]);
    }
    const currentEvents = [];
    const staleEvents = [];
    for (const event of events) {
        if (targetsByReminderId.has(event.reminderId)) {
            currentEvents.push(event);
            continue;
        }
        staleEvents.push(event);
    }
    if (staleEvents.length > 0) {
        log_1.log.warn('idle reminder wake dropped stale event because reminder target disappeared', undefined, {
            dialogId: dialog.id.valueOf(),
            rootId: dialog.id.rootId,
            selfId: dialog.id.selfId,
            droppedEventCount: staleEvents.length,
            droppedEvents: staleEvents.map((event) => ({
                eventId: event.eventId,
                reminderId: event.reminderId,
            })),
        });
    }
    return { events: currentEvents, targetsByReminderId };
}
async function applyWakeEventUpdates(dialog, events, targetsByReminderId) {
    const eventsWithUpdates = events.filter((event) => event.updatedContent !== undefined || event.updatedMeta !== undefined);
    if (eventsWithUpdates.length === 0)
        return;
    const byReminderId = new Map();
    for (const event of eventsWithUpdates) {
        const existing = byReminderId.get(event.reminderId);
        if (existing && existing.eventId !== event.eventId) {
            throw new Error(`idle reminder wake invariant violation: multiple wake updates for reminder ${event.reminderId}`);
        }
        byReminderId.set(event.reminderId, event);
    }
    const appliedReminderIds = new Set();
    for (const [reminderId, event] of byReminderId.entries()) {
        const targets = targetsByReminderId.get(reminderId);
        if (targets === undefined) {
            throw new Error(`idle reminder wake invariant violation: current wake event lost target mapping (${reminderId})`);
        }
        appliedReminderIds.add(reminderId);
        for (const target of targets) {
            const nextContent = event.updatedContent ?? target.reminder.content;
            const nextMeta = event.updatedMeta !== undefined ? event.updatedMeta : target.reminder.meta;
            if (target.source === 'dialog') {
                dialog.updateReminder(target.index, nextContent, nextMeta, {
                    renderMode: target.reminder.renderMode,
                });
                continue;
            }
            await (0, shared_reminders_1.mutateSharedReminders)(target.target, (reminders) => {
                const index = reminders.findIndex((reminder) => reminder.id === target.reminder.id);
                if (index < 0) {
                    throw new Error(`idle reminder wake invariant violation: shared reminder ${target.reminder.id} disappeared before update`);
                }
                const previous = reminders[index];
                if (!previous) {
                    throw new Error(`idle reminder wake invariant violation: shared reminder ${target.reminder.id} missing at resolved index`);
                }
                reminders[index] = (0, tool_1.materializeReminder)({
                    id: previous.id,
                    content: nextContent,
                    owner: previous.owner,
                    meta: nextMeta,
                    echoback: previous.echoback,
                    scope: previous.scope,
                    createdAt: previous.createdAt,
                    priority: previous.priority,
                    renderMode: previous.renderMode,
                });
            });
            dialog.touchReminders();
        }
    }
    for (const reminderId of byReminderId.keys()) {
        if (appliedReminderIds.has(reminderId))
            continue;
        throw new Error(`idle reminder wake invariant violation: reminder update target disappeared before apply (${reminderId})`);
    }
    await dialog.processReminderUpdates();
}
async function loadFreshIdleWakeEligibility(dialog) {
    if ((0, dialog_display_state_1.hasActiveRun)(dialog.id))
        return false;
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialog.id, 'running');
    if (!latest)
        return false;
    if (latest.displayState?.kind !== 'idle_waiting_user')
        return false;
    if (latest.executionMarker?.kind === 'dead')
        return false;
    if (latest.executionMarker?.kind === 'interrupted')
        return false;
    if (latest.userWait?.kind === 'awaiting_user_answer')
        return false;
    return true;
}
function collectWakeCapableReminders(reminders) {
    const result = [];
    for (const reminder of reminders) {
        if (!reminder.owner?.waitForReminderWakeEvent)
            continue;
        result.push({ ownerName: reminder.owner.name, reminder });
    }
    return result;
}
function dedupeAndSortWakeEvents(events) {
    const byId = new Map();
    for (const event of events) {
        const existing = byId.get(event.eventId);
        if (existing) {
            if (existing.content !== event.content ||
                existing.reminderId !== event.reminderId ||
                existing.updatedContent !== event.updatedContent ||
                JSON.stringify(existing.updatedMeta ?? null) !== JSON.stringify(event.updatedMeta ?? null)) {
                throw new Error(`idle reminder wake invariant violation: conflicting duplicate eventId ${event.eventId}`);
            }
            continue;
        }
        byId.set(event.eventId, event);
    }
    return [...byId.values()].sort((left, right) => left.eventId.localeCompare(right.eventId));
}
async function runIdleReminderWakeTask(dialog, callbacks, task) {
    if (!(await loadFreshIdleWakeEligibility(dialog)))
        return;
    const targets = collectWakeCapableReminders(await dialog.listVisibleReminders());
    if (targets.length === 0)
        return;
    const waitController = new AbortController();
    task.controller.signal.addEventListener('abort', () => {
        waitController.abort();
    }, { once: true });
    if (task.controller.signal.aborted) {
        waitController.abort();
    }
    const collected = [];
    let pending = targets.length;
    let resolveFirstEvent;
    const firstEvent = new Promise((resolve) => {
        resolveFirstEvent = resolve;
    });
    const settleOne = () => {
        pending -= 1;
        if (pending === 0 && collected.length === 0) {
            resolveFirstEvent?.();
        }
    };
    for (const target of targets) {
        const owner = target.reminder.owner;
        if (!owner?.waitForReminderWakeEvent) {
            settleOne();
            continue;
        }
        void owner
            .waitForReminderWakeEvent(dialog, [target.reminder], waitController.signal)
            .then((result) => {
            const events = normalizeWakeEvents(result);
            if (events.length > 0) {
                collected.push(...events);
                resolveFirstEvent?.();
            }
        })
            .catch((error) => {
            if (!waitController.signal.aborted && !isAbortLikeError(error)) {
                log_1.log.warn('idle reminder wake owner wait failed', error, {
                    dialogId: dialog.id.valueOf(),
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                    ownerName: target.ownerName,
                    reminderId: target.reminder.id,
                });
            }
        })
            .finally(() => {
            settleOne();
        });
    }
    await firstEvent;
    if (task.controller.signal.aborted || collected.length === 0)
        return;
    await sleep(IDLE_WAKE_AGGREGATION_WINDOW_MS, task.controller.signal);
    if (task.controller.signal.aborted || !isCurrentTask(dialog.id, task))
        return;
    waitController.abort();
    const events = dedupeAndSortWakeEvents(collected);
    if (events.length === 0)
        return;
    const currentWakeEvents = await loadCurrentWakeEvents(dialog, events);
    if (currentWakeEvents.events.length === 0)
        return;
    await applyWakeEventUpdates(dialog, currentWakeEvents.events, currentWakeEvents.targetsByReminderId);
    if (task.controller.signal.aborted || !isCurrentTask(dialog.id, task))
        return;
    if (!(await loadFreshIdleWakeEligibility(dialog))) {
        log_1.log.debug('idle reminder wake dropped because dialog is no longer eligible', undefined, {
            dialogId: dialog.id.valueOf(),
            rootId: dialog.id.rootId,
            selfId: dialog.id.selfId,
            eventIds: currentWakeEvents.events.map((event) => event.eventId),
        });
        return;
    }
    const prompt = {
        content: buildAggregatedWakePromptContent(currentWakeEvents.events),
        msgId: (0, id_1.generateShortId)(),
        grammar: 'markdown',
        origin: 'runtime',
    };
    await callbacks.driveDialog(dialog, {
        humanPrompt: prompt,
        waitInQue: true,
        driveOptions: {
            source: 'kernel_driver_idle_reminder_wake',
            reason: 'idle_reminder_wake',
        },
    });
}
function cancelIdleReminderWake(dialogId, reason) {
    const task = idleReminderWakeTasks.get(dialogId.key());
    if (!task)
        return;
    idleReminderWakeTasks.delete(dialogId.key());
    task.controller.abort();
    log_1.log.debug('idle reminder wake task canceled', undefined, {
        dialogId: dialogId.valueOf(),
        rootId: dialogId.rootId,
        selfId: dialogId.selfId,
        reason,
        startedAt: task.startedAt,
    });
}
function maybeStartIdleReminderWake(dialog, callbacks, reason) {
    cancelIdleReminderWake(dialog.id, `replace:${reason}`);
    const task = {
        dialogKey: dialog.id.key(),
        controller: new AbortController(),
        startedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
    };
    idleReminderWakeTasks.set(dialog.id.key(), task);
    log_1.log.debug('idle reminder wake task started', undefined, {
        dialogId: dialog.id.valueOf(),
        rootId: dialog.id.rootId,
        selfId: dialog.id.selfId,
        reason,
        startedAt: task.startedAt,
    });
    void (async () => {
        try {
            if (!(await loadFreshIdleWakeEligibility(dialog)))
                return;
            if (task.controller.signal.aborted || !isCurrentTask(dialog.id, task))
                return;
            await runIdleReminderWakeTask(dialog, callbacks, task);
        }
        catch (error) {
            if (!task.controller.signal.aborted && !isAbortLikeError(error)) {
                log_1.log.warn('idle reminder wake task failed', error, {
                    dialogId: dialog.id.valueOf(),
                    rootId: dialog.id.rootId,
                    selfId: dialog.id.selfId,
                });
            }
        }
        finally {
            if (isCurrentTask(dialog.id, task)) {
                idleReminderWakeTasks.delete(dialog.id.key());
            }
        }
    })();
}
