"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketNotificationGateway = void 0;
const ws_1 = __importDefault(require("ws"));
const isIdentifyMessage = (data) => {
    if (typeof data !== 'object' || data === null) {
        return false;
    }
    const message = data;
    return message.type === 'identify' && typeof message.deviceId === 'string' && message.deviceId.length > 0;
};
/**
 * WebSocket implementation of the notification gateway.
 *
 * Responsibilities:
 *   - Accept raw WebSocket connections.
 *   - Wait for an `identify` message containing a deviceId.
 *   - Maintain a map of connected deviceIds to sockets.
 *   - Deliver JSON messages to connected devices.
 *
 * This class does NOT decide which devices should receive a message or what
 * the message content should be; that belongs to the application layer.
 */
class WebSocketNotificationGateway {
    logger;
    sockets = new Map();
    constructor(logger) {
        this.logger = logger;
    }
    handleConnection(socket) {
        socket.on('message', (raw) => {
            try {
                const data = JSON.parse(raw.toString());
                if (isIdentifyMessage(data)) {
                    this.registerSocket(data.deviceId, socket);
                    this.sendRaw(socket, { type: 'identified', deviceId: data.deviceId });
                    return;
                }
                this.sendRaw(socket, { type: 'error', message: 'Expected identify message first' });
                socket.close(1002, 'Expected identify message first');
            }
            catch (error) {
                this.logger.warn('[WebSocket] Invalid message received', { error });
                this.sendRaw(socket, { type: 'error', message: 'Invalid JSON' });
            }
        });
        socket.on('close', () => {
            this.removeSocket(socket);
        });
        socket.on('error', (error) => {
            this.logger.warn('[WebSocket] Socket error', { error });
        });
        // Give the client a short window to identify before closing the socket.
        // This is defense-in-depth; well-behaved clients identify immediately.
        const identifyTimeout = setTimeout(() => {
            if (!this.findDeviceIdBySocket(socket)) {
                this.sendRaw(socket, { type: 'error', message: 'Identification timeout' });
                socket.close(1002, 'Identification timeout');
            }
        }, 30_000);
        socket.once('close', () => {
            clearTimeout(identifyTimeout);
        });
    }
    sendToDevice(deviceId, message) {
        const socket = this.sockets.get(deviceId);
        if (!socket || socket.readyState !== ws_1.default.OPEN) {
            return;
        }
        this.sendRaw(socket, message);
    }
    isDeviceConnected(deviceId) {
        const socket = this.sockets.get(deviceId);
        return socket !== undefined && socket.readyState === ws_1.default.OPEN;
    }
    registerSocket(deviceId, socket) {
        const existing = this.sockets.get(deviceId);
        if (existing && existing !== socket) {
            existing.close(1000, 'Replaced by new connection');
        }
        this.sockets.set(deviceId, socket);
        this.logger.debug('[WebSocket] Device identified', { deviceId });
    }
    removeSocket(socket) {
        const deviceId = this.findDeviceIdBySocket(socket);
        if (deviceId) {
            this.sockets.delete(deviceId);
            this.logger.debug('[WebSocket] Device disconnected', { deviceId });
        }
    }
    findDeviceIdBySocket(socket) {
        for (const [deviceId, registeredSocket] of this.sockets.entries()) {
            if (registeredSocket === socket) {
                return deviceId;
            }
        }
        return undefined;
    }
    sendRaw(socket, message) {
        if (socket.readyState !== ws_1.default.OPEN) {
            return;
        }
        try {
            socket.send(JSON.stringify(message));
        }
        catch (error) {
            this.logger.warn('[WebSocket] Failed to send message', { error });
        }
    }
}
exports.WebSocketNotificationGateway = WebSocketNotificationGateway;
//# sourceMappingURL=websocket-gateway.js.map