// 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 { convertConferencePhoneInfo } from './Converter';
import { convertFromSDKRealTimeTextToRealTimeTextInfoState } from './Converter';
import { createClientLogger, getLogLevel } from '@azure/logger';
import { EventEmitter } from 'events';
import { enableMapSet, enablePatches, produce } from 'immer';
import { _safeJSONStringify, toFlatCommunicationIdentifier } from "../../acs-ui-common/src";
import { CallError } from './CallClientState';
import { callingStatefulLogger } from './Logger';
import { CallIdHistory } from './CallIdHistory';
import { convertFromTeamsSDKToCaptionInfoState } from './Converter';
import { convertFromSDKToCaptionInfoState } from './Converter';
import { convertFromSDKToRaisedHandState } from './Converter';
enableMapSet();
// Needed to generate state diff for verbose logging.
enablePatches();
// TODO: How can we make this configurable?
/**
 * @private
 */
export const MAX_CALL_HISTORY_LENGTH = 10;
/**
 * @private
 */
export const REACTION_ANIMATION_TIME_MS = 4133;
/**
 * @private
 */
export class CallContext {
    constructor(userId, maxListeners = 50) {
        this._callIdHistory = new CallIdHistory();
        this._timeOutId = {};
        this._latestCallIdOfNotification = {};
        /**
         * Tees direct errors to state.
         * @remarks
         * This is typically used for errors that are caught and intended to be shown to the user.
         *
         * @param error The raw error to report.
         * @param target The error target to tee error to.
         *
         * @private
         */
        this.teeErrorToState = (error, target) => {
            const callError = toCallError(target, error);
            this.setLatestError(target, callError);
        };
        this._logger = createClientLogger('communication-react:calling-context');
        this._state = {
            calls: {},
            callsEnded: {},
            incomingCalls: {},
            incomingCallsEnded: {},
            deviceManager: {
                isSpeakerSelectionAvailable: false,
                cameras: [],
                microphones: [],
                speakers: [],
                unparentedViews: []
            },
            callAgent: undefined,
            userId: userId,
            environmentInfo: undefined,
            latestErrors: {},
            latestNotifications: {}
        };
        this._emitter = new EventEmitter();
        this._emitter.setMaxListeners(maxListeners);
        this._atomicId = 0;
    }
    getState() {
        return this._state;
    }
    modifyState(modifier) {
        const priorState = this._state;
        this._state = produce(this._state, modifier, (patches) => {
            if (getLogLevel() === 'verbose') {
                // Log to `info` because AzureLogger.verbose() doesn't show up in console.
                this._logger.info(`State change: ${_safeJSONStringify(patches)}`);
            }
        });
        if (this._state !== priorState) {
            this._emitter.emit('stateChanged', this._state);
        }
    }
    onStateChange(handler) {
        this._emitter.on('stateChanged', handler);
    }
    offStateChange(handler) {
        this._emitter.off('stateChanged', handler);
    }
    // Disposing of the CallAgentDeclarative will not clear the state. If we create a new CallAgentDeclarative, we should
    // make sure the state is clean because any left over state (if previous CallAgentDeclarative was disposed) may be
    // invalid.
    clearCallRelatedState() {
        this.modifyState((draft) => {
            draft.calls = {};
            draft.incomingCalls = {};
            draft.callsEnded = {};
            draft.incomingCallsEnded = {};
        });
    }
    setCallAgent(callAgent) {
        this.modifyState((draft) => {
            draft.callAgent = callAgent;
        });
    }
    setCall(call) {
        this.modifyState((draft) => {
            const latestCallId = this._callIdHistory.latestCallId(call.id);
            const existingCall = draft.calls[latestCallId];
            if (existingCall) {
                existingCall.callerInfo = call.callerInfo;
                existingCall.state = call.state;
                existingCall.callEndReason = call.callEndReason;
                existingCall.direction = call.direction;
                existingCall.isMuted = call.isMuted;
                existingCall.isScreenSharingOn = call.isScreenSharingOn;
                existingCall.localVideoStreams = call.localVideoStreams;
                existingCall.remoteParticipants = call.remoteParticipants;
                existingCall.transcription.isTranscriptionActive = call.transcription.isTranscriptionActive;
                existingCall.optimalVideoCount.maxRemoteVideoStreams = call.optimalVideoCount.maxRemoteVideoStreams;
                existingCall.recording.isRecordingActive = call.recording.isRecordingActive;
                existingCall.raiseHand.raisedHands = call.raiseHand.raisedHands;
                existingCall.pptLive.isActive = call.pptLive.isActive;
                existingCall.raiseHand.localParticipantRaisedHand = call.raiseHand.localParticipantRaisedHand;
                existingCall.role = call.role;
                // We don't update the startTime and endTime if we are updating an existing active call
                existingCall.captionsFeature.currentSpokenLanguage = call.captionsFeature.currentSpokenLanguage;
                existingCall.captionsFeature.currentCaptionLanguage = call.captionsFeature.currentCaptionLanguage;
                existingCall.info = call.info;
                existingCall.meetingConference = call.meetingConference;
            }
            else {
                draft.calls[latestCallId] = call;
            }
        });
        /**
         * Remove the incoming call that matches the call if there is one
         */
        if (this._state.incomingCalls[call.id]) {
            this.removeIncomingCall(call.id);
        }
    }
    removeCall(callId) {
        this.modifyState((draft) => {
            delete draft.calls[this._callIdHistory.latestCallId(callId)];
        });
    }
    setCallEnded(callId, callEndReason) {
        const latestCallId = this._callIdHistory.latestCallId(callId);
        this.modifyState((draft) => {
            const call = draft.calls[latestCallId];
            if (call) {
                call.endTime = new Date();
                call.callEndReason = callEndReason;
                delete draft.calls[latestCallId];
                // Performance note: This loop should run only once because the number of entries
                // is never allowed to exceed MAX_CALL_HISTORY_LENGTH. A loop is used for correctness.
                while (Object.keys(draft.callsEnded).length >= MAX_CALL_HISTORY_LENGTH) {
                    const oldestCall = findOldestCallEnded(draft.callsEnded);
                    if (oldestCall) {
                        delete draft.callsEnded[oldestCall];
                    }
                }
                draft.callsEnded[latestCallId] = call;
            }
        });
    }
    setCallState(callId, state) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.state = state;
            }
        });
    }
    setCallId(newCallId, oldCallId) {
        this._callIdHistory.updateCallIdHistory(newCallId, oldCallId);
        this.modifyState((draft) => {
            const call = draft.calls[oldCallId];
            if (call) {
                call.id = newCallId;
                delete draft.calls[oldCallId];
                draft.calls[newCallId] = call;
            }
            // Update call ids in latestCallIdsThatPushedNotifications
            Object.keys(this._latestCallIdOfNotification).forEach((key) => {
                if (this._latestCallIdOfNotification[key] === oldCallId) {
                    this._latestCallIdOfNotification[key] = newCallId;
                }
            });
            // Update the open breakout room call id if it matches the old call id
            if (this._openBreakoutRoom === oldCallId) {
                this._openBreakoutRoom = newCallId;
            }
        });
    }
    setEnvironmentInfo(envInfo) {
        this.modifyState((draft) => {
            draft.environmentInfo = envInfo;
        });
    }
    setCallIsScreenSharingOn(callId, isScreenSharingOn) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.isScreenSharingOn = isScreenSharingOn;
            }
        });
    }
    setCallRemoteParticipants(callId, addRemoteParticipant, removeRemoteParticipant) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                removeRemoteParticipant.forEach((id) => {
                    delete call.remoteParticipants[id];
                });
                addRemoteParticipant.forEach((participant) => {
                    call.remoteParticipants[toFlatCommunicationIdentifier(participant.identifier)] = participant;
                });
            }
        });
    }
    setCallRemoteParticipantsEnded(callId, addRemoteParticipant, removeRemoteParticipant) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                removeRemoteParticipant.forEach((id) => {
                    delete call.remoteParticipantsEnded[id];
                });
                addRemoteParticipant.forEach((participant) => {
                    call.remoteParticipantsEnded[toFlatCommunicationIdentifier(participant.identifier)] = participant;
                });
            }
        });
    }
    setCallLocalVideoStream(callId, streamsAdded, streamsRemoved) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                for (const removedStream of streamsRemoved) {
                    const index = call.localVideoStreams.findIndex(i => i.mediaStreamType === removedStream.mediaStreamType);
                    if (index > -1) {
                        call.localVideoStreams.splice(index, 1);
                    }
                }
                for (const addedStream of streamsAdded) {
                    const index = call.localVideoStreams.findIndex(i => i.mediaStreamType === addedStream.mediaStreamType);
                    if (index > -1) {
                        call.localVideoStreams[index] = addedStream;
                    }
                    else {
                        call.localVideoStreams.push(addedStream);
                    }
                }
            }
        });
    }
    setCallLocalVideoStreamVideoEffects(callId, videoEffects) {
        this.modifyState((draft) => {
            var _a;
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const stream = (_a = call.localVideoStreams) === null || _a === void 0 ? void 0 : _a.find(i => i.mediaStreamType === 'Video');
                if (stream) {
                    stream.videoEffects = videoEffects;
                }
            }
        });
    }
    setCallIsMicrophoneMuted(callId, isMicrophoneMuted) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.isMuted = isMicrophoneMuted;
            }
        });
    }
    setRole(callId, role) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.role = role;
            }
        });
    }
    setCallDominantSpeakers(callId, dominantSpeakers) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.dominantSpeakers = dominantSpeakers;
            }
        });
    }
    setCallRecordingActive(callId, isRecordingActive) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.recording.isRecordingActive = isRecordingActive;
            }
        });
    }
    setCallPPTLiveActive(callId, isActive) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.pptLive.isActive = isActive;
            }
        });
    }
    setCallParticipantPPTLive(callId, participantKey, target) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call && participantKey) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.contentSharingStream = target;
                    call.contentSharingRemoteParticipant = participantKey;
                }
            }
        });
    }
    setTogetherModeVideoStreams(callId, addedStreams, removedStreams) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                for (const stream of removedStreams) {
                    if (stream.mediaStreamType === 'Video') {
                        call.togetherMode.streams.mainVideoStream = undefined;
                        call.togetherMode.isActive = false;
                        call.togetherMode.seatingPositions = {};
                    }
                }
                for (const newStream of addedStreams) {
                    // This should only be called by the subscriber and some properties are add by other components so if the
                    // stream already exists, only update the values that subscriber knows about.
                    const mainVideoStream = call.togetherMode.streams.mainVideoStream;
                    if (mainVideoStream && mainVideoStream.id === newStream.id) {
                        mainVideoStream.mediaStreamType = newStream.mediaStreamType;
                        mainVideoStream.isAvailable = newStream.isAvailable;
                        mainVideoStream.isReceiving = newStream.isReceiving;
                    }
                    else {
                        call.togetherMode.streams.mainVideoStream = newStream;
                    }
                    call.togetherMode.isActive = true;
                }
            }
        });
    }
    setTogetherModeVideoStreamIsAvailable(callId, streamId, isAvailable) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const stream = call.togetherMode.streams.mainVideoStream;
                if (stream && (stream === null || stream === void 0 ? void 0 : stream.id) === streamId) {
                    stream.isAvailable = isAvailable;
                }
            }
        });
    }
    setTogetherModeVideoStreamIsReceiving(callId, streamId, isReceiving) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const stream = call.togetherMode.streams.mainVideoStream;
                if (stream && (stream === null || stream === void 0 ? void 0 : stream.id) === streamId) {
                    stream.isReceiving = isReceiving;
                }
            }
        });
    }
    setTogetherModeVideoStreamSize(callId, streamId, size) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const stream = call.togetherMode.streams.mainVideoStream;
                if (stream && (stream === null || stream === void 0 ? void 0 : stream.id) === streamId) {
                    stream.streamSize = size;
                }
            }
        });
    }
    removeTogetherModeVideoStream(callId, removedStream) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                for (const stream of removedStream) {
                    if (stream.mediaStreamType === 'Video') {
                        call.togetherMode.streams.mainVideoStream = undefined;
                        call.togetherMode.isActive = false;
                    }
                }
            }
        });
    }
    setTogetherModeSeatingCoordinates(callId, seatingMap) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const seatingPositions = {};
                for (const [userId, seatingPosition] of seatingMap.entries()) {
                    seatingPositions[userId] = seatingPosition;
                }
                call.togetherMode.seatingPositions = seatingPositions;
            }
        });
    }
    setCallRaisedHands(callId, raisedHands) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.raiseHand.raisedHands = raisedHands.map(raisedHand => {
                    return convertFromSDKToRaisedHandState(raisedHand);
                });
                const raisedHand = raisedHands.find(raisedHand => toFlatCommunicationIdentifier(raisedHand.identifier) === toFlatCommunicationIdentifier(this._state.userId));
                if (raisedHand) {
                    call.raiseHand.localParticipantRaisedHand = convertFromSDKToRaisedHandState(raisedHand);
                }
                else {
                    call.raiseHand.localParticipantRaisedHand = undefined;
                }
            }
        });
    }
    setParticipantIsRaisedHand(callId, participantKey, raisedHand) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.raisedHand = raisedHand ? convertFromSDKToRaisedHandState(raisedHand) : raisedHand;
                }
            }
        });
    }
    setReceivedReactionFromParticipant(callId, participantKey, reactionMessage) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (!call) {
                return;
            }
            clearTimeout(this._timeOutId[participantKey]);
            const participant = call.remoteParticipants[participantKey];
            const newReactionState = reactionMessage ? {
                reactionMessage: reactionMessage,
                receivedOn: new Date()
            } : undefined;
            if (participantKey === toFlatCommunicationIdentifier(this._state.userId)) {
                call.localParticipantReaction = newReactionState;
            }
            else if (!participant) {
                // Warn if we can't find the participant in the call but we are trying to set their reaction state to a new reaction.
                if (reactionMessage !== null) {
                    console.warn(`Participant ${participantKey} not found in call ${callId}. Cannot set reaction state.`);
                }
            }
            else {
                participant.reactionState = newReactionState;
            }
            if (reactionMessage) {
                this._timeOutId[participantKey] = setTimeout(() => {
                    clearParticipantReactionState(this, callId, participantKey);
                }, REACTION_ANIMATION_TIME_MS);
            }
        });
    }
    setCallTranscriptionActive(callId, isTranscriptionActive) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.transcription.isTranscriptionActive = isTranscriptionActive;
            }
        });
    }
    setCapabilities(callId, capabilities, capabilitiesChangeInfo) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.capabilitiesFeature = {
                    capabilities,
                    latestCapabilitiesChangeInfo: capabilitiesChangeInfo
                };
            }
        });
    }
    setHideAttendeeNames(callId, capabilitiesChangeInfo) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (capabilitiesChangeInfo.oldValue.viewAttendeeNames !== capabilitiesChangeInfo.newValue.viewAttendeeNames) {
                const viewAttendeeNames = capabilitiesChangeInfo.newValue.viewAttendeeNames;
                if (call && viewAttendeeNames && !viewAttendeeNames.isPresent && viewAttendeeNames.reason === 'MeetingRestricted') {
                    call.hideAttendeeNames = true;
                }
                else if (call) {
                    call.hideAttendeeNames = false;
                }
            }
        });
    }
    setSpotlight(callId, spotlightedParticipants, maxParticipantsToSpotlight) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.spotlight = Object.assign(Object.assign({}, call.spotlight), { spotlightedParticipants,
                    maxParticipantsToSpotlight });
            }
        });
    }
    setTeamsMeetingConference(callId, teamsMeetingConferenceDetails) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.meetingConference = {
                    conferencePhones: convertConferencePhoneInfo(teamsMeetingConferenceDetails)
                };
            }
        });
    }
    setParticipantSpotlighted(callId, spotlightedParticipant) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[toFlatCommunicationIdentifier(spotlightedParticipant.identifier)];
                if (participant) {
                    participant.spotlight = {
                        spotlightedOrderPosition: spotlightedParticipant.order
                    };
                }
                else if (call.spotlight && toFlatCommunicationIdentifier(draft.userId) === toFlatCommunicationIdentifier(spotlightedParticipant.identifier)) {
                    call.spotlight.localParticipantSpotlight = {
                        spotlightedOrderPosition: spotlightedParticipant.order
                    };
                }
            }
        });
    }
    setParticipantNotSpotlighted(callId, spotlightedParticipant) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[toFlatCommunicationIdentifier(spotlightedParticipant.identifier)];
                if (participant) {
                    participant.spotlight = undefined;
                }
                else if (call.spotlight && toFlatCommunicationIdentifier(draft.userId) === toFlatCommunicationIdentifier(spotlightedParticipant.identifier)) {
                    call.spotlight.localParticipantSpotlight = undefined;
                }
            }
        });
    }
    setAssignedBreakoutRoom(callId, breakoutRoom) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.breakoutRooms = Object.assign(Object.assign({}, call.breakoutRooms), { assignedBreakoutRoom: breakoutRoom });
            }
        });
    }
    setBreakoutRoomSettings(callId, breakoutRoomSettings) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.breakoutRooms = Object.assign(Object.assign({}, call.breakoutRooms), { breakoutRoomSettings: breakoutRoomSettings });
            }
        });
    }
    setBreakoutRoomDisplayName(callId, breakoutRoomDisplayName) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.breakoutRooms = Object.assign(Object.assign({}, call.breakoutRooms), { breakoutRoomDisplayName });
            }
        });
    }
    setOpenBreakoutRoom(callId) {
        this._openBreakoutRoom = callId;
    }
    deleteOpenBreakoutRoom() {
        this._openBreakoutRoom = undefined;
    }
    getOpenBreakoutRoom() {
        return this._openBreakoutRoom;
    }
    setCallScreenShareParticipant(callId, participantKey) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.screenShareRemoteParticipant = participantKey;
            }
        });
    }
    setLocalVideoStreamRendererView(callId, localVideoMediaStreamType, view) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const localVideoStream = call.localVideoStreams.find(localVideoStream => localVideoStream.mediaStreamType === localVideoMediaStreamType);
                if (localVideoStream) {
                    localVideoStream.view = view;
                }
            }
        });
    }
    setTogetherModeVideoStreamRendererView(callId, togetherModeStreamType, view) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                if (togetherModeStreamType === 'Video') {
                    const togetherModeStream = call.togetherMode.streams.mainVideoStream;
                    if (togetherModeStream) {
                        togetherModeStream.view = view;
                    }
                }
            }
        });
    }
    setParticipantState(callId, participantKey, state) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.state = state;
                }
            }
        });
    }
    setParticipantIsMuted(callId, participantKey, muted) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.isMuted = muted;
                }
            }
        });
    }
    setOptimalVideoCount(callId, optimalVideoCount) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.optimalVideoCount.maxRemoteVideoStreams = optimalVideoCount;
            }
        });
    }
    setParticipantRole(callId, participantKey, role) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.role = role;
                }
            }
        });
    }
    setParticipantDisplayName(callId, participantKey, displayName) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.displayName = displayName;
                }
            }
        });
    }
    setParticipantIsSpeaking(callId, participantKey, isSpeaking) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    participant.isSpeaking = isSpeaking;
                }
            }
        });
    }
    setParticipantVideoStream(callId, participantKey, stream) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    // Set is called by subscriber will not modify any rendered stream so if there is existing stream only
                    // modify the values that subscriber has access to.
                    const existingStream = participant.videoStreams[stream.id];
                    if (existingStream) {
                        existingStream.isAvailable = stream.isAvailable;
                        existingStream.isReceiving = stream.isReceiving;
                        existingStream.mediaStreamType = stream.mediaStreamType;
                    }
                    else {
                        participant.videoStreams[stream.id] = stream;
                    }
                }
            }
        });
    }
    setRemoteVideoStreamIsAvailable(callId, participantKey, streamId, isAvailable) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    const stream = participant.videoStreams[streamId];
                    if (stream) {
                        stream.isAvailable = isAvailable;
                    }
                }
            }
        });
    }
    setRemoteVideoStreamIsReceiving(callId, participantKey, streamId, isReceiving) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    const stream = participant.videoStreams[streamId];
                    if (stream) {
                        stream.isReceiving = isReceiving;
                    }
                }
            }
        });
    }
    setRemoteVideoStreamSize(callId, participantKey, streamId, size) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    const stream = participant.videoStreams[streamId];
                    if (stream) {
                        stream.streamSize = size;
                    }
                }
            }
        });
    }
    setRemoteVideoStreams(callId, participantKey, addRemoteVideoStream, removeRemoteVideoStream) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    for (const id of removeRemoteVideoStream) {
                        delete participant.videoStreams[id];
                    }
                    for (const newStream of addRemoteVideoStream) {
                        // This should only be called by the subscriber and some properties are add by other components so if the
                        // stream already exists, only update the values that subscriber knows about.
                        const stream = participant.videoStreams[newStream.id];
                        if (stream) {
                            stream.mediaStreamType = newStream.mediaStreamType;
                            stream.isAvailable = newStream.isAvailable;
                            stream.isReceiving = newStream.isReceiving;
                        }
                        else {
                            participant.videoStreams[newStream.id] = newStream;
                        }
                    }
                }
            }
        });
    }
    setRemoteVideoStreamRendererView(callId, participantKey, streamId, view) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    const stream = participant.videoStreams[streamId];
                    if (stream) {
                        stream.view = view;
                    }
                }
            }
        });
    }
    setRemoteVideoStreamViewScalingMode(callId, participantKey, streamId, scalingMode) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const participant = call.remoteParticipants[participantKey];
                if (participant) {
                    const stream = participant.videoStreams[streamId];
                    if (stream && stream.view) {
                        stream.view.scalingMode = scalingMode;
                    }
                }
            }
        });
    }
    setIncomingCall(call) {
        this.modifyState((draft) => {
            const existingCall = draft.incomingCalls[call.id];
            if (existingCall) {
                existingCall.callerInfo = call.callerInfo;
            }
            else {
                draft.incomingCalls[call.id] = call;
            }
        });
    }
    removeIncomingCall(callId) {
        this.modifyState((draft) => {
            delete draft.incomingCalls[callId];
        });
    }
    setIncomingCallEnded(callId, callEndReason) {
        this.modifyState((draft) => {
            const call = draft.incomingCalls[callId];
            if (call) {
                call.endTime = new Date();
                call.callEndReason = callEndReason;
                delete draft.incomingCalls[callId];
                // Performance note: This loop should run only once because the number of entries
                // is never allowed to exceed MAX_CALL_HISTORY_LENGTH. A loop is used for correctness.
                while (Object.keys(draft.incomingCallsEnded).length >= MAX_CALL_HISTORY_LENGTH) {
                    const oldestCall = findOldestCallEnded(draft.incomingCallsEnded);
                    if (oldestCall) {
                        delete draft.incomingCallsEnded[oldestCall];
                    }
                }
                draft.incomingCallsEnded[callId] = call;
            }
        });
    }
    setDeviceManagerIsSpeakerSelectionAvailable(isSpeakerSelectionAvailable) {
        this.modifyState((draft) => {
            draft.deviceManager.isSpeakerSelectionAvailable = isSpeakerSelectionAvailable;
        });
    }
    setDeviceManagerSelectedMicrophone(selectedMicrophone) {
        this.modifyState((draft) => {
            draft.deviceManager.selectedMicrophone = selectedMicrophone;
        });
    }
    setDeviceManagerSelectedSpeaker(selectedSpeaker) {
        this.modifyState((draft) => {
            draft.deviceManager.selectedSpeaker = selectedSpeaker;
        });
    }
    setDeviceManagerSelectedCamera(selectedCamera) {
        this.modifyState((draft) => {
            draft.deviceManager.selectedCamera = selectedCamera;
        });
    }
    setDeviceManagerCameras(cameras) {
        this.modifyState((draft) => {
            /**
             * SDK initializes cameras with one dummy camera with value { id: 'camera:id', name: '', deviceType: 'USBCamera' } immediately after
             * camera permissions are granted. So selectedCamera will have this value before the actual cameras are obtained. Therefore we should reset
             * selectedCamera to the first camera when there are cameras AND when current selectedCamera does not exist in the new array of cameras *
             */
            if (cameras.length > 0 && !cameras.some(camera => { var _a; return camera.id === ((_a = draft.deviceManager.selectedCamera) === null || _a === void 0 ? void 0 : _a.id); })) {
                draft.deviceManager.selectedCamera = cameras[0];
            }
            draft.deviceManager.cameras = cameras;
        });
    }
    setDeviceManagerMicrophones(microphones) {
        this.modifyState((draft) => {
            draft.deviceManager.microphones = microphones;
        });
    }
    setDeviceManagerSpeakers(speakers) {
        this.modifyState((draft) => {
            draft.deviceManager.speakers = speakers;
        });
    }
    setDeviceManagerDeviceAccess(deviceAccess) {
        this.modifyState((draft) => {
            draft.deviceManager.deviceAccess = deviceAccess;
        });
    }
    setDeviceManagerUnparentedView(localVideoStream, view) {
        this.modifyState((draft) => {
            draft.deviceManager.unparentedViews.push({
                source: localVideoStream.source,
                mediaStreamType: localVideoStream.mediaStreamType,
                view: view
            });
        });
    }
    deleteDeviceManagerUnparentedView(localVideoStream) {
        this.modifyState((draft) => {
            const foundIndex = draft.deviceManager.unparentedViews.findIndex(stream => stream.mediaStreamType === localVideoStream.mediaStreamType);
            if (foundIndex !== -1) {
                draft.deviceManager.unparentedViews.splice(foundIndex, 1);
            }
        });
    }
    setDeviceManagerUnparentedViewVideoEffects(localVideoStream, videoEffects) {
        this.modifyState((draft) => {
            const view = draft.deviceManager.unparentedViews.find(stream => stream.mediaStreamType === localVideoStream.mediaStreamType);
            if (view) {
                view.videoEffects = videoEffects;
            }
        });
    }
    getAndIncrementAtomicId() {
        const id = this._atomicId;
        this._atomicId++;
        return id;
    }
    processNewRealTimeText(realTimeTexts, newRealTimeText) {
        var _a, _b, _c, _d, _e;
        // if the new message is final, push it to completed messages
        if (newRealTimeText.resultType === 'Final') {
            if (!realTimeTexts.completedMessages) {
                realTimeTexts.completedMessages = [];
            }
            (_a = realTimeTexts.completedMessages) === null || _a === void 0 ? void 0 : _a.push(newRealTimeText);
            // clear the corresponding in progress message
            if (newRealTimeText.isMe) {
                realTimeTexts.myInProgress = undefined;
            }
            else {
                realTimeTexts.currentInProgress = (_b = realTimeTexts.currentInProgress) === null || _b === void 0 ? void 0 : _b.filter(message => message.sequenceId !== newRealTimeText.sequenceId);
            }
        }
        else {
            // if receive partial real time text
            // if message is from me, assign it to myInProgress
            if (newRealTimeText.isMe) {
                realTimeTexts.myInProgress = newRealTimeText;
            }
            // if message is from others, assign it to currentInProgress
            else {
                if (!realTimeTexts.currentInProgress) {
                    realTimeTexts.currentInProgress = [];
                    realTimeTexts.currentInProgress.push(newRealTimeText);
                    return;
                }
                // find the index of the existing in progress message
                // replace the existing in progress message with the new one
                const existingIndex = (_c = realTimeTexts.currentInProgress) === null || _c === void 0 ? void 0 : _c.findIndex(message => message.sequenceId === newRealTimeText.sequenceId);
                if (existingIndex === -1) {
                    (_d = realTimeTexts.currentInProgress) === null || _d === void 0 ? void 0 : _d.push(newRealTimeText);
                }
                else {
                    realTimeTexts.currentInProgress[existingIndex] = newRealTimeText;
                }
            }
        }
        // edge case check for in progress messages time out
        if (!realTimeTexts.completedMessages) {
            realTimeTexts.completedMessages = [];
        }
        this.findTimeoutRealTimeText(realTimeTexts.currentInProgress, realTimeTexts.completedMessages);
        this.findTimeoutRealTimeText(realTimeTexts.myInProgress, realTimeTexts.completedMessages);
        // we only want to store up to 50 finalized messages
        if (realTimeTexts.completedMessages && ((_e = realTimeTexts.completedMessages) === null || _e === void 0 ? void 0 : _e.length) > 50) {
            realTimeTexts.completedMessages.shift();
        }
    }
    findTimeoutRealTimeText(inProgressRealTimeTexts, completedRealTimeTexts) {
        // if inProgressRealTimeTexts is an array
        if (inProgressRealTimeTexts && Array.isArray(inProgressRealTimeTexts)) {
            // find the in progress real time text that has not been updated for 5 seconds
            for (let i = inProgressRealTimeTexts.length - 1; i >= 0; i--) {
                const realTimeText = inProgressRealTimeTexts[i];
                if ((realTimeText === null || realTimeText === void 0 ? void 0 : realTimeText.updatedTimestamp) && Date.now() - (realTimeText === null || realTimeText === void 0 ? void 0 : realTimeText.updatedTimestamp.getTime()) > 5000) {
                    // turn the in progress real time text to final
                    realTimeText.resultType = 'Final';
                    // move the in progress real time text to completed
                    completedRealTimeTexts.push(realTimeText);
                    // remove the in progress real time text from in progress
                    if (inProgressRealTimeTexts) {
                        inProgressRealTimeTexts.splice(i, 1);
                    }
                }
            }
        }
        else {
            // if inProgressRealTimeTexts is a single object
            if (inProgressRealTimeTexts && inProgressRealTimeTexts.updatedTimestamp && Date.now() - inProgressRealTimeTexts.updatedTimestamp.getTime() > 5000) {
                // turn the in progress real time text to final
                inProgressRealTimeTexts.resultType = 'Final';
                // move the in progress real time text to completed
                completedRealTimeTexts.push(inProgressRealTimeTexts);
                // remove the in progress real time text from in progress
                inProgressRealTimeTexts = undefined;
            }
        }
    }
    processNewCaption(captions, newCaption) {
        var _a;
        // time stamp when new caption comes in
        newCaption.lastUpdatedTimestamp = new Date();
        // if this is the first caption, push it in
        if (captions.length === 0) {
            captions.push(newCaption);
        }
        // if the last caption is final, then push the new one in
        else if (((_a = captions[captions.length - 1]) === null || _a === void 0 ? void 0 : _a.resultType) === 'Final') {
            captions.push(newCaption);
        }
        // if the last caption is Partial, then check if the speaker is the same as the new caption, if so, update the last caption
        else {
            const lastCaption = captions[captions.length - 1];
            if (lastCaption && lastCaption.speaker.identifier && newCaption.speaker.identifier && toFlatCommunicationIdentifier(lastCaption.speaker.identifier) === toFlatCommunicationIdentifier(newCaption.speaker.identifier)) {
                captions[captions.length - 1] = newCaption;
            }
            // if different speaker, ignore the interjector until the current speaker finishes
            // edge case: if we dont receive the final caption from the current speaker for 5 secs, we turn the current speaker caption to final and push in the new interjector
            else if (lastCaption === null || lastCaption === void 0 ? void 0 : lastCaption.lastUpdatedTimestamp) {
                if (Date.now() - lastCaption.lastUpdatedTimestamp.getTime() > 5000) {
                    lastCaption.resultType = 'Final';
                    captions.push(newCaption);
                }
            }
        }
        // If the array length exceeds 50, remove the oldest caption
        if (captions.length > 50) {
            captions.shift();
        }
    }
    addTeamsCaption(callId, caption) {
        this.modifyState((draft) => {
            var _a;
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                const currentCaptionLanguage = call.captionsFeature.currentCaptionLanguage;
                if (caption.captionLanguage.toUpperCase() === currentCaptionLanguage.toUpperCase() || currentCaptionLanguage === '' || currentCaptionLanguage === undefined) {
                    this.processNewCaption((_a = call === null || call === void 0 ? void 0 : call.captionsFeature.captions) !== null && _a !== void 0 ? _a : [], convertFromTeamsSDKToCaptionInfoState(caption));
                }
            }
        });
    }
    addCaption(callId, caption) {
        this.modifyState((draft) => {
            var _a;
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                this.processNewCaption((_a = call === null || call === void 0 ? void 0 : call.captionsFeature.captions) !== null && _a !== void 0 ? _a : [], convertFromSDKToCaptionInfoState(caption));
            }
        });
    }
    addRealTimeText(callId, realTimeText) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                this.processNewRealTimeText(call.realTimeTextFeature.realTimeTexts, convertFromSDKRealTimeTextToRealTimeTextInfoState(realTimeText));
            }
        });
    }
    setCaptionsKind(callId, kind) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.captionsKind = kind;
            }
        });
    }
    clearCaptions(callId) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.captions = call.captionsFeature.captions.filter(c => 'message' in c);
            }
        });
    }
    setIsCaptionActive(callId, isCaptionsActive) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.isCaptionsFeatureActive = isCaptionsActive;
            }
        });
    }
    setIsRealTimeTextActive(callId, isRealTimeTextActive) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.realTimeTextFeature.isRealTimeTextFeatureActive = isRealTimeTextActive;
            }
        });
    }
    setStartCaptionsInProgress(callId, startCaptionsInProgress) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.startCaptionsInProgress = startCaptionsInProgress;
            }
        });
    }
    setSelectedSpokenLanguage(callId, spokenLanguage) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.currentSpokenLanguage = spokenLanguage;
            }
        });
    }
    setSelectedCaptionLanguage(callId, captionLanguage) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.currentCaptionLanguage = captionLanguage;
            }
        });
    }
    setAvailableCaptionLanguages(callId, captionLanguages) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.supportedCaptionLanguages = captionLanguages;
            }
        });
    }
    setAvailableSpokenLanguages(callId, spokenLanguages) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.captionsFeature.supportedSpokenLanguages = spokenLanguages;
            }
        });
    }
    setAcceptedTransfer(callId, acceptedTransfer) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (call) {
                call.transfer.acceptedTransfers[acceptedTransfer.callId] = acceptedTransfer;
            }
        });
    }
    /**
     * Tees any errors encountered in an async function to the state.
     *
     * @param action Async function to execute.
     * @param target The error target to tee error to.
     * @returns Result of calling `f`. Also re-raises any exceptions thrown from `f`.
     * @throws CallError. Exceptions thrown from `f` are tagged with the failed `target.
     */
    withAsyncErrorTeedToState(action, target) {
        return (...args) => __awaiter(this, void 0, void 0, function* () {
            try {
                return yield action(...args);
            }
            catch (error) {
                const callError = toCallError(target, error);
                this.setLatestError(target, callError);
                throw callError;
            }
        });
    }
    /**
     * Tees any errors encountered in an function to the state.
     *
     * @param action Function to execute.
     * @param target The error target to tee error to.
     * @returns Result of calling `f`. Also re-raises any exceptions thrown from `f`.
     * @throws CallError. Exceptions thrown from `f` are tagged with the failed `target.
     */
    withErrorTeedToState(action, target) {
        return (...args) => {
            try {
                callingStatefulLogger.info(`Calling stateful client target function called: ${target}`);
                return action(...args);
            }
            catch (error) {
                const callError = toCallError(target, error);
                this.setLatestError(target, callError);
                throw callError;
            }
        };
    }
    setLatestError(target, error) {
        this.modifyState((draft) => {
            draft.latestErrors[target] = error;
        });
    }
    setLatestNotification(callId, notification) {
        this._latestCallIdOfNotification[notification.target] = callId;
        this.modifyState((draft) => {
            draft.latestNotifications[notification.target] = notification;
        });
    }
    deleteLatestNotification(notificationTarget, callId) {
        const callIdOfNotification = this._latestCallIdOfNotification[notificationTarget];
        // Only delete the notification if the call that pushed the notification is the same as the callId if specified
        if (callId && callIdOfNotification !== callId) {
            return;
        }
        this.modifyState((draft) => {
            delete draft.latestNotifications[notificationTarget];
        });
    }
    setMediaAccesses(callId, mediaAccesses) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (!call) {
                return;
            }
            mediaAccesses.forEach(participantMediaAccess => {
                const participant = call.remoteParticipants[toFlatCommunicationIdentifier(participantMediaAccess.participant)];
                if (participant) {
                    participant.mediaAccess = {
                        isAudioPermitted: participantMediaAccess.isAudioPermitted,
                        isVideoPermitted: participantMediaAccess.isVideoPermitted
                    };
                }
            });
        });
    }
    setMeetingMediaAccess(callId, meetingMediaAccess) {
        this.modifyState((draft) => {
            const call = draft.calls[this._callIdHistory.latestCallId(callId)];
            if (!call) {
                return;
            }
            if (meetingMediaAccess) {
                call.meetingMediaAccess = {
                    isAudioPermitted: meetingMediaAccess.isAudioPermitted,
                    isVideoPermitted: meetingMediaAccess.isVideoPermitted
                };
            }
        });
    }
}
const toCallError = (target, error) => {
    if (error instanceof Error) {
        return new CallError(target, error);
    }
    return new CallError(target, new Error(error));
};
const findOldestCallEnded = (calls) => {
    var _a, _b, _c, _d;
    const callEntries = Object.entries(calls);
    const firstCallEntry = callEntries[0];
    if (!firstCallEntry) {
        return undefined; // no calls exist
    }
    let [oldestCallId, oldestCall] = firstCallEntry;
    if (oldestCall.endTime === undefined) {
        return oldestCallId;
    }
    for (const [callId, call] of callEntries.slice(1)) {
        if (call.endTime === undefined) {
            return callId;
        }
        if (((_b = (_a = call.endTime) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0) < ((_d = (_c = oldestCall.endTime) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : 0)) {
            [oldestCallId, oldestCall] = [callId, call];
        }
    }
    return oldestCallId;
};
function clearParticipantReactionState(callContext, callId, participantKey) {
    callContext.setReceivedReactionFromParticipant(callId, participantKey, null);
}
//# sourceMappingURL=CallContext.js.map