// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
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 { compositeLogger } from '../../../Logger';
import { _isInCall, _isInLobbyOrConnecting } from "../../../../../calling-component-bindings/src";
import { _createStatefulCallClientInner } from "../../../../../calling-stateful-client/src";
import { _isTeamsCallAgent } from "../../../../../calling-stateful-client/src";
import { LocalVideoStream as SDKLocalVideoStream } from '@azure/communication-calling';
import { Features } from '@azure/communication-calling';
import { EventEmitter } from 'events';
import { getCallCompositePage, getLocatorOrTargetCallees, IsCallEndedPage, isCameraOn } from '../utils';
import { toFlatCommunicationIdentifier, _toCommunicationIdentifier, _isValidIdentifier } from "../../../../../acs-ui-common/src";
import { isMicrosoftTeamsUserIdentifier, isMicrosoftTeamsAppIdentifier } from '@azure/communication-common';
import { isCommunicationUserIdentifier } from '@azure/communication-common';
import { isPhoneNumberIdentifier } from '@azure/communication-common';
import { ParticipantSubscriber } from './ParticipantSubcriber';
import { DiagnosticsForwarder } from './DiagnosticsForwarder';
import { useEffect, useRef, useState } from 'react';
import { createHandlers } from './createHandlers';
import { createProfileStateModifier } from './OnFetchProfileCallback';
import { getBackgroundEffectFromSelectedEffect } from '../utils';
import { getSelectedCameraFromAdapterState } from '../utils';
import { CallingSoundSubscriber } from './CallingSoundSubscriber';
/**
 * For each time that we use the hook {@link useSelector} in the {@link CallComposite} we add another listener
 * to the `stateChanged` event on the this adapter. This number is set in relation to the number of
 * times that we are using the hook useSelector in the CallComposite.
 *
 * We will need to update this as the threshold is reached with more usages of useSelector.
 */
const EVENT_LISTENER_WARNING_WARNING_LIMIT = 125;
/** Context of call, which is a centralized context for all state updates */
class CallContext {
    constructor(clientState, isTeamsCall, isTeamsMeeting, isRoomsCall, options, targetCallees) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j;
        this.emitter = new EventEmitter();
        this.isReturningFromBreakoutRoom = false;
        this.state = {
            isLocalPreviewMicrophoneEnabled: false,
            userId: clientState.userId,
            displayName: (_a = clientState.callAgent) === null || _a === void 0 ? void 0 : _a.displayName,
            devices: clientState.deviceManager,
            call: undefined,
            targetCallees: targetCallees,
            page: 'configuration',
            latestErrors: clientState.latestErrors,
            latestNotifications: clientState.latestNotifications,
            isTeamsCall,
            isTeamsMeeting,
            isRoomsCall,
            alternateCallerId: options === null || options === void 0 ? void 0 : options.alternateCallerId,
            environmentInfo: clientState.environmentInfo,
            videoBackgroundImages: (_b = options === null || options === void 0 ? void 0 : options.videoBackgroundOptions) === null || _b === void 0 ? void 0 : _b.videoBackgroundImages,
            onResolveVideoEffectDependency: (_c = options === null || options === void 0 ? void 0 : options.videoBackgroundOptions) === null || _c === void 0 ? void 0 : _c.onResolveDependency,
            onResolveDeepNoiseSuppressionDependency: (_d = options === null || options === void 0 ? void 0 : options.deepNoiseSuppressionOptions) === null || _d === void 0 ? void 0 : _d.onResolveDependency,
            deepNoiseSuppressionOnByDefault: (_f = (_e = options === null || options === void 0 ? void 0 : options.deepNoiseSuppressionOptions) === null || _e === void 0 ? void 0 : _e.deepNoiseSuppressionOnByDefault) !== null && _f !== void 0 ? _f : true,
            hideDeepNoiseSuppressionButton: (_h = (_g = options === null || options === void 0 ? void 0 : options.deepNoiseSuppressionOptions) === null || _g === void 0 ? void 0 : _g.hideDeepNoiseSuppressionButton) !== null && _h !== void 0 ? _h : false,
            selectedVideoBackgroundEffect: undefined,
            cameraStatus: undefined,
            sounds: options === null || options === void 0 ? void 0 : options.callingSounds,
            reactions: options === null || options === void 0 ? void 0 : options.reactionResources
        };
        this.emitter.setMaxListeners((_j = options === null || options === void 0 ? void 0 : options.maxListeners) !== null && _j !== void 0 ? _j : EVENT_LISTENER_WARNING_WARNING_LIMIT);
        this.bindPublicMethods();
        this.displayNameModifier = (options === null || options === void 0 ? void 0 : options.onFetchProfile) ? createProfileStateModifier(options.onFetchProfile, () => {
            this.setState(this.getState());
        }) : undefined;
    }
    bindPublicMethods() { }
    onStateChange(handler) {
        this.emitter.on('stateChanged', handler);
    }
    offStateChange(handler) {
        this.emitter.off('stateChanged', handler);
    }
    setState(state) {
        this.state = this.displayNameModifier ? this.displayNameModifier(state) : state;
        // This context privately tracks how captions was started to determine if captions is running only in the background.
        // If so we should not show the UI.
        this.state = captionsUIVisibilityModifier(this.state);
        this.emitter.emit('stateChanged', this.state);
    }
    updateCaptionsStartedInBackground(captionsStartedInBackground) {
        var _a;
        const captionsTriggers = (_a = this.state.captionsTriggers) !== null && _a !== void 0 ? _a : {};
        captionsTriggers.captionsStartedInBackground = captionsStartedInBackground;
        this.setState(Object.assign(Object.assign({}, this.state), { captionsTriggers }));
    }
    updateCaptionsStartedWithUI(captionsStartedWithUI) {
        var _a;
        const captionsTriggers = (_a = this.state.captionsTriggers) !== null && _a !== void 0 ? _a : {};
        captionsTriggers.captionsStartedWithUI = captionsStartedWithUI;
        this.setState(Object.assign(Object.assign({}, this.state), { captionsTriggers }));
    }
    getState() {
        return this.state;
    }
    setIsLocalMicrophoneEnabled(isLocalPreviewMicrophoneEnabled) {
        this.setState(Object.assign(Object.assign({}, this.state), { isLocalPreviewMicrophoneEnabled }));
    }
    // This is the key to find current call object in client state
    setCurrentCallId(callId) {
        this.callId = callId;
    }
    setTargetCallee(targetCallees) {
        this.setState(Object.assign(Object.assign({}, this.state), { targetCallees }));
    }
    setIsReturningFromBreakoutRoom(isReturningFromBreakoutRoom) {
        this.isReturningFromBreakoutRoom = isReturningFromBreakoutRoom;
    }
    onCallEnded(handler) {
        this.emitter.on('callEnded', handler);
    }
    offCallEnded(handler) {
        this.emitter.off('callEnded', handler);
    }
    updateClientState(clientState) {
        var _a, _b, _c;
        let call = this.callId ? clientState.calls[this.callId] : undefined;
        const latestEndedCall = clientState.callsEnded ? findLatestEndedCall(clientState.callsEnded) : undefined;
        // As the state is transitioning to a new state, trigger appropriate callback events.
        const oldPage = this.state.page;
        const latestAcceptedTransfer = (call === null || call === void 0 ? void 0 : call.transfer.acceptedTransfers) ? findLatestAcceptedTransfer(call.transfer.acceptedTransfers) : undefined;
        const transferCall = latestAcceptedTransfer ? clientState.calls[latestAcceptedTransfer.callId] : undefined;
        if ((call === null || call === void 0 ? void 0 : call.state) === 'Connected' || (call === null || call === void 0 ? void 0 : call.state) === 'Connecting') {
            this.setIsReturningFromBreakoutRoom(false);
        }
        let isReturningFromBreakoutRoom = false;
        isReturningFromBreakoutRoom = this.isReturningFromBreakoutRoom;
        const newPage = getCallCompositePage(call, latestEndedCall, transferCall, isReturningFromBreakoutRoom);
        if (!IsCallEndedPage(oldPage) && IsCallEndedPage(newPage)) {
            /**
             * We want to make sure that the id of the call that is ending
             * is the same as the call in the adapter as this is a scenario where
             * the call has ended and not been transferred and report the codes for this call.
             */
            if (this.callId === (latestEndedCall === null || latestEndedCall === void 0 ? void 0 : latestEndedCall.id)) {
                this.emitter.emit('callEnded', {
                    callId: latestEndedCall === null || latestEndedCall === void 0 ? void 0 : latestEndedCall.id,
                    code: (_a = latestEndedCall === null || latestEndedCall === void 0 ? void 0 : latestEndedCall.callEndReason) === null || _a === void 0 ? void 0 : _a.code,
                    subCode: (_b = latestEndedCall === null || latestEndedCall === void 0 ? void 0 : latestEndedCall.callEndReason) === null || _b === void 0 ? void 0 : _b.subCode
                });
            }
            // Reset the callId to undefined as the call has ended.
            this.setCurrentCallId(undefined);
            // Make sure that the call is set to undefined in the state.
            call = undefined;
        }
        if (this.state.page) {
            this.setState(Object.assign(Object.assign({}, this.state), { userId: clientState.userId, displayName: (_c = clientState.callAgent) === null || _c === void 0 ? void 0 : _c.displayName, call, page: newPage, endedCall: latestEndedCall, devices: clientState.deviceManager, latestErrors: clientState.latestErrors, latestNotifications: clientState.latestNotifications, cameraStatus: (call === null || call === void 0 ? void 0 : call.localVideoStreams.find(s => s.mediaStreamType === 'Video')) || clientState.deviceManager.unparentedViews.find(s => s.mediaStreamType === 'Video') ? 'On' : 'Off', acceptedTransferCallState: transferCall }));
        }
    }
    setBackroundPickerImages(videoBackgroundImages) {
        this.setState(Object.assign(Object.assign({}, this.state), { videoBackgroundImages }));
    }
    setSelectedVideoBackgroundEffect(selectedVideoBackgroundEffect) {
        this.setState(Object.assign(Object.assign({}, this.state), { selectedVideoBackgroundEffect }));
    }
    setAcceptedTransferCall(call) {
        this.setState(Object.assign(Object.assign({}, this.state), { acceptedTransferCallState: call }));
    }
}
const findLatestEndedCall = (calls) => {
    var _a, _b, _c, _d;
    const callStates = Object.values(calls);
    if (callStates.length === 0) {
        return undefined;
    }
    let latestCall = callStates[0];
    for (const call of callStates.slice(1)) {
        if (((_b = (_a = call.endTime) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) > ((_d = (_c = latestCall === null || latestCall === void 0 ? void 0 : latestCall.endTime) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : 0)) {
            latestCall = call;
        }
    }
    return latestCall;
};
const findLatestAcceptedTransfer = (acceptedTransfers) => {
    var _a, _b, _c, _d;
    const acceptedTransferValues = Object.values(acceptedTransfers);
    if (acceptedTransferValues.length === 0) {
        return undefined;
    }
    let latestAcceptedTransfer = acceptedTransferValues[0];
    for (const acceptedTransfer of acceptedTransferValues.slice(1)) {
        if (((_b = (_a = acceptedTransfer.timestamp) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) > ((_d = (_c = latestAcceptedTransfer === null || latestAcceptedTransfer === void 0 ? void 0 : latestAcceptedTransfer.timestamp) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : 0)) {
            latestAcceptedTransfer = acceptedTransfer;
        }
    }
    return latestAcceptedTransfer;
};
/**
 * @private
 */
export class AzureCommunicationCallAdapter {
    get call() {
        return this._call;
    }
    set call(newCall) {
        this.resetDiagnosticsForwarder(newCall);
        this._call = newCall;
    }
    constructor(callClient, locatorOrTargetCalless, callAgent, deviceManager, options) {
        var _a, _b, _c;
        this.participantSubscribers = new Map();
        this.emitter = new EventEmitter();
        this.isMyMutedChanged = () => {
            var _a;
            this.emitter.emit('isMutedChanged', {
                participantId: this.getState().userId,
                isMuted: (_a = this.call) === null || _a === void 0 ? void 0 : _a.isMuted
            });
        };
        this.bindPublicMethods();
        this.callClient = callClient;
        this.callAgent = callAgent;
        this.targetCallees = getLocatorOrTargetCallees(locatorOrTargetCalless) === true ? locatorOrTargetCalless : undefined;
        this.locator = getLocatorOrTargetCallees(locatorOrTargetCalless) === false ? locatorOrTargetCalless : undefined;
        this.deviceManager = deviceManager;
        const isTeamsMeeting = this.locator ? 'meetingLink' in this.locator || 'meetingId' in this.locator : false;
        let isTeamsCall;
        (_a = this.targetCallees) === null || _a === void 0 ? void 0 : _a.forEach(callee => {
            if (isMicrosoftTeamsUserIdentifier(callee) || isMicrosoftTeamsAppIdentifier(callee)) {
                isTeamsCall = true;
            }
        });
        const isRoomsCall = this.locator ? 'roomId' in this.locator : false;
        this.onResolveVideoBackgroundEffectsDependency = (_b = options === null || options === void 0 ? void 0 : options.videoBackgroundOptions) === null || _b === void 0 ? void 0 : _b.onResolveDependency;
        this.onResolveDeepNoiseSuppressionDependency = (_c = options === null || options === void 0 ? void 0 : options.deepNoiseSuppressionOptions) === null || _c === void 0 ? void 0 : _c.onResolveDependency;
        this.context = new CallContext(callClient.getState(), !!isTeamsCall, isTeamsMeeting, isRoomsCall, options, this.targetCallees);
        this.context.onCallEnded(endCallData => this.emitter.emit('callEnded', endCallData));
        const onStateChange = (clientState) => {
            var _a, _b, _c, _d, _e;
            // unsubscribe when the instance gets disposed
            if (!this) {
                callClient.offStateChange(onStateChange);
                return;
            }
            // `updateClientState` searches for the current call from all the calls in the state using a cached `call.id`
            // from the call object. `call.id` can change during a call. We must update the cached `call.id` before
            // calling `updateClientState` so that we find the correct state object for the call even when `call.id`
            // has changed.
            // https://github.com/Azure/communication-ui-library/pull/1820
            if ((_a = this.call) === null || _a === void 0 ? void 0 : _a.id) {
                this.context.setCurrentCallId(this.call.id);
            }
            // if the call hits the connected state we want to pause all calling sounds if playing.
            if (((_b = this.call) === null || _b === void 0 ? void 0 : _b.state) === 'Connected' && ((_c = this.callingSoundSubscriber) === null || _c === void 0 ? void 0 : _c.playingSounds)) {
                this.callingSoundSubscriber.pauseSounds();
            }
            // If the call connects we need to clean up any previous unparentedViews
            if ((((_d = this.call) === null || _d === void 0 ? void 0 : _d.state) === 'InLobby' || ((_e = this.call) === null || _e === void 0 ? void 0 : _e.state) === 'Connected') && this.callClient.getState().deviceManager.unparentedViews.length > 0) {
                this.callClient.getState().deviceManager.unparentedViews.forEach(view => {
                    this.callClient.disposeView(undefined, undefined, view);
                });
            }
            this.context.updateClientState(clientState);
        };
        this.handlers = createHandlers(callClient, callAgent, deviceManager, undefined, {
            onResolveVideoBackgroundEffectsDependency: this.onResolveVideoBackgroundEffectsDependency,
            onResolveDeepNoiseSuppressionDependency: this.onResolveDeepNoiseSuppressionDependency
        });
        this.onClientStateChange = onStateChange;
        this.subscribeDeviceManagerEvents();
        this.callClient.onStateChange(onStateChange);
        if (this.callAgent.kind === 'CallAgent') {
            const onCallsUpdated = (args) => {
                var _a;
                if (!((_a = this.call) === null || _a === void 0 ? void 0 : _a.id)) {
                    return;
                }
                const removedCall = args.removed.find(call => { var _a; return call.id === ((_a = this.call) === null || _a === void 0 ? void 0 : _a.id); });
                if (!removedCall) {
                    return;
                }
                const removedCallState = this.callClient.getState().callsEnded[removedCall.id];
                if (!removedCallState) {
                    return;
                }
                const latestAcceptedTransfer = findLatestAcceptedTransfer(removedCallState.transfer.acceptedTransfers);
                const _callAgent = callAgent;
                const transferCall = _callAgent.calls.find((call) => call.id === (latestAcceptedTransfer === null || latestAcceptedTransfer === void 0 ? void 0 : latestAcceptedTransfer.callId));
                if (!transferCall) {
                    return;
                }
                this.processNewCall(transferCall);
            };
            this.callAgent.on('callsUpdated', onCallsUpdated);
        }
        if (this.callAgent.kind === 'TeamsCallAgent') {
            const onTeamsCallsUpdated = (args) => {
                var _a;
                if (!((_a = this.call) === null || _a === void 0 ? void 0 : _a.id)) {
                    return;
                }
                const removedCall = args.removed.find(call => { var _a; return call.id === ((_a = this.call) === null || _a === void 0 ? void 0 : _a.id); });
                if (!removedCall) {
                    return;
                }
                const removedCallState = this.callClient.getState().callsEnded[removedCall.id];
                if (!removedCallState) {
                    return;
                }
                const latestAcceptedTransfer = findLatestAcceptedTransfer(removedCallState.transfer.acceptedTransfers);
                const _callAgent = callAgent;
                const transferCall = _callAgent.calls.find((call) => call.id === (latestAcceptedTransfer === null || latestAcceptedTransfer === void 0 ? void 0 : latestAcceptedTransfer.callId));
                if (!transferCall) {
                    return;
                }
                this.processNewCall(transferCall);
            };
            this.callAgent.on('callsUpdated', onTeamsCallsUpdated);
        }
    }
    // TODO: update this to include the 'selectedCameraChanged' when calling adds it to the device manager
    subscribeDeviceManagerEvents() {
        this.deviceManager.on('selectedMicrophoneChanged', () => {
            this.emitter.emit('selectedMicrophoneChanged');
        });
        this.deviceManager.on('selectedSpeakerChanged', () => {
            this.emitter.emit('selectedSpeakerChanged');
        });
    }
    bindPublicMethods() {
        this.onStateChange.bind(this);
        this.offStateChange.bind(this);
        this.getState.bind(this);
        this.dispose.bind(this);
        this.joinCall.bind(this);
        this.leaveCall.bind(this);
        this.setCamera.bind(this);
        this.setMicrophone.bind(this);
        this.setSpeaker.bind(this);
        this.askDevicePermission.bind(this);
        this.queryCameras.bind(this);
        this.queryMicrophones.bind(this);
        this.querySpeakers.bind(this);
        this.startCamera.bind(this);
        this.stopCamera.bind(this);
        this.mute.bind(this);
        this.unmute.bind(this);
        this.startCall.bind(this);
        this.startScreenShare.bind(this);
        this.stopScreenShare.bind(this);
        this.raiseHand.bind(this);
        this.onReactionClick.bind(this);
        this.lowerHand.bind(this);
        this.removeParticipant.bind(this);
        this.createStreamView.bind(this);
        this.disposeStreamView.bind(this);
        this.createTogetherModeStreamView.bind(this);
        this.startTogetherMode.bind(this);
        this.setTogetherModeSceneSize.bind(this);
        this.disposeTogetherModeStreamView.bind(this);
        this.disposeScreenShareStreamView.bind(this);
        this.disposeRemoteVideoStreamView.bind(this);
        this.disposeLocalVideoStreamView.bind(this);
        this.on.bind(this);
        this.off.bind(this);
        this.processNewCall.bind(this);
        this.addParticipant.bind(this);
        this.holdCall.bind(this);
        this.resumeCall.bind(this);
        this.sendDtmfTone.bind(this);
        this.startCaptions.bind(this);
        this.stopCaptions.bind(this);
        this.setSpokenLanguage.bind(this);
        this.setCaptionLanguage.bind(this);
        this.sendRealTimeText.bind(this);
        this.startVideoBackgroundEffect.bind(this);
        this.stopVideoBackgroundEffects.bind(this);
        this.updateBackgroundPickerImages.bind(this);
        this.startNoiseSuppressionEffect.bind(this);
        this.stopNoiseSuppressionEffect.bind(this);
        this.submitSurvey.bind(this);
        this.startSpotlight.bind(this);
        this.stopSpotlight.bind(this);
        this.stopAllSpotlight.bind(this);
        this.muteParticipant.bind(this);
        this.muteAllRemoteParticipants.bind(this);
        this.forbidOthersAudio.bind(this);
        this.permitOthersAudio.bind(this);
        this.forbidOthersAudio.bind(this);
        this.permitOthersAudio.bind(this);
    }
    dispose() {
        this.resetDiagnosticsForwarder();
        this.callClient.offStateChange(this.onClientStateChange);
        this.callAgent.dispose();
    }
    queryCameras() {
        return __awaiter(this, void 0, void 0, function* () {
            const startTime = new Date().getTime();
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                const cameras = yield this.deviceManager.getCameras();
                const endTime = new Date().getTime();
                compositeLogger.info('time to query cameras', endTime - startTime, 'ms');
                return cameras;
            }));
        });
    }
    queryMicrophones() {
        return __awaiter(this, void 0, void 0, function* () {
            const startTime = new Date().getTime();
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                const microphones = yield this.deviceManager.getMicrophones();
                const endTime = new Date().getTime();
                compositeLogger.info('time to query microphones', endTime - startTime, 'ms');
                return microphones;
            }));
        });
    }
    querySpeakers() {
        return __awaiter(this, void 0, void 0, function* () {
            const startTime = new Date().getTime();
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                const speakers = (yield this.deviceManager.isSpeakerSelectionAvailable) ? this.deviceManager.getSpeakers() : [];
                const endTime = new Date().getTime();
                compositeLogger.info('time to query speakers', endTime - startTime, 'ms');
                return speakers;
            }));
        });
    }
    askDevicePermission(constrain) {
        return __awaiter(this, void 0, void 0, function* () {
            const startTime = new Date().getTime();
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                const result = yield this.deviceManager.askDevicePermission(constrain);
                const endTime = new Date().getTime();
                compositeLogger.info('time to query askDevicePermissions', endTime - startTime, 'ms');
                return result;
            }));
        });
    }
    joinCall(options) {
        var _a, _b;
        if (_isInCall((_b = (_a = this.getState().call) === null || _a === void 0 ? void 0 : _a.state) !== null && _b !== void 0 ? _b : 'None')) {
            throw new Error('You are already in the call!');
        }
        else if (this.locator === undefined) {
            throw new Error('Locator is not defined!');
        }
        return this.teeErrorToEventEmitter(() => {
            // Default to keeping camera/mic on if no override argument specified
            let shouldCameraBeOnInCall = this.getState().cameraStatus === 'On';
            let shouldMicrophoneBeOnInCall = this.getState().isLocalPreviewMicrophoneEnabled;
            // Apply override arguments
            if (typeof options === 'boolean') {
                // Deprecated joinCall API (boolen)
                shouldMicrophoneBeOnInCall = options;
            }
            else if (typeof options === 'object') {
                // Options bag API
                if (options.microphoneOn && options.microphoneOn !== 'keep') {
                    shouldMicrophoneBeOnInCall = options.microphoneOn;
                }
                if (options.cameraOn && options.cameraOn !== 'keep') {
                    shouldCameraBeOnInCall = options.cameraOn;
                }
            }
            const audioOptions = {
                muted: !shouldMicrophoneBeOnInCall
            };
            const selectedCamera = getSelectedCameraFromAdapterState(this.getState());
            const videoOptions = selectedCamera && shouldCameraBeOnInCall ? {
                localVideoStreams: [new SDKLocalVideoStream(selectedCamera)]
            } : {};
            const call = this._joinCall(audioOptions, videoOptions);
            this.originCall = call;
            this.processNewCall(call);
            return call;
        });
    }
    _joinCall(audioOptions, videoOptions) {
        const isTeamsMeeting = this.locator ? 'meetingLink' in this.locator : false;
        const isTeamsMeetingId = this.locator ? 'meetingId' in this.locator : false;
        const isRoomsCall = this.locator ? 'roomId' in this.locator : false;
        if (_isTeamsCallAgent(this.callAgent)) {
            if (isTeamsMeeting) {
                return this.callAgent.join(this.locator, {
                    audioOptions,
                    videoOptions
                });
            }
            if (isTeamsMeetingId) {
                return this.callAgent.join(this.locator, {
                    audioOptions,
                    videoOptions
                });
            }
            throw new Error('Locator not supported by TeamsCallAgent');
        }
        if (isTeamsMeeting) {
            return this.callAgent.join(this.locator, {
                audioOptions,
                videoOptions
            });
        }
        if (isTeamsMeetingId) {
            return this.callAgent.join(this.locator, {
                audioOptions,
                videoOptions
            });
        }
        if (isRoomsCall) {
            return this.callAgent.join(this.locator, {
                audioOptions,
                videoOptions
            });
        }
        return this.callAgent.join(this.locator, {
            audioOptions,
            videoOptions
        });
    }
    createStreamView(remoteUserId, options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (remoteUserId === undefined) {
                return yield this.handlers.onCreateLocalStreamView(options);
            }
            else {
                return yield this.handlers.onCreateRemoteStreamView(remoteUserId, options);
            }
        });
    }
    disposeStreamView(remoteUserId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (remoteUserId === undefined) {
                yield this.handlers.onDisposeLocalStreamView();
            }
            else {
                yield this.handlers.onDisposeRemoteStreamView(remoteUserId);
            }
        });
    }
    disposeScreenShareStreamView(remoteUserId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (remoteUserId !== '') {
                yield this.handlers.onDisposeRemoteScreenShareStreamView(remoteUserId);
            }
            else {
                yield this.handlers.onDisposeLocalScreenShareStreamView();
            }
        });
    }
    disposeRemoteVideoStreamView(remoteUserId) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.handlers.onDisposeRemoteVideoStreamView(remoteUserId);
        });
    }
    disposeLocalVideoStreamView() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.handlers.onDisposeLocalStreamView();
        });
    }
    createTogetherModeStreamView(options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.handlers.onCreateTogetherModeStreamView(options);
        });
    }
    startTogetherMode() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.handlers.onStartTogetherMode();
        });
    }
    setTogetherModeSceneSize(width, height) {
        return this.handlers.onSetTogetherModeSceneSize(width, height);
    }
    disposeTogetherModeStreamView() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.handlers.onDisposeTogetherModeStreamView();
        });
    }
    leaveCall(forEveryone) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.getState().page === 'transferring') {
                const transferCall = this.callAgent.calls.filter(call => { var _a; return call.id === ((_a = this.getState().acceptedTransferCallState) === null || _a === void 0 ? void 0 : _a.id); })[0];
                transferCall === null || transferCall === void 0 ? void 0 : transferCall.hangUp();
            }
            yield this.handlers.onHangUp(forEveryone);
            this.unsubscribeCallEvents();
            this.handlers = createHandlers(this.callClient, this.callAgent, this.deviceManager, this.call, {
                onResolveVideoBackgroundEffectsDependency: this.onResolveVideoBackgroundEffectsDependency,
                onResolveDeepNoiseSuppressionDependency: this.onResolveDeepNoiseSuppressionDependency
            });
            // We set the adapter.call object to undefined immediately when a call is ended.
            // We do not set the context.callId to undefined because it is a part of the immutable data flow loop.
            this.call = undefined;
            this.stopCamera();
            this.mute();
        });
    }
    setCamera(device, options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onSelectCamera(device, options);
            }));
        });
    }
    setMicrophone(device) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onSelectMicrophone(device);
            }));
        });
    }
    setSpeaker(device) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onSelectSpeaker(device);
            }));
        });
    }
    startCamera(options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                if (!isCameraOn(this.getState())) {
                    // First kick off the effect on the local device before starting the camera in the call.
                    // This prevents the effect not being applied for a brief moment when the camera is started.
                    {
                        const selectedEffect = this.getState().selectedVideoBackgroundEffect;
                        const selectedCamera = getSelectedCameraFromAdapterState(this.getState());
                        if (selectedEffect && selectedCamera && this.onResolveVideoBackgroundEffectsDependency) {
                            const stream = new SDKLocalVideoStream(selectedCamera);
                            const effect = getBackgroundEffectFromSelectedEffect(selectedEffect, yield this.onResolveVideoBackgroundEffectsDependency());
                            if (effect) {
                                yield stream.feature(Features.VideoEffects).startEffects(effect);
                            }
                            else {
                                yield stream.feature(Features.VideoEffects).stopEffects();
                            }
                        }
                    }
                    yield this.handlers.onToggleCamera(options);
                }
            }));
        });
    }
    stopCamera() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                if (isCameraOn(this.getState())) {
                    yield this.handlers.onToggleCamera();
                }
            }));
        });
    }
    mute() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                this.context.setIsLocalMicrophoneEnabled(false);
                if (_isInCall((_a = this.call) === null || _a === void 0 ? void 0 : _a.state) && !((_b = this.call) === null || _b === void 0 ? void 0 : _b.isMuted)) {
                    yield this.handlers.onToggleMicrophone();
                }
            }));
        });
    }
    unmute() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                this.context.setIsLocalMicrophoneEnabled(true);
                if ((_isInCall((_a = this.call) === null || _a === void 0 ? void 0 : _a.state) || _isInLobbyOrConnecting((_b = this.call) === null || _b === void 0 ? void 0 : _b.state)) && ((_c = this.call) === null || _c === void 0 ? void 0 : _c.isMuted)) {
                    yield this.handlers.onToggleMicrophone();
                }
            }));
        });
    }
    startScreenShare() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                var _a;
                if (!((_a = this.call) === null || _a === void 0 ? void 0 : _a.isScreenSharingOn)) {
                    yield this.handlers.onToggleScreenShare();
                }
            }));
        });
    }
    stopScreenShare() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                var _a;
                if ((_a = this.call) === null || _a === void 0 ? void 0 : _a.isScreenSharingOn) {
                    yield this.handlers.onToggleScreenShare();
                }
            }));
        });
    }
    raiseHand() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onToggleRaiseHand();
            }));
        });
    }
    lowerHand() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onToggleRaiseHand();
            }));
        });
    }
    onReactionClick(reaction) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.asyncTeeErrorToEventEmitter(() => __awaiter(this, void 0, void 0, function* () {
                yield this.handlers.onReactionClick(reaction);
            }));
        });
    }
    startVideoBackgroundEffect(videoBackgroundEffect) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.isBlurEffect(videoBackgroundEffect)) {
                const blurConfig = videoBackgroundEffect;
                yield this.handlers.onBlurVideoBackground(blurConfig);
            }
            else if (this.isReplacementEffect(videoBackgroundEffect)) {
                const replaceConfig = videoBackgroundEffect;
                yield this.handlers.onReplaceVideoBackground(replaceConfig);
            }
        });
    }
    stopVideoBackgroundEffects() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.handlers.onRemoveVideoBackgroundEffects();
        });
    }
    updateBackgroundPickerImages(backgroundImages) {
        this.context.setBackroundPickerImages(backgroundImages);
    }
    updateSelectedVideoBackgroundEffect(selectedVideoBackground) {
        this.context.setSelectedVideoBackgroundEffect(selectedVideoBackground);
    }
    startNoiseSuppressionEffect() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.handlers.onStartNoiseSuppressionEffect();
        });
    }
    stopNoiseSuppressionEffect() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.handlers.onStopNoiseSuppressionEffect();
        });
    }
    startCall(participants, options) {
        var _a, _b;
        if (_isInCall((_b = (_a = this.getState().call) === null || _a === void 0 ? void 0 : _a.state) !== null && _b !== void 0 ? _b : 'None')) {
            throw new Error('You are already in the call.');
        }
        const isCameraOn = this.getState().cameraStatus === 'On';
        const selectedCamera = getSelectedCameraFromAdapterState(this.getState());
        /* we only configure the video options here since the Calling SDK always unmutes the participant when starting a call */
        const startCallVideoOptions = selectedCamera ? {
            videoOptions: isCameraOn ? {
                localVideoStreams: [new SDKLocalVideoStream(selectedCamera)]
            } : undefined
        } : {};
        const combinedCallOptions = Object.assign(Object.assign({}, startCallVideoOptions), options);
        const idsToAdd = participants.map(participant => {
            const backendId = _toCommunicationIdentifier(participant);
            if ('phoneNumber' in backendId) {
                if ((options === null || options === void 0 ? void 0 : options.alternateCallerId) === undefined) {
                    throw new Error('Unable to start call, PSTN user present with no alternateCallerId.');
                }
            }
            return backendId;
        });
        this.context.setTargetCallee(idsToAdd);
        const call = this.handlers.onStartCall(idsToAdd, combinedCallOptions);
        if (!call) {
            throw new Error('Unable to start call.');
        }
        this.processNewCall(call);
        return call;
    }
    processNewCall(call) {
        this.call = call;
        this.context.setCurrentCallId(call.id);
        // Resync state after callId is set
        this.context.updateClientState(this.callClient.getState());
        this.handlers = createHandlers(this.callClient, this.callAgent, this.deviceManager, this.call, {
            onResolveVideoBackgroundEffectsDependency: this.onResolveVideoBackgroundEffectsDependency,
            onResolveDeepNoiseSuppressionDependency: this.onResolveDeepNoiseSuppressionDependency
        });
        this.subscribeCallEvents();
    }
    isBlurEffect(effect) {
        return effect.effectName === 'blur';
    }
    isReplacementEffect(effect) {
        return effect.effectName === 'replacement';
    }
    removeParticipant(userId) {
        return __awaiter(this, void 0, void 0, function* () {
            let participant = userId;
            participant = _toCommunicationIdentifier(userId);
            this.handlers.onRemoveParticipant(participant);
        });
    }
    addParticipant(participant, options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (isPhoneNumberIdentifier(participant) && options) {
                this.handlers.onAddParticipant(participant, options);
            }
            else if (isCommunicationUserIdentifier(participant)) {
                this.handlers.onAddParticipant(participant);
            }
        });
    }
    holdCall() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.state) !== 'LocalHold') {
                if ((_b = this.call) === null || _b === void 0 ? void 0 : _b.isLocalVideoStarted) {
                    this.stopCamera().then(() => {
                        this.handlers.onToggleHold();
                    });
                }
                else {
                    this.handlers.onToggleHold();
                }
            }
        });
    }
    resumeCall() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.state) === 'LocalHold') {
                this.handlers.onToggleHold().then(() => {
                    var _a;
                    if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Capabilities).capabilities.turnVideoOn.isPresent) === false) {
                        this.stopCamera();
                    }
                });
            }
        });
    }
    sendDtmfTone(dtmfTone) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onSendDtmfTone(dtmfTone);
        });
    }
    startCaptions(options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (options === null || options === void 0 ? void 0 : options.startInBackground) {
                this.context.updateCaptionsStartedInBackground(true);
            }
            else {
                this.context.updateCaptionsStartedWithUI(true);
            }
            this.handlers.onStartCaptions(options);
        });
    }
    stopCaptions(options) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            if (options === null || options === void 0 ? void 0 : options.stopInBackground) {
                this.context.updateCaptionsStartedInBackground(false);
            }
            this.context.updateCaptionsStartedWithUI(false);
            // Only stop captions if they are not still running in the background
            if (!((_a = this.context.getState().captionsTriggers) === null || _a === void 0 ? void 0 : _a.captionsStartedInBackground)) {
                this.handlers.onStopCaptions();
            }
        });
    }
    setCaptionLanguage(language) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onSetCaptionLanguage(language);
        });
    }
    setSpokenLanguage(language) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onSetSpokenLanguage(language);
        });
    }
    sendRealTimeText(text, isFinalized) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onSendRealTimeText(text, isFinalized);
        });
    }
    submitSurvey(survey) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.handlers.onSubmitSurvey(survey);
        });
    }
    muteParticipant(userId) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onMuteParticipant(userId);
        });
    }
    muteAllRemoteParticipants() {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onMuteAllRemoteParticipants();
        });
    }
    startSpotlight(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onStartSpotlight(userIds);
        });
    }
    stopSpotlight(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onStopSpotlight(userIds);
        });
    }
    stopAllSpotlight() {
        return __awaiter(this, void 0, void 0, function* () {
            this.handlers.onStopAllSpotlight();
        });
    }
    forbidAudio(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onForbidAudio) === null || _b === void 0 ? void 0 : _b.call(_a, userIds);
        });
    }
    permitAudio(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onPermitAudio) === null || _b === void 0 ? void 0 : _b.call(_a, userIds);
        });
    }
    forbidOthersAudio() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onForbidOthersAudio) === null || _b === void 0 ? void 0 : _b.call(_a);
        });
    }
    permitOthersAudio() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onPermitOthersAudio) === null || _b === void 0 ? void 0 : _b.call(_a);
        });
    }
    forbidVideo(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onForbidVideo) === null || _b === void 0 ? void 0 : _b.call(_a, userIds);
        });
    }
    permitVideo(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onPermitVideo) === null || _b === void 0 ? void 0 : _b.call(_a, userIds);
        });
    }
    forbidOthersVideo() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onForbidOthersVideo) === null || _b === void 0 ? void 0 : _b.call(_a);
        });
    }
    permitOthersVideo() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            (_b = (_a = this.handlers).onPermitOthersVideo) === null || _b === void 0 ? void 0 : _b.call(_a);
        });
    }
    returnFromBreakoutRoom() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            const callState = ((_a = this.call) === null || _a === void 0 ? void 0 : _a.id) ? this.callClient.getState().calls[this.call.id] : undefined;
            const assignedBreakoutRoom = (_b = callState === null || callState === void 0 ? void 0 : callState.breakoutRooms) === null || _b === void 0 ? void 0 : _b.assignedBreakoutRoom;
            if (!assignedBreakoutRoom) {
                throw new Error('Could not return from breakout room because assigned breakout room state could not be retrieved.');
            }
            this.context.setIsReturningFromBreakoutRoom(true);
            const mainMeeting = yield assignedBreakoutRoom.returnToMainMeeting();
            // Mute main meeting if the breakout room was muted
            if ((_c = this.call) === null || _c === void 0 ? void 0 : _c.isMuted) {
                mainMeeting.mute();
            }
            this.originCall = mainMeeting;
            this.processNewCall(mainMeeting);
        });
    }
    getState() {
        return this.context.getState();
    }
    onStateChange(handler) {
        this.context.onStateChange(handler);
    }
    offStateChange(handler) {
        this.context.offStateChange(handler);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    on(event, listener) {
        this.emitter.on(event, listener);
    }
    subscribeToCaptionEvents() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions);
        if ((captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.captions.kind) === 'TeamsCaptions' && (this.context.getState().isTeamsCall || this.context.getState().isTeamsMeeting)) {
            const teamsCaptionsFeature = captionsFeature.captions;
            teamsCaptionsFeature.on('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
            teamsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
            teamsCaptionsFeature.on('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
            teamsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
        }
        else {
            const acsCaptionsFeature = captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.captions;
            acsCaptionsFeature.on('CaptionsReceived', this.captionsReceived.bind(this));
            acsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
            acsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
            captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.on('CaptionsKindChanged', this.captionsKindChanged.bind(this));
        }
    }
    unsubscribeFromCaptionEvents() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions);
        if ((captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.captions.kind) === 'TeamsCaptions' && (this.context.getState().isTeamsCall || this.context.getState().isTeamsMeeting)) {
            const teamsCaptionsFeature = captionsFeature.captions;
            teamsCaptionsFeature.off('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
            teamsCaptionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
            teamsCaptionsFeature.off('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
            teamsCaptionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
        }
        else {
            const acsCaptionsFeature = captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.captions;
            acsCaptionsFeature.off('CaptionsReceived', this.captionsReceived.bind(this));
            acsCaptionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
            acsCaptionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
            captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.off('CaptionsKindChanged', this.captionsKindChanged.bind(this));
        }
    }
    subscribeCallEvents() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
        if (this.call) {
            this.callingSoundSubscriber = new CallingSoundSubscriber(this.call, this.getState().targetCallees, this.getState().sounds);
        }
        (_a = this.call) === null || _a === void 0 ? void 0 : _a.on('remoteParticipantsUpdated', this.onRemoteParticipantsUpdated.bind(this));
        (_b = this.call) === null || _b === void 0 ? void 0 : _b.on('isMutedChanged', this.isMyMutedChanged.bind(this));
        (_c = this.call) === null || _c === void 0 ? void 0 : _c.on('isScreenSharingOnChanged', this.isScreenSharingOnChanged.bind(this));
        (_d = this.call) === null || _d === void 0 ? void 0 : _d.on('idChanged', this.callIdChanged.bind(this));
        (_e = this.call) === null || _e === void 0 ? void 0 : _e.on('roleChanged', this.roleChanged.bind(this));
        this.subscribeToCaptionEvents();
        (_f = this.call) === null || _f === void 0 ? void 0 : _f.feature(Features.Transfer).on('transferAccepted', this.transferAccepted.bind(this));
        (_g = this.call) === null || _g === void 0 ? void 0 : _g.feature(Features.Capabilities).on('capabilitiesChanged', this.capabilitiesChanged.bind(this));
        (_h = this.call) === null || _h === void 0 ? void 0 : _h.feature(Features.Spotlight).on('spotlightChanged', this.spotlightChanged.bind(this));
        try {
            (_j = this.call) === null || _j === void 0 ? void 0 : _j.feature(Features.RealTimeText).on('realTimeTextReceived', this.realTimeTextReceived.bind(this));
        }
        catch (_l) {
            console.log('RealTimeText feature is not supported');
        }
        const breakoutRoomsFeature = (_k = this.call) === null || _k === void 0 ? void 0 : _k.feature(Features.BreakoutRooms);
        if (breakoutRoomsFeature) {
            breakoutRoomsFeature.on('breakoutRoomsUpdated', this.breakoutRoomsUpdated.bind(this));
        }
    }
    unsubscribeCallEvents() {
        var _a, _b, _c, _d, _e, _f, _g;
        for (const subscriber of this.participantSubscribers.values()) {
            subscriber.unsubscribeAll();
        }
        this.participantSubscribers.clear();
        (_a = this.call) === null || _a === void 0 ? void 0 : _a.off('remoteParticipantsUpdated', this.onRemoteParticipantsUpdated.bind(this));
        (_b = this.call) === null || _b === void 0 ? void 0 : _b.off('isMutedChanged', this.isMyMutedChanged.bind(this));
        (_c = this.call) === null || _c === void 0 ? void 0 : _c.off('isScreenSharingOnChanged', this.isScreenSharingOnChanged.bind(this));
        (_d = this.call) === null || _d === void 0 ? void 0 : _d.off('idChanged', this.callIdChanged.bind(this));
        (_e = this.call) === null || _e === void 0 ? void 0 : _e.off('roleChanged', this.roleChanged.bind(this));
        this.unsubscribeFromCaptionEvents();
        try {
            (_f = this.call) === null || _f === void 0 ? void 0 : _f.feature(Features.RealTimeText).off('realTimeTextReceived', this.realTimeTextReceived.bind(this));
        }
        catch (_h) {
            console.log('RealTimeText feature is not supported');
        }
        if (this.callingSoundSubscriber) {
            this.callingSoundSubscriber.unsubscribeAll();
        }
        const breakoutRoomsFeature = (_g = this.call) === null || _g === void 0 ? void 0 : _g.feature(Features.BreakoutRooms);
        if (breakoutRoomsFeature) {
            breakoutRoomsFeature.off('breakoutRoomsUpdated', this.breakoutRoomsUpdated.bind(this));
        }
    }
    captionsKindChanged() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions);
        const teamsCaptionsFeature = captionsFeature === null || captionsFeature === void 0 ? void 0 : captionsFeature.captions;
        teamsCaptionsFeature.on('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
        teamsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
        teamsCaptionsFeature.on('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
        teamsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
    }
    onRemoteParticipantsUpdated({ added, removed }) {
        if (added && added.length > 0) {
            this.emitter.emit('participantsJoined', {
                joined: added
            });
        }
        if (removed && removed.length > 0) {
            this.emitter.emit('participantsLeft', {
                removed: removed
            });
        }
        added.forEach(participant => {
            this.participantSubscribers.set(toFlatCommunicationIdentifier(participant.identifier), new ParticipantSubscriber(participant, this.emitter));
        });
        removed.forEach(participant => {
            const subscriber = this.participantSubscribers.get(toFlatCommunicationIdentifier(participant.identifier));
            subscriber && subscriber.unsubscribeAll();
            this.participantSubscribers.delete(toFlatCommunicationIdentifier(participant.identifier));
        });
    }
    isScreenSharingOnChanged() {
        var _a;
        this.emitter.emit('isLocalScreenSharingActiveChanged', {
            isScreenSharingOn: (_a = this.call) === null || _a === void 0 ? void 0 : _a.isScreenSharingOn
        });
    }
    teamsCaptionsReceived(captionsInfo) {
        this.emitter.emit('captionsReceived', {
            captionsInfo
        });
    }
    captionsReceived(captionsInfo) {
        this.emitter.emit('captionsReceived', {
            captionsInfo
        });
    }
    realTimeTextReceived(realTimeText) {
        this.emitter.emit('realTimeTextReceived', {
            realTimeText
        });
    }
    isCaptionsActiveChanged() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions).captions;
        this.emitter.emit('isCaptionsActiveChanged', {
            isActive: captionsFeature.isCaptionsFeatureActive
        });
    }
    isSpokenLanguageChanged() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions).captions;
        this.emitter.emit('isSpokenLanguageChanged', {
            activeSpokenLanguage: captionsFeature.activeSpokenLanguage
        });
    }
    isCaptionLanguageChanged() {
        var _a;
        const captionsFeature = (_a = this.call) === null || _a === void 0 ? void 0 : _a.feature(Features.Captions).captions;
        this.emitter.emit('isCaptionLanguageChanged', {
            activeCaptionLanguage: captionsFeature.activeCaptionLanguage
        });
    }
    transferAccepted(args) {
        this.emitter.emit('transferAccepted', args);
    }
    capabilitiesChanged(data) {
        var _a, _b;
        if (((_a = data.newValue.turnVideoOn) === null || _a === void 0 ? void 0 : _a.isPresent) === false) {
            // stopCamera is handled by web sdk when video hard muted.
            this.disposeLocalVideoStreamView();
        }
        if (((_b = data.newValue.shareScreen) === null || _b === void 0 ? void 0 : _b.isPresent) === false) {
            this.stopScreenShare();
        }
        this.emitter.emit('capabilitiesChanged', data);
    }
    roleChanged() {
        var _a, _b;
        if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.role) === 'Consumer') {
            (_b = this.call) === null || _b === void 0 ? void 0 : _b.feature(Features.RaiseHand).lowerHand();
        }
        this.emitter.emit('roleChanged');
    }
    spotlightChanged(args) {
        this.emitter.emit('spotlightChanged', args);
    }
    breakoutRoomsUpdated(eventData) {
        if (eventData.type === 'assignedBreakoutRooms') {
            this.assignedBreakoutRoomUpdated(eventData.data);
        }
        else if (eventData.type === 'join') {
            this.breakoutRoomJoined(eventData.data);
        }
        this.emitter.emit('breakoutRoomsUpdated', eventData);
    }
    assignedBreakoutRoomUpdated(breakoutRoom) {
        var _a, _b, _c;
        if (!((_a = this.call) === null || _a === void 0 ? void 0 : _a.id)) {
            return;
        }
        if (((_b = this.originCall) === null || _b === void 0 ? void 0 : _b.id) !== ((_c = this.call) === null || _c === void 0 ? void 0 : _c.id) && (!breakoutRoom || breakoutRoom.state === 'closed')) {
            this.returnFromBreakoutRoom();
        }
    }
    breakoutRoomJoined(call) {
        var _a, _b;
        if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.id) !== call.id) {
            // Mute the breakout room call if the main call was muted
            if ((_b = this.call) === null || _b === void 0 ? void 0 : _b.isMuted) {
                call.mute();
            }
            this.processNewCall(call);
        }
        // Hang up other breakout room calls in case we are joining a new breakout room while already in one
        this.hangupOtherBreakoutRoomCalls(call.id);
    }
    hangupOtherBreakoutRoomCalls(currentBreakoutRoomCallId) {
        // Get other breakout room calls by checking which other calls have breakout room settings defined
        const otherBreakoutRoomCallStates = Object.values(this.callClient.getState().calls).filter(callState => {
            var _a;
            return ((_a = callState.breakoutRooms) === null || _a === void 0 ? void 0 : _a.breakoutRoomSettings) && callState.id !== currentBreakoutRoomCallId;
        });
        // Hang up other breakout room calls
        for (const breakoutRoom of otherBreakoutRoomCallStates) {
            const breakoutRoomCall = this.callAgent.calls.find(callAgentCall => {
                return callAgentCall.id === breakoutRoom.id;
            });
            breakoutRoomCall === null || breakoutRoomCall === void 0 ? void 0 : breakoutRoomCall.hangUp();
        }
    }
    callIdChanged() {
        var _a;
        ((_a = this.call) === null || _a === void 0 ? void 0 : _a.id) && this.emitter.emit('callIdChanged', {
            callId: this.call.id
        });
    }
    resetDiagnosticsForwarder(newCall) {
        if (this.diagnosticsForwarder) {
            this.diagnosticsForwarder.unsubscribe();
        }
        if (newCall) {
            this.diagnosticsForwarder = new DiagnosticsForwarder(this.emitter, newCall);
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    off(event, listener) {
        this.emitter.off(event, listener);
    }
    asyncTeeErrorToEventEmitter(f) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield f();
            }
            catch (error) {
                if (isCallError(error)) {
                    this.emitter.emit('error', error);
                }
                throw error;
            }
        });
    }
    teeErrorToEventEmitter(f) {
        try {
            return f();
        }
        catch (error) {
            if (isCallError(error)) {
                this.emitter.emit('error', error);
            }
            throw error;
        }
    }
}
/**
 * @public
 */
/**
 * Create a {@link CallAdapter} backed by Azure Communication Services.
 *
 * This is the default implementation of {@link CallAdapter} provided by this library.
 *
 * Note: `displayName` can be a maximum of 256 characters.
 *
 * @public
 */
export function createAzureCommunicationCallAdapter(args) {
    return __awaiter(this, void 0, void 0, function* () {
        if (isMicrosoftTeamsUserIdentifier(args.userId)) {
            throw new Error('Microsoft Teams user identifier is not supported by AzureCommunicationCallAdapter. Instead use TeamsCallAdapter.');
        }
        return _createAzureCommunicationCallAdapterInner({
            userId: args.userId,
            displayName: args.displayName,
            credential: args.credential,
            locator: args.locator,
            targetCallees: args.targetCallees,
            options: args.options
        });
    });
}
/**
 * This inner function is used to allow injection of TelemetryImplementationHint without changing the public API.
 *
 * @internal
 */
export const _createAzureCommunicationCallAdapterInner = (_a) => __awaiter(void 0, [_a], void 0, function* ({ userId, displayName, credential, locator, targetCallees, options, telemetryImplementationHint = 'Call' }) {
    if (!_isValidIdentifier(userId)) {
        throw new Error('Invalid identifier. Please provide valid identifier object.');
    }
    const callClient = _createStatefulCallClientInner({
        userId
    }, undefined, telemetryImplementationHint);
    const callAgent = yield callClient.createCallAgent(credential, {
        displayName
    });
    let adapter;
    if (locator) {
        adapter = createAzureCommunicationCallAdapterFromClient(callClient, callAgent, locator, options);
    }
    else {
        adapter = createAzureCommunicationCallAdapterFromClient(callClient, callAgent, targetCallees, options);
    }
    return adapter;
});
/**
 * @public
 */
export const createTeamsCallAdapter = (args) => __awaiter(void 0, void 0, void 0, function* () {
    const { userId, credential, options } = args;
    if (isCommunicationUserIdentifier(userId)) {
        throw new Error('Communication User identifier is not supported by TeamsCallAdapter, please use our AzureCommunicationCallAdapter.');
    }
    const callClient = _createStatefulCallClientInner({
        userId
    }, undefined, 'Call');
    const callAgent = yield callClient.createTeamsCallAgent(credential, {
        undefined
    });
    const adapter = createTeamsCallAdapterFromClient(callClient, callAgent, args.locator, options);
    return adapter;
});
/**
 * @private
 */
function useAzureCommunicationCallAdapterGeneric(args, adapterKind = 'AzureCommunication', afterCreate, beforeDispose) {
    const { credential, userId } = args;
    const locator = 'locator' in args ? args.locator : undefined;
    const targetCallees = 'targetCallees' in args ? args.targetCallees : undefined;
    const displayName = 'displayName' in args ? args.displayName : undefined;
    const options = 'options' in args ? args.options : undefined;
    // State update needed to rerender the parent component when a new adapter is created.
    const [adapter, setAdapter] = useState(undefined);
    // Ref needed for cleanup to access the old adapter created asynchronously.
    const adapterRef = useRef(undefined);
    const creatingAdapterRef = useRef(false);
    const afterCreateRef = useRef(undefined);
    const beforeDisposeRef = useRef(undefined);
    // These refs are updated on *each* render, so that the latest values
    // are used in the `useEffect` closures below.
    // Using a Ref ensures that new values for the callbacks do not trigger the
    // useEffect blocks, and a new adapter creation / distruction is not triggered.
    afterCreateRef.current = afterCreate;
    beforeDisposeRef.current = beforeDispose;
    useEffect(() => {
        if (!credential || !locator && !targetCallees || !userId) {
            return;
        }
        if (adapterKind === 'AzureCommunication' && !displayName) {
            return;
        }
        (() => __awaiter(this, void 0, void 0, function* () {
            if (adapterRef.current) {
                // Dispose the old adapter when a new one is created.
                //
                // This clean up function uses `adapterRef` because `adapter` can not be added to the dependency array of
                // this `useEffect` -- we do not want to trigger a new adapter creation because of the first adapter
                // creation.
                if (beforeDisposeRef.current) {
                    yield beforeDisposeRef.current(adapterRef.current);
                }
                adapterRef.current.dispose();
                adapterRef.current = undefined;
            }
            let newAdapter = undefined;
            if (adapterKind === 'AzureCommunication') {
                // This is just the type check to ensure that displayName is defined.
                if (!displayName) {
                    throw new Error('Unreachable code, displayName already checked above.');
                }
                if (creatingAdapterRef.current) {
                    console.warn('Adapter is already being created, please see storybook for more information: https://azure.github.io/communication-ui-library/?path=/story/troubleshooting--page');
                    return;
                }
                creatingAdapterRef.current = true;
                if (locator) {
                    newAdapter = (yield createAzureCommunicationCallAdapter({
                        credential,
                        displayName: displayName,
                        locator,
                        userId: userId,
                        options
                    }));
                }
                else if (targetCallees) {
                    newAdapter = (yield createAzureCommunicationCallAdapter({
                        credential,
                        displayName: displayName,
                        targetCallees,
                        userId: userId,
                        options
                    }));
                }
            }
            else if (adapterKind === 'Teams') {
                if (creatingAdapterRef.current) {
                    console.warn('Adapter is already being created, skipping creation.');
                    return;
                }
                creatingAdapterRef.current = true;
                if (targetCallees) { }
                else {
                    newAdapter = (yield createTeamsCallAdapter({
                        credential,
                        locator: locator,
                        userId: userId,
                        options
                    }));
                }
            }
            else {
                throw new Error('Unreachable code, unknown adapterKind');
            }
            if (!newAdapter) {
                throw Error('Unreachable code! Get undefined adapter');
            }
            if (afterCreateRef.current) {
                newAdapter = yield afterCreateRef.current(newAdapter);
            }
            adapterRef.current = newAdapter;
            creatingAdapterRef.current = false;
            setAdapter(newAdapter);
        }))();
    }, 
    // Explicitly list all arguments so that caller doesn't have to memoize the `args` object.
    [adapterRef, afterCreateRef, beforeDisposeRef, adapterKind, credential, locator, userId, displayName, options, targetCallees]);
    // Dispose any existing adapter when the component unmounts.
    useEffect(() => {
        return () => {
            (() => __awaiter(this, void 0, void 0, function* () {
                if (adapterRef.current) {
                    if (beforeDisposeRef.current) {
                        yield beforeDisposeRef.current(adapterRef.current);
                    }
                    adapterRef.current.dispose();
                    adapterRef.current = undefined;
                }
            }))();
        };
    }, []);
    return adapter;
}
/**
 * A custom React hook to simplify the creation of {@link CallAdapter}.
 *
 * Similar to {@link createAzureCommunicationCallAdapter}, but takes care of asynchronous
 * creation of the adapter internally.
 *
 * Allows arguments to be undefined so that you can respect the rule-of-hooks and pass in arguments
 * as they are created. The adapter is only created when all arguments are defined.
 *
 * Note that you must memoize the arguments to avoid recreating adapter on each render.
 * See storybook for typical usage examples.
 *
 * @public
 */
export const useAzureCommunicationCallAdapter = (args, afterCreate, beforeDispose) => {
    return useAzureCommunicationCallAdapterGeneric(args, 'AzureCommunication', afterCreate, beforeDispose);
};
/**
 * A custom React hook to simplify the creation of {@link TeamsCallAdapter}.
 *
 * Similar to {@link createTeamsAzureCommunicationCallAdapter}, but takes care of asynchronous
 * creation of the adapter internally.
 *
 * Allows arguments to be undefined so that you can respect the rule-of-hooks and pass in arguments
 * as they are created. The adapter is only created when all arguments are defined.
 *
 * Note that you must memoize the arguments to avoid recreating adapter on each render.
 * See storybook for typical usage examples.
 *
 * @public
 */
export const useTeamsCallAdapter = (args, afterCreate, beforeDispose) => {
    return useAzureCommunicationCallAdapterGeneric(args, 'Teams', afterCreate, beforeDispose);
};
/**
 * Create a {@link CallAdapter} using the provided {@link StatefulCallClient}.
 *
 * Useful if you want to keep a reference to {@link StatefulCallClient}.
 * Consider using {@link createAzureCommunicationCallAdapter} for a simpler API.
 *
 * @public
 */
export function createAzureCommunicationCallAdapterFromClient(callClient, callAgent, locatorOrtargetCallees, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const deviceManager = (yield callClient.getDeviceManager());
        yield Promise.all([deviceManager.getCameras(), deviceManager.getMicrophones()]);
        if (deviceManager.isSpeakerSelectionAvailable) {
            yield deviceManager.getSpeakers();
        }
        if (getLocatorOrTargetCallees(locatorOrtargetCallees)) {
            return new AzureCommunicationCallAdapter(callClient, locatorOrtargetCallees, callAgent, deviceManager, options);
        }
        else {
            return new AzureCommunicationCallAdapter(callClient, locatorOrtargetCallees, callAgent, deviceManager, options);
        }
    });
}
/**
 * Create a {@link TeamsCallAdapter} using the provided {@link StatefulCallClient}.
 *
 * Useful if you want to keep a reference to {@link StatefulCallClient}.
 * Consider using {@link createAzureCommunicationCallAdapter} for a simpler API.
 *
 * @public
 */
export const createTeamsCallAdapterFromClient = (callClient, callAgent, locator, options) => __awaiter(void 0, void 0, void 0, function* () {
    const deviceManager = (yield callClient.getDeviceManager());
    yield Promise.all([deviceManager.getCameras(), deviceManager.getMicrophones()]);
    if (deviceManager.isSpeakerSelectionAvailable) {
        yield deviceManager.getSpeakers();
    }
    if (Array.isArray(locator)) {
        return new AzureCommunicationCallAdapter(callClient, locator, callAgent, deviceManager, options);
    }
    else {
        return new AzureCommunicationCallAdapter(callClient, locator, callAgent, deviceManager, options);
    }
});
const captionsUIVisibilityModifier = (state) => {
    var _a, _b, _c;
    // To hide captions to the UI, set isCaptionsFeatureActive to false and startCaptionsInProgress to false
    if (((_a = state.call) === null || _a === void 0 ? void 0 : _a.captionsFeature) && ((_b = state.captionsTriggers) === null || _b === void 0 ? void 0 : _b.captionsStartedInBackground) && !((_c = state.captionsTriggers) === null || _c === void 0 ? void 0 : _c.captionsStartedWithUI)) {
        return Object.assign(Object.assign({}, state), { call: Object.assign(Object.assign({}, state.call), { captionsFeature: Object.assign(Object.assign({}, state.call.captionsFeature), { isCaptionsFeatureActive: false, startCaptionsInProgress: false }) }) });
    }
    return state;
};
const isCallError = (e) => {
    return 'target' in e && 'innerError' in e;
};
//# sourceMappingURL=AzureCommunicationCallAdapter.js.map