/**
 * WebSocketHandler - Unified WebSocket Support
 *
 * Provides cross-framework WebSocket handling for real-time AI interactions.
 * Supports connection management, message routing, and graceful shutdown.
 */
import { WebSocketError, WebSocketConnectionError } from "../errors.js";
import { logger } from "../../utils/logger.js";
/**
 * Default WebSocket configuration
 */
const DEFAULT_CONFIG = {
    path: "/ws",
    maxConnections: 1000,
    pingInterval: 30000,
    pongTimeout: 10000,
    maxMessageSize: 1024 * 1024, // 1MB
    auth: {
        strategy: "none",
        required: false,
    },
};
/**
 * Generate unique connection ID
 */
function generateConnectionId() {
    return `ws_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;
}
/**
 * WebSocket connection manager
 */
export class WebSocketConnectionManager {
    connections = new Map();
    config;
    pingIntervals = new Map();
    handlers = new Map();
    constructor(config = {}) {
        this.config = { ...DEFAULT_CONFIG, ...config };
    }
    /**
     * Register a handler for a path
     */
    registerHandler(path, handler) {
        this.handlers.set(path, handler);
        logger.debug(`[WebSocket] Registered handler for ${path}`);
    }
    /**
     * Get handler for a path
     */
    getHandler(path) {
        return this.handlers.get(path);
    }
    /**
     * Handle new connection
     */
    async handleConnection(socket, path, user) {
        // Check max connections
        if (this.connections.size >= this.config.maxConnections) {
            throw new WebSocketConnectionError(`Maximum connections (${this.config.maxConnections}) reached`);
        }
        const connection = {
            id: generateConnectionId(),
            socket,
            user,
            metadata: { path },
            createdAt: Date.now(),
            lastActivity: Date.now(),
        };
        this.connections.set(connection.id, connection);
        // Start ping interval
        this.startPingInterval(connection);
        // Call handler
        const handler = this.handlers.get(path);
        if (handler?.onOpen) {
            try {
                await handler.onOpen(connection);
            }
            catch (error) {
                logger.error(`[WebSocket] Error in onOpen handler: ${error.message}`);
            }
        }
        logger.debug(`[WebSocket] Connection opened: ${connection.id}`);
        return connection;
    }
    /**
     * Handle incoming message
     */
    async handleMessage(connectionId, data, isBinary) {
        const connection = this.connections.get(connectionId);
        if (!connection) {
            throw new WebSocketError("Connection not found", undefined, connectionId);
        }
        // Update activity
        connection.lastActivity = Date.now();
        // Check message size
        const size = typeof data === "string" ? data.length : data.byteLength;
        if (size > this.config.maxMessageSize) {
            throw new WebSocketError(`Message exceeds max size (${this.config.maxMessageSize} bytes)`, undefined, connectionId);
        }
        const message = {
            type: isBinary ? "binary" : "text",
            data,
            timestamp: Date.now(),
        };
        // Call handler
        const path = connection.metadata.path;
        const handler = this.handlers.get(path);
        if (handler?.onMessage) {
            try {
                await handler.onMessage(connection, message);
            }
            catch (error) {
                logger.error(`[WebSocket] Error in onMessage handler: ${error.message}`);
                throw error;
            }
        }
    }
    /**
     * Handle connection close
     */
    async handleClose(connectionId, code, reason) {
        const connection = this.connections.get(connectionId);
        if (!connection) {
            return;
        }
        // Stop ping interval
        this.stopPingInterval(connectionId);
        // Remove connection
        this.connections.delete(connectionId);
        // Call handler
        const path = connection.metadata.path;
        const handler = this.handlers.get(path);
        if (handler?.onClose) {
            try {
                await handler.onClose(connection, code, reason);
            }
            catch (error) {
                logger.error(`[WebSocket] Error in onClose handler: ${error.message}`);
            }
        }
        logger.debug(`[WebSocket] Connection closed: ${connectionId} (${code}: ${reason})`);
    }
    /**
     * Handle connection error
     */
    async handleError(connectionId, error) {
        const connection = this.connections.get(connectionId);
        if (!connection) {
            return;
        }
        // Call handler
        const path = connection.metadata.path;
        const handler = this.handlers.get(path);
        if (handler?.onError) {
            try {
                await handler.onError(connection, error);
            }
            catch (handlerError) {
                logger.error(`[WebSocket] Error in onError handler: ${handlerError.message}`);
            }
        }
        logger.error(`[WebSocket] Connection error: ${connectionId} - ${error.message}`);
    }
    /**
     * Get connection by ID
     */
    getConnection(connectionId) {
        return this.connections.get(connectionId);
    }
    /**
     * Get all connections
     */
    getAllConnections() {
        return Array.from(this.connections.values());
    }
    /**
     * Get connections for a user
     */
    getConnectionsByUser(userId) {
        return Array.from(this.connections.values()).filter((conn) => conn.user?.id === userId);
    }
    /**
     * Get connections for a path
     */
    getConnectionsByPath(path) {
        return Array.from(this.connections.values()).filter((conn) => conn.metadata.path === path);
    }
    /**
     * Send message to a connection
     */
    send(connectionId, data) {
        const connection = this.connections.get(connectionId);
        if (!connection) {
            throw new WebSocketError("Connection not found", undefined, connectionId);
        }
        const socket = connection.socket;
        try {
            socket.send(data);
        }
        catch (error) {
            throw new WebSocketError(`Failed to send message: ${error.message}`, error, connectionId);
        }
    }
    /**
     * Broadcast message to all connections
     */
    broadcast(data, filter) {
        for (const connection of this.connections.values()) {
            if (filter && !filter(connection)) {
                continue;
            }
            try {
                this.send(connection.id, data);
            }
            catch (error) {
                logger.error(`[WebSocket] Broadcast error for ${connection.id}: ${error.message}`);
            }
        }
    }
    /**
     * Close a connection
     */
    async close(connectionId, code = 1000, reason = "Normal closure") {
        const connection = this.connections.get(connectionId);
        if (!connection) {
            return;
        }
        const socket = connection.socket;
        try {
            socket.close(code, reason);
        }
        catch (error) {
            logger.error(`[WebSocket] Error closing connection: ${error.message}`);
        }
        await this.handleClose(connectionId, code, reason);
    }
    /**
     * Close all connections
     */
    async closeAll(code = 1001, reason = "Server shutdown") {
        const closePromises = Array.from(this.connections.keys()).map((id) => this.close(id, code, reason));
        await Promise.all(closePromises);
    }
    /**
     * Get connection count
     */
    getConnectionCount() {
        return this.connections.size;
    }
    /**
     * Start ping interval for a connection
     */
    startPingInterval(connection) {
        if (this.config.pingInterval <= 0) {
            return;
        }
        const interval = setInterval(() => {
            const socket = connection.socket;
            try {
                // Try native ping if available
                if (typeof socket.ping === "function") {
                    socket.ping();
                }
                else {
                    // Send ping as message
                    socket.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
                }
            }
            catch (error) {
                logger.error(`[WebSocket] Ping error for ${connection.id}: ${error.message}`);
                this.close(connection.id, 1001, "Ping failed");
            }
        }, this.config.pingInterval);
        this.pingIntervals.set(connection.id, interval);
    }
    /**
     * Stop ping interval for a connection
     */
    stopPingInterval(connectionId) {
        const interval = this.pingIntervals.get(connectionId);
        if (interval) {
            clearInterval(interval);
            this.pingIntervals.delete(connectionId);
        }
    }
}
/**
 * WebSocket message router for handling different message types
 */
export class WebSocketMessageRouter {
    routes = new Map();
    /**
     * Register a message route
     */
    route(type, handler) {
        this.routes.set(type, handler);
    }
    /**
     * Handle incoming message
     */
    async handle(connection, message) {
        if (message.type !== "text") {
            throw new WebSocketError("Only text messages are supported for routing");
        }
        let parsed;
        try {
            parsed = JSON.parse(message.data);
        }
        catch {
            throw new WebSocketError("Invalid JSON message");
        }
        const { type, payload } = parsed;
        if (!type) {
            throw new WebSocketError("Message type is required");
        }
        const handler = this.routes.get(type);
        if (!handler) {
            throw new WebSocketError(`Unknown message type: ${type}`);
        }
        return handler(connection, payload);
    }
    /**
     * Get registered routes
     */
    getRoutes() {
        return Array.from(this.routes.keys());
    }
}
/**
 * Create a WebSocket handler for AI agent interactions
 */
export function createAgentWebSocketHandler(_neurolink) {
    const router = new WebSocketMessageRouter();
    // Register message routes
    router.route("generate", async (connection, payload) => {
        const { prompt, options: _options } = payload;
        // TODO: Implement generate using neurolink
        return { type: "response", data: `Received: ${prompt}` };
    });
    router.route("stream", async (connection, payload) => {
        const { prompt, options: _options } = payload;
        // TODO: Implement streaming using neurolink
        return { type: "stream_start", data: { prompt } };
    });
    router.route("tool_call", async (connection, payload) => {
        const { toolName, args: _args } = payload;
        // TODO: Implement tool call using neurolink
        return { type: "tool_result", data: { toolName, result: null } };
    });
    return {
        onOpen: async (connection) => {
            logger.info(`[AgentWebSocket] Client connected: ${connection.id}`);
            const socket = connection.socket;
            socket.send(JSON.stringify({
                type: "connected",
                connectionId: connection.id,
                timestamp: Date.now(),
            }));
        },
        onMessage: async (connection, message) => {
            try {
                const result = await router.handle(connection, message);
                if (result) {
                    const socket = connection.socket;
                    socket.send(JSON.stringify(result));
                }
            }
            catch (error) {
                const socket = connection.socket;
                socket.send(JSON.stringify({
                    type: "error",
                    error: error.message,
                }));
            }
        },
        onClose: async (connection, code, reason) => {
            logger.info(`[AgentWebSocket] Client disconnected: ${connection.id} (${code}: ${reason})`);
        },
        onError: async (connection, error) => {
            logger.error(`[AgentWebSocket] Error for ${connection.id}: ${error.message}`);
        },
    };
}
