/**
 * Message Router Service
 *
 * Provides transparent cross-device message routing for agent communication.
 * When a target agent session is not found locally, the router queries
 * CloudSyncService for same-account devices that host the agent and forwards
 * the message through the Cloud message queue.
 *
 * On the receiving end, listens for incoming `agent_message` events from
 * CloudSyncService and delivers them to the local agent session via the
 * standard terminal delivery path.
 *
 * This makes cross-device communication fully transparent to callers —
 * orchestrator skills (send-message, delegate-task) require zero changes.
 *
 * @module services/messaging/message-router.service
 */
import { LoggerService } from '../core/logger.service.js';
import { CloudSyncService } from '../cloud/cloud-sync.service.js';
import { isAgentMessagePayload } from '../cloud/cloud-sync.types.js';
import { ORCHESTRATOR_SESSION_NAME } from '../../constants.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** Constants for the MessageRouterService. */
export const MESSAGE_ROUTER_CONSTANTS = {
    /** Maximum time to wait for remote delivery confirmation (ms) */
    REMOTE_DELIVERY_TIMEOUT_MS: 15_000,
    /** Log component name */
    COMPONENT_NAME: 'MessageRouterService',
};
// ---------------------------------------------------------------------------
// Service
// ---------------------------------------------------------------------------
/**
 * MessageRouterService singleton.
 *
 * Transparently routes agent messages across devices under the same
 * Cloud account. Local sessions are delivered directly; remote sessions
 * are forwarded via the Cloud message queue.
 *
 * @example
 * ```typescript
 * const router = MessageRouterService.getInstance();
 * router.setLocalDeliveryHandler(myDeliveryFn);
 * router.startListening();
 *
 * // Route a message — automatically determines local vs remote
 * const result = await router.routeRemote('crewly-product-leo', 'Hello Leo');
 * if (result.routed) {
 *   console.log(`Routed to device ${result.deviceName}`);
 * }
 * ```
 */
export class MessageRouterService {
    static instance = null;
    logger;
    /** Whether we are listening for incoming agent_message events */
    listening = false;
    /** Bound handler reference for cleanup */
    boundMessageHandler = null;
    /** Handler for delivering messages to local agent sessions */
    localDeliveryHandler = null;
    /** Handler for enqueuing messages into the message queue */
    messageEnqueueHandler = null;
    constructor() {
        this.logger = LoggerService.getInstance().createComponentLogger(MESSAGE_ROUTER_CONSTANTS.COMPONENT_NAME);
    }
    /**
     * Get the singleton instance.
     *
     * @returns MessageRouterService instance
     */
    static getInstance() {
        if (!MessageRouterService.instance) {
            MessageRouterService.instance = new MessageRouterService();
        }
        return MessageRouterService.instance;
    }
    /**
     * Reset the singleton (for testing).
     */
    static resetInstance() {
        if (MessageRouterService.instance) {
            MessageRouterService.instance.stopListening();
        }
        MessageRouterService.instance = null;
    }
    // -------------------------------------------------------------------------
    // Configuration
    // -------------------------------------------------------------------------
    /**
     * Set the handler used to deliver messages to local agent sessions.
     * Must be called before startListening() to enable incoming message delivery.
     *
     * @param handler - Function that delivers a message to a local session
     */
    setLocalDeliveryHandler(handler) {
        this.localDeliveryHandler = handler;
    }
    /**
     * Set the handler for enqueuing messages into the message queue.
     * Used by cross-machine message handling to route messages through
     * the standard queue pipeline (with source metadata for reply routing).
     */
    setMessageEnqueueHandler(handler) {
        this.messageEnqueueHandler = handler;
    }
    // -------------------------------------------------------------------------
    // Outbound: Remote Routing
    // -------------------------------------------------------------------------
    /**
     * Find which remote device hosts the given agent session.
     *
     * Searches the cached device list from CloudSyncService, inspecting
     * each device's team summaries for matching agent session names.
     *
     * @param sessionName - Target agent session name
     * @returns The SyncDevice hosting the session, or null if not found
     */
    findDeviceForSession(sessionName) {
        const sync = CloudSyncService.getInstance();
        if (!sync.isStarted())
            return null;
        const onlineDevices = sync.getOnlineDevices();
        for (const device of onlineDevices) {
            if (!device.teams)
                continue;
            for (const team of device.teams) {
                if (!team.agents)
                    continue;
                for (const agent of team.agents) {
                    if (agent.sessionName === sessionName) {
                        return device;
                    }
                }
            }
        }
        return null;
    }
    /**
     * Route a message to a remote device via the Cloud message queue.
     *
     * Called when a target agent session is not found locally. Queries
     * CloudSyncService for the device hosting that session and sends
     * an `agent_message` through the Cloud queue.
     *
     * @param targetSession - Target agent session name
     * @param message - Message content to deliver
     * @param priority - Delivery priority (default: 'normal')
     * @returns RouteResult indicating success/failure
     *
     * @example
     * ```typescript
     * const result = await router.routeRemote('crewly-product-leo', 'Build the feature');
     * if (!result.routed) {
     *   console.error('Remote routing failed:', result.error);
     * }
     * ```
     */
    async routeRemote(targetSession, message, priority = 'normal') {
        const sync = CloudSyncService.getInstance();
        // Guard: CloudSync must be running
        if (!sync.isStarted()) {
            return { routed: false, error: 'CloudSyncService not started' };
        }
        // Find the device hosting this session
        const targetDevice = this.findDeviceForSession(targetSession);
        if (!targetDevice) {
            return {
                routed: false,
                error: `Session '${targetSession}' not found on any online device`,
            };
        }
        // Build the agent_message payload
        const config = this.getLocalDeviceInfo();
        const payload = {
            targetSession,
            message,
            fromDevice: config.deviceId,
            fromDeviceName: config.deviceName,
            priority,
        };
        try {
            await sync.sendMessage(targetDevice.deviceId, 'agent_message', payload);
            this.logger.info('Message routed to remote device', {
                targetSession,
                deviceId: targetDevice.deviceId,
                deviceName: targetDevice.deviceName,
                messageLength: message.length,
            });
            return {
                routed: true,
                deviceId: targetDevice.deviceId,
                deviceName: targetDevice.deviceName,
            };
        }
        catch (error) {
            const errorMsg = error instanceof Error ? error.message : String(error);
            this.logger.error('Failed to route message to remote device', {
                targetSession,
                deviceId: targetDevice.deviceId,
                error: errorMsg,
            });
            return { routed: false, error: errorMsg };
        }
    }
    // -------------------------------------------------------------------------
    // Inbound: Incoming Message Handling
    // -------------------------------------------------------------------------
    /**
     * Start listening for incoming `agent_message` events from CloudSyncService.
     *
     * When an `agent_message` is received, it extracts the target session name
     * and delivers the message to the local agent via the configured
     * localDeliveryHandler.
     */
    startListening() {
        if (this.listening)
            return;
        const sync = CloudSyncService.getInstance();
        this.boundMessageHandler = (msg) => {
            this.handleIncomingMessage(msg).catch((err) => {
                this.logger.error('Error handling incoming agent_message', {
                    error: err instanceof Error ? err.message : String(err),
                    messageId: msg.id,
                });
            });
        };
        sync.on('message', this.boundMessageHandler);
        this.listening = true;
        this.logger.info('MessageRouterService started listening for agent_message events');
    }
    /**
     * Stop listening for incoming messages.
     */
    stopListening() {
        if (!this.listening || !this.boundMessageHandler)
            return;
        const sync = CloudSyncService.getInstance();
        sync.removeListener('message', this.boundMessageHandler);
        this.boundMessageHandler = null;
        this.listening = false;
        this.logger.info('MessageRouterService stopped listening');
    }
    /**
     * Handle an incoming message from CloudSyncService.
     * Only processes messages of type `agent_message`.
     *
     * @param msg - The incoming message from another device
     */
    async handleIncomingMessage(msg) {
        // Handle cross-machine messages (device-to-device via CloudSync)
        if (msg.type === 'cross-machine') {
            await this.handleCrossMachineMessage(msg);
            return;
        }
        // Handle browser command responses relayed from Chrome Extension
        if (msg.type === 'browser_response') {
            const { BrowserRelayAdapter } = await import('../browser/browser-relay-adapter.service.js');
            const adapter = BrowserRelayAdapter.getInstance();
            adapter.handleRelayResponse(msg.payload);
            return;
        }
        // Only handle agent_message type beyond this point
        if (msg.type !== 'agent_message')
            return;
        const payload = msg.payload;
        if (!isAgentMessagePayload(payload)) {
            this.logger.warn('Received agent_message with invalid payload', {
                messageId: msg.id,
                from: msg.from,
            });
            return;
        }
        this.logger.info('Received remote agent_message', {
            messageId: msg.id,
            from: msg.fromDeviceName,
            targetSession: payload.targetSession,
            messageLength: payload.message.length,
        });
        // Deliver to the local agent session
        if (!this.localDeliveryHandler) {
            this.logger.error('No localDeliveryHandler configured — cannot deliver remote message', {
                targetSession: payload.targetSession,
            });
            return;
        }
        try {
            const result = await this.localDeliveryHandler(payload.targetSession, payload.message);
            if (result.success) {
                this.logger.info('Remote message delivered to local agent', {
                    targetSession: payload.targetSession,
                    from: payload.fromDeviceName,
                });
            }
            else {
                this.logger.warn('Failed to deliver remote message to local agent', {
                    targetSession: payload.targetSession,
                    error: result.error,
                });
            }
        }
        catch (error) {
            this.logger.error('Exception delivering remote message to local agent', {
                targetSession: payload.targetSession,
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
    /**
     * Check if the router is currently listening for incoming messages.
     *
     * @returns True if listening
     */
    isListening() {
        return this.listening;
    }
    /**
     * Handle an incoming cross-machine message from CloudSync.
     *
     * Cross-machine messages wrap a CrossMachineMessage object in the payload.
     * The message content is delivered to the orchestrator via the message queue,
     * formatted similarly to a Slack/chat message so it appears in the
     * orchestrator's conversation flow.
     *
     * @param msg - The incoming CloudSync message with type 'cross-machine'
     */
    async handleCrossMachineMessage(msg) {
        const payload = msg.payload;
        const fromName = payload?.fromName || msg.fromDeviceName || msg.from;
        const fromDeviceId = payload?.from || msg.from;
        const msgType = payload?.type || 'unknown';
        const innerPayload = payload?.payload || {};
        const messageContent = innerPayload?.message
            || payload?.message
            || JSON.stringify(payload);
        this.logger.info('Received cross-machine message via CloudSync', {
            messageId: msg.id,
            from: fromName,
            fromDeviceId,
            type: msgType,
        });
        // Enqueue through the message queue (same as Slack/Chat messages) so the
        // orchestrator gets proper routing metadata. The [REMOTE:deviceId] prefix
        // tells the orchestrator to use reply-remote skill for responses.
        try {
            const conversationId = `remote-${fromDeviceId}`;
            const content = `[REMOTE from ${fromName}] (${msgType}) ${messageContent}`;
            if (this.messageEnqueueHandler) {
                this.messageEnqueueHandler({
                    content,
                    conversationId,
                    source: 'remote',
                    sourceMetadata: {
                        fromDeviceId,
                        fromDeviceName: fromName,
                        messageType: msgType,
                    },
                });
            }
            else {
                // Fallback: deliver directly to orchestrator PTY
                if (this.localDeliveryHandler) {
                    await this.localDeliveryHandler(ORCHESTRATOR_SESSION_NAME, content);
                }
            }
            this.logger.info('Cross-machine message enqueued for orchestrator', {
                from: fromName,
                fromDeviceId,
                type: msgType,
                conversationId,
            });
        }
        catch (error) {
            this.logger.error('Failed to enqueue cross-machine message', {
                error: error instanceof Error ? error.message : String(error),
            });
        }
    }
    // -------------------------------------------------------------------------
    // Internal Helpers
    // -------------------------------------------------------------------------
    /**
     * Get local device identity info from CloudSyncService config.
     * Falls back to defaults if sync is not started.
     *
     * @returns Object with deviceId and deviceName
     */
    getLocalDeviceInfo() {
        // Access the device list and find our own entry
        const sync = CloudSyncService.getInstance();
        const devices = sync.getDevices();
        const local = devices.find((d) => d.isLocal);
        if (local) {
            return { deviceId: local.deviceId, deviceName: local.deviceName };
        }
        // Fallback: return placeholder (will still work for logging)
        return { deviceId: 'unknown', deviceName: 'unknown' };
    }
}
//# sourceMappingURL=message-router.service.js.map