// 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 { toFlatCommunicationIdentifier } from "../../../../../acs-ui-common/src";
import { callWithChatAdapterStateFromBackingStates, mergeCallAdapterStateIntoCallWithChatAdapterState, mergeChatAdapterStateIntoCallWithChatAdapterState } from '../state/CallWithChatAdapterState';
import { _createAzureCommunicationChatAdapterInner, _createLazyAzureCommunicationChatAdapterInner, createAzureCommunicationChatAdapterFromClient } from '../../ChatComposite/adapter/AzureCommunicationChatAdapter';
import { EventEmitter } from 'events';
import { isCommunicationUserIdentifier } from '@azure/communication-common';
import { _createAzureCommunicationCallAdapterInner } from '../../CallComposite/adapter/AzureCommunicationCallAdapter';
import { createAzureCommunicationCallAdapterFromClient } from '../../CallComposite/adapter/AzureCommunicationCallAdapter';
import { useEffect, useRef, useState } from 'react';
import { _toCommunicationIdentifier } from "../../../../../acs-ui-common/src";
import { busyWait } from '../../common/utils';
/**
 * For each time that we use the hook {@link useSelector} in the {@link CallWithChatComposite} 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 MAX_EVENT_LISTENERS = 125;
/** Context of Call with Chat, which is a centralized context for all state updates */
class CallWithChatContext {
    constructor(clientState, maxListeners = MAX_EVENT_LISTENERS) {
        this.emitter = new EventEmitter();
        this.state = clientState;
        this.emitter.setMaxListeners(maxListeners);
    }
    onStateChange(handler) {
        this.emitter.on('stateChanged', handler);
    }
    offStateChange(handler) {
        this.emitter.off('stateChanged', handler);
    }
    setState(state) {
        this.state = state;
        this.emitter.emit('stateChanged', this.state);
    }
    getState() {
        return this.state;
    }
    updateClientState(clientState) {
        this.setState(clientState);
    }
    updateClientStateWithChatState(chatAdapterState) {
        this.updateClientState(mergeChatAdapterStateIntoCallWithChatAdapterState(this.state, chatAdapterState));
    }
    unsetChatState() {
        this.updateClientState(Object.assign(Object.assign({}, this.state), { chat: undefined }));
    }
    updateClientStateWithCallState(callAdapterState) {
        this.updateClientState(mergeCallAdapterStateIntoCallWithChatAdapterState(this.state, callAdapterState));
    }
}
/**
 * CallWithChat adapter backed by Azure Communication Services.
 * Created for easy use with the {@link CallWithChatComposite}.
 */
export class AzureCommunicationCallWithChatAdapter {
    // This function is used to store a listener to the internal map of chat event listeners and should be
    // used when a listener subscribes to a chat event
    storeChatEventListener(event, listener) {
        var _a;
        if (!this.chatEventListeners.has(event)) {
            this.chatEventListeners.set(event, new Set());
        }
        (_a = this.chatEventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.add(listener);
    }
    // This function is used to remove a listener from the internal map of chat event listeners and should
    // be used when a listener unsubscribes from a chat event
    removeChatEventListener(event, listener) {
        var _a;
        if (this.chatEventListeners.has(event)) {
            (_a = this.chatEventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.delete(listener);
        }
    }
    constructor(callAdapter, chatAdapter) {
        this.emitter = new EventEmitter();
        this.isAdapterDisposed = false;
        // This map is used to store the listeners subscribed to chat events so that we can re-subscribe them
        // to the new chat adapter when the thread id changes
        this.chatEventListeners = new Map();
        this.bindPublicMethods();
        this.callAdapter = callAdapter;
        this.context = new CallWithChatContext(callWithChatAdapterStateFromBackingStates(callAdapter));
        const onChatStateChange = (newChatAdapterState) => {
            this.context.updateClientStateWithChatState(newChatAdapterState);
        };
        this.onChatStateChange = onChatStateChange;
        if (chatAdapter) {
            this.updateChatAdapter(chatAdapter);
            this.originCallChatAdapter = chatAdapter;
        }
        const onCallStateChange = (newCallAdapterState) => {
            this.context.updateClientStateWithCallState(newCallAdapterState);
        };
        this.callAdapter.onStateChange(onCallStateChange);
        this.callAdapter.on('breakoutRoomsUpdated', (eventData) => __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            if (eventData.type === 'join') {
                yield this.breakoutRoomJoined(eventData.data);
            }
            else if (eventData.type === 'assignedBreakoutRooms') {
                if (!eventData.data || eventData.data.state === 'closed') {
                    if (this.originCallChatAdapter && ((_a = this.originCallChatAdapter) === null || _a === void 0 ? void 0 : _a.getState().thread.threadId) !== ((_b = this.context.getState().chat) === null || _b === void 0 ? void 0 : _b.threadId)) {
                        this.updateChatAdapter(this.originCallChatAdapter);
                    }
                }
            }
        }));
        this.callAdapter.on('callEnded', () => {
            var _a, _b, _c;
            // If the call ended is a breakout room call with breakout room settings then update the chat adapter to the
            // origin call
            if ((_b = (_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.breakoutRooms) === null || _b === void 0 ? void 0 : _b.breakoutRoomSettings) {
                // Unsubscribe from chat adapter state changes
                (_c = this.chatAdapter) === null || _c === void 0 ? void 0 : _c.offStateChange(this.onChatStateChange);
                // Unassign chat adapter
                this.chatAdapter = undefined;
                // Set chat state to undefined to ensure that the chat thread of the breakout room is not shown
                this.context.unsetChatState();
                // Update chat state to the origin call chat adapter
                if (this.originCallChatAdapter) {
                    this.updateChatAdapter(this.originCallChatAdapter);
                }
            }
        });
        this.onCallStateChange = onCallStateChange;
    }
    breakoutRoomJoined(call) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const targetThreadId = call.info.threadId;
            // If the chat adapter is not on the target thread then we need to switch to the breakout room chat adapter
            if (targetThreadId && this.chatAdapter && this.chatAdapter.getState().thread.threadId !== targetThreadId) {
                // Unsubscribe from chat adapter state changes
                this.chatAdapter.offStateChange(this.onChatStateChange);
                // Set chat state to undefined to prevent showing chat thread of origin call
                this.context.unsetChatState();
                // Check if the breakout room chat adapter has been initialized
                this.breakoutRoomChatAdapter = yield this.setBreakoutRoomChatAdapterToThread(targetThreadId);
                // Wait for the user to be added to the thread of chat adapter before updating the current chat adapter
                // to avoid chat errors of not having access to the chat thread. This delayed access to the chat thread
                // is also seen in Teams
                // Check up to 20 times every 500ms and then continue.
                yield busyWait(() => {
                    var _a, _b;
                    // If the call adapter's call id has been changed, then stop waiting and don't update the chat adapter
                    if (((_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.id) !== call.id) {
                        return true;
                    }
                    const userAddedToThread = !!((_b = this.breakoutRoomChatAdapter) === null || _b === void 0 ? void 0 : _b.getState().thread.participants[toFlatCommunicationIdentifier(this.context.getState().userId)]);
                    return userAddedToThread;
                }, 20);
                // If the call adapter's call has been changed while for waiting for breakout room chat adapter to be ready
                // then don't update the chat adapter
                if (((_a = this.context.getState().call) === null || _a === void 0 ? void 0 : _a.id) !== call.id) {
                    return;
                }
                this.updateChatAdapter(this.breakoutRoomChatAdapter);
            }
        });
    }
    setBreakoutRoomChatAdapterToThread(targetThreadId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.breakoutRoomChatAdapter) {
                // If the breakout room chat adapter is not set on the target thread then unsubscribe, dispose, and
                // reinitialize the chat adapter for the target thread
                if (this.breakoutRoomChatAdapter.getState().thread.threadId !== targetThreadId) {
                    this.breakoutRoomChatAdapter.offStateChange(this.onChatStateChange);
                    this.breakoutRoomChatAdapter.dispose();
                    const newBreakoutRoomChatAdapter = yield this.createNewChatAdapterForThread(targetThreadId);
                    return newBreakoutRoomChatAdapter;
                }
                else {
                    return this.breakoutRoomChatAdapter;
                }
            }
            else {
                // Initiliaze the breakout room chat adapter for the target thread
                const newBreakoutRoomChatAdapter = yield this.createNewChatAdapterForThread(targetThreadId);
                return newBreakoutRoomChatAdapter;
            }
        });
    }
    setChatAdapterPromise(chatAdapter) {
        chatAdapter.then(adapter => {
            if (!this.isAdapterDisposed) {
                this.updateChatAdapter(adapter);
                this.originCallChatAdapter = adapter;
            }
        });
    }
    setCreateChatAdapterCallback(chatThreadCallBack) {
        this.createChatAdapterCallback = chatThreadCallBack;
    }
    createNewChatAdapterForThread(threadId) {
        if (this.createChatAdapterCallback) {
            return this.createChatAdapterCallback(threadId);
        }
        throw new Error('Unable to create chat adapter for thread because createChatAdapterCallback is not set');
    }
    updateChatAdapter(chatAdapter) {
        var _a;
        (_a = this.chatAdapter) === null || _a === void 0 ? void 0 : _a.offStateChange(this.onChatStateChange);
        this.updateChatEventListeners(chatAdapter);
        this.chatAdapter = chatAdapter;
        this.chatAdapter.onStateChange(this.onChatStateChange);
        this.context.updateClientStateWithChatState(chatAdapter.getState());
        this.emitter.emit('chatInitialized', this.chatAdapter);
    }
    updateChatEventListeners(chatAdapter) {
        var _a;
        for (const [eventName, listeners] of this.chatEventListeners) {
            for (const listener of listeners) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                (_a = this.chatAdapter) === null || _a === void 0 ? void 0 : _a.off(eventName, listener);
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                chatAdapter.on(eventName, listener);
            }
        }
    }
    bindPublicMethods() {
        this.joinCall.bind(this);
        this.leaveCall.bind(this);
        this.startCall.bind(this);
        this.onStateChange.bind(this);
        this.offStateChange.bind(this);
        this.getState.bind(this);
        this.dispose.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.startScreenShare.bind(this);
        this.stopScreenShare.bind(this);
        this.raiseHand.bind(this);
        this.lowerHand.bind(this);
        this.onReactionClick.bind(this);
        this.removeParticipant.bind(this);
        this.createStreamView.bind(this);
        this.disposeStreamView.bind(this);
        this.disposeScreenShareStreamView.bind(this);
        this.fetchInitialData.bind(this);
        this.sendMessage.bind(this);
        this.sendReadReceipt.bind(this);
        this.sendTypingIndicator.bind(this);
        this.loadPreviousChatMessages.bind(this);
        this.updateMessage.bind(this);
        this.deleteMessage.bind(this);
        this.on.bind(this);
        this.off.bind(this);
        this.downloadResourceToCache = this.downloadResourceToCache.bind(this);
        this.removeResourceFromCache = this.removeResourceFromCache.bind(this);
        this.holdCall.bind(this);
        this.resumeCall.bind(this);
        this.addParticipant.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);
    }
    /** Join existing Call. */
    joinCall(options) {
        if (typeof options === 'boolean') {
            return this.callAdapter.joinCall(options);
        }
        else {
            return this.callAdapter.joinCall(options);
        }
    }
    /** Leave current Call. */
    leaveCall(forEveryone) {
        return __awaiter(this, void 0, void 0, function* () {
            // Only remove self from the GroupCall. Contoso must manage access to Chat.
            yield this.callAdapter.leaveCall(forEveryone);
        });
    }
    /** Start a new Call. */
    startCall(participants, options) {
        if (participants.length === 0) {
            throw new Error('At least one participant is required to start a call');
        }
        if (typeof participants[0] === 'string') {
            return this.callAdapter.startCall(participants, options);
        }
        else {
            return this.callAdapter.startCall(participants, options);
        }
    }
    /**
     * Subscribe to state change events.
     * @param handler - handler to be called when the state changes. This is passed the new state.
     */
    onStateChange(handler) {
        this.context.onStateChange(handler);
    }
    /**
     * Unsubscribe to state change events.
     * @param handler - handler to be no longer called when state changes.
     */
    offStateChange(handler) {
        this.context.offStateChange(handler);
    }
    /** Get current Call and Chat state. */
    getState() {
        return this.context.getState();
    }
    /** Dispose of the current CallWithChatAdapter. */
    dispose() {
        this.isAdapterDisposed = true;
        if (this.chatAdapter) {
            this.chatAdapter.offStateChange(this.onChatStateChange);
            this.chatAdapter.dispose();
        }
        this.callAdapter.offStateChange(this.onCallStateChange);
        this.callAdapter.dispose();
        // Clear chat event listeners
        this.chatEventListeners.clear();
    }
    /** Remove a participant from the Call only. */
    removeParticipant(userId) {
        return __awaiter(this, void 0, void 0, function* () {
            let participant = userId;
            participant = _toCommunicationIdentifier(userId);
            yield this.callAdapter.removeParticipant(participant);
        });
    }
    setCamera(device, options) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.setCamera(device, options);
        });
    }
    /** Set the microphone to be used in the Call. */
    setMicrophone(device) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.setMicrophone(device);
        });
    }
    /** Set the speaker to be used in the Call. */
    setSpeaker(device) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.setSpeaker(device);
        });
    }
    askDevicePermission(constraints) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.askDevicePermission(constraints);
        });
    }
    /** Query for available cameras. */
    queryCameras() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.queryCameras();
        });
    }
    /** Query for available microphones. */
    queryMicrophones() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.queryMicrophones();
        });
    }
    /** Query for available speakers. */
    querySpeakers() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.querySpeakers();
        });
    }
    /** Start the camera for the user in the Call. */
    startCamera(options) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.startCamera(options);
        });
    }
    /** Stop the camera for the user in the Call. */
    stopCamera() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.stopCamera();
        });
    }
    /** Mute the user in the Call. */
    mute() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.mute();
        });
    }
    /** Unmute the user in the Call. */
    unmute() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.unmute();
        });
    }
    /** Trigger the user to start screen share. */
    startScreenShare() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.startScreenShare();
        });
    }
    /** Stop the current active screen share. */
    stopScreenShare() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.stopScreenShare();
        });
    }
    /** Raise hand for local user. */
    raiseHand() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.raiseHand();
        });
    }
    /** Lower hand for local user. */
    lowerHand() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.lowerHand();
        });
    }
    /** Reaction clicked by the local user. */
    onReactionClick(reaction) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.onReactionClick(reaction);
        });
    }
    /** Create a stream view for a remote participants video feed. */
    createStreamView(remoteUserId, options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.createStreamView(remoteUserId, options);
        });
    }
    /** Dispose of a created stream view of a remote participants video feed. */
    disposeStreamView(remoteUserId, options) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.disposeStreamView(remoteUserId, options);
        });
    }
    /** Dispose of a remote screen share */
    disposeScreenShareStreamView(remoteUserId) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.disposeScreenShareStreamView(remoteUserId);
        });
    }
    /** Dispose of a remote video stream */
    disposeRemoteVideoStreamView(remoteUserId) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.disposeRemoteVideoStreamView(remoteUserId);
        });
    }
    /** Dispose of the local video stream */
    disposeLocalVideoStreamView() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.disposeLocalVideoStreamView();
        });
    }
    /** Create a together mode stream view */
    createTogetherModeStreamView(options) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.createTogetherModeStreamView(options);
        });
    }
    /** Start together mode for all participants */
    startTogetherMode() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.startTogetherMode();
        });
    }
    /** Set together mode scene size */
    setTogetherModeSceneSize(width, height) {
        return this.callAdapter.setTogetherModeSceneSize(width, height);
    }
    /** Dispose together mode video stream */
    disposeTogetherModeStreamView() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.disposeTogetherModeStreamView();
        });
    }
    /** Fetch initial Call and Chat data such as chat messages. */
    fetchInitialData() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.fetchInitialData();
            });
        });
    }
    /** Send a chat message. */
    sendMessage(content) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.sendMessage(content);
            });
        });
    }
    /** Send a chat read receipt. */
    sendReadReceipt(chatMessageId) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.sendReadReceipt(chatMessageId);
            });
        });
    }
    /** Send an isTyping indicator. */
    sendTypingIndicator() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.sendTypingIndicator();
            });
        });
    }
    /** Load previous Chat messages. */
    loadPreviousChatMessages(messagesToLoad) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.loadPreviousChatMessages(messagesToLoad);
            });
        });
    }
    /** Update an existing message. */
    updateMessage(messageId, content, options) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.executeWithResolvedChatAdapter(adapter => {
                return adapter.updateMessage(messageId, content, options);
            });
        });
    }
    /** Delete an existing message. */
    deleteMessage(messageId) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.executeWithResolvedChatAdapter(adapter => {
                return adapter.deleteMessage(messageId);
            });
        });
    }
    downloadResourceToCache(resourceDetails) {
        return __awaiter(this, void 0, void 0, function* () {
            this.executeWithResolvedChatAdapter(adapter => {
                adapter.downloadResourceToCache(resourceDetails);
            });
        });
    }
    removeResourceFromCache(resourceDetails) {
        this.executeWithResolvedChatAdapter(adapter => {
            adapter.removeResourceFromCache(resourceDetails);
        });
    }
    holdCall() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.holdCall();
        });
    }
    resumeCall() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.resumeCall();
        });
    }
    addParticipant(participant, options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (isCommunicationUserIdentifier(participant)) {
                return yield this.callAdapter.addParticipant(participant);
            }
            else {
                return yield this.callAdapter.addParticipant(participant, options);
            }
        });
    }
    sendDtmfTone(dtmfTone) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.sendDtmfTone(dtmfTone);
        });
    }
    startCaptions(options) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.startCaptions(options);
        });
    }
    stopCaptions(options) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.stopCaptions(options);
        });
    }
    setCaptionLanguage(language) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.setCaptionLanguage(language);
        });
    }
    setSpokenLanguage(language) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.setSpokenLanguage(language);
        });
    }
    sendRealTimeText(text, isFinalized) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.sendRealTimeText(text, isFinalized);
        });
    }
    startVideoBackgroundEffect(videoBackgroundEffect) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.callAdapter.startVideoBackgroundEffect(videoBackgroundEffect);
        });
    }
    stopVideoBackgroundEffects() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.stopVideoBackgroundEffects();
        });
    }
    updateBackgroundPickerImages(backgroundImages) {
        return this.callAdapter.updateBackgroundPickerImages(backgroundImages);
    }
    updateSelectedVideoBackgroundEffect(selectedVideoBackground) {
        return this.callAdapter.updateSelectedVideoBackgroundEffect(selectedVideoBackground);
    }
    startNoiseSuppressionEffect() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.startNoiseSuppressionEffect();
        });
    }
    stopNoiseSuppressionEffect() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.callAdapter.stopNoiseSuppressionEffect();
        });
    }
    submitSurvey(survey) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.submitSurvey(survey);
        });
    }
    startSpotlight(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.startSpotlight(userIds);
        });
    }
    stopSpotlight(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.stopSpotlight(userIds);
        });
    }
    stopAllSpotlight() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.stopAllSpotlight();
        });
    }
    muteParticipant(userId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.muteParticipant(userId);
        });
    }
    muteAllRemoteParticipants() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.muteAllRemoteParticipants();
        });
    }
    returnFromBreakoutRoom() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c, _d;
            if (this.originCallChatAdapter && ((_a = this.context.getState().chat) === null || _a === void 0 ? void 0 : _a.threadId) !== this.originCallChatAdapter.getState().thread.threadId) {
                (_b = this.breakoutRoomChatAdapter) === null || _b === void 0 ? void 0 : _b.dispose();
                this.updateChatAdapter(this.originCallChatAdapter);
            }
            // Check if breakout room settings are defined to verify the user is in a breakout room before
            // returning to the origin call.
            const breakoutRoomSettings = (_d = (_c = this.context.getState().call) === null || _c === void 0 ? void 0 : _c.breakoutRooms) === null || _d === void 0 ? void 0 : _d.breakoutRoomSettings;
            if (breakoutRoomSettings) {
                yield this.callAdapter.returnFromBreakoutRoom();
            }
        });
    }
    forbidAudio(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.forbidAudio(userIds);
        });
    }
    permitAudio(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.permitAudio(userIds);
        });
    }
    forbidOthersAudio() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.forbidOthersAudio();
        });
    }
    permitOthersAudio() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.permitOthersAudio();
        });
    }
    forbidVideo(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.forbidVideo(userIds);
        });
    }
    permitVideo(userIds) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.permitVideo(userIds);
        });
    }
    forbidOthersVideo() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.forbidOthersVideo();
        });
    }
    permitOthersVideo() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.callAdapter.permitOthersVideo();
        });
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    on(event, listener) {
        switch (event) {
            case 'callParticipantsJoined':
                this.callAdapter.on('participantsJoined', listener);
                break;
            case 'callParticipantsLeft':
                this.callAdapter.on('participantsLeft', listener);
                break;
            case 'callEnded':
                this.callAdapter.on('callEnded', listener);
                break;
            case 'isMutedChanged':
                this.callAdapter.on('isMutedChanged', listener);
                break;
            case 'callIdChanged':
                this.callAdapter.on('callIdChanged', listener);
                break;
            case 'isLocalScreenSharingActiveChanged':
                this.callAdapter.on('isLocalScreenSharingActiveChanged', listener);
                break;
            case 'displayNameChanged':
                this.callAdapter.on('displayNameChanged', listener);
                break;
            case 'isSpeakingChanged':
                this.callAdapter.on('isSpeakingChanged', listener);
                break;
            case 'selectedMicrophoneChanged':
                this.callAdapter.on('selectedMicrophoneChanged', listener);
                break;
            case 'selectedSpeakerChanged':
                this.callAdapter.on('selectedSpeakerChanged', listener);
                break;
            case 'captionsReceived':
                this.callAdapter.on('captionsReceived', listener);
                break;
            case 'realTimeTextReceived':
                this.callAdapter.on('realTimeTextReceived', listener);
                break;
            case 'isCaptionsActiveChanged':
                this.callAdapter.on('isCaptionsActiveChanged', listener);
                break;
            case 'isCaptionLanguageChanged':
                this.callAdapter.on('isCaptionLanguageChanged', listener);
                break;
            case 'isSpokenLanguageChanged':
                this.callAdapter.on('isSpokenLanguageChanged', listener);
                break;
            case 'messageReceived':
                this.storeChatEventListener('messageReceived', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('messageReceived', listener);
                });
                break;
            case 'messageEdited':
                this.storeChatEventListener('messageEdited', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('messageEdited', listener);
                });
                break;
            case 'messageDeleted':
                this.storeChatEventListener('messageDeleted', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('messageDeleted', listener);
                });
                break;
            case 'messageSent':
                this.storeChatEventListener('messageSent', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('messageSent', listener);
                });
                break;
            case 'messageRead':
                this.storeChatEventListener('messageRead', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('messageRead', listener);
                });
                break;
            case 'chatParticipantsAdded':
                this.storeChatEventListener('chatParticipantsAdded', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('participantsAdded', listener);
                });
                break;
            case 'chatParticipantsRemoved':
                this.storeChatEventListener('chatParticipantsRemoved', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('participantsRemoved', listener);
                });
                break;
            case 'callError':
                this.callAdapter.on('error', listener);
                break;
            case 'chatError':
                this.storeChatEventListener('chatError', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.on('error', listener);
                });
                break;
            case 'chatInitialized':
                this.emitter.on(event, listener);
                break;
            case 'capabilitiesChanged':
                this.callAdapter.on('capabilitiesChanged', listener);
                break;
            case 'spotlightChanged':
                this.callAdapter.on('spotlightChanged', listener);
                break;
            case 'breakoutRoomsUpdated':
                this.callAdapter.on('breakoutRoomsUpdated', listener);
                break;
            default:
                throw `Unknown AzureCommunicationCallWithChatAdapter Event: ${event}`;
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    off(event, listener) {
        switch (event) {
            case 'callParticipantsJoined':
                this.callAdapter.off('participantsJoined', listener);
                break;
            case 'callParticipantsLeft':
                this.callAdapter.off('participantsLeft', listener);
                break;
            case 'callEnded':
                this.callAdapter.off('callEnded', listener);
                break;
            case 'isMutedChanged':
                this.callAdapter.off('isMutedChanged', listener);
                break;
            case 'callIdChanged':
                this.callAdapter.off('callIdChanged', listener);
                break;
            case 'isLocalScreenSharingActiveChanged':
                this.callAdapter.off('isLocalScreenSharingActiveChanged', listener);
                break;
            case 'displayNameChanged':
                this.callAdapter.off('displayNameChanged', listener);
                break;
            case 'isSpeakingChanged':
                this.callAdapter.off('isSpeakingChanged', listener);
                break;
            case 'selectedMicrophoneChanged':
                this.callAdapter.off('selectedMicrophoneChanged', listener);
                break;
            case 'selectedSpeakerChanged':
                this.callAdapter.off('selectedSpeakerChanged', listener);
                break;
            case 'captionsReceived':
                this.callAdapter.off('captionsReceived', listener);
                break;
            case 'realTimeTextReceived':
                this.callAdapter.off('realTimeTextReceived', listener);
                break;
            case 'isCaptionsActiveChanged':
                this.callAdapter.off('isCaptionsActiveChanged', listener);
                break;
            case 'isCaptionLanguageChanged':
                this.callAdapter.off('isCaptionLanguageChanged', listener);
                break;
            case 'isSpokenLanguageChanged':
                this.callAdapter.off('isSpokenLanguageChanged', listener);
                break;
            case 'messageReceived':
                this.removeChatEventListener('messageReceived', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('messageReceived', listener);
                });
                break;
            case 'messageEdited':
                this.removeChatEventListener('messageEdited', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('messageEdited', listener);
                });
                break;
            case 'messageDeleted':
                this.removeChatEventListener('messageDeleted', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('messageDeleted', listener);
                });
                break;
            case 'messageSent':
                this.removeChatEventListener('messageSent', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('messageSent', listener);
                });
                break;
            case 'messageRead':
                this.removeChatEventListener('messageRead', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('messageRead', listener);
                });
                break;
            case 'chatParticipantsAdded':
                this.removeChatEventListener('chatParticipantsAdded', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('participantsAdded', listener);
                });
                break;
            case 'chatParticipantsRemoved':
                this.removeChatEventListener('chatParticipantsRemoved', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('participantsRemoved', listener);
                });
                break;
            case 'callError':
                this.callAdapter.off('error', listener);
                break;
            case 'chatError':
                this.removeChatEventListener('chatError', listener);
                this.executeWithResolvedChatAdapter(adapter => {
                    adapter.off('error', listener);
                });
                break;
            case 'chatInitialized':
                this.emitter.off(event, listener);
                break;
            case 'capabilitiesChanged':
                this.callAdapter.off('capabilitiesChanged', listener);
                break;
            case 'spotlightChanged':
                this.callAdapter.off('spotlightChanged', listener);
                break;
            case 'breakoutRoomsUpdated':
                this.callAdapter.off('breakoutRoomsUpdated', listener);
                break;
            default:
                throw `Unknown AzureCommunicationCallWithChatAdapter Event: ${event}`;
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    executeWithResolvedChatAdapter(callback) {
        if (!this.chatAdapter) {
            console.error('Chat is not initialized');
        }
        else {
            return callback(this.chatAdapter);
        }
    }
}
/**
 * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Group Call with an associated Chat thread.
 * @private
 */
export class CallAndChatProvider {
    constructor(locator) {
        this.locator = locator;
    }
    isCallInfoRequired() {
        return false;
    }
    getChatThreadPromise() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getChatThread();
        });
    }
    getChatThread() {
        return this.locator.chatThreadId;
    }
}
/**
 * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Teams meeting with an associated Chat thread.
 *
 * @private
 */
export class TeamsMeetingLinkProvider {
    constructor(locator, callAdapterPromise) {
        this.locator = locator;
        this.callAdapterPromise = callAdapterPromise;
    }
    isCallInfoRequired() {
        return true;
    }
    getChatThread() {
        throw new Error('Chat thread ID should be retrieved from call.callInfo using method getChatThreadPromise');
    }
    getChatThreadPromise() {
        return __awaiter(this, void 0, void 0, function* () {
            {
                // Wait for the call to be connected and get the chat thread ID from `call.callInfo`.
                const chatThreadPromise = new Promise(resolve => {
                    this.callAdapterPromise.then(callAdapter => {
                        // Ensure function is idempotent by removing any existing subscription.
                        this.callAdapterSubscription && callAdapter.offStateChange(this.callAdapterSubscription);
                        this.callAdapterSubscription = (state) => {
                            var _a, _b, _c;
                            if (((_a = state.call) === null || _a === void 0 ? void 0 : _a.state) === 'Connected' && ((_b = state.call.info) === null || _b === void 0 ? void 0 : _b.threadId)) {
                                this.callAdapterSubscription && callAdapter.offStateChange(this.callAdapterSubscription);
                                this.callAdapterSubscription = undefined;
                                resolve((_c = state.call.info) === null || _c === void 0 ? void 0 : _c.threadId);
                            }
                        };
                        callAdapter.onStateChange(this.callAdapterSubscription);
                    });
                });
                return chatThreadPromise;
            }
        });
    }
}
/**
 * Arguments for use in {@link createAzureCommunicationCallWithChatAdapter} to join a Teams meeting using meeting id.
 *
 * @private
 */
export class TeamsMeetingIdProvider {
    constructor(locator, callAdapter) {
        this.locator = locator;
        this.callAdapter = callAdapter;
    }
    isCallInfoRequired() {
        return true;
    }
    getChatThread() {
        throw new Error('Chat thread ID is not available for Teams meeting ID');
    }
    /**
     * Wait call to be connected to get thread ID.
     * @returns the chat thread ID for the given meeting ID.
     */
    getChatThreadPromise() {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise(resolve => {
                const stateChangeListener = (state) => {
                    var _a, _b, _c;
                    if (((_a = state.call) === null || _a === void 0 ? void 0 : _a.state) === 'Connected' && ((_b = state.call.info) === null || _b === void 0 ? void 0 : _b.threadId)) {
                        this.callAdapter.then(adapter => {
                            adapter.offStateChange(stateChangeListener);
                        });
                        resolve((_c = state.call.info) === null || _c === void 0 ? void 0 : _c.threadId);
                    }
                };
                this.callAdapter.then(adapter => {
                    var _a, _b, _c;
                    const callState = (_a = adapter.getState().call) === null || _a === void 0 ? void 0 : _a.state;
                    const threadId = (_c = (_b = adapter.getState().call) === null || _b === void 0 ? void 0 : _b.info) === null || _c === void 0 ? void 0 : _c.threadId;
                    if (callState === 'Connected' && threadId) {
                        resolve(threadId);
                    }
                    else {
                        adapter.onStateChange(stateChangeListener);
                    }
                });
            });
        });
    }
}
/**
 * Create a CallWithChatAdapter backed by Azure Communication services
 * to plug into the {@link CallWithChatComposite}.
 *
 * @public
 */
export const createAzureCommunicationCallWithChatAdapter = (_a) => __awaiter(void 0, [_a], void 0, function* ({ userId, displayName, credential, endpoint, locator, alternateCallerId, callAdapterOptions }) {
    const callAdapterLocator = isTeamsMeetingLocator(locator) ? locator : locator.callLocator;
    const callAdapter = _createAzureCommunicationCallAdapterInner({
        userId,
        displayName,
        credential,
        locator: callAdapterLocator,
        alternateCallerId,
        options: callAdapterOptions,
        telemetryImplementationHint: 'CallWithChat'
    });
    const chatThreadAdapter = _createChatThreadAdapterInner(locator, callAdapter);
    const chatAdapterOptions = {
        onFetchProfile: callAdapterOptions === null || callAdapterOptions === void 0 ? void 0 : callAdapterOptions.onFetchProfile
    };
    if (chatThreadAdapter.isCallInfoRequired()) {
        const callWithChatAdapter = new AzureCommunicationCallWithChatAdapter(yield callAdapter);
        const chatAdapterPromise = _createLazyAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, chatThreadAdapter.getChatThreadPromise(), 'CallWithChat', chatAdapterOptions);
        callWithChatAdapter.setChatAdapterPromise(chatAdapterPromise);
        callWithChatAdapter.setCreateChatAdapterCallback((threadId) => _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, threadId, 'CallWithChat', chatAdapterOptions));
        return callWithChatAdapter;
    }
    else {
        const chatAdapter = _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, chatThreadAdapter.getChatThread(), 'CallWithChat', chatAdapterOptions);
        const callWithChatAdapter = new AzureCommunicationCallWithChatAdapter(yield callAdapter, yield chatAdapter);
        callWithChatAdapter.setCreateChatAdapterCallback((threadId) => _createAzureCommunicationChatAdapterInner(endpoint, userId, displayName, credential, threadId, 'CallWithChat', chatAdapterOptions));
        return callWithChatAdapter;
    }
});
/**
 * A custom React hook to simplify the creation of {@link CallWithChatAdapter}.
 *
 * Similar to {@link createAzureCommunicationCallWithChatAdapter}, 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 useAzureCommunicationCallWithChatAdapter = (args, afterCreate, beforeDispose) => {
    const { credential, displayName, endpoint, locator, userId, alternateCallerId, callAdapterOptions } = args;
    // 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 || !displayName || !endpoint || !locator || !userId) {
            return;
        }
        (() => __awaiter(void 0, 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;
            }
            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;
            let newAdapter = yield createAzureCommunicationCallWithChatAdapter({
                credential,
                displayName,
                endpoint,
                locator,
                userId,
                alternateCallerId,
                callAdapterOptions
            });
            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, alternateCallerId, beforeDisposeRef, credential, displayName, endpoint, locator, userId, callAdapterOptions]);
    // Dispose any existing adapter when the component unmounts.
    useEffect(() => {
        return () => {
            (() => __awaiter(void 0, void 0, void 0, function* () {
                if (adapterRef.current) {
                    if (beforeDisposeRef.current) {
                        yield beforeDisposeRef.current(adapterRef.current);
                    }
                    adapterRef.current.dispose();
                    adapterRef.current = undefined;
                }
            }))();
        };
    }, []);
    return adapter;
};
/**
 * Create a {@link CallWithChatAdapter} using the provided {@link StatefulChatClient} and {@link StatefulCallClient}.
 *
 * Useful if you want to keep a reference to {@link StatefulChatClient} and {@link StatefulCallClient}.
 * Please note that chatThreadClient has to be created by StatefulChatClient via chatClient.getChatThreadClient(chatThreadId) API.
 * Consider using {@link createAzureCommunicationCallWithChatAdapter} for a simpler API.
 *
 * @public
 */
export const createAzureCommunicationCallWithChatAdapterFromClients = (_b) => __awaiter(void 0, [_b], void 0, function* ({ callClient, callAgent, callLocator, chatClient, chatThreadClient, callAdapterOptions }) {
    const callAdapter = yield createAzureCommunicationCallAdapterFromClient(callClient, callAgent, callLocator, callAdapterOptions);
    const chatAdapter = yield createAzureCommunicationChatAdapterFromClient(chatClient, chatThreadClient);
    const callWithChatAdapter = new AzureCommunicationCallWithChatAdapter(callAdapter, chatAdapter);
    callWithChatAdapter.setCreateChatAdapterCallback((threadId) => createAzureCommunicationChatAdapterFromClient(chatClient, chatClient.getChatThreadClient(threadId)));
    return callWithChatAdapter;
});
/**
 * Create a {@link CallWithChatAdapter} from the underlying adapters.
 *
 * This is an internal factory function used by browser tests to inject fake adapters for call and chat.
 *
 * @internal
 */
export const _createAzureCommunicationCallWithChatAdapterFromAdapters = (callAdapter, chatAdapter) => new AzureCommunicationCallWithChatAdapter(callAdapter, chatAdapter);
const isTeamsMeetingLocator = (locator) => {
    return 'meetingLink' in locator || 'meetingId' in locator;
};
const _createChatThreadAdapterInner = (locator, adapter) => {
    if ('meetingLink' in locator) {
        return new TeamsMeetingLinkProvider(locator, adapter);
    }
    if ('meetingId' in locator) {
        return new TeamsMeetingIdProvider(locator, adapter);
    }
    return new CallAndChatProvider(locator);
};
//# sourceMappingURL=AzureCommunicationCallWithChatAdapter.js.map