import { MediaConnectionEventNames, LocalStreamEventNames, RoapMediaConnection, } from '@webex/internal-media-core';
import { createMachine, interpret } from 'xstate';
import { v4 as uuid } from 'uuid';
import { EffectEvent } from '@webex/media-helpers';
import { RtcMetrics } from '@webex/internal-plugin-metrics';
import { ERROR_LAYER, ERROR_TYPE } from '../../Errors/types';
import { handleCallErrors, modifySdpForIPv4, parseMediaQualityStatistics, serviceErrorCodeHandler, uploadLogs, } from '../../common/Utils';
import { ALLOWED_SERVICES, CallDirection, HTTP_METHODS, } from '../../common/types';
import { createCallError } from '../../Errors/catalog/CallError';
import { CALL_ENDPOINT_RESOURCE, CALL_FILE, CALL_HOLD_SERVICE, CALL_STATUS_RESOURCE, CALL_TRANSFER_SERVICE, CALLING_USER_AGENT, CALLS_ENDPOINT_RESOURCE, CISCO_DEVICE_URL, DEFAULT_LOCAL_CALL_ID, DEFAULT_SESSION_TIMER, DEVICES_ENDPOINT_RESOURCE, HOLD_ENDPOINT, ICE_CANDIDATES_TIMEOUT, INITIAL_SEQ_NUMBER, MAX_CALL_KEEPALIVE_RETRY_COUNT, MEDIA_ENDPOINT_RESOURCE, METHODS, NOISE_REDUCTION_EFFECT, RESUME_ENDPOINT, SPARK_USER_AGENT, SUPPLEMENTARY_SERVICES_TIMEOUT, TRANSFER_ENDPOINT, } from '../constants';
import SDKConnector from '../../SDKConnector';
import { Eventing } from '../../Events/impl';
import { CALL_EVENT_KEYS, MEDIA_CONNECTION_EVENT_KEYS, MOBIUS_MIDCALL_STATE, SUPPLEMENTARY_SERVICES, } from '../../Events/types';
import { DisconnectCause, DisconnectCode, MidCallEventType, MobiusCallState, MUTE_TYPE, RoapScenario, TransferType, } from './types';
import log from '../../Logger';
import { createCallerId } from './CallerId';
import { METRIC_TYPE, METRIC_EVENT, TRANSFER_ACTION, MEDIA_CONNECTION_ACTION, } from '../../Metrics/types';
import { getMetricManager } from '../../Metrics';
import { METHOD_START_MESSAGE, SERVICES_ENDPOINT } from '../../common/constants';
import { APIRequest } from '../utils/request';
export class Call extends Eventing {
    sdkConnector;
    webex;
    destination;
    direction;
    callId;
    correlationId;
    deviceId;
    lineId;
    disconnectReason;
    callStateMachine;
    mediaStateMachine;
    seq;
    mediaConnection;
    earlyMedia;
    connected;
    mediaInactivity;
    callerInfo;
    localRoapMessage;
    mobiusUrl;
    remoteRoapMessage;
    deleteCb;
    callerId;
    sessionTimer;
    supplementaryServicesTimer;
    muted;
    held;
    metricManager;
    broadworksCorrelationInfo;
    serviceIndicator;
    mediaNegotiationCompleted;
    connectPending;
    receivedRoapOKSeq;
    localAudioStream;
    rtcMetrics;
    callKeepaliveRetryCount = 0;
    apiRequest;
    handleMediaRoapEvent = async (event) => {
        log.info(`ROAP message to send (rcv from MEDIA-SDK) :
          \n type:  ${event.roapMessage?.messageType}, seq: ${event.roapMessage.seq} , version: ${event.roapMessage.version}`, { file: CALL_FILE, method: METHODS.MEDIA_ROAP_EVENTS_LISTENER });
        log.info(`SDP message to send : \n ${event.roapMessage?.sdp}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
        });
        switch (event.roapMessage.messageType) {
            case RoapScenario.OK: {
                const mediaOk = {
                    received: false,
                    message: event.roapMessage,
                };
                this.sendMediaStateMachineEvt({ type: 'E_ROAP_OK', data: mediaOk });
                break;
            }
            case RoapScenario.OFFER: {
                if (!event.roapMessage.sdp) {
                    log.warn('Received OFFER without SDP from media SDK', {
                        file: CALL_FILE,
                        method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
                    });
                    break;
                }
                log.info(`before modifying sdp: ${event.roapMessage.sdp}`, {
                    file: CALL_FILE,
                    method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
                });
                event.roapMessage.sdp = modifySdpForIPv4(event.roapMessage.sdp);
                const sdpVideoPortZero = event.roapMessage.sdp.replace(/^m=(video) (?:\d+) /gim, 'm=$1 0 ');
                log.info(`after modification sdp: ${sdpVideoPortZero}`, {
                    file: CALL_FILE,
                    method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
                });
                event.roapMessage.sdp = sdpVideoPortZero;
                this.localRoapMessage = event.roapMessage;
                this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_SETUP', data: event.roapMessage });
                break;
            }
            case RoapScenario.ANSWER:
                if (!event.roapMessage.sdp) {
                    log.warn('Received OFFER without SDP from media SDK', {
                        file: CALL_FILE,
                        method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
                    });
                    break;
                }
                event.roapMessage.sdp = modifySdpForIPv4(event.roapMessage.sdp);
                this.localRoapMessage = event.roapMessage;
                if (this.connectPending) {
                    this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_CONNECT' });
                }
                this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_ANSWER', data: event.roapMessage });
                break;
            case RoapScenario.ERROR:
                this.sendMediaStateMachineEvt({ type: 'E_ROAP_ERROR', data: event.roapMessage });
                break;
            case RoapScenario.OFFER_RESPONSE:
                if (!event.roapMessage.sdp) {
                    log.warn('Received OFFER without SDP from media SDK', {
                        file: CALL_FILE,
                        method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
                    });
                    break;
                }
                event.roapMessage.sdp = modifySdpForIPv4(event.roapMessage.sdp);
                this.localRoapMessage = event.roapMessage;
                if (this.connectPending) {
                    this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_CONNECT' });
                }
                this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER', data: event.roapMessage });
                break;
            default:
        }
    };
    handleRemoteTrackAdded = (event) => {
        if (event.type === MEDIA_CONNECTION_EVENT_KEYS.MEDIA_TYPE_AUDIO) {
            this.emit(CALL_EVENT_KEYS.REMOTE_MEDIA, event.track);
        }
    };
    static getPeerConnectionStateFromEvent(event, preferredKey) {
        return event[preferredKey] || event.state || 'unknown';
    }
    handleIceGatheringStateChanged = (event) => {
        const iceGatheringState = Call.getPeerConnectionStateFromEvent(event, 'iceGatheringState');
        log.info(`ICE gathering state changed to: ${iceGatheringState}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ICE_EVENTS_LISTENER,
        });
        this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA, MEDIA_CONNECTION_ACTION.ICE_GATHERING_STATE_CHANGED, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined, undefined, iceGatheringState);
    };
    handlePeerConnectionStateChanged = (event) => {
        const connectionState = Call.getPeerConnectionStateFromEvent(event, 'connectionState');
        log.info(`Peer connection state changed to: ${connectionState}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ICE_EVENTS_LISTENER,
        });
        this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA, MEDIA_CONNECTION_ACTION.PEER_CONNECTION_STATE_CHANGED, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined, undefined, connectionState);
    };
    handleIceConnectionStateChanged = (event) => {
        const iceConnectionState = Call.getPeerConnectionStateFromEvent(event, 'iceConnectionState');
        log.info(`ICE connection state changed to: ${iceConnectionState}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ICE_EVENTS_LISTENER,
        });
        this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA, MEDIA_CONNECTION_ACTION.ICE_CONNECTION_STATE_CHANGED, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined, undefined, iceConnectionState);
    };
    handleIceCandidateError = (event) => {
        const iceErrorPayload = {
            address: event.address ?? null,
            errorCode: event.errorCode,
            errorText: event.errorText,
            port: event.port ?? null,
            url: event.url,
        };
        log.warn(`ICE candidate error occurred: ${JSON.stringify(iceErrorPayload)}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ICE_EVENTS_LISTENER,
        });
        const callError = createCallError(`ICE candidate error occurred: ${JSON.stringify(iceErrorPayload)}`, { file: CALL_FILE, method: METHODS.MEDIA_ICE_EVENTS_LISTENER }, ERROR_TYPE.CALL_ERROR, this.correlationId, ERROR_LAYER.MEDIA);
        this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA_ERROR, MEDIA_CONNECTION_ACTION.ICE_CANDIDATE_ERROR, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined, undefined, undefined, callError);
    };
    handleRoapFailure = (error) => {
        const failureMessage = error.message || 'Unknown ROAP failure received from media SDK';
        log.warn(`ROAP failure occurred: ${failureMessage}`, {
            file: CALL_FILE,
            method: METHODS.MEDIA_ROAP_EVENTS_LISTENER,
        });
        const callError = createCallError(`ROAP failure occurred: ${failureMessage}`, { file: CALL_FILE, method: METHODS.MEDIA_ROAP_EVENTS_LISTENER }, ERROR_TYPE.CALL_ERROR, this.correlationId, ERROR_LAYER.MEDIA);
        this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA_ERROR, MEDIA_CONNECTION_ACTION.ROAP_FAILURE, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined, undefined, undefined, callError);
    };
    isMuted() {
        return this.muted;
    }
    isConnected() {
        return this.connected;
    }
    isHeld() {
        return this.held;
    }
    constructor(activeUrl, webex, direction, deviceId, lineId, deleteCb, indicator, destination) {
        super();
        this.destination = destination;
        this.direction = direction;
        this.sdkConnector = SDKConnector;
        this.deviceId = deviceId;
        this.serviceIndicator = indicator;
        this.lineId = lineId;
        if (!this.sdkConnector.getWebex()) {
            SDKConnector.setWebex(webex);
        }
        this.webex = this.sdkConnector.getWebex();
        this.metricManager = getMetricManager(this.webex, this.serviceIndicator);
        this.callId = `${DEFAULT_LOCAL_CALL_ID}_${uuid()}`;
        this.correlationId = uuid();
        this.deleteCb = deleteCb;
        this.connected = false;
        this.mediaInactivity = false;
        this.held = false;
        this.earlyMedia = false;
        this.callerInfo = {};
        this.localRoapMessage = {};
        this.mobiusUrl = activeUrl;
        this.receivedRoapOKSeq = 0;
        this.mediaNegotiationCompleted = false;
        this.connectPending = false;
        log.info(`Webex Calling Url:- ${this.mobiusUrl}`, {
            file: CALL_FILE,
            method: METHODS.CONSTRUCTOR,
        });
        this.seq = INITIAL_SEQ_NUMBER;
        this.callerId = createCallerId(webex, (callerInfo) => {
            this.callerInfo = callerInfo;
            const emitObj = {
                correlationId: this.correlationId,
                callerId: this.callerInfo,
            };
            this.emit(CALL_EVENT_KEYS.CALLER_ID, emitObj);
        });
        this.remoteRoapMessage = null;
        this.disconnectReason = { code: DisconnectCode.NORMAL, cause: DisconnectCause.NORMAL };
        this.rtcMetrics = new RtcMetrics(this.webex, { callId: this.callId }, this.correlationId);
        this.apiRequest = APIRequest.getInstance({ webex: this.webex });
        const callMachine = createMachine({
            schema: {
                context: {},
                events: {},
            },
            id: 'call-state',
            initial: 'S_IDLE',
            context: {},
            states: {
                S_IDLE: {
                    on: {
                        E_RECV_CALL_SETUP: {
                            target: 'S_RECV_CALL_SETUP',
                            actions: ['incomingCallSetup'],
                        },
                        E_SEND_CALL_SETUP: {
                            target: 'S_SEND_CALL_SETUP',
                            actions: ['outgoingCallSetup'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_RECV_CALL_SETUP: {
                    after: {
                        10000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_SEND_CALL_ALERTING: {
                            target: 'S_SEND_CALL_PROGRESS',
                            actions: ['outgoingCallAlerting'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_SEND_CALL_SETUP: {
                    after: {
                        10000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_RECV_CALL_PROGRESS: {
                            target: 'S_RECV_CALL_PROGRESS',
                            actions: ['incomingCallProgress'],
                        },
                        E_RECV_CALL_CONNECT: {
                            target: 'S_RECV_CALL_CONNECT',
                            actions: ['incomingCallConnect'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_RECV_CALL_PROGRESS: {
                    after: {
                        60000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_RECV_CALL_CONNECT: {
                            target: 'S_RECV_CALL_CONNECT',
                            actions: ['incomingCallConnect'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_RECV_CALL_PROGRESS: {
                            target: 'S_RECV_CALL_PROGRESS',
                            actions: ['incomingCallProgress'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_SEND_CALL_PROGRESS: {
                    after: {
                        60000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_SEND_CALL_CONNECT: {
                            target: 'S_SEND_CALL_CONNECT',
                            actions: ['outgoingCallConnect'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_RECV_CALL_CONNECT: {
                    after: {
                        10000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_CALL_ESTABLISHED: {
                            target: 'S_CALL_ESTABLISHED',
                            actions: ['callEstablished'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_SEND_CALL_CONNECT: {
                    after: {
                        10000: {
                            target: 'S_CALL_CLEARED',
                            actions: ['triggerTimeout'],
                        },
                    },
                    on: {
                        E_SEND_CALL_CONNECT: {
                            cond: () => this.connectPending,
                            target: 'S_SEND_CALL_CONNECT',
                            actions: ['outgoingCallConnect'],
                        },
                        E_CALL_ESTABLISHED: {
                            target: 'S_CALL_ESTABLISHED',
                            actions: ['callEstablished'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_CALL_HOLD: {
                    on: {
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_CALL_ESTABLISHED: {
                            target: 'S_CALL_ESTABLISHED',
                            actions: ['callEstablished'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_CALL_RESUME: {
                    on: {
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_CALL_ESTABLISHED: {
                            target: 'S_CALL_ESTABLISHED',
                            actions: ['callEstablished'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_CALL_ESTABLISHED: {
                    on: {
                        E_CALL_HOLD: {
                            target: 'S_CALL_HOLD',
                            actions: ['initiateCallHold'],
                        },
                        E_CALL_RESUME: {
                            target: 'S_CALL_RESUME',
                            actions: ['initiateCallResume'],
                        },
                        E_RECV_CALL_DISCONNECT: {
                            target: 'S_RECV_CALL_DISCONNECT',
                            actions: ['incomingCallDisconnect'],
                        },
                        E_SEND_CALL_DISCONNECT: {
                            target: 'S_SEND_CALL_DISCONNECT',
                            actions: ['outgoingCallDisconnect'],
                        },
                        E_CALL_ESTABLISHED: {
                            target: 'S_CALL_ESTABLISHED',
                            actions: ['callEstablished'],
                        },
                        E_UNKNOWN: {
                            target: 'S_UNKNOWN',
                            actions: ['unknownState'],
                        },
                    },
                },
                S_RECV_CALL_DISCONNECT: {
                    on: {
                        E_CALL_CLEARED: 'S_CALL_CLEARED',
                    },
                },
                S_SEND_CALL_DISCONNECT: {
                    on: {
                        E_CALL_CLEARED: 'S_CALL_CLEARED',
                    },
                },
                S_UNKNOWN: {
                    on: {
                        E_CALL_CLEARED: 'S_CALL_CLEARED',
                    },
                },
                S_ERROR: {
                    on: {
                        E_CALL_CLEARED: 'S_CALL_CLEARED',
                    },
                },
                S_CALL_CLEARED: {
                    type: 'final',
                },
            },
        }, {
            actions: {
                incomingCallSetup: (context, event) => this.handleIncomingCallSetup(event),
                outgoingCallSetup: (context, event) => this.handleOutgoingCallSetup(event),
                incomingCallProgress: (context, event) => this.handleIncomingCallProgress(event),
                outgoingCallAlerting: (context, event) => this.handleOutgoingCallAlerting(event),
                incomingCallConnect: (context, event) => this.handleIncomingCallConnect(event),
                outgoingCallConnect: (context, event) => this.handleOutgoingCallConnect(event),
                callEstablished: (context, event) => this.handleCallEstablished(event),
                initiateCallHold: (context, event) => this.handleCallHold(event),
                initiateCallResume: (context, event) => this.handleCallResume(event),
                incomingCallDisconnect: (context, event) => this.handleIncomingCallDisconnect(event),
                outgoingCallDisconnect: (context, event) => this.handleOutgoingCallDisconnect(event),
                unknownState: (context, event) => this.handleUnknownState(event),
                triggerTimeout: () => this.handleTimeout(),
            },
        });
        const mediaMachine = createMachine({
            schema: {
                context: {},
                events: {},
            },
            id: 'roap-state',
            initial: 'S_ROAP_IDLE',
            context: {},
            states: {
                S_ROAP_IDLE: {
                    on: {
                        E_RECV_ROAP_OFFER_REQUEST: {
                            target: 'S_RECV_ROAP_OFFER_REQUEST',
                            actions: ['incomingRoapOfferRequest'],
                        },
                        E_RECV_ROAP_OFFER: {
                            target: 'S_RECV_ROAP_OFFER',
                            actions: ['incomingRoapOffer'],
                        },
                        E_SEND_ROAP_OFFER: {
                            target: 'S_SEND_ROAP_OFFER',
                            actions: ['outgoingRoapOffer'],
                        },
                    },
                },
                S_RECV_ROAP_OFFER_REQUEST: {
                    on: {
                        E_SEND_ROAP_OFFER: {
                            target: 'S_SEND_ROAP_OFFER',
                            actions: ['outgoingRoapOffer'],
                        },
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                    },
                },
                S_RECV_ROAP_OFFER: {
                    on: {
                        E_SEND_ROAP_ANSWER: {
                            target: 'S_SEND_ROAP_ANSWER',
                            actions: ['outgoingRoapAnswer'],
                        },
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                    },
                },
                S_SEND_ROAP_OFFER: {
                    on: {
                        E_RECV_ROAP_ANSWER: {
                            target: 'S_RECV_ROAP_ANSWER',
                            actions: ['incomingRoapAnswer'],
                        },
                        E_SEND_ROAP_ANSWER: {
                            target: 'S_SEND_ROAP_ANSWER',
                            actions: ['outgoingRoapAnswer'],
                        },
                        E_SEND_ROAP_OFFER: {
                            target: 'S_SEND_ROAP_OFFER',
                            actions: ['outgoingRoapOffer'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                    },
                },
                S_RECV_ROAP_ANSWER: {
                    on: {
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                    },
                },
                S_SEND_ROAP_ANSWER: {
                    on: {
                        E_RECV_ROAP_OFFER_REQUEST: {
                            target: 'S_RECV_ROAP_OFFER_REQUEST',
                            actions: ['incomingRoapOfferRequest'],
                        },
                        E_RECV_ROAP_OFFER: {
                            target: 'S_RECV_ROAP_OFFER',
                            actions: ['incomingRoapOffer'],
                        },
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                        E_SEND_ROAP_ANSWER: {
                            target: 'S_SEND_ROAP_ANSWER',
                            actions: ['outgoingRoapAnswer'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                    },
                },
                S_ROAP_OK: {
                    on: {
                        E_RECV_ROAP_OFFER_REQUEST: {
                            target: 'S_RECV_ROAP_OFFER_REQUEST',
                            actions: ['incomingRoapOfferRequest'],
                        },
                        E_RECV_ROAP_OFFER: {
                            target: 'S_RECV_ROAP_OFFER',
                            actions: ['incomingRoapOffer'],
                        },
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                        E_SEND_ROAP_OFFER: {
                            target: 'S_SEND_ROAP_OFFER',
                            actions: ['outgoingRoapOffer'],
                        },
                        E_ROAP_ERROR: {
                            target: 'S_ROAP_ERROR',
                            actions: ['roapError'],
                        },
                        E_ROAP_TEARDOWN: {
                            target: 'S_ROAP_TEARDOWN',
                        },
                    },
                },
                S_ROAP_ERROR: {
                    on: {
                        E_ROAP_TEARDOWN: {
                            target: 'S_ROAP_TEARDOWN',
                        },
                        E_RECV_ROAP_OFFER_REQUEST: {
                            target: 'S_RECV_ROAP_OFFER_REQUEST',
                            actions: ['incomingRoapOfferRequest'],
                        },
                        E_RECV_ROAP_OFFER: {
                            target: 'S_RECV_ROAP_OFFER',
                            actions: ['incomingRoapOffer'],
                        },
                        E_RECV_ROAP_ANSWER: {
                            target: 'S_RECV_ROAP_ANSWER',
                            actions: ['incomingRoapAnswer'],
                        },
                        E_ROAP_OK: {
                            target: 'S_ROAP_OK',
                            actions: ['roapEstablished'],
                        },
                    },
                },
                S_ROAP_TEARDOWN: {
                    type: 'final',
                },
            },
        }, {
            actions: {
                incomingRoapOffer: (context, event) => this.handleIncomingRoapOffer(context, event),
                incomingRoapAnswer: (context, event) => this.handleIncomingRoapAnswer(context, event),
                incomingRoapOfferRequest: (context, event) => this.handleIncomingRoapOfferRequest(context, event),
                outgoingRoapOffer: (context, event) => this.handleOutgoingRoapOffer(context, event),
                outgoingRoapAnswer: (context, event) => this.handleOutgoingRoapAnswer(context, event),
                roapEstablished: (context, event) => this.handleRoapEstablished(context, event),
                roapError: (context, event) => this.handleRoapError(context, event),
            },
        });
        this.callStateMachine = interpret(callMachine)
            .onTransition((state, event) => {
            log.log(`Call StateMachine:- state=${state.value}, event=${JSON.stringify(event.type)}`, {
                file: CALL_FILE,
                method: METHODS.CONSTRUCTOR,
            });
            if (state.value !== 'S_UNKNOWN') {
                this.metricManager.submitCallMetric(METRIC_EVENT.CALL, state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined);
            }
        })
            .start();
        this.mediaStateMachine = interpret(mediaMachine)
            .onTransition((state, event) => {
            log.log(`Media StateMachine:- state=${state.value}, event=${JSON.stringify(event.type)}`, {
                file: CALL_FILE,
                method: METHODS.CONSTRUCTOR,
            });
            if (state.value !== 'S_ROAP_ERROR') {
                this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA, state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, this.localRoapMessage.sdp, this.remoteRoapMessage?.sdp, undefined);
            }
        })
            .start();
        this.muted = false;
    }
    handleIncomingCallSetup(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_CALL_SETUP,
        });
        this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_ALERTING' });
    }
    async handleOutgoingCallSetup(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_CALL_SETUP,
        });
        const message = event.data;
        try {
            const response = await this.post(message);
            log.info(`Response: ${JSON.stringify(response)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_SETUP,
            });
            log.info(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_SETUP,
            });
            this.setCallId(response.body.callId);
            log.log(`Call setup successful for callId: ${response.body.callId}`, {
                file: CALL_FILE,
                method: this.handleOutgoingCallSetup.name,
            });
        }
        catch (e) {
            log.error(`Failed to setup the call: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_SETUP,
            });
            const errData = e;
            handleCallErrors((error) => {
                this.emit(CALL_EVENT_KEYS.CALL_ERROR, error);
                this.submitCallErrorMetric(error);
                this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
            }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.HANDLE_OUTGOING_CALL_SETUP, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    async handleCallHold(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_CALL_HOLD,
        });
        try {
            const response = await this.postSSRequest(undefined, SUPPLEMENTARY_SERVICES.HOLD);
            log.log(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_CALL_HOLD,
            });
            if (this.isHeld() === false) {
                this.supplementaryServicesTimer = setTimeout(async () => {
                    const errorContext = { file: CALL_FILE, method: METHODS.HANDLE_CALL_HOLD };
                    log.warn('Hold response timed out', {
                        file: CALL_FILE,
                        method: METHODS.HANDLE_CALL_HOLD,
                    });
                    const callError = createCallError('An error occurred while placing the call on hold. Wait a moment and try again.', errorContext, ERROR_TYPE.TIMEOUT, this.getCorrelationId(), ERROR_LAYER.CALL_CONTROL);
                    this.emit(CALL_EVENT_KEYS.HOLD_ERROR, callError);
                    this.submitCallErrorMetric(callError);
                }, SUPPLEMENTARY_SERVICES_TIMEOUT);
            }
        }
        catch (e) {
            log.error(`Failed to put the call on hold: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_CALL_HOLD,
            });
            const errData = e;
            handleCallErrors((error) => {
                this.emit(CALL_EVENT_KEYS.HOLD_ERROR, error);
                this.submitCallErrorMetric(error);
                this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
            }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.HANDLE_CALL_HOLD, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    async handleCallResume(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_CALL_RESUME,
        });
        try {
            const response = await this.postSSRequest(undefined, SUPPLEMENTARY_SERVICES.RESUME);
            log.log(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_CALL_RESUME,
            });
            if (this.isHeld() === true) {
                this.supplementaryServicesTimer = setTimeout(async () => {
                    const errorContext = { file: CALL_FILE, method: METHODS.HANDLE_CALL_RESUME };
                    log.warn('Resume response timed out', {
                        file: CALL_FILE,
                        method: METHODS.HANDLE_CALL_RESUME,
                    });
                    const callError = createCallError('An error occurred while resuming the call. Wait a moment and try again.', errorContext, ERROR_TYPE.TIMEOUT, this.getCorrelationId(), ERROR_LAYER.CALL_CONTROL);
                    this.emit(CALL_EVENT_KEYS.RESUME_ERROR, callError);
                    this.submitCallErrorMetric(callError);
                }, SUPPLEMENTARY_SERVICES_TIMEOUT);
            }
        }
        catch (e) {
            log.error(`Failed to resume the call: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_CALL_RESUME,
            });
            const errData = e;
            handleCallErrors((error) => {
                this.emit(CALL_EVENT_KEYS.RESUME_ERROR, error);
                this.submitCallErrorMetric(error);
                this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
            }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.HANDLE_CALL_RESUME, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    handleIncomingCallProgress(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_CALL_PROGRESS,
        });
        const data = event.data;
        if (data?.callProgressData?.inbandMedia) {
            log.log('Inband media present. Setting Early Media flag', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_PROGRESS,
            });
            this.earlyMedia = true;
        }
        else {
            log.log('Inband media not present.', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_PROGRESS,
            });
        }
        if (data?.callerId) {
            log.info('Processing Caller-Id data', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_PROGRESS,
            });
            this.startCallerIdResolution(data.callerId);
        }
        this.emit(CALL_EVENT_KEYS.PROGRESS, this.correlationId);
    }
    handleIncomingRoapOfferRequest(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_ROAP_OFFER_REQUEST,
        });
        const message = event.data;
        if (!this.mediaConnection) {
            log.info('Media connection is not up, buffer the remote Offer Request for later handling', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER_REQUEST,
            });
            this.seq = message.seq;
            log.info(`Setting Sequence No: ${this.seq}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER_REQUEST,
            });
            this.remoteRoapMessage = message;
        }
        else if (this.receivedRoapOKSeq === message.seq - 2) {
            log.info('Waiting for Roap OK, buffer the remote Offer Request for later handling', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER_REQUEST,
            });
            this.remoteRoapMessage = message;
        }
        else {
            message.seq = this.seq + 1;
            this.seq = message.seq;
            this.mediaConnection.roapMessageReceived(message);
        }
    }
    async handleOutgoingCallAlerting(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_CALL_ALERTING,
        });
        try {
            const res = await this.patch(MobiusCallState.ALERTING);
            log.log(`PATCH response: ${res.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_ALERTING,
            });
        }
        catch (e) {
            log.error(`Failed to signal call progression: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_ALERTING,
            });
            const errData = e;
            handleCallErrors((error) => {
                this.emit(CALL_EVENT_KEYS.CALL_ERROR, error);
                this.submitCallErrorMetric(error);
                this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
            }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.HANDLE_OUTGOING_CALL_ALERTING, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    handleIncomingCallConnect(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_CALL_CONNECT,
        });
        this.emit(CALL_EVENT_KEYS.CONNECT, this.correlationId);
        if (this.earlyMedia || this.mediaNegotiationCompleted) {
            this.mediaNegotiationCompleted = false;
            this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
        }
    }
    async handleOutgoingCallConnect(event) {
        this.connectPending = false;
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_CALL_CONNECT,
        });
        if (!this.remoteRoapMessage) {
            log.warn('Offer not yet received from remote end... Exiting', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_CONNECT,
            });
            this.connectPending = true;
            return;
        }
        try {
            this.mediaConnection.roapMessageReceived(this.remoteRoapMessage);
            const res = await this.patch(MobiusCallState.CONNECTED);
            log.log(`PATCH response: ${res.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_CONNECT,
            });
        }
        catch (e) {
            log.error(`Failed to connect the call: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_CONNECT,
            });
            const errData = e;
            handleCallErrors((error) => {
                this.emit(CALL_EVENT_KEYS.CALL_ERROR, error);
                this.submitCallErrorMetric(error);
                this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
            }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.HANDLE_OUTGOING_CALL_CONNECT, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    async handleIncomingCallDisconnect(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_CALL_DISCONNECT,
        });
        this.emit(CALL_EVENT_KEYS.DISCONNECT, this.correlationId);
        this.setDisconnectReason();
        try {
            const response = await this.delete();
            log.log(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_DISCONNECT,
            });
        }
        catch (e) {
            log.warn(`Failed to delete the call: ${JSON.stringify(e)}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_DISCONNECT,
            });
            uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
        this.deleteCb(this.correlationId);
        this.unregisterListeners();
        if (this.sessionTimer) {
            clearInterval(this.sessionTimer);
        }
        if (this.mediaConnection) {
            this.unregisterMediaConnectionListeners();
            this.mediaConnection.close();
            log.info('Closing media channel', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_CALL_DISCONNECT,
            });
        }
        this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
        this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
    }
    async handleOutgoingCallDisconnect(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_CALL_DISCONNECT,
        });
        this.setDisconnectReason();
        try {
            const response = await this.delete();
            log.log(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_DISCONNECT,
            });
            log.log(`Call disconnected successfully: ${this.correlationId}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_DISCONNECT,
            });
        }
        catch (e) {
            log.warn('Failed to delete the call', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_DISCONNECT,
            });
            uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
        this.deleteCb(this.correlationId);
        this.unregisterListeners();
        if (this.sessionTimer) {
            clearInterval(this.sessionTimer);
        }
        if (this.mediaConnection) {
            this.unregisterMediaConnectionListeners();
            this.mediaConnection.close();
            log.info('Closing media channel', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_CALL_DISCONNECT,
            });
        }
        this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
        this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
    }
    callKeepaliveRetryCallback = (interval) => {
        if (this.callKeepaliveRetryCount === MAX_CALL_KEEPALIVE_RETRY_COUNT) {
            log.warn(`Max keepalive retry attempts reached. Aborting call keepalive for callId: ${this.callId}`, {
                file: CALL_FILE,
                method: 'keepaliveRetryCallback',
            });
            return;
        }
        this.callKeepaliveRetryCount += 1;
        setTimeout(async () => {
            try {
                await this.postStatus();
                this.scheduleCallKeepaliveInterval();
            }
            catch (err) {
                await this.handleCallKeepaliveError(err);
            }
        }, interval * 1000);
    };
    handleCallKeepaliveError = async (err) => {
        const error = err;
        if (this.sessionTimer) {
            clearInterval(this.sessionTimer);
        }
        const abort = await handleCallErrors((callError) => {
            this.emit(CALL_EVENT_KEYS.CALL_ERROR, callError);
            this.submitCallErrorMetric(callError);
        }, ERROR_LAYER.CALL_CONTROL, this.callKeepaliveRetryCallback, this.getCorrelationId(), error, 'handleCallEstablished', CALL_FILE);
        if (abort) {
            this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_DISCONNECT' });
            this.emit(CALL_EVENT_KEYS.DISCONNECT, this.getCorrelationId());
            this.callKeepaliveRetryCount = 0;
        }
        await uploadLogs({
            correlationId: this.correlationId,
            callId: this.callId,
            broadworksCorrelationInfo: this.broadworksCorrelationInfo,
        });
    };
    scheduleCallKeepaliveInterval = () => {
        const loggerContext = {
            file: CALL_FILE,
            method: 'scheduleCallKeepaliveInterval',
        };
        clearInterval(this.sessionTimer);
        this.sessionTimer = setInterval(async () => {
            try {
                const res = await this.postStatus();
                log.info(`Session refresh successful`, loggerContext);
            }
            catch (err) {
                await this.handleCallKeepaliveError(err);
            }
        }, DEFAULT_SESSION_TIMER);
    };
    handleCallEstablished(event) {
        const loggerContext = {
            file: CALL_FILE,
            method: METHODS.HANDLE_CALL_ESTABLISHED,
        };
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, loggerContext);
        this.emit(CALL_EVENT_KEYS.ESTABLISHED, this.correlationId);
        this.earlyMedia = false;
        this.connected = true;
        this.scheduleCallKeepaliveInterval();
    }
    async handleUnknownState(event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_UNKNOWN_STATE,
        });
        const eventData = event.data;
        if (!eventData?.media) {
            log.warn('Call failed due to signalling issue', {
                file: CALL_FILE,
                method: METHODS.HANDLE_UNKNOWN_STATE,
            });
        }
        try {
            this.setDisconnectReason();
            const response = await this.delete();
            log.log(`Response code: ${response.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_UNKNOWN_STATE,
            });
        }
        catch (e) {
            log.warn('Failed to delete the call', {
                file: CALL_FILE,
                method: METHODS.HANDLE_UNKNOWN_STATE,
            });
            uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
        this.deleteCb(this.correlationId);
        if (this.sessionTimer) {
            clearInterval(this.sessionTimer);
        }
        if (this.mediaConnection) {
            this.unregisterMediaConnectionListeners();
            this.mediaConnection.close();
            log.info('Closing media channel', {
                file: CALL_FILE,
                method: METHODS.HANDLE_UNKNOWN_STATE,
            });
        }
        this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
        this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
    }
    getEmitterCallback(errData) {
        return (error) => {
            switch (this.callStateMachine.state.value) {
                case 'S_CALL_HOLD':
                    this.emit(CALL_EVENT_KEYS.HOLD_ERROR, error);
                    if (this.supplementaryServicesTimer) {
                        clearTimeout(this.supplementaryServicesTimer);
                        this.supplementaryServicesTimer = undefined;
                    }
                    this.submitCallErrorMetric(error);
                    this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
                    return;
                case 'S_CALL_RESUME':
                    this.emit(CALL_EVENT_KEYS.RESUME_ERROR, error);
                    this.submitCallErrorMetric(error);
                    this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
                    return;
                default:
                    this.emit(CALL_EVENT_KEYS.CALL_ERROR, error);
                    this.submitCallErrorMetric(error);
                    if (!this.connected) {
                        this.sendMediaStateMachineEvt({ type: 'E_ROAP_ERROR', data: errData });
                    }
            }
        };
    }
    async handleRoapEstablished(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_ROAP_ESTABLISHED,
        });
        const { received, message } = event.data;
        this.receivedRoapOKSeq = message.seq;
        if (!received) {
            log.info('Sending Media Ok to the remote End', {
                file: CALL_FILE,
                method: METHODS.HANDLE_ROAP_ESTABLISHED,
            });
            try {
                if (this.callStateMachine.state.value === 'S_RECV_CALL_PROGRESS' ||
                    this.callStateMachine.state.value === 'S_SEND_CALL_SETUP') {
                    log.info('Media negotiation completed before call connect. Setting media negotiation completed flag.', {
                        file: CALL_FILE,
                        method: METHODS.HANDLE_ROAP_ESTABLISHED,
                    });
                    this.mediaNegotiationCompleted = true;
                }
                message.seq = this.seq;
                const res = await this.postMedia(message);
                log.log(`Response code: ${res.statusCode}`, {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_ROAP_ESTABLISHED,
                });
                if (!this.earlyMedia && !this.mediaNegotiationCompleted) {
                    this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
                }
            }
            catch (err) {
                log.warn('Failed to process MediaOk request', {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_ROAP_ESTABLISHED,
                });
                const errData = err;
                handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
                    if (this.connected) {
                        setTimeout(() => {
                            this.sendMediaStateMachineEvt({ type: 'E_ROAP_OK', data: event.data });
                        }, interval * 1000);
                    }
                }, this.getCorrelationId(), errData, this.handleRoapEstablished.name, CALL_FILE);
                await uploadLogs({
                    correlationId: this.correlationId,
                    callId: this.callId,
                    broadworksCorrelationInfo: this.broadworksCorrelationInfo,
                });
            }
        }
        else {
            log.info('Notifying internal-media-core about ROAP OK message', {
                file: CALL_FILE,
                method: METHODS.HANDLE_ROAP_ESTABLISHED,
            });
            message.seq = this.seq;
            if (this.mediaConnection) {
                this.mediaConnection.roapMessageReceived(message);
            }
            if (!this.earlyMedia) {
                this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
            }
            if (this.remoteRoapMessage && this.remoteRoapMessage.seq > this.seq) {
                if (this.remoteRoapMessage.messageType === 'OFFER_REQUEST') {
                    this.sendMediaStateMachineEvt({
                        type: 'E_RECV_ROAP_OFFER_REQUEST',
                        data: this.remoteRoapMessage,
                    });
                }
                else if (this.remoteRoapMessage.messageType === 'OFFER') {
                    this.sendMediaStateMachineEvt({ type: 'E_RECV_ROAP_OFFER', data: this.remoteRoapMessage });
                }
            }
        }
    }
    async handleRoapError(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_ROAP_ERROR,
        });
        const message = event.data;
        if (message && message.messageType === 'ERROR') {
            try {
                const res = await this.postMedia(message);
                log.info(`Response code: ${res.statusCode}`, {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_ROAP_ERROR,
                });
            }
            catch (err) {
                log.warn('Failed to communicate ROAP error to Webex Calling', {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_ROAP_ERROR,
                });
                const errData = err;
                handleCallErrors((error) => {
                    this.emit(CALL_EVENT_KEYS.CALL_ERROR, error);
                    this.submitCallErrorMetric(error);
                }, ERROR_LAYER.MEDIA, (interval) => undefined, this.getCorrelationId(), errData, this.handleRoapError.name, CALL_FILE);
                await uploadLogs({
                    correlationId: this.correlationId,
                    callId: this.callId,
                    broadworksCorrelationInfo: this.broadworksCorrelationInfo,
                });
            }
        }
        if (!this.connected) {
            log.warn('Call failed due to media issue', {
                file: CALL_FILE,
                method: METHODS.HANDLE_ROAP_ERROR,
            });
            this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: { media: true } });
        }
    }
    async handleOutgoingRoapOffer(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_ROAP_OFFER,
        });
        const message = event.data;
        if (!message?.sdp) {
            log.info('Initializing Offer...', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_ROAP_OFFER,
            });
            this.mediaConnection.initiateOffer();
            return;
        }
        try {
            const res = await this.postMedia(message);
            log.log(`Response code: ${res.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_ROAP_OFFER,
            });
        }
        catch (err) {
            log.warn('Failed to send MediaOffer request', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_ROAP_OFFER,
            });
            const errData = err;
            handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
                if (this.connected) {
                    setTimeout(() => {
                        this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER', data: event.data });
                    }, interval * 1000);
                }
            }, this.getCorrelationId(), errData, METHODS.HANDLE_OUTGOING_ROAP_OFFER, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    async handleOutgoingRoapAnswer(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_OUTGOING_ROAP_ANSWER,
        });
        const message = event.data;
        try {
            message.seq = this.seq;
            const res = await this.postMedia(message);
            log.log(`Response code: ${res.statusCode}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_ROAP_ANSWER,
            });
        }
        catch (err) {
            log.warn('Failed to send MediaAnswer request', {
                file: CALL_FILE,
                method: METHODS.HANDLE_OUTGOING_ROAP_ANSWER,
            });
            const errData = err;
            handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
                if (this.connected) {
                    setTimeout(() => {
                        this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_ANSWER', data: event.data });
                    }, interval * 1000);
                }
            }, this.getCorrelationId(), errData, METHODS.HANDLE_OUTGOING_ROAP_ANSWER, CALL_FILE);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    }
    handleIncomingRoapOffer(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_ROAP_OFFER,
        });
        const message = event.data;
        this.remoteRoapMessage = message;
        if (!this.mediaConnection) {
            log.info('Media connection is not up, buffer the remote offer for later handling', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER,
            });
            this.seq = message.seq;
            log.info(`Setting Sequence No: ${this.seq}`, {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER,
            });
        }
        else if (this.receivedRoapOKSeq === message.seq - 2) {
            log.info('Waiting for Roap OK, buffer the remote offer for later handling', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER,
            });
            this.remoteRoapMessage = message;
        }
        else {
            log.info('Handling new offer...', {
                file: CALL_FILE,
                method: METHODS.HANDLE_INCOMING_ROAP_OFFER,
            });
            this.seq = message.seq;
            if (this.mediaConnection) {
                this.mediaConnection.roapMessageReceived(message);
            }
        }
    }
    handleIncomingRoapAnswer(context, event) {
        log.info(`${METHOD_START_MESSAGE} with: ${this.getCorrelationId()}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_INCOMING_ROAP_ANSWER,
        });
        const message = event.data;
        this.remoteRoapMessage = message;
        message.seq = this.seq;
        if (this.mediaConnection) {
            this.mediaConnection.roapMessageReceived(message);
        }
    }
    forceSendStatsReport = async ({ callFrom }) => {
        const loggerContext = {
            file: CALL_FILE,
            method: METHODS.FORCE_SEND_STATS_REPORT,
        };
        try {
            await this.mediaConnection.forceRtcMetricsSend();
            log.info(`Successfully uploaded available webrtc telemetry statistics`, loggerContext);
            log.info(`callFrom: ${callFrom}`, loggerContext);
        }
        catch (error) {
            const errorInfo = error;
            const errorStatus = await serviceErrorCodeHandler(errorInfo, loggerContext);
            log.error(`Failed to upload webrtc telemetry statistics. ${JSON.stringify(errorStatus)}`, loggerContext);
            await uploadLogs({
                correlationId: this.correlationId,
                callId: this.callId,
                broadworksCorrelationInfo: this.broadworksCorrelationInfo,
            });
        }
    };
    initMediaConnection(localAudioTrack, debugId) {
        const mediaConnection = new RoapMediaConnection({
            skipInactiveTransceivers: true,
            iceServers: [],
            iceCandidatesTimeout: ICE_CANDIDATES_TIMEOUT,
            sdpMunging: {
                convertPort9to0: true,
                addContentSlides: false,
                copyClineToSessionLevel: true,
            },
        }, {
            localTracks: { audio: localAudioTrack },
            direction: {
                audio: 'sendrecv',
                video: 'inactive',
                screenShareVideo: 'inactive',
            },
        }, debugId || `WebexCallSDK-${this.correlationId}`, (data) => this.rtcMetrics.addMetrics(data), () => this.rtcMetrics.closeMetrics(), () => this.rtcMetrics.sendMetricsInQueue());
        this.mediaConnection = mediaConnection;
    }
    getDirection = () => this.direction;
    getCallId = () => this.callId;
    getCorrelationId = () => this.correlationId;
    sendCallStateMachineEvt(event) {
        this.callStateMachine.send(event);
    }
    sendMediaStateMachineEvt(event) {
        this.mediaStateMachine.send(event);
    }
    setCallId = (callId) => {
        this.callId = callId;
        this.rtcMetrics.updateCallId(callId);
        log.info(`Setting callId : ${this.callId} for correlationId: ${this.correlationId}`, {
            file: CALL_FILE,
            method: METHODS.SET_CALL_ID,
        });
        this.callId = callId;
        this.rtcMetrics.updateCallId(callId);
    };
    setDisconnectReason() {
        if (this.mediaInactivity) {
            this.disconnectReason.code = DisconnectCode.MEDIA_INACTIVITY;
            this.disconnectReason.cause = DisconnectCause.MEDIA_INACTIVITY;
        }
        else if (this.connected || this.direction === CallDirection.OUTBOUND) {
            this.disconnectReason.code = DisconnectCode.NORMAL;
            this.disconnectReason.cause = DisconnectCause.NORMAL;
        }
        else {
            this.disconnectReason.code = DisconnectCode.BUSY;
            this.disconnectReason.cause = DisconnectCause.BUSY;
        }
    }
    getDisconnectReason = () => {
        return this.disconnectReason;
    };
    async answer(localAudioStream) {
        log.info(`${METHOD_START_MESSAGE} with stream`, {
            file: CALL_FILE,
            method: METHODS.ANSWER,
        });
        this.localAudioStream = localAudioStream;
        const localAudioTrack = localAudioStream.outputStream.getAudioTracks()[0];
        if (!localAudioTrack) {
            log.warn(`Did not find a local track while answering the call ${this.getCorrelationId()}`, {
                file: CALL_FILE,
                method: METHODS.ANSWER,
            });
            this.mediaInactivity = true;
            this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_DISCONNECT' });
            return;
        }
        localAudioTrack.enabled = true;
        if (!this.mediaConnection) {
            this.initMediaConnection(localAudioTrack);
            this.mediaRoapEventsListener();
            this.mediaTrackListener();
            this.mediaIceEventsListener();
            this.registerListeners(localAudioStream);
        }
        if (this.callStateMachine.state.value === 'S_SEND_CALL_PROGRESS') {
            this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_CONNECT' });
        }
        else {
            log.warn(`Call cannot be answered because the state is : ${this.callStateMachine.state.value}`, { file: CALL_FILE, method: METHODS.ANSWER });
        }
    }
    async dial(localAudioStream) {
        log.info(`${METHOD_START_MESSAGE} with stream`, {
            file: CALL_FILE,
            method: METHODS.DIAL,
        });
        this.localAudioStream = localAudioStream;
        const localAudioTrack = localAudioStream.outputStream.getAudioTracks()[0];
        if (!localAudioTrack) {
            log.warn(`Did not find a local track while dialing the call ${this.getCorrelationId()}`, {
                file: CALL_FILE,
                method: METHODS.DIAL,
            });
            this.deleteCb(this.getCorrelationId());
            this.emit(CALL_EVENT_KEYS.DISCONNECT, this.getCorrelationId());
            return;
        }
        localAudioTrack.enabled = true;
        if (!this.mediaConnection) {
            this.initMediaConnection(localAudioTrack);
            this.mediaRoapEventsListener();
            this.mediaTrackListener();
            this.mediaIceEventsListener();
            this.registerListeners(localAudioStream);
        }
        if (this.mediaStateMachine.state.value === 'S_ROAP_IDLE') {
            this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER' });
        }
        else {
            log.warn(`Call cannot be dialed because the state is already : ${this.mediaStateMachine.state.value}`, { file: CALL_FILE, method: METHODS.DIAL });
        }
    }
    post = async (roapMessage) => {
        const basePayload = {
            device: {
                deviceId: this.deviceId,
                correlationId: this.correlationId,
            },
            localMedia: {
                roap: roapMessage,
                mediaId: uuid(),
            },
        };
        return this.apiRequest.makeRequest({
            uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALL_ENDPOINT_RESOURCE}`,
            method: HTTP_METHODS.POST,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: this.destination
                ? {
                    ...basePayload,
                    callee: {
                        type: this.destination.type,
                        address: this.destination.address,
                    },
                }
                : basePayload,
        });
    };
    async patch(state) {
        log.info(`Send a PATCH for ${state} to Webex Calling`, {
            file: CALL_FILE,
            method: 'patch',
        });
        return this.apiRequest.makeRequest({
            uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}`,
            method: HTTP_METHODS.PATCH,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: {
                device: {
                    deviceId: this.deviceId,
                    correlationId: this.correlationId,
                },
                callId: this.callId,
                callState: state,
                inbandMedia: false,
            },
        });
    }
    async postSSRequest(context, type) {
        const request = {
            uri: `${this.mobiusUrl}${SERVICES_ENDPOINT}`,
            method: HTTP_METHODS.POST,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: {
                device: {
                    deviceId: this.deviceId,
                    correlationId: this.correlationId,
                },
                callId: this.callId,
            },
        };
        switch (type) {
            case SUPPLEMENTARY_SERVICES.HOLD: {
                request.uri = `${request.uri}/${CALL_HOLD_SERVICE}/${HOLD_ENDPOINT}`;
                break;
            }
            case SUPPLEMENTARY_SERVICES.RESUME: {
                request.uri = `${request.uri}/${CALL_HOLD_SERVICE}/${RESUME_ENDPOINT}`;
                break;
            }
            case SUPPLEMENTARY_SERVICES.TRANSFER: {
                request.uri = `${request.uri}/${CALL_TRANSFER_SERVICE}/${TRANSFER_ENDPOINT}`;
                const transferContext = context;
                if (transferContext.destination) {
                    Object.assign(request.body, { blindTransferContext: transferContext });
                    Object.assign(request.body, { transferType: TransferType.BLIND });
                }
                else if (transferContext.transferToCallId) {
                    Object.assign(request.body, { consultTransferContext: transferContext });
                    Object.assign(request.body, { transferType: TransferType.CONSULT });
                }
                break;
            }
            default: {
                log.warn(`Unknown type for PUT request: ${type}`, {
                    file: CALL_FILE,
                    method: METHODS.POST_SS_REQUEST,
                });
            }
        }
        return this.apiRequest.makeRequest(request);
    }
    async postStatus() {
        return this.apiRequest.makeRequest({
            uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}/${CALL_STATUS_RESOURCE}`,
            method: HTTP_METHODS.POST,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: {
                device: {
                    deviceId: this.deviceId,
                    correlationId: this.correlationId,
                },
                callId: this.callId,
            },
        });
    }
    async completeTransfer(transferType, transferCallId, transferTarget) {
        if (transferType === TransferType.BLIND && transferTarget) {
            log.info(`Initiating Blind transfer with : ${transferTarget}`, {
                file: CALL_FILE,
                method: METHODS.COMPLETE_TRANSFER,
            });
            const context = {
                transferorCallId: this.getCallId(),
                destination: transferTarget,
            };
            try {
                await this.postSSRequest(context, SUPPLEMENTARY_SERVICES.TRANSFER);
                log.info(`Blind Transfer completed for correlationId ${this.getCorrelationId()}`, {
                    file: CALL_FILE,
                    method: METHODS.COMPLETE_TRANSFER,
                });
                this.metricManager.submitCallMetric(METRIC_EVENT.CALL, TRANSFER_ACTION.BLIND, METRIC_TYPE.BEHAVIORAL, this.getCallId(), this.getCorrelationId(), undefined);
            }
            catch (e) {
                log.warn(`Blind Transfer failed for correlationId ${this.getCorrelationId()}`, {
                    file: CALL_FILE,
                    method: METHODS.COMPLETE_TRANSFER,
                });
                const errData = e;
                handleCallErrors((error) => {
                    this.emit(CALL_EVENT_KEYS.TRANSFER_ERROR, error);
                    this.submitCallErrorMetric(error, TRANSFER_ACTION.BLIND);
                }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.COMPLETE_TRANSFER, CALL_FILE);
                await uploadLogs({
                    correlationId: this.correlationId,
                    callId: this.callId,
                    broadworksCorrelationInfo: this.broadworksCorrelationInfo,
                });
            }
        }
        else if (transferType === TransferType.CONSULT && transferCallId) {
            log.info(`Initiating Consult transfer between : ${this.callId} and ${transferCallId}`, {
                file: CALL_FILE,
                method: METHODS.COMPLETE_TRANSFER,
            });
            const context = {
                transferorCallId: this.getCallId(),
                transferToCallId: transferCallId,
            };
            try {
                await this.postSSRequest(context, SUPPLEMENTARY_SERVICES.TRANSFER);
                log.info(`Consult Transfer completed for correlationId ${this.getCorrelationId()}`, {
                    file: CALL_FILE,
                    method: METHODS.COMPLETE_TRANSFER,
                });
                this.metricManager.submitCallMetric(METRIC_EVENT.CALL, TRANSFER_ACTION.CONSULT, METRIC_TYPE.BEHAVIORAL, this.getCallId(), this.getCorrelationId(), undefined);
            }
            catch (e) {
                log.warn(`Consult Transfer failed for correlationId ${this.getCorrelationId()}`, {
                    file: CALL_FILE,
                    method: METHODS.COMPLETE_TRANSFER,
                });
                const errData = e;
                handleCallErrors((error) => {
                    this.emit(CALL_EVENT_KEYS.TRANSFER_ERROR, error);
                    this.submitCallErrorMetric(error, TRANSFER_ACTION.CONSULT);
                }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, METHODS.COMPLETE_TRANSFER, CALL_FILE);
                await uploadLogs({
                    correlationId: this.correlationId,
                    callId: this.callId,
                    broadworksCorrelationInfo: this.broadworksCorrelationInfo,
                });
            }
        }
        else {
            log.warn(`Invalid information received, transfer failed for correlationId: ${this.getCorrelationId()}`, {
                file: CALL_FILE,
                method: METHODS.COMPLETE_TRANSFER,
            });
        }
    }
    async getCallStats() {
        let stats;
        try {
            stats = await this.mediaConnection.getStats();
        }
        catch (err) {
            log.warn('Stats collection failed, using dummy stats', {
                file: CALL_FILE,
                method: METHODS.GET_CALL_STATS,
            });
        }
        return parseMediaQualityStatistics(stats);
    }
    async postMedia(roapMessage) {
        log.log('Posting message to Webex Calling', {
            file: CALL_FILE,
            method: METHODS.POST_MEDIA,
        });
        return this.apiRequest.makeRequest({
            uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}/${MEDIA_ENDPOINT_RESOURCE}`,
            method: HTTP_METHODS.POST,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: {
                device: {
                    deviceId: this.deviceId,
                    correlationId: this.correlationId,
                },
                callId: this.callId,
                localMedia: {
                    roap: roapMessage,
                    mediaId: uuid(),
                },
            },
        });
    }
    mediaRoapEventsListener() {
        this.mediaConnection.on(MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND, this.handleMediaRoapEvent);
        this.mediaConnection.on(MediaConnectionEventNames.ROAP_FAILURE, this.handleRoapFailure);
    }
    mediaTrackListener() {
        this.mediaConnection.on(MediaConnectionEventNames.REMOTE_TRACK_ADDED, this.handleRemoteTrackAdded);
    }
    mediaIceEventsListener() {
        this.mediaConnection.on(MediaConnectionEventNames.ICE_GATHERING_STATE_CHANGED, this.handleIceGatheringStateChanged);
        this.mediaConnection.on(MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED, this.handlePeerConnectionStateChanged);
        this.mediaConnection.on(MediaConnectionEventNames.ICE_CONNECTION_STATE_CHANGED, this.handleIceConnectionStateChanged);
        this.mediaConnection.on(MediaConnectionEventNames.ICE_CANDIDATE_ERROR, this.handleIceCandidateError);
    }
    unregisterMediaConnectionListeners() {
        if (!this.mediaConnection || typeof this.mediaConnection.off !== 'function') {
            return;
        }
        this.mediaConnection.off(MediaConnectionEventNames.ROAP_MESSAGE_TO_SEND, this.handleMediaRoapEvent);
        this.mediaConnection.off(MediaConnectionEventNames.ROAP_FAILURE, this.handleRoapFailure);
        this.mediaConnection.off(MediaConnectionEventNames.REMOTE_TRACK_ADDED, this.handleRemoteTrackAdded);
        this.mediaConnection.off(MediaConnectionEventNames.ICE_GATHERING_STATE_CHANGED, this.handleIceGatheringStateChanged);
        this.mediaConnection.off(MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED, this.handlePeerConnectionStateChanged);
        this.mediaConnection.off(MediaConnectionEventNames.ICE_CONNECTION_STATE_CHANGED, this.handleIceConnectionStateChanged);
        this.mediaConnection.off(MediaConnectionEventNames.ICE_CANDIDATE_ERROR, this.handleIceCandidateError);
    }
    onEffectEnabled = () => {
        this.metricManager.submitBNRMetric(METRIC_EVENT.BNR_ENABLED, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId);
    };
    onEffectDisabled = () => {
        this.metricManager.submitBNRMetric(METRIC_EVENT.BNR_DISABLED, METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId);
    };
    updateTrack = (audioTrack) => {
        this.mediaConnection.updateLocalTracks({ audio: audioTrack });
    };
    registerEffectListener = (addedEffect) => {
        if (this.localAudioStream) {
            const effect = this.localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);
            if (effect === addedEffect) {
                effect.on(EffectEvent.Enabled, this.onEffectEnabled);
                effect.on(EffectEvent.Disabled, this.onEffectDisabled);
            }
        }
    };
    unregisterListeners() {
        if (this.localAudioStream) {
            const effect = this.localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);
            if (effect) {
                effect.off(EffectEvent.Enabled, this.onEffectEnabled);
                effect.off(EffectEvent.Disabled, this.onEffectDisabled);
            }
            this.localAudioStream.off(LocalStreamEventNames.EffectAdded, this.registerEffectListener);
            this.localAudioStream.off(LocalStreamEventNames.OutputTrackChange, this.updateTrack);
        }
    }
    registerListeners(localAudioStream) {
        localAudioStream.on(LocalStreamEventNames.OutputTrackChange, this.updateTrack);
        localAudioStream.on(LocalStreamEventNames.EffectAdded, this.registerEffectListener);
        const effect = localAudioStream.getEffectByKind(NOISE_REDUCTION_EFFECT);
        if (effect) {
            effect.on(EffectEvent.Enabled, this.onEffectEnabled);
            effect.on(EffectEvent.Disabled, this.onEffectDisabled);
            if (effect.isEnabled) {
                this.onEffectEnabled();
            }
        }
    }
    async delete() {
        const disconnectMetrics = await this.getCallStats();
        return this.apiRequest.makeRequest({
            uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}`,
            method: HTTP_METHODS.DELETE,
            service: ALLOWED_SERVICES.MOBIUS,
            headers: {
                [CISCO_DEVICE_URL]: this.webex.internal.device.url,
                [SPARK_USER_AGENT]: CALLING_USER_AGENT,
            },
            body: {
                device: {
                    deviceId: this.deviceId,
                    correlationId: this.correlationId,
                },
                callId: this.callId,
                metrics: disconnectMetrics,
                causecode: this.disconnectReason.code,
                cause: this.disconnectReason.cause,
            },
        });
    }
    submitCallErrorMetric(error, transferMetricAction) {
        if (error.getCallError().errorLayer === ERROR_LAYER.CALL_CONTROL) {
            this.metricManager.submitCallMetric(METRIC_EVENT.CALL_ERROR, transferMetricAction || this.callStateMachine.state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, error);
        }
        else {
            this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA_ERROR, this.mediaStateMachine.state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, this.localRoapMessage.sdp, this.remoteRoapMessage?.sdp, undefined, error);
        }
    }
    handleMidCallEvent(event) {
        const { eventType, eventData } = event;
        switch (eventType) {
            case MidCallEventType.CALL_INFO: {
                log.log(`Received Midcall CallInfo Event for correlationId : ${this.correlationId}`, {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_MID_CALL_EVENT,
                });
                const callerData = eventData;
                this.startCallerIdResolution(callerData.callerId);
                break;
            }
            case MidCallEventType.CALL_STATE: {
                log.log(`Received Midcall call event for correlationId : ${this.correlationId}`, {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_MID_CALL_EVENT,
                });
                const data = eventData;
                switch (data.callState) {
                    case MOBIUS_MIDCALL_STATE.HELD: {
                        log.log(`Call is successfully held : ${this.correlationId}`, {
                            file: CALL_FILE,
                            method: METHODS.HANDLE_MID_CALL_EVENT,
                        });
                        this.emit(CALL_EVENT_KEYS.HELD, this.correlationId);
                        this.held = true;
                        if (this.supplementaryServicesTimer) {
                            clearTimeout(this.supplementaryServicesTimer);
                            this.supplementaryServicesTimer = undefined;
                        }
                        break;
                    }
                    case MOBIUS_MIDCALL_STATE.CONNECTED: {
                        log.log(`Call is successfully resumed : ${this.correlationId}`, {
                            file: CALL_FILE,
                            method: METHODS.HANDLE_MID_CALL_EVENT,
                        });
                        this.emit(CALL_EVENT_KEYS.RESUMED, this.correlationId);
                        this.held = false;
                        if (this.supplementaryServicesTimer) {
                            clearTimeout(this.supplementaryServicesTimer);
                            this.supplementaryServicesTimer = undefined;
                        }
                        break;
                    }
                    default: {
                        log.warn(`Unknown Supplementary service state: ${data.callState} for correlationId : ${this.correlationId}`, {
                            file: CALL_FILE,
                            method: METHODS.HANDLE_MID_CALL_EVENT,
                        });
                    }
                }
                break;
            }
            default: {
                log.warn(`Unknown Midcall type: ${eventType} for correlationId : ${this.correlationId}`, {
                    file: CALL_FILE,
                    method: METHODS.HANDLE_MID_CALL_EVENT,
                });
            }
        }
    }
    getCallerInfo = () => this.callerInfo;
    end = () => {
        log.info(`${METHOD_START_MESSAGE}`, {
            file: CALL_FILE,
            method: METHODS.END,
        });
        this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_DISCONNECT' });
    };
    doHoldResume = () => {
        if (this.held) {
            this.sendCallStateMachineEvt({ type: 'E_CALL_RESUME' });
        }
        else {
            this.sendCallStateMachineEvt({ type: 'E_CALL_HOLD' });
        }
    };
    startCallerIdResolution(callerInfo) {
        this.callerInfo = this.callerId.fetchCallerDetails(callerInfo);
    }
    sendDigit(tone) {
        log.info(`${METHOD_START_MESSAGE} with: ${tone}`, {
            file: CALL_FILE,
            method: METHODS.SEND_DIGIT,
        });
        try {
            this.mediaConnection.insertDTMF(tone);
        }
        catch (e) {
            log.warn(`Unable to send digit on call: ${e.message}`, {
                file: CALL_FILE,
                method: METHODS.SEND_DIGIT,
            });
        }
    }
    mute = (localAudioStream, muteType) => {
        log.info(`${METHOD_START_MESSAGE} with: ${muteType || 'user mute'}`, {
            file: CALL_FILE,
            method: METHODS.MUTE,
        });
        if (!localAudioStream) {
            log.warn(`Did not find a local stream while muting the call ${this.getCorrelationId()}.`, {
                file: CALL_FILE,
                method: METHODS.MUTE,
            });
            return;
        }
        if (muteType === MUTE_TYPE.SYSTEM) {
            if (!localAudioStream.userMuted) {
                this.muted = localAudioStream.systemMuted;
            }
            else {
                log.info(`Call is muted by the user already - ${this.getCorrelationId()}.`, {
                    file: CALL_FILE,
                    method: METHODS.MUTE,
                });
            }
        }
        else if (!localAudioStream.systemMuted) {
            localAudioStream.setUserMuted(!this.muted);
            this.muted = !this.muted;
        }
        else {
            log.info(`Call is muted on the system - ${this.getCorrelationId()}.`, {
                file: CALL_FILE,
                method: METHODS.MUTE,
            });
        }
    };
    updateMedia = (newAudioStream) => {
        const localAudioTrack = newAudioStream.outputStream.getAudioTracks()[0];
        if (!localAudioTrack) {
            log.warn(`Did not find a local track while updating media for call ${this.getCorrelationId()}. Will not update media`, {
                file: CALL_FILE,
                method: METHODS.UPDATE_MEDIA,
            });
            return;
        }
        try {
            this.mediaConnection.updateLocalTracks({
                audio: localAudioTrack,
            });
            this.unregisterListeners();
            this.registerListeners(newAudioStream);
            this.localAudioStream = newAudioStream;
        }
        catch (e) {
            log.warn(`Unable to update media on call ${this.getCorrelationId()}. Error: ${e.message}`, {
                file: CALL_FILE,
                method: METHODS.UPDATE_MEDIA,
            });
        }
    };
    setBroadworksCorrelationInfo(broadworksCorrelationInfo) {
        this.broadworksCorrelationInfo = broadworksCorrelationInfo;
    }
    getBroadworksCorrelationInfo() {
        return this.broadworksCorrelationInfo;
    }
    getCallRtpStats() {
        return this.getCallStats();
    }
    async handleTimeout() {
        log.warn(`Call timed out`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_TIMEOUT,
        });
        this.deleteCb(this.getCorrelationId());
        this.emit(CALL_EVENT_KEYS.DISCONNECT, this.getCorrelationId());
        const response = await this.delete();
        log.log(`Response code: ${response.statusCode}`, {
            file: CALL_FILE,
            method: METHODS.HANDLE_TIMEOUT,
        });
    }
}
export const createCall = (activeUrl, webex, dir, deviceId, lineId, deleteCb, indicator, dest) => new Call(activeUrl, webex, dir, deviceId, lineId, deleteCb, indicator, dest);
