var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { Subject } from 'rxjs';
import { HeadsetEvents } from 'softphone-vendor-headsets';
import { SdkHeadsetServiceFake } from './sdk-headset-service-fake';
import { SessionTypes } from 'genesys-cloud-streaming-client';
import { SdkHeadsetService } from './sdk-headset-service';
import { HeadsetChangesQueue } from './headset-utils';
import { MediaHandling } from '../types/enums';
const REQUEST_PRIORITY = {
    'mediaHelper': 30,
    'prioritized': 20,
    'standard': 10
};
const ORCHESTRATION_WAIT_TIME = 1500;
export class HeadsetProxyService {
    constructor(sdk) {
        this.sdk = sdk;
        // TODO: PCM-2060 - remove this
        this.useHeadsetOrchestration = true;
        this.orchestrationState = 'notStarted';
        this.headsetEventsSub = new Subject();
        this.headsetEvents$ = this.headsetEventsSub.asObservable();
    }
    initialize() {
        if (this.sdk.isGuest) {
            return;
        }
        this.sdk._streamingConnection.messenger.on('mediaMessage', this.handleMediaMessage.bind(this));
        this.setUseHeadsets(!!this.sdk._config.useHeadsets);
    }
    // this is to be called externally to start/stop headsets, not internally
    setUseHeadsets(useHeadsets) {
        // TODO: PCM-2060 - remove this
        this.useHeadsetOrchestration = !this.sdk._config.disableHeadsetControlsOrchestration;
        if (this.sdk._mediaHandling === MediaHandling.reducedMedia) {
            this.sdk.logger.warn('setUseHeadsets was called with `true` but media handling is set to `reducedMedia`; headsets are not supported in this configuration - not handling media. Not activating headsets.');
            useHeadsets = false;
        }
        // currently only softphone is supported
        const headsetsIsSupported = this.sdk._config.allowedSessionTypes.includes(SessionTypes.softphone);
        if (useHeadsets && !headsetsIsSupported) {
            this.sdk.logger.warn('setUseHeadsets was called with `true` but headsets are not supported in this configuration - headset is not supported. Not activating headsets.');
            useHeadsets = false;
        }
        if (this.currentHeadsetService) {
            // if this is the real headset service, this will clean up the current device
            this.currentHeadsetService.updateAudioInputDevice(null);
        }
        if (this.currentEventSubscription) {
            this.currentEventSubscription.unsubscribe();
        }
        if (useHeadsets) {
            this.currentHeadsetService = new SdkHeadsetService(this.sdk);
            this.currentEventSubscription = this.currentHeadsetService.headsetEvents$.subscribe((event) => this.handleHeadsetEvent(event));
            this.setOrchestrationState('notStarted');
            // select sdk default device or system default if one exists
            const initialDeviceId = this.sdk._config.defaults.audioDeviceId ||
                (this.sdk.media.getAudioDevices().length && this.sdk.media.getAudioDevices()[0].deviceId);
            this.updateAudioInputDevice(initialDeviceId);
        }
        else {
            this.currentHeadsetService = new SdkHeadsetServiceFake(this.sdk);
        }
    }
    get currentSelectedImplementation() {
        return this.currentHeadsetService.currentSelectedImplementation;
    }
    handleHeadsetEvent(event) {
        if (event.event === HeadsetEvents.deviceConnectionStatusChanged && event.payload === 'noVendor' && this.orchestrationState === 'alternativeClient') {
            return;
        }
        this.headsetEventsSub.next(event);
    }
    updateAudioInputDevice(newMicDeviceId) {
        if (!this.sdk._config.useHeadsets) {
            return;
        }
        // if deviceId is falsey, we will pass it to the headset service so it deactivates the service
        //
        // updating the input device to a supported device triggers the activation of the headset controls so
        // we only want to update the device if we have headset controls
        // TODO: PCM-2060 - remove !this.useHeadsetOrchestration condition
        if (!newMicDeviceId || this.orchestrationState === 'hasControls' || !this.useHeadsetOrchestration) {
            return this.currentHeadsetService.updateAudioInputDevice(newMicDeviceId);
        }
        // if the device is a supported device and we don't have controls yet, start the orchestration and let
        // it assign the device afterwards
        const device = this.sdk.media.findCachedDeviceByIdAndKind(newMicDeviceId, 'audioinput');
        const isSupported = device && this.currentHeadsetService.deviceIsSupported({ micLabel: device.label });
        if (isSupported) {
            if (this.orchestrationState === 'notStarted' || this.orchestrationState === 'negotiating') {
                this.startHeadsetOrchestration(device);
            }
            else {
                this.setOrchestrationState('alternativeClient', true);
            }
        }
        else {
            // this can happen particularly during initialization where we might start negotiating a sys default
            // then change to an actual device. If this happens, we need to cancel the negotiation timer.
            if (this.orchestrationState === 'negotiating') {
                this.orchestrationState = 'notStarted';
                clearTimeout(this.orchestrationWaitTimer);
            }
            this.headsetEventsSub.next({ event: HeadsetEvents.deviceConnectionStatusChanged, payload: 'noVendor' });
        }
    }
    startHeadsetOrchestration(deviceToActiveOnSuccess) {
        return __awaiter(this, void 0, void 0, function* () {
            clearTimeout(this.orchestrationWaitTimer);
            this.setOrchestrationState('negotiating');
            this.orchestrationWaitTimer = setTimeout(() => {
                this.sdk.logger.info('No rejections received during orchestration, taking headsetCallControls');
                this.sendControlsChangedMessage(true);
                this.setOrchestrationState('hasControls');
                this.updateAudioInputDevice(deviceToActiveOnSuccess.deviceId);
            }, ORCHESTRATION_WAIT_TIME);
            this.sdk.logger.info('Starting headsetCallControls orchestration');
            let requestType;
            if (this.sdk._mediaHandling === MediaHandling.alertingLeaderMedia) {
                requestType = 'prioritized';
            }
            else {
                requestType = this.sdk._config.headsetRequestType || 'standard';
            }
            const headsetControlsRequest = {
                jsonrpc: '2.0',
                method: 'headsetControlsRequest',
                params: {
                    requestType
                }
            };
            this.sdk._streamingConnection.messenger.broadcastMessage({
                mediaMessage: headsetControlsRequest
            });
        });
    }
    setOrchestrationState(state, forceUpdate = false) {
        // TODO: PCM-2060 - remove this
        if (!this.useHeadsetOrchestration) {
            return;
        }
        if (state === this.orchestrationState && !forceUpdate) {
            return;
        }
        this.sdk.logger.debug('Headset Orchestration state change', { oldState: this.orchestrationState, newState: state });
        if (state === 'alternativeClient') {
            clearTimeout(this.orchestrationWaitTimer);
        }
        this.orchestrationState = state;
        this.headsetEventsSub.next({
            event: HeadsetEvents.deviceConnectionStatusChanged,
            payload: this.orchestrationState
        });
    }
    // this fn handles xmpp messages needed to orchestrate which client/instance gets to have headset controls
    handleMediaMessage(msg) {
        // TODO: PCM-2060 - remove !this.useHeadsetOrchestration condition
        if (!this.sdk._config.useHeadsets || !this.useHeadsetOrchestration) {
            return;
        }
        // I cant think of a case where we would care to handle the message if it is an echo from this client
        if (msg.fromMyClient) {
            return;
        }
        switch (msg.mediaMessage.method) {
            case 'headsetControlsRequest':
                this.handleHeadsetControlsRequest(msg);
                break;
            case 'headsetControlsRejection':
                this.handleHeadsetControlsRejection(msg);
                break;
            case 'headsetControlsChanged':
                this.handleHeadsetControlsChanged(msg);
                break;
        }
    }
    getRequestPriority(requestType) {
        let priority = REQUEST_PRIORITY[requestType];
        if (!priority) {
            this.sdk.logger.warn('Unable to resolve requestType priority, defaulting to standard', { requestType });
            priority = REQUEST_PRIORITY.standard;
        }
        return priority;
    }
    // in all these handlers we need to handle if we are receiving the message during negotiation or during a headset connected state
    handleHeadsetControlsRequest(msg) {
        const mediaMessage = msg.mediaMessage;
        this.sdk.logger.debug('Received headsetControlsRequest message', { requestType: mediaMessage.params.requestType });
        if (this.sdk._mediaHandling === MediaHandling.alertingLeaderMedia) {
            // we still yield to media-helper
            if (this.getRequestPriority(mediaMessage.params.requestType) === this.getRequestPriority('mediaHelper')) {
                this.sdk.logger.info('Handling alerting leader media, but yielding headset controls to media-helper', { requestType: mediaMessage.params.requestType });
                this.setOrchestrationState('alternativeClient');
            }
            else if (this.getRequestPriority(mediaMessage.params.requestType) === this.getRequestPriority('prioritized')) {
                this.sdk.logger.info('Currently handling alerting leader media, but yielding headset controls to new alerting leader', { requestType: mediaMessage.params.requestType });
                this.setOrchestrationState('alternativeClient');
            }
            else {
                this.sendControlsRejectionMessage(msg, 'priority');
            }
            return;
        }
        // if incoming request is lower priority, reject
        if (this.getRequestPriority(mediaMessage.params.requestType) < this.getRequestPriority(this.sdk._config.headsetRequestType)) {
            this.sendControlsRejectionMessage(msg, this.sdk._config.headsetRequestType === 'mediaHelper' ? 'mediaHelper' : 'priority');
        }
        // if incoming request is same or higher priority
        if (this.getRequestPriority(mediaMessage.params.requestType) >= this.getRequestPriority(this.sdk._config.headsetRequestType)) {
            // we are in the negotiating state, we want to yield
            if (this.orchestrationState === 'negotiating') {
                this.sdk.logger.info('Yielding headset controls to requestor', { requestType: mediaMessage.params.requestType });
                this.setOrchestrationState('alternativeClient');
                // if we have we have an active call OR an idle persistent connection, reject
            }
            else if (this.sdk.sessionManager.getAllActiveSessions().filter(s => s.sessionType === 'softphone').length) {
                this.sendControlsRejectionMessage(msg, 'activeCall');
            }
        }
    }
    handleHeadsetControlsRejection(msg) {
        const mediaMessage = msg.mediaMessage;
        if (this.orchestrationState === 'negotiating') {
            this.sdk.logger.info('Received headsetControlsRejection message', { reason: mediaMessage.params.reason });
            this.setOrchestrationState('alternativeClient');
        }
    }
    handleHeadsetControlsChanged(msg) {
        const mediaMessage = msg.mediaMessage;
        const hasControlOrWaitingForControl = ['hasControls', 'negotiating'].includes(this.orchestrationState);
        // if some other client has taken control, we yield
        if (mediaMessage.params.hasControls && hasControlOrWaitingForControl) {
            HeadsetChangesQueue.clearQueue();
            this.currentHeadsetService.updateAudioInputDevice(null, 'alternativeClient');
            if (this.orchestrationState === 'hasControls') {
                this.sendControlsChangedMessage(false);
            }
            this.setOrchestrationState('alternativeClient');
        }
    }
    sendControlsRejectionMessage(request, reason) {
        this.sdk._streamingConnection.messenger.broadcastMessage({
            mediaMessage: {
                jsonrpc: '2.0',
                method: 'headsetControlsRejection',
                params: {
                    requestId: request.id,
                    reason
                }
            }
        });
    }
    sendControlsChangedMessage(hasControls) {
        this.sdk._streamingConnection.messenger.broadcastMessage({
            mediaMessage: {
                jsonrpc: '2.0',
                method: 'headsetControlsChanged',
                params: {
                    hasControls
                }
            }
        });
    }
    showRetry() {
        return this.currentHeadsetService.showRetry();
    }
    retryConnection(micDeviceLabel) {
        return this.currentHeadsetService.retryConnection(micDeviceLabel);
    }
    setRinging(callInfo, hasOtherActiveCalls) {
        return this.currentHeadsetService.setRinging(callInfo, hasOtherActiveCalls);
    }
    outgoingCall(callInfo) {
        return this.currentHeadsetService.outgoingCall(callInfo);
    }
    endCurrentCall(conversationId, hasOtherActiveCalls) {
        return this.currentHeadsetService.endCurrentCall(conversationId, hasOtherActiveCalls);
    }
    endAllCalls() {
        return this.currentHeadsetService.endAllCalls();
    }
    answerIncomingCall(conversationId, autoAnswer) {
        return this.currentHeadsetService.answerIncomingCall(conversationId, autoAnswer);
    }
    rejectIncomingCall(conversationId, expectExistingConversation = true) {
        return this.currentHeadsetService.rejectIncomingCall(conversationId, expectExistingConversation);
    }
    setMute(isMuted) {
        return this.currentHeadsetService.setMute(isMuted);
    }
    setHold(conversationId, isHeld) {
        return this.currentHeadsetService.setHold(conversationId, isHeld);
    }
    resetHeadsetStateForCall(conversationId) {
        return this.currentHeadsetService.resetHeadsetStateForCall(conversationId);
    }
}
//# sourceMappingURL=headset.js.map