const listenersByUser = new Map();
/**
 * Register a listener for `userId`'s notifications. Returns an
 * unsubscribe function — callers MUST invoke it on disconnect, or the
 * bus will leak listener references indefinitely (each listener pins
 * its enclosing closure).
 */
export function subscribeUserNotifications(userId, listener) {
    if (!userId) {
        // Defensive: an empty userId would silently bucket every "unknown"
        // subscriber into the same set, leaking notifications across
        // sessions. Reject at the entry point.
        throw new Error('subscribeUserNotifications requires a non-empty userId');
    }
    let set = listenersByUser.get(userId);
    if (!set) {
        set = new Set();
        listenersByUser.set(userId, set);
    }
    set.add(listener);
    return () => {
        const current = listenersByUser.get(userId);
        if (!current)
            return;
        current.delete(listener);
        if (current.size === 0)
            listenersByUser.delete(userId);
    };
}
/**
 * Publish `notification` to every listener registered for its
 * `userId`. Listener exceptions are isolated — one buggy stream must
 * not take down every other subscriber for the same user (and never
 * the calling write path).
 *
 * Returns `true` when at least one listener was reachable, `false`
 * otherwise (useful in tests to assert SSE wiring; production code can
 * ignore the return value).
 */
export function publishNotification(notification) {
    // The DTO's `userId` is the source of truth; we deliberately don't
    // accept a separate userId arg so callers can't accidentally publish
    // to the wrong channel (e.g. publishing reply notifications to the
    // actor instead of the recipient).
    const set = listenersByUser.get(notification.userId);
    if (!set || set.size === 0)
        return false;
    // Snapshot the listener set before iteration — a listener may
    // synchronously unsubscribe (e.g. close its stream on the first
    // event), and mutating the Set during forEach would skip neighbours.
    for (const listener of [...set]) {
        try {
            listener(notification);
        }
        catch {
            /* isolated; see contract above */
        }
    }
    return true;
}
/**
 * How many active subscribers does this user have? Useful for tests
 * and operator dashboards ("is anyone listening?"). Not part of the
 * write-path contract.
 */
export function notificationSubscriberCount(userId) {
    return listenersByUser.get(userId)?.size ?? 0;
}
/**
 * Test helper. Drops every subscription. Production code never calls
 * this — listener leaks should be visible in the test suite, not
 * papered over in production.
 */
export function __resetNotificationBus() {
    listenersByUser.clear();
}
//# sourceMappingURL=notification-bus.js.map