"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageBus = void 0;
const events_1 = require("events");
/**
 * MessageBus
 *
 * Enables inter-agent communication via pub/sub and request/response patterns.
 * The nervous system of multi-agent orchestration.
 */
class MessageBus extends events_1.EventEmitter {
    constructor() {
        super();
        // Configuration
        this.maxQueueSize = 10000;
        this.defaultTimeoutMs = 30000;
        this.processingIntervalMs = 100;
        this.subscriptions = new Map();
        this.pendingRequests = new Map();
        this.messageQueue = [];
        this.stats = {
            totalMessages: 0,
            messagesPerType: {
                request: 0,
                response: 0,
                notification: 0,
                broadcast: 0,
                error: 0,
            },
            totalSubscriptions: 0,
            activeSubscriptions: 0,
            averageDeliveryTimeMs: 0,
            queuedMessages: 0,
            failedDeliveries: 0,
        };
    }
    /**
     * Start message processing
     */
    start() {
        if (this.processingInterval)
            return;
        this.processingInterval = setInterval(() => {
            this.processQueue();
        }, this.processingIntervalMs);
    }
    /**
     * Stop message processing
     */
    stop() {
        if (this.processingInterval) {
            clearInterval(this.processingInterval);
            this.processingInterval = undefined;
        }
    }
    /**
     * Subscribe to a topic
     */
    subscribe(topic, callback, options) {
        const subscriptionId = `sub-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        const subscription = {
            subscriptionId,
            agentInstanceId: options?.agentInstanceId || "global",
            topic,
            callback,
            filter: options?.filter,
            priority: options?.priority || 0,
        };
        this.subscriptions.set(subscriptionId, subscription);
        this.stats.totalSubscriptions++;
        this.stats.activeSubscriptions++;
        return subscriptionId;
    }
    /**
     * Unsubscribe from a topic
     */
    unsubscribe(subscriptionId) {
        const removed = this.subscriptions.delete(subscriptionId);
        if (removed) {
            this.stats.activeSubscriptions--;
        }
        return removed;
    }
    /**
     * Publish a message
     */
    async publish(message) {
        const fullMessage = {
            ...message,
            messageId: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
            timestamp: new Date().toISOString(),
            deliveryAttempts: 0,
        };
        // Update stats
        this.stats.totalMessages++;
        this.stats.messagesPerType[fullMessage.type]++;
        // Handle based on type
        switch (fullMessage.type) {
            case "response":
                this.handleResponse(fullMessage);
                break;
            case "request":
            case "notification":
            case "broadcast":
            case "error":
                await this.enqueue(fullMessage);
                break;
        }
        // Emit event for listeners
        this.emit("message", fullMessage);
    }
    /**
     * Request/response pattern
     */
    async request(topic, payload, options) {
        return new Promise(async (resolve, reject) => {
            const correlationId = `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
            const timeoutMs = options?.timeoutMs || this.defaultTimeoutMs;
            // Set up timeout
            const timeout = setTimeout(() => {
                this.pendingRequests.delete(correlationId);
                reject(new Error(`Request timed out after ${timeoutMs}ms`));
            }, timeoutMs);
            // Track request
            this.pendingRequests.set(correlationId, {
                resolve: (response) => {
                    clearTimeout(timeout);
                    resolve(response.payload);
                },
                reject: (error) => {
                    clearTimeout(timeout);
                    reject(error);
                },
                timeout,
            });
            // Publish request
            await this.publish({
                type: "request",
                from: options?.from || "unknown",
                to: options?.to,
                topic,
                payload,
                correlationId,
                replyTo: options?.from,
                priority: options?.priority || "medium",
                ttl: timeoutMs,
            });
        });
    }
    /**
     * Send a response
     */
    async respond(request, payload, options) {
        if (!request.correlationId) {
            throw new Error("Cannot respond to message without correlationId");
        }
        await this.publish({
            type: "response",
            from: options?.from || "unknown",
            to: request.from,
            topic: request.topic,
            payload,
            correlationId: request.correlationId,
            priority: request.priority,
        });
    }
    /**
     * Broadcast to all subscribers
     */
    async broadcast(topic, payload, options) {
        await this.publish({
            type: "broadcast",
            from: options?.from || "system",
            topic,
            payload,
            priority: options?.priority || "low",
            ttl: options?.ttl,
        });
    }
    /**
     * Send notification
     */
    async notify(topic, payload, options) {
        await this.publish({
            type: "notification",
            from: options?.from || "system",
            to: options?.to,
            topic,
            payload,
            priority: options?.priority || "low",
        });
    }
    /**
     * Get statistics
     */
    getStats() {
        return {
            ...this.stats,
            queuedMessages: this.messageQueue.length,
            activeSubscriptions: this.subscriptions.size,
        };
    }
    /**
     * Clear queue
     */
    clearQueue() {
        this.messageQueue = [];
        this.stats.queuedMessages = 0;
    }
    /**
     * Enqueue message
     */
    async enqueue(message) {
        if (this.messageQueue.length >= this.maxQueueSize) {
            // Remove oldest low-priority message
            const lowPriorityIndex = this.messageQueue.findIndex((m) => m.priority === "low");
            if (lowPriorityIndex !== -1) {
                this.messageQueue.splice(lowPriorityIndex, 1);
            }
            else {
                throw new Error("Message queue full");
            }
        }
        this.messageQueue.push(message);
        // Sort by priority (critical > high > medium > low)
        this.messageQueue.sort((a, b) => {
            const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
            return priorityOrder[a.priority] - priorityOrder[b.priority];
        });
        this.stats.queuedMessages = this.messageQueue.length;
    }
    /**
     * Process message queue
     */
    async processQueue() {
        if (this.messageQueue.length === 0)
            return;
        const batchSize = 10; // Process up to 10 messages per interval
        const batch = this.messageQueue.splice(0, batchSize);
        for (const message of batch) {
            await this.deliverMessage(message);
        }
        this.stats.queuedMessages = this.messageQueue.length;
    }
    /**
     * Deliver message to subscribers
     */
    async deliverMessage(message) {
        const startTime = Date.now();
        let delivered = false;
        // Find matching subscriptions
        const matchingSubscriptions = Array.from(this.subscriptions.values())
            .filter((sub) => this.topicMatches(sub.topic, message.topic))
            .filter((sub) => !message.to || sub.agentInstanceId === message.to)
            .filter((sub) => !sub.filter || sub.filter(message))
            .sort((a, b) => b.priority - a.priority);
        // Deliver to all matching subscribers
        for (const subscription of matchingSubscriptions) {
            try {
                await subscription.callback(message);
                delivered = true;
            }
            catch (error) {
                console.error(`Error delivering message to ${subscription.subscriptionId}:`, error);
                this.stats.failedDeliveries++;
            }
        }
        if (!delivered && message.type !== "broadcast") {
            console.warn(`No subscribers for message on topic: ${message.topic}`);
        }
        // Update delivery stats
        const deliveryTime = Date.now() - startTime;
        this.stats.averageDeliveryTimeMs =
            (this.stats.averageDeliveryTimeMs * (this.stats.totalMessages - 1) +
                deliveryTime) /
                this.stats.totalMessages;
        message.deliveryAttempts++;
        message.deliveredAt = new Date().toISOString();
    }
    /**
     * Handle response message
     */
    handleResponse(message) {
        if (!message.correlationId)
            return;
        const pending = this.pendingRequests.get(message.correlationId);
        if (pending) {
            this.pendingRequests.delete(message.correlationId);
            pending.resolve(message);
        }
    }
    /**
     * Check if topic matches pattern (supports wildcards)
     */
    topicMatches(pattern, topic) {
        // Exact match
        if (pattern === topic)
            return true;
        // Wildcard matching
        // Pattern: "agent:*:completed" matches "agent:planner:completed"
        const patternParts = pattern.split(":");
        const topicParts = topic.split(":");
        if (patternParts.length !== topicParts.length)
            return false;
        for (let i = 0; i < patternParts.length; i++) {
            if (patternParts[i] === "*")
                continue;
            if (patternParts[i] !== topicParts[i])
                return false;
        }
        return true;
    }
    /**
     * Configure message bus
     */
    configure(options) {
        if (options.maxQueueSize !== undefined) {
            this.maxQueueSize = options.maxQueueSize;
        }
        if (options.defaultTimeoutMs !== undefined) {
            this.defaultTimeoutMs = options.defaultTimeoutMs;
        }
        if (options.processingIntervalMs !== undefined) {
            this.processingIntervalMs = options.processingIntervalMs;
            // Restart with new interval
            if (this.processingInterval) {
                this.stop();
                this.start();
            }
        }
    }
}
exports.MessageBus = MessageBus;
//# sourceMappingURL=MessageBus.js.map