/**
 * Chat Gateway Module
 *
 * Integrates chat functionality with the WebSocket infrastructure.
 * Forwards chat events from ChatService to connected clients.
 *
 * @module websocket/chat.gateway
 */
import { getChatV2Service } from '../services/chat-v2/chat-v2.singleton.js';
import { SYSTEM_PRINCIPAL, v2MessageToLegacy, v2ChannelToLegacy, senderToV2, } from '../services/chat-v2/legacy-dto.utils.js';
import { LoggerService } from '../services/core/logger.service.js';
/**
 * Chat Gateway class for WebSocket-based chat messaging.
 *
 * Provides:
 * - Real-time chat message broadcasting
 * - Typing indicator support
 * - Conversation update notifications
 * - Terminal output to chat message processing
 */
export class ChatGateway {
    io;
    logger;
    chatV2;
    initialized = false;
    /**
     * Create a new ChatGateway.
     *
     * @param io - Socket.IO server instance
     */
    constructor(io) {
        this.io = io;
        this.logger = LoggerService.getInstance().createComponentLogger('ChatGateway');
        this.chatV2 = getChatV2Service();
    }
    /**
     * Initialize the chat gateway.
     *
     * Subscribes to chat-v2 message events and wires the
     * client-facing WebSocket handlers.
     */
    async initialize() {
        if (this.initialized)
            return;
        this.setupChatV2Listeners();
        this.setupWebSocketHandlers();
        this.initialized = true;
        this.logger.info('ChatGateway initialized');
    }
    /**
     * Subscribe to ChatV2Service events and broadcast to WebSocket clients.
     * Phase 6c of the unified-chat-message-store spec — replaces the
     * legacy ChatService EventEmitter wiring.
     */
    setupChatV2Listeners() {
        this.chatV2.on('chat_message', (dto) => {
            const legacy = v2MessageToLegacy(dto);
            this.logger.debug('Broadcasting chat_message', { messageId: legacy.id });
            this.broadcast('chat_message', {
                type: 'chat_message',
                data: legacy,
                timestamp: new Date().toISOString(),
            });
        });
    }
    /**
     * Set up WebSocket event handlers for chat-specific events.
     */
    setupWebSocketHandlers() {
        this.io.on('connection', (socket) => {
            // Handle chat conversation subscription
            socket.on('subscribe_to_chat', async (conversationId) => {
                this.logger.debug('Client subscribing to chat', {
                    socketId: socket.id,
                    conversationId,
                });
                // Join chat room
                socket.join('chat');
                if (conversationId) {
                    socket.join(`chat_${conversationId}`);
                }
                // Send current conversation if requested
                if (conversationId) {
                    try {
                        const channel = this.chatV2.getChannel(conversationId, SYSTEM_PRINCIPAL);
                        const messageCount = this.chatV2.countChannelMessages(conversationId, SYSTEM_PRINCIPAL);
                        socket.emit('chat_conversation', {
                            type: 'chat_conversation',
                            data: v2ChannelToLegacy(channel, messageCount),
                            timestamp: new Date().toISOString(),
                        });
                    }
                    catch {
                        // Channel not found / unauthorized — silently skip; the
                        // client will see no chat_conversation event.
                    }
                }
                socket.emit('chat_subscribed', {
                    type: 'chat_subscribed',
                    data: { conversationId },
                    timestamp: new Date().toISOString(),
                });
            });
            // Handle unsubscription from chat
            socket.on('unsubscribe_from_chat', (conversationId) => {
                socket.leave('chat');
                if (conversationId) {
                    socket.leave(`chat_${conversationId}`);
                }
            });
            // Handle typing indicator from client
            socket.on('chat_typing', (data) => {
                // User typing indicators are just echoed to other clients
                socket.to(`chat_${data.conversationId}`).emit('chat_typing', {
                    type: 'chat_typing',
                    data: {
                        conversationId: data.conversationId,
                        sender: { type: 'user' },
                        isTyping: data.isTyping,
                    },
                    timestamp: new Date().toISOString(),
                });
            });
        });
    }
    /**
     * Broadcast a message to all clients in the chat room.
     *
     * @param event - Event name
     * @param message - Message to broadcast
     */
    broadcast(event, message) {
        this.io.to('chat').emit(event, message);
    }
    /**
     * Broadcast a message to a specific conversation room.
     *
     * @param conversationId - Conversation ID
     * @param event - Event name
     * @param message - Message to broadcast
     */
    broadcastToConversation(conversationId, event, message) {
        this.io.to(`chat_${conversationId}`).emit(event, message);
    }
    /**
     * Process terminal output and convert to chat message if applicable.
     *
     * Checks for response markers in terminal output and creates
     * chat messages from extracted content.
     *
     * @param sessionId - The session/agent ID that produced the output
     * @param output - Raw terminal output
     * @param conversationId - Target conversation ID
     * @returns The created chat message, or null if no response marker found
     */
    async processTerminalOutput(sessionId, output, conversationId) {
        // Only process if there's an active conversation
        if (!conversationId)
            return null;
        // Check if output contains response markers
        const hasResponseMarker = output.includes('[RESPONSE]') ||
            output.includes('[CHAT_RESPONSE]') ||
            output.includes('```response');
        if (!hasResponseMarker)
            return null;
        try {
            const channel = this.chatV2.ensureChannelForLegacyConversation({
                conversationId,
                agentSession: 'crewly-orc',
            });
            const { message } = this.chatV2.recordTurn({
                channelId: channel.id,
                senderType: 'agent',
                senderId: sessionId,
                content: output,
                metadata: { source: 'pty-runtime', sessionId },
            });
            const legacyMessage = v2MessageToLegacy(message);
            this.logger.debug('Added agent message to chat', {
                messageId: legacyMessage.id,
                conversationId,
            });
            return legacyMessage;
        }
        catch (error) {
            this.logger.error('Failed to process terminal output for chat', {
                sessionId,
                error: error instanceof Error ? error.message : 'Unknown error',
            });
            return null;
        }
    }
    /**
     * Process a unified [NOTIFY] message and add it directly to a conversation.
     *
     * Unlike `processTerminalOutput`, this method takes pre-extracted content
     * from a [NOTIFY] payload — no regex extraction needed. Optional metadata
     * (e.g. Slack delivery tracking fields) is merged into the message metadata.
     *
     * @param sessionId - The session/agent ID that produced the output
     * @param content - Pre-extracted markdown content from NotifyPayload.message
     * @param conversationId - Target conversation ID
     * @param metadata - Optional additional metadata to attach to the message
     * @returns The created chat message, or null on failure
     */
    async processNotifyMessage(sessionId, content, conversationId, metadata) {
        try {
            const channel = this.chatV2.ensureChannelForLegacyConversation({
                conversationId,
                agentSession: 'crewly-orc',
            });
            const { message } = this.chatV2.recordTurn({
                channelId: channel.id,
                senderType: 'agent',
                senderId: sessionId,
                content,
                metadata: {
                    source: 'in-process-runtime',
                    sessionId,
                    ...(metadata ?? {}),
                },
            });
            const legacyMessage = v2MessageToLegacy(message);
            this.logger.debug('Added notify message to chat', {
                messageId: legacyMessage.id,
                conversationId,
            });
            return legacyMessage;
        }
        catch (error) {
            this.logger.error('Failed to process notify message for chat', {
                sessionId,
                conversationId,
                error: error instanceof Error ? error.message : 'Unknown error',
            });
            return null;
        }
    }
    /**
     * Emit a typing indicator for the orchestrator.
     *
     * Call this when the orchestrator starts/stops processing a message.
     *
     * @param conversationId - Conversation ID
     * @param isTyping - Whether the orchestrator is typing
     */
    emitOrchestratorTyping(conversationId, isTyping) {
        this.broadcastTyping(conversationId, { type: 'orchestrator', name: 'Orchestrator' }, isTyping);
    }
    /**
     * Emit a typing indicator for an agent.
     *
     * @param conversationId - Conversation ID
     * @param sender - Agent sender information
     * @param isTyping - Whether the agent is typing
     */
    emitAgentTyping(conversationId, sender, isTyping) {
        this.broadcastTyping(conversationId, sender, isTyping);
    }
    /**
     * Emit a typing indicator via the WebSocket broadcast channel.
     * Phase 6c: typing indicators were previously emitted on the
     * legacy ChatService EventEmitter; with that retired, the gateway
     * sources them directly. Typing is transient and never persisted,
     * so no chat-v2 write is involved.
     *
     * @param conversationId - Conversation ID
     * @param sender - Sender descriptor
     * @param isTyping - Whether the sender is typing
     */
    broadcastTyping(conversationId, sender, isTyping) {
        // Resolve sender type via the shared mapper so the wire shape
        // matches what existing subscribers expect.
        senderToV2(sender);
        this.broadcast('chat_typing', {
            type: 'chat_typing',
            data: { conversationId, sender, isTyping },
            timestamp: new Date().toISOString(),
        });
    }
    /**
     * Check if the gateway is initialized.
     *
     * @returns True if initialized
     */
    isInitialized() {
        return this.initialized;
    }
}
// =============================================================================
// Singleton Instance
// =============================================================================
let chatGatewayInstance = null;
/**
 * Initialize the ChatGateway with a Socket.IO server.
 *
 * @param io - Socket.IO server instance
 * @returns The ChatGateway instance
 */
export async function initializeChatGateway(io) {
    if (!chatGatewayInstance) {
        chatGatewayInstance = new ChatGateway(io);
        await chatGatewayInstance.initialize();
    }
    return chatGatewayInstance;
}
/**
 * Get the ChatGateway instance.
 *
 * @returns The ChatGateway instance or null if not initialized
 */
export function getChatGateway() {
    return chatGatewayInstance;
}
/**
 * Reset the ChatGateway instance (for testing).
 */
export function resetChatGateway() {
    chatGatewayInstance = null;
}
//# sourceMappingURL=chat.gateway.js.map