"use strict";
/**
 * Module: evt-registry
 *
 * PubChan registry for managing dialog event streams.
 * Maps dialog IDs to PubChans for real-time event streaming during dialog driving.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.dialogEventRegistry = void 0;
exports.setGlobalDialogEventBroadcaster = setGlobalDialogEventBroadcaster;
exports.postDialogEvent = postDialogEvent;
exports.postDialogEventById = postDialogEventById;
exports.createEvent = createEvent;
const evt_1 = require("@longrun-ai/kernel/evt");
const time_1 = require("@longrun-ai/kernel/utils/time");
const log_1 = require("./log");
class DialogEventRegistryImpl {
    pubChans = new Map();
    log = (0, log_1.createLogger)('evt-registry');
    // Some dialog events are global UI state updates. They must reach ALL connected clients,
    // not only those subscribed to a specific dialog stream.
    globalDialogEventBroadcaster = null;
    setGlobalDialogEventBroadcaster(fn) {
        this.globalDialogEventBroadcaster = fn;
    }
    broadcastGlobalEvent(evt) {
        const fn = this.globalDialogEventBroadcaster;
        if (!fn)
            return;
        fn(evt);
    }
    emitDialogTouched(source) {
        // `full_reminders_update` is a reminder snapshot/sync event and should not mutate
        // dialog ordering timestamps. Emitting touched here causes list reordering on mere display.
        if (source.type === 'dlg_touched_evt' || source.type === 'full_reminders_update')
            return;
        const touchedEvt = {
            dialog: source.dialog,
            timestamp: source.timestamp,
            type: 'dlg_touched_evt',
            sourceType: source.type,
        };
        this.broadcastGlobalEvent(touchedEvt);
    }
    dispatchGloballyIfNeeded(evt) {
        // Global-only delivery prevents duplicate deliveries from two independent paths
        // (global broadcaster + dialog-scoped stream).
        switch (evt.type) {
            case 'new_q4h_asked':
            case 'q4h_answered':
            case 'new_a2h_answered':
            case 'a2h_acknowledged':
            case 'sideDialog_created_evt':
            case 'dlg_background_callee_summary_evt':
            case 'dlg_touched_evt':
                break;
            default:
                return false;
        }
        const fn = this.globalDialogEventBroadcaster;
        if (!fn) {
            throw new Error(`Global dialog event broadcaster missing: cannot publish ${evt.type} for dialog=${evt.dialog.selfId}`);
        }
        fn(evt);
        return true;
    }
    /**
     * Get or create a PubChan for a specific dialog ID
     */
    getPubChan(dialogId) {
        let chan = this.pubChans.get(dialogId.key());
        if (chan === undefined) {
            chan = (0, evt_1.createPubChan)();
            this.pubChans.set(dialogId.key(), chan);
        }
        return chan;
    }
    /**
     * Create a SubChan for a specific dialog ID
     */
    createSubChan(dialogId) {
        const pubChan = this.getPubChan(dialogId);
        return (0, evt_1.createSubChan)(pubChan);
    }
    /**
     * Remove PubChan for a dialog
     */
    removePubChan(dialogId) {
        this.pubChans.delete(dialogId.key());
    }
    /**
     * Create a TypedDialogEvent with common metadata (dialog info and timestamp)
     */
    createTypedEvent(dialogId, event) {
        return {
            dialog: {
                selfId: dialogId.selfId,
                rootId: dialogId.rootId,
            },
            timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
            ...event,
        };
    }
    /**
     * Post an event to the appropriate PubChan with proper hierarchy information
     */
    postEvent(dlg, event) {
        const typedEvent = this.createTypedEvent(dlg.id, event);
        this.emitDialogTouched(typedEvent);
        if (this.dispatchGloballyIfNeeded(typedEvent)) {
            return;
        }
        const chan = this.getPubChan(dlg.id);
        chan.write(typedEvent);
    }
    postEventById(dialogId, event) {
        const typedEvent = this.createTypedEvent(dialogId, event);
        this.emitDialogTouched(typedEvent);
        if (this.dispatchGloballyIfNeeded(typedEvent)) {
            return;
        }
        const chan = this.getPubChan(dialogId);
        chan.write(typedEvent);
    }
    /**
     * End-of-stream for a specific dialog
     */
    endStream(dialogId) {
        const chan = this.pubChans.get(dialogId.key());
        if (chan) {
            chan.write(evt_1.EndOfStream);
        }
    }
    /**
     * Clean up all PubChans (for server shutdown)
     */
    cleanup() {
        for (const chan of this.pubChans.values()) {
            chan.write(evt_1.EndOfStream);
        }
        this.pubChans.clear();
    }
}
// Export singleton instance
exports.dialogEventRegistry = new DialogEventRegistryImpl();
function setGlobalDialogEventBroadcaster(fn) {
    exports.dialogEventRegistry.setGlobalDialogEventBroadcaster(fn);
}
// Export helper function to import in other modules
function postDialogEvent(dlg, event) {
    exports.dialogEventRegistry.postEvent(dlg, event);
}
/**
 * Post an event to a specific dialog's PubChan by ID.
 * Useful for posting sideDialog events when only the sideDialog ID is available.
 */
function postDialogEventById(dialogId, event) {
    exports.dialogEventRegistry.postEventById(dialogId, event);
}
// Helper functions to create events with simpler API
function createEvent(type) {
    return { type };
}
