import { logger } from '../utils/logger.js';
import { emitCommunicationBlocked, emitCommunicationDelivered, emitCommunicationSent } from '../runtime/emitters/index.js';
import { summarizeError } from '../utils/error-display.js';
import { communicationRoleForTemplate, evaluateCommunicationRoute, } from './communication-policy.js';
const DEFAULT_TTL_MS = 5 * 60 * 1000;
export class AgentMessageBus {
    messages = new Map();
    subscriptions = new Map();
    identities = new Map([
        ['orchestrator', { agentId: 'orchestrator', role: 'orchestrator' }],
    ]);
    runtimeBus = null;
    setRuntimeBus(runtimeBus) {
        this.runtimeBus = runtimeBus;
    }
    registerAgent(meta) {
        this.identities.set(meta.agentId, {
            agentId: meta.agentId,
            role: meta.role ?? communicationRoleForTemplate(meta.template),
            ...(meta.parentAgentId !== undefined ? { parentAgentId: meta.parentAgentId } : {}),
            ...(meta.cohort !== undefined ? { cohort: meta.cohort } : {}),
            ...(meta.wrfcId !== undefined ? { wrfcId: meta.wrfcId } : {}),
        });
    }
    resolveOptions(ttlOrOptions) {
        if (typeof ttlOrOptions === 'number') {
            return { ttlMs: ttlOrOptions, kind: 'directive', scope: 'direct' };
        }
        return {
            ttlMs: ttlOrOptions?.ttlMs ?? DEFAULT_TTL_MS,
            kind: ttlOrOptions?.kind ?? 'directive',
            scope: ttlOrOptions?.scope ?? 'direct',
            ...(ttlOrOptions?.cohort !== undefined ? { cohort: ttlOrOptions.cohort } : {}),
            ...(ttlOrOptions?.wrfcId !== undefined ? { wrfcId: ttlOrOptions.wrfcId } : {}),
            ...(ttlOrOptions?.parentAgentId !== undefined ? { parentAgentId: ttlOrOptions.parentAgentId } : {}),
        };
    }
    send(fromId, toId, content, ttlOrOptions = DEFAULT_TTL_MS) {
        this.cleanup();
        const options = this.resolveOptions(ttlOrOptions);
        const fromMeta = this.identities.get(fromId);
        const toMeta = this.identities.get(toId);
        if (fromMeta && toMeta) {
            const decision = evaluateCommunicationRoute({
                from: fromMeta,
                to: toMeta,
                kind: options.kind,
                scope: 'direct',
            });
            if (!decision.allowed) {
                const blockedId = crypto.randomUUID();
                this.emitBlocked(blockedId, fromId, toId, content, options, decision.reason ?? 'communication blocked by policy', fromMeta, toMeta);
                return false;
            }
        }
        const message = {
            id: crypto.randomUUID(),
            from: fromId,
            to: toId,
            content,
            timestamp: Date.now(),
            ttlMs: options.ttlMs,
            kind: options.kind,
            scope: 'direct',
            ...(fromMeta?.role !== undefined ? { fromRole: fromMeta.role } : {}),
            ...(toMeta?.role !== undefined ? { toRole: toMeta.role } : {}),
            ...(options.cohort ?? fromMeta?.cohort ?? toMeta?.cohort ? { cohort: options.cohort ?? fromMeta?.cohort ?? toMeta?.cohort } : {}),
            ...(options.wrfcId ?? fromMeta?.wrfcId ?? toMeta?.wrfcId ? { wrfcId: options.wrfcId ?? fromMeta?.wrfcId ?? toMeta?.wrfcId } : {}),
            ...(options.parentAgentId ?? fromMeta?.parentAgentId ? { parentAgentId: options.parentAgentId ?? fromMeta?.parentAgentId } : {}),
        };
        this.store(toId, message);
        this.deliver(toId, message);
        this.emitSent(message);
        this.emitDelivered(message);
        return true;
    }
    broadcast(fromId, content, ttlOrOptions = DEFAULT_TTL_MS) {
        this.cleanup();
        const options = this.resolveOptions(ttlOrOptions);
        const fromMeta = this.identities.get(fromId);
        if (fromMeta) {
            const decision = evaluateCommunicationRoute({
                from: fromMeta,
                to: { agentId: '*', role: 'general', ...(fromMeta.cohort !== undefined ? { cohort: fromMeta.cohort } : {}) },
                kind: options.kind,
                scope: 'broadcast',
            });
            if (!decision.allowed) {
                const blockedId = crypto.randomUUID();
                this.emitBlocked(blockedId, fromId, '*', content, { ...options, scope: 'broadcast' }, decision.reason ?? 'broadcast blocked by policy', fromMeta);
                return false;
            }
        }
        const message = {
            id: crypto.randomUUID(),
            from: fromId,
            to: '*',
            content,
            timestamp: Date.now(),
            ttlMs: options.ttlMs,
            kind: options.kind,
            scope: 'broadcast',
            ...(fromMeta?.role !== undefined ? { fromRole: fromMeta.role } : {}),
            ...(options.cohort ?? fromMeta?.cohort ? { cohort: options.cohort ?? fromMeta?.cohort } : {}),
            ...(options.wrfcId ?? fromMeta?.wrfcId ? { wrfcId: options.wrfcId ?? fromMeta?.wrfcId } : {}),
            ...(options.parentAgentId ?? fromMeta?.parentAgentId ? { parentAgentId: options.parentAgentId ?? fromMeta?.parentAgentId } : {}),
        };
        this.store('*', message);
        for (const callbacks of this.subscriptions.values()) {
            for (const callback of callbacks) {
                try {
                    callback(message);
                }
                catch (error) {
                    logger.warn('MessageBus: subscriber error in broadcast', {
                        error: summarizeError(error),
                    });
                }
            }
        }
        this.emitSent(message);
        this.emitDelivered(message);
        return true;
    }
    subscribe(agentId, callback) {
        if (!this.subscriptions.has(agentId)) {
            this.subscriptions.set(agentId, new Set());
        }
        this.subscriptions.get(agentId)?.add(callback);
        return () => {
            const callbacks = this.subscriptions.get(agentId);
            if (!callbacks)
                return;
            callbacks.delete(callback);
            if (callbacks.size === 0) {
                this.subscriptions.delete(agentId);
            }
        };
    }
    getMessages(agentId) {
        this.cleanup();
        const now = Date.now();
        const direct = (this.messages.get(agentId) ?? []).filter((message) => now - message.timestamp < message.ttlMs);
        const broadcasts = (this.messages.get('*') ?? []).filter((message) => now - message.timestamp < message.ttlMs);
        return [...direct, ...broadcasts].sort((a, b) => a.timestamp - b.timestamp);
    }
    emitSent(message) {
        if (!this.runtimeBus)
            return;
        emitCommunicationSent(this.runtimeBus, {
            sessionId: 'agent-communication',
            traceId: `agent-communication:${message.id}:sent`,
            source: 'agent-message-bus',
        }, {
            messageId: message.id,
            fromId: message.from,
            toId: message.to,
            scope: message.scope,
            kind: message.kind,
            content: message.content,
            ...(message.fromRole !== undefined ? { fromRole: message.fromRole } : {}),
            ...(message.toRole !== undefined ? { toRole: message.toRole } : {}),
            ...(message.cohort !== undefined ? { cohort: message.cohort } : {}),
            ...(message.wrfcId !== undefined ? { wrfcId: message.wrfcId } : {}),
            ...(message.parentAgentId !== undefined ? { parentAgentId: message.parentAgentId } : {}),
        });
    }
    emitDelivered(message) {
        if (!this.runtimeBus)
            return;
        emitCommunicationDelivered(this.runtimeBus, {
            sessionId: 'agent-communication',
            traceId: `agent-communication:${message.id}:delivered`,
            source: 'agent-message-bus',
        }, {
            messageId: message.id,
            fromId: message.from,
            toId: message.to,
            scope: message.scope,
            kind: message.kind,
        });
    }
    emitBlocked(messageId, fromId, toId, content, options, reason, fromMeta, toMeta) {
        if (!this.runtimeBus)
            return;
        emitCommunicationBlocked(this.runtimeBus, {
            sessionId: 'agent-communication',
            traceId: `agent-communication:${messageId}:blocked`,
            source: 'agent-message-bus',
        }, {
            messageId,
            fromId,
            toId,
            scope: options.scope,
            kind: options.kind,
            reason,
            ...(fromMeta?.role !== undefined ? { fromRole: fromMeta.role } : {}),
            ...(toMeta?.role !== undefined ? { toRole: toMeta.role } : {}),
            ...(options.cohort ?? fromMeta?.cohort ?? toMeta?.cohort ? { cohort: options.cohort ?? fromMeta?.cohort ?? toMeta?.cohort } : {}),
            ...(options.wrfcId ?? fromMeta?.wrfcId ?? toMeta?.wrfcId ? { wrfcId: options.wrfcId ?? fromMeta?.wrfcId ?? toMeta?.wrfcId } : {}),
            ...(options.parentAgentId ?? fromMeta?.parentAgentId ? { parentAgentId: options.parentAgentId ?? fromMeta?.parentAgentId } : {}),
        });
        logger.warn('MessageBus: communication blocked', {
            fromId,
            toId,
            kind: options.kind,
            scope: options.scope,
            reason,
            content,
        });
    }
    cleanup() {
        const now = Date.now();
        for (const [key, messages] of this.messages) {
            const alive = messages.filter((message) => now - message.timestamp < message.ttlMs);
            if (alive.length === 0) {
                this.messages.delete(key);
                continue;
            }
            this.messages.set(key, alive);
        }
    }
    store(recipientId, message) {
        if (!this.messages.has(recipientId)) {
            this.messages.set(recipientId, []);
        }
        this.messages.get(recipientId)?.push(message);
    }
    deliver(agentId, message) {
        const callbacks = this.subscriptions.get(agentId);
        if (!callbacks)
            return;
        for (const callback of callbacks) {
            try {
                callback(message);
            }
            catch (error) {
                logger.warn('MessageBus: subscriber error in deliver', {
                    error: summarizeError(error),
                });
            }
        }
    }
}
