"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.globalDialogRegistry = void 0;
const evt_1 = require("@longrun-ai/kernel/evt");
const dialog_1 = require("./dialog");
const log_1 = require("./log");
const persistence_1 = require("./persistence");
const log = (0, log_1.createLogger)('dialog-global-registry');
class GlobalDialogRegistry {
    static instance;
    /**
     * Runtime-owned roots keyed by rootId.
     *
     * Do not add an API that enumerates this map for backend driving. A dialog becomes driveable only
     * through an explicit business/runtime scheduling event (`queueRootDrive`, result arrival,
     * active-run clear, etc.).
     * Reintroducing "scan every loaded root and see what happens" makes hidden polling loops easy to
     * create and obscures the business event that should own the next action.
     */
    entries = new Map();
    queuedRootIds = [];
    queuedRootIdSet = new Set();
    queuedRootIdReadIndex = 0;
    lastDriveTriggerByRootId = new Map();
    driveTriggerPubChan = (0, evt_1.createPubChan)();
    driveTriggerSubChan = (0, evt_1.createSubChan)(this.driveTriggerPubChan);
    static getInstance() {
        if (!GlobalDialogRegistry.instance) {
            GlobalDialogRegistry.instance = new GlobalDialogRegistry();
        }
        return GlobalDialogRegistry.instance;
    }
    get(rootId) {
        return this.entries.get(rootId)?.mainDialog;
    }
    register(mainDialog) {
        // This registry is keyed by the *tree root id*.
        // Only the canonical main dialog (selfId === rootId) should be stored here.
        if (mainDialog.id.selfId !== mainDialog.id.rootId) {
            return;
        }
        const existing = this.entries.get(mainDialog.id.rootId);
        if (existing) {
            return;
        }
        this.entries.set(mainDialog.id.rootId, {
            mainDialog,
            driveQueued: false,
            activeRunClearedDrivePending: false,
        });
        void (async () => {
            try {
                const hasPendingNextStepTriggers = await persistence_1.DialogPersistence.hasPendingNextStepTriggers(mainDialog.id);
                const wakeQueueEntries = await persistence_1.DialogPersistence.loadWakeQueueEntries(mainDialog.id);
                if (hasPendingNextStepTriggers || wakeQueueEntries.length > 0) {
                    this.queueRootDrive(mainDialog.id.rootId, {
                        source: 'dialog_registry_hydration',
                        reason: hasPendingNextStepTriggers
                            ? 'persisted_next_step_triggers'
                            : 'persisted_wake_queue',
                    });
                }
            }
            catch (error) {
                log.warn('Failed to hydrate persisted root drive queue for registered main dialog', error, {
                    rootId: mainDialog.id.rootId,
                    selfId: mainDialog.id.selfId,
                });
            }
        })();
    }
    unregister(rootId) {
        this.entries.delete(rootId);
        this.queuedRootIdSet.delete(rootId);
        this.lastDriveTriggerByRootId.delete(rootId);
        (0, dialog_1.scheduleGlobalDialogMutexCleanupForRoot)(rootId);
    }
    enqueueRoot(rootId) {
        if (this.queuedRootIdSet.has(rootId)) {
            return;
        }
        this.queuedRootIdSet.add(rootId);
        this.queuedRootIds.push(rootId);
    }
    compactQueuedRootsIfNeeded() {
        if (this.queuedRootIdReadIndex === 0) {
            return;
        }
        if (this.queuedRootIdReadIndex < 256 &&
            this.queuedRootIdReadIndex * 2 < this.queuedRootIds.length) {
            return;
        }
        this.queuedRootIds.splice(0, this.queuedRootIdReadIndex);
        this.queuedRootIdReadIndex = 0;
    }
    compactSparseQueuedRootsIfNeeded() {
        if (this.queuedRootIds.length < 512) {
            return;
        }
        if (this.queuedRootIdSet.size * 2 >= this.queuedRootIds.length - this.queuedRootIdReadIndex) {
            return;
        }
        const compacted = [];
        for (let index = this.queuedRootIdReadIndex; index < this.queuedRootIds.length; index += 1) {
            const rootId = this.queuedRootIds[index];
            if (rootId !== undefined && this.queuedRootIdSet.has(rootId)) {
                compacted.push(rootId);
            }
        }
        this.queuedRootIds.splice(0, this.queuedRootIds.length, ...compacted);
        this.queuedRootIdReadIndex = 0;
    }
    publishDriveTrigger(args) {
        const trigger = {
            type: 'drive_trigger_evt',
            action: args.action,
            rootId: args.rootId,
            entryFound: args.entryFound,
            previousDriveQueued: args.previousDriveQueued,
            nextDriveQueued: args.nextDriveQueued,
            source: args.meta.source,
            reason: args.meta.reason,
            emittedAtMs: Date.now(),
        };
        this.lastDriveTriggerByRootId.set(args.rootId, trigger);
        this.driveTriggerPubChan.write(trigger);
    }
    async waitForDriveTrigger() {
        for (;;) {
            const trigger = await this.driveTriggerSubChan.read();
            if (trigger !== evt_1.EndOfStream) {
                return trigger;
            }
            // Recreate subscription if EOS is ever observed (should not happen in normal runtime).
            this.driveTriggerSubChan = (0, evt_1.createSubChan)(this.driveTriggerPubChan);
        }
    }
    queueRootDrive(rootId, meta) {
        const triggerMeta = meta ?? {
            source: 'unknown',
            reason: 'unspecified',
        };
        const entry = this.entries.get(rootId);
        const previousDriveQueued = entry ? entry.driveQueued : null;
        if (entry) {
            entry.driveQueued = true;
            // A fresh root drive queueing supersedes any earlier "drive once active run clears" debt.
            entry.activeRunClearedDrivePending = false;
            this.enqueueRoot(rootId);
        }
        this.publishDriveTrigger({
            action: 'queue_root_drive',
            rootId,
            entryFound: entry !== undefined,
            previousDriveQueued,
            nextDriveQueued: true,
            meta: triggerMeta,
        });
    }
    clearRootDriveQueue(rootId, meta) {
        const triggerMeta = meta ?? {
            source: 'unknown',
            reason: 'unspecified',
        };
        const entry = this.entries.get(rootId);
        const previousDriveQueued = entry ? entry.driveQueued : null;
        if (entry) {
            entry.driveQueued = false;
            entry.activeRunClearedDrivePending = false;
            this.queuedRootIdSet.delete(rootId);
            this.compactSparseQueuedRootsIfNeeded();
        }
        this.publishDriveTrigger({
            action: 'clear_root_drive_queue',
            rootId,
            entryFound: entry !== undefined,
            previousDriveQueued,
            nextDriveQueued: false,
            meta: triggerMeta,
        });
    }
    notifyActiveRunCleared(rootId, meta) {
        const triggerMeta = meta ?? {
            source: 'unknown',
            reason: 'unspecified',
        };
        const entry = this.entries.get(rootId);
        if (!entry) {
            return;
        }
        if (!entry.activeRunClearedDrivePending) {
            entry.activeRunClearedDrivePending = false;
            return;
        }
        const currentDriveQueued = entry.driveQueued;
        entry.activeRunClearedDrivePending = false;
        entry.driveQueued = true;
        this.enqueueRoot(rootId);
        this.publishDriveTrigger({
            action: 'active_run_cleared',
            rootId,
            entryFound: true,
            previousDriveQueued: currentDriveQueued,
            nextDriveQueued: entry.driveQueued,
            meta: triggerMeta,
        });
    }
    noteActiveRunBlockedQueuedDrive(rootId) {
        const entry = this.entries.get(rootId);
        if (!entry) {
            return;
        }
        entry.activeRunClearedDrivePending = true;
    }
    hasPendingActiveRunClearedDrive(rootId) {
        return this.entries.get(rootId)?.activeRunClearedDrivePending === true;
    }
    isRootDriveQueued(rootId) {
        return this.entries.get(rootId)?.driveQueued === true;
    }
    getLastDriveTrigger(rootId) {
        return this.lastDriveTriggerByRootId.get(rootId);
    }
    consumeQueuedMainDialogs() {
        const queued = [];
        while (this.queuedRootIdReadIndex < this.queuedRootIds.length) {
            const rootId = this.queuedRootIds[this.queuedRootIdReadIndex];
            this.queuedRootIdReadIndex += 1;
            if (rootId === undefined || !this.queuedRootIdSet.has(rootId)) {
                continue;
            }
            this.queuedRootIdSet.delete(rootId);
            const entry = this.entries.get(rootId);
            if (!entry) {
                continue;
            }
            entry.driveQueued = false;
            queued.push(entry.mainDialog);
        }
        this.compactQueuedRootsIfNeeded();
        return queued;
    }
    get size() {
        return this.entries.size;
    }
}
exports.globalDialogRegistry = GlobalDialogRegistry.getInstance();
