import { logger } from '../utils/logger';
/**
 * Create a live, per-interaction context (GENC-1390). Framework-agnostic: the
 * `subscribe`/`notify` pair is a plain observer set, not FAST reactivity. See
 * {@link InteractionContext} for the lifetime/semantics contract.
 *
 * @internal
 */
export function createInteractionContext(interactionId) {
    const state = {};
    const onceCache = new Map();
    const listeners = new Set();
    const disposers = new Set();
    let disposed = false;
    const context = {
        interactionId,
        state,
        once(key, factory) {
            if (onceCache.has(key))
                return onceCache.get(key);
            // Interaction already ended (resolve/timeout/evict): refuse to run the factory.
            // A resource created here would leak — dispose has already run every `onDispose`
            // and won't run again. Return null so a stray late call (e.g. an async callback
            // that lands after a `timeoutMs` timeout) degrades instead of crashing or leaking.
            if (disposed) {
                logger.warn(`InteractionContext.once("${key}") called after dispose; not creating a resource on an ended interaction.`);
                return null;
            }
            const value = factory();
            onceCache.set(key, value);
            return value;
        },
        notify() {
            // A Set tolerates a listener unsubscribing mid-walk (matches
            // interaction-panel-session's forEach); a throwing subscriber must not stop the rest.
            for (const listener of listeners) {
                try {
                    listener();
                }
                catch (e) {
                    logger.error('InteractionContext subscriber threw during notify()', e);
                }
            }
        },
        subscribe(listener) {
            listeners.add(listener);
            return () => {
                listeners.delete(listener);
            };
        },
        onDispose(fn) {
            // Late registration after the interaction already ended: run immediately so
            // callers never leak a resource by registering a beat too late.
            if (disposed) {
                fn();
                return;
            }
            disposers.add(fn);
        },
    };
    return {
        context,
        dispose() {
            if (disposed)
                return;
            disposed = true;
            for (const fn of disposers) {
                try {
                    fn();
                }
                catch (e) {
                    logger.error('InteractionContext onDispose callback threw', e);
                }
            }
            disposers.clear();
            listeners.clear();
            // Release memoised `once` values (sockets, timers, service handles) for GC — the resources
            // themselves are closed via their `onDispose`, but drop the references too.
            onceCache.clear();
        },
    };
}
