/**
 * In-memory room manager for WebRTC signaling.
 * Tracks rooms and their connected peers.
 *
 * @example
 * ```ts
 * import { createRouter, webrtc } from '@agentuity/runtime';
 *
 * const router = createRouter();
 *
 * // Basic usage
 * router.get('/call/signal', webrtc());
 *
 * // With callbacks for monitoring
 * router.get('/call/signal', webrtc({
 *   maxPeers: 2,
 *   callbacks: {
 *     onRoomCreated: (roomId) => console.log(`Room ${roomId} created`),
 *     onPeerJoin: (peerId, roomId) => console.log(`${peerId} joined ${roomId}`),
 *     onPeerLeave: (peerId, roomId, reason) => {
 *       analytics.track('peer_left', { peerId, roomId, reason });
 *     },
 *     onMessage: (type, from, to, roomId) => {
 *       metrics.increment(`webrtc.${type}`);
 *     },
 *   },
 * }));
 * ```
 */
export class WebRTCRoomManager {
    // roomId -> Map<peerId, PeerConnection>
    rooms = new Map();
    // ws -> peerId (reverse lookup for cleanup)
    wsToPeer = new Map();
    maxPeers;
    peerIdCounter = 0;
    callbacks;
    constructor(options) {
        this.maxPeers = options?.maxPeers ?? 2;
        this.callbacks = options?.callbacks ?? {};
    }
    generatePeerId() {
        return `peer-${Date.now()}-${++this.peerIdCounter}`;
    }
    send(ws, msg) {
        ws.send(JSON.stringify(msg));
    }
    broadcast(roomId, msg, excludePeerId) {
        const room = this.rooms.get(roomId);
        if (!room)
            return;
        for (const [peerId, peer] of room) {
            if (peerId !== excludePeerId) {
                this.send(peer.ws, msg);
            }
        }
    }
    /**
     * Handle a peer joining a room
     */
    handleJoin(ws, roomId) {
        let room = this.rooms.get(roomId);
        const isNewRoom = !room;
        // Create room if it doesn't exist
        if (!room) {
            room = new Map();
            this.rooms.set(roomId, room);
        }
        // Check room capacity
        if (room.size >= this.maxPeers) {
            const error = new Error(`Room is full (max ${this.maxPeers} peers)`);
            this.callbacks.onError?.(error, undefined, roomId);
            this.send(ws, { t: 'error', message: error.message });
            return;
        }
        const peerId = this.generatePeerId();
        const existingPeers = Array.from(room.keys());
        // Add peer to room
        room.set(peerId, { ws, roomId });
        this.wsToPeer.set(ws, { peerId, roomId });
        // Fire callbacks
        if (isNewRoom) {
            this.callbacks.onRoomCreated?.(roomId);
        }
        this.callbacks.onPeerJoin?.(peerId, roomId);
        // Send joined confirmation with list of existing peers
        this.send(ws, { t: 'joined', peerId, roomId, peers: existingPeers });
        // Notify existing peers about new peer
        this.broadcast(roomId, { t: 'peer-joined', peerId }, peerId);
    }
    /**
     * Handle a peer disconnecting
     */
    handleDisconnect(ws) {
        const peerInfo = this.wsToPeer.get(ws);
        if (!peerInfo)
            return;
        const { peerId, roomId } = peerInfo;
        const room = this.rooms.get(roomId);
        if (room) {
            room.delete(peerId);
            // Fire callback
            this.callbacks.onPeerLeave?.(peerId, roomId, 'disconnect');
            // Notify remaining peers
            this.broadcast(roomId, { t: 'peer-left', peerId });
            // Clean up empty room
            if (room.size === 0) {
                this.rooms.delete(roomId);
                this.callbacks.onRoomDestroyed?.(roomId);
            }
        }
        this.wsToPeer.delete(ws);
    }
    /**
     * Relay SDP message to target peer(s)
     */
    handleSDP(ws, to, description) {
        const peerInfo = this.wsToPeer.get(ws);
        if (!peerInfo) {
            const error = new Error('Not in a room');
            this.callbacks.onError?.(error);
            this.send(ws, { t: 'error', message: error.message });
            return;
        }
        const { peerId, roomId } = peerInfo;
        const room = this.rooms.get(roomId);
        if (!room)
            return;
        // Fire callback
        this.callbacks.onMessage?.('sdp', peerId, to, roomId);
        // Server injects 'from' to prevent spoofing
        const msg = { t: 'sdp', from: peerId, description };
        if (to) {
            // Send to specific peer
            const targetPeer = room.get(to);
            if (targetPeer) {
                this.send(targetPeer.ws, msg);
            }
        }
        else {
            // Broadcast to all peers in room
            this.broadcast(roomId, msg, peerId);
        }
    }
    /**
     * Relay ICE candidate to target peer(s)
     */
    handleICE(ws, to, candidate) {
        const peerInfo = this.wsToPeer.get(ws);
        if (!peerInfo) {
            const error = new Error('Not in a room');
            this.callbacks.onError?.(error);
            this.send(ws, { t: 'error', message: error.message });
            return;
        }
        const { peerId, roomId } = peerInfo;
        const room = this.rooms.get(roomId);
        if (!room)
            return;
        // Fire callback
        this.callbacks.onMessage?.('ice', peerId, to, roomId);
        // Server injects 'from' to prevent spoofing
        const msg = { t: 'ice', from: peerId, candidate };
        if (to) {
            // Send to specific peer
            const targetPeer = room.get(to);
            if (targetPeer) {
                this.send(targetPeer.ws, msg);
            }
        }
        else {
            // Broadcast to all peers in room
            this.broadcast(roomId, msg, peerId);
        }
    }
    /**
     * Handle incoming signaling message
     */
    handleMessage(ws, data) {
        let msg;
        try {
            msg = JSON.parse(data);
        }
        catch {
            const error = new Error('Invalid JSON');
            this.callbacks.onError?.(error);
            this.send(ws, { t: 'error', message: error.message });
            return;
        }
        // Validate message format
        if (!msg || typeof msg.t !== 'string') {
            const error = new Error('Invalid message format');
            this.callbacks.onError?.(error);
            this.send(ws, { t: 'error', message: error.message });
            return;
        }
        switch (msg.t) {
            case 'join':
                if (!msg.roomId || typeof msg.roomId !== 'string') {
                    this.send(ws, { t: 'error', message: 'Missing or invalid roomId' });
                    return;
                }
                this.handleJoin(ws, msg.roomId);
                break;
            case 'sdp':
                if (!msg.description || typeof msg.description !== 'object') {
                    this.send(ws, { t: 'error', message: 'Missing or invalid description' });
                    return;
                }
                this.handleSDP(ws, msg.to, msg.description);
                break;
            case 'ice':
                if (!msg.candidate || typeof msg.candidate !== 'object') {
                    this.send(ws, { t: 'error', message: 'Missing or invalid candidate' });
                    return;
                }
                this.handleICE(ws, msg.to, msg.candidate);
                break;
            default:
                this.send(ws, {
                    t: 'error',
                    message: `Unknown message type: ${msg.t}`,
                });
        }
    }
    /**
     * Get room stats for debugging
     */
    getRoomStats() {
        let totalPeers = 0;
        for (const room of this.rooms.values()) {
            totalPeers += room.size;
        }
        return { roomCount: this.rooms.size, totalPeers };
    }
}
//# sourceMappingURL=webrtc-signaling.js.map