import { InvalidValueError, MissingEntityError, logger } from '@crosslab/service-common';
import { config } from '../../config.js';
import { repositories } from '../../database/dataSource.js';
import { ConcreteDeviceModel } from '../../database/model.js';
import { getDevice } from '../device.js';
import { peerconnectionUrlFromId } from '../urlFromId.js';
import { SignalingQueue } from './signalingQueue.js';
export class SignalingQueueManager {
    queueMap;
    static instance;
    constructor() {
        this.queueMap = new Map();
    }
    static getInstance() {
        if (!SignalingQueueManager.instance) {
            SignalingQueueManager.instance = new SignalingQueueManager();
        }
        return SignalingQueueManager.instance;
    }
    async createSignalingQueues(peerconnectionId, addCreateMessages) {
        const peerconnectionUrl = peerconnectionUrlFromId(peerconnectionId);
        logger.log('info', `Trying to create signaling queues for peerconnection '${peerconnectionUrl}'`);
        // TODO: change to more meaningful error
        if (this.queueMap.has(peerconnectionUrl))
            throw new Error(`Peerconnection '${peerconnectionUrl}' already has signaling queues`);
        const peerconnectionModel = await repositories.peerconnection.findOneOrFail({
            where: {
                uuid: peerconnectionId,
            },
        });
        if (peerconnectionModel.status !== 'new') {
            logger.log('info', `Status of peerconnection '${peerconnectionUrl}' is not 'new', '${peerconnectionModel.status}'`);
            return;
        }
        // create SignalingQueues
        const queueDeviceA = new SignalingQueue(peerconnectionUrl, peerconnectionModel.deviceA.url);
        const queueDeviceB = new SignalingQueue(peerconnectionUrl, peerconnectionModel.deviceB.url);
        const queues = {
            deviceA: { url: peerconnectionModel.deviceA.url, queue: queueDeviceA },
            deviceB: { url: peerconnectionModel.deviceB.url, queue: queueDeviceB },
        };
        if (addCreateMessages)
            this.addCreatePeerconnectionMessages(peerconnectionModel, peerconnectionUrl, queues);
        this.queueMap.set(peerconnectionUrl, queues);
        logger.log('info', `Successfully created signaling queues for peerconnection '${peerconnectionUrl}'`);
    }
    async addCreatePeerconnectionMessages(peerconnectionModel, peerconnectionUrl, queues) {
        const webSocketUrls = peerconnectionModel.type === 'websocket'
            ? getWebSocketUrls(peerconnectionModel)
            : undefined;
        // prepare createPeerconnection messages
        const common = {
            messageType: 'command',
            command: 'createPeerconnection',
            connectionType: peerconnectionModel.type,
            connectionUrl: peerconnectionUrl,
            connectionOptions: peerconnectionModel.configuration,
        };
        if (peerconnectionModel.type === 'websocket') {
            delete common.connectionOptions?.webSocketUrls;
        }
        else if (peerconnectionModel.type === 'webrtc') {
            common.config = {
                iceServers: config.WEBRTC_ICE_SERVERS,
            };
        }
        const deviceA = await getDevice(peerconnectionModel.deviceA);
        const deviceB = await getDevice(peerconnectionModel.deviceB);
        const hasVideo = (device) => device.services.some(s => s.serviceType &&
            s.serviceType.includes('webcam') &&
            (s.serviceDirection === 'producer' || s.serviceDirection === 'out'));
        const deviceAHasVideo = deviceA instanceof ConcreteDeviceModel && hasVideo(deviceA);
        const deviceBHasVideo = deviceB instanceof ConcreteDeviceModel && hasVideo(deviceB);
        logger.log('info', `deviceAHasVideo: ${deviceAHasVideo}, deviceBHasVideo: ${deviceBHasVideo}`);
        const tiebreaker = deviceAHasVideo && !deviceBHasVideo ? true : false;
        const createPeerConnectionMessageA = {
            ...common,
            services: peerconnectionModel.deviceA.config?.services ?? [],
            tiebreaker: tiebreaker,
        };
        const createPeerConnectionMessageB = {
            ...common,
            services: peerconnectionModel.deviceB.config?.services ?? [],
            tiebreaker: !tiebreaker,
        };
        if (peerconnectionModel.type === 'websocket') {
            createPeerConnectionMessageA.connectionOptions = {
                ...createPeerConnectionMessageA.connectionOptions,
                webSocketUrl: webSocketUrls?.[0],
            };
            createPeerConnectionMessageB.connectionOptions = {
                ...createPeerConnectionMessageB.connectionOptions,
                webSocketUrl: webSocketUrls?.[1],
            };
        }
        // add createPeerconnection messages
        queues.deviceA.queue.add(createPeerConnectionMessageA);
        queues.deviceB.queue.add(createPeerConnectionMessageB);
    }
    startSignalingQueues(peerconnectionId) {
        const peerconnectionUrl = peerconnectionUrlFromId(peerconnectionId);
        const queues = this.queueMap.get(peerconnectionUrl);
        if (!queues)
            throw new MissingEntityError(`Peerconnection '${peerconnectionUrl}' does not have any associated signaling queues`, 500);
        queues.deviceA.queue.start();
        queues.deviceB.queue.start();
    }
    stopSignalingQueues(peerconnectionId) {
        const peerconnectionUrl = peerconnectionUrlFromId(peerconnectionId);
        const queues = this.queueMap.get(peerconnectionUrl);
        if (!queues)
            throw new MissingEntityError(`Peerconnection '${peerconnectionUrl}' does not have any associated signaling queues`, 500);
        queues.deviceA.queue.stop();
        queues.deviceB.queue.stop();
    }
    addSignalingMessage(peerconnectionUrl, deviceUrl, signalingMessage) {
        const queues = this.queueMap.get(peerconnectionUrl);
        if (queues?.deviceA.url === deviceUrl)
            return queues.deviceA.queue.add(signalingMessage);
        if (queues?.deviceB.url === deviceUrl)
            return queues.deviceB.queue.add(signalingMessage);
        throw new MissingEntityError(`Peerconnection '${peerconnectionUrl}' does not have any associated signaling queue for device '${deviceUrl}'`, 500);
    }
    async closeSignalingQueues(peerconnectionId) {
        const peerconnectionUrl = peerconnectionUrlFromId(peerconnectionId);
        const queues = this.queueMap.get(peerconnectionUrl);
        // TODO: change to more meaningful error
        if (!queues)
            throw new MissingEntityError(`Peerconnection '${peerconnectionUrl}' does not have any associated signaling queues`, 500);
        return Promise.all([queues.deviceA.queue.close(), queues.deviceB.queue.close()]);
    }
    async deleteSignalingQueues(peerconnectionId) {
        const peerconnectionUrl = peerconnectionUrlFromId(peerconnectionId);
        const queues = this.queueMap.get(peerconnectionUrl);
        if (!queues)
            return;
        if (queues.deviceA.queue.state !== 'closed' ||
            queues.deviceB.queue.state !== 'closed')
            await this.closeSignalingQueues(peerconnectionId);
        this.queueMap.delete(peerconnectionUrl);
    }
}
export const signalingQueueManager = SignalingQueueManager.getInstance();
function getWebSocketUrls(peerconnectionModel) {
    const configuration = peerconnectionModel.configuration;
    if (!('webSocketUrls' in configuration)) {
        throw new InvalidValueError(
        // prettier-ignore
        `Expected property "webSocketUrls" does not exist in configuration of peerconnection "${peerconnectionUrlFromId(peerconnectionModel.uuid)}" of type "websocket"`, 400);
    }
    const webSocketUrls = configuration.webSocketUrls;
    if (!Array.isArray(webSocketUrls)) {
        throw new InvalidValueError(
        // prettier-ignore
        `Property "webSocketUrls" is not an array in the configuration of "${peerconnectionUrlFromId(peerconnectionModel.uuid)}"`, 400);
    }
    if (webSocketUrls.length !== 2) {
        throw new InvalidValueError(
        // prettier-ignore
        `Property "webSocketUrls" has length ${webSocketUrls.length} instead of 2 in the configuration of "${peerconnectionUrlFromId(peerconnectionModel.uuid)}"`, 400);
    }
    for (const webSocketUrl in webSocketUrls) {
        if (typeof webSocketUrl !== 'string') {
            throw new InvalidValueError(
            // prettier-ignore
            `Property "webSocketUrls" contains a value of type "${typeof webSocketUrl}" in the configuration of "${peerconnectionUrlFromId(peerconnectionModel.uuid)}" but should only contain values of type "string"`, 400);
        }
    }
    return webSocketUrls;
}
//# sourceMappingURL=signalingQueueManager.js.map