import * as platform from 'platform';
import { v4 as uuid } from 'uuid';
import { METRIC_EVENT, METRIC_TYPE, UPLOAD_LOGS_ACTION } from '../Metrics/types';
import { getMetricManager } from '../Metrics';
import { createCallError } from '../Errors/catalog/CallError';
import { DEVICE_ERROR_CODE, ERROR_CODE, ERROR_TYPE, CALL_ERROR_CODE, } from '../Errors/types';
import { CALLING_BACKEND, HTTP_METHODS, RegistrationStatus, SORT, ServiceIndicator, } from './types';
import log from '../Logger';
import { createClientError } from '../Errors/catalog/CallingDeviceError';
import { BYTES_RECEIVED, BYTES_SENT, CALLING_USER_AGENT, CISCO_DEVICE_URL, CODEC_ID, DUMMY_METRICS, INBOUND_CODEC_MATCH, INBOUND_RTP, JITTER_BUFFER_DELAY, JITTER_BUFFER_EMITTED_COUNT, LOCAL_CANDIDATE_ID, MEDIA_ID, MEDIA_SOURCE, MIME_TYPE, NETWORK_TYPE, OUTBOUND_CODEC_MATCH, OUTBOUND_RTP, PACKETS_DISCARDED, PACKETS_LOST, PACKETS_RECEIVED, PACKETS_SENT, REMOTE_INBOUND_RTP, ROUND_TRIP_TIME_MEASUREMENTS, RTC_CODEC, RTC_ICE_CANDIDATE, RTC_ICE_CANDIDATE_PAIR, RTP_RX_STAT, RTP_TX_STAT, SELECTED_CANDIDATE_PAIR_ID, SPARK_USER_AGENT, TARGET_BIT_RATE, TIMESTAMP, TOTAL_ROUND_TRIP_TIME, TOTAL_SAMPLES_DURATION, TRANSPORT, TYPE, URL_ENDPOINT, UTILS_FILE, METHODS, DEFAULT_KEEPALIVE_INTERVAL, } from '../CallingClient/constants';
import { DEVICES, ITEMS, SETTINGS, VALUES, KEY, TIME, PLACEHOLDER_KEY, XSI_ACTION_ENDPOINT_ORG_URL_PARAM, XSI_ACTION_ENDPOINT, INFER_ID_CONSTANT, BW_XSI_URL, ENTITLEMENT_BASIC, ENTITLEMENT_BROADWORKS_CONN, ENTITLEMENT_STANDARD, NATIVE_WEBEX_TEAMS_CALLING, NATIVE_SIP_CALL_TO_UCM, BW_XSI_ENDPOINT_VERSION, IDENTITY_ENDPOINT_RESOURCE, SCIM_ENDPOINT_RESOURCE, SCIM_USER_FILTER, WEBEX_API_PROD, WEBEX_API_BTS, BW_XSI_ENDPOINT_VERSION_WITH_SLASH, } from './constants';
import SDKConnector from '../SDKConnector';
import { createLineError } from '../Errors/catalog/LineError';
export function filterMobiusUris(mobiusServers, defaultMobiusUrl) {
    const logContext = {
        file: UTILS_FILE,
        method: 'filterMobiusUris',
    };
    const urisArrayPrimary = [];
    const urisArrayBackup = [];
    const wssArrayPrimary = [];
    const wssArrayBackup = [];
    if (mobiusServers?.primary?.uris) {
        log.info('Adding Primary uris', logContext);
        for (const uri of mobiusServers.primary.uris) {
            urisArrayPrimary.push(`${uri}${URL_ENDPOINT}`);
        }
    }
    if (mobiusServers?.primary?.wss) {
        log.info('Adding Primary wss uris', logContext);
        for (const wssUri of mobiusServers.primary.wss) {
            wssArrayPrimary.push(wssUri);
        }
    }
    if (mobiusServers?.backup?.uris) {
        log.info('Adding Backup uris', logContext);
        for (const uri of mobiusServers.backup.uris) {
            urisArrayBackup.push(`${uri}${URL_ENDPOINT}`);
        }
    }
    if (mobiusServers?.backup?.wss) {
        log.info('Adding Backup wss uris', logContext);
        for (const wssUri of mobiusServers.backup.wss) {
            wssArrayBackup.push(wssUri);
        }
    }
    log.info('Adding Default uri', logContext);
    if (!urisArrayPrimary.length && !urisArrayBackup.length) {
        urisArrayPrimary.push(`${defaultMobiusUrl}${URL_ENDPOINT}`);
    }
    else {
        urisArrayBackup.push(`${defaultMobiusUrl}${URL_ENDPOINT}`);
    }
    const primaryUris = [];
    const backupUris = [];
    const primaryWss = [];
    const backupWss = [];
    for (let i = 0; i < urisArrayPrimary.length; i += 1) {
        if (primaryUris.indexOf(urisArrayPrimary[i]) === -1) {
            primaryUris.push(urisArrayPrimary[i]);
        }
    }
    for (let i = 0; i < urisArrayBackup.length; i += 1) {
        if (backupUris.indexOf(urisArrayBackup[i]) === -1) {
            backupUris.push(urisArrayBackup[i]);
        }
    }
    for (let i = 0; i < wssArrayPrimary.length; i += 1) {
        if (primaryWss.indexOf(wssArrayPrimary[i]) === -1) {
            primaryWss.push(wssArrayPrimary[i]);
        }
    }
    for (let i = 0; i < wssArrayBackup.length; i += 1) {
        if (backupWss.indexOf(wssArrayBackup[i]) === -1) {
            backupWss.push(wssArrayBackup[i]);
        }
    }
    return { primary: primaryUris, backup: backupUris, primaryWss, backupWss };
}
function updateCallErrorContext(errContext, type, message, correlationId, callError) {
    const errObj = {};
    errObj.context = errContext;
    errObj.type = type;
    errObj.message = message;
    errObj.correlationId = correlationId;
    callError.setCallError(errObj);
}
function updateLineErrorContext(errContext, type, message, status, lineError) {
    const errObj = {};
    errObj.context = errContext;
    errObj.type = type;
    errObj.message = message;
    errObj.status = status;
    lineError.setError(errObj);
}
function updateErrorContext(errContext, type, message, clientError) {
    const errObj = {};
    errObj.context = errContext;
    errObj.type = type;
    errObj.message = message;
    clientError.setError(errObj);
}
export function emitFinalFailure(emitterCb, loggerContext, message) {
    const clientError = createLineError(message || '', {}, ERROR_TYPE.DEFAULT, RegistrationStatus.INACTIVE);
    updateLineErrorContext(loggerContext, ERROR_TYPE.SERVICE_UNAVAILABLE, message ||
        'An unknown error occurred. Wait a moment and try again. Please contact the administrator if the problem persists.', RegistrationStatus.INACTIVE, clientError);
    emitterCb(clientError);
}
export async function handleRegistrationErrors(err, emitterCb, loggerContext, retry429Cb, restoreRegCb, serverCount = 1) {
    let shouldDisconnect = false;
    const lineError = createLineError('', {}, ERROR_TYPE.DEFAULT, RegistrationStatus.INACTIVE);
    const errorCode = Number(err.statusCode);
    let finalError = false;
    log.warn(`Status code: -> ${errorCode}`, loggerContext);
    switch (errorCode) {
        case ERROR_CODE.BAD_REQUEST: {
            finalError = true;
            log.warn(`400 Bad Request`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.BAD_REQUEST, 'Invalid input. Please verify the required parameters, sign out and then sign back in with the valid data', RegistrationStatus.INACTIVE, lineError);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
            break;
        }
        case ERROR_CODE.UNAUTHORIZED: {
            finalError = true;
            log.warn(`401 Unauthorized`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.TOKEN_ERROR, 'User is unauthorized due to an expired token. Sign out, then sign back in.', RegistrationStatus.INACTIVE, lineError);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
            break;
        }
        case ERROR_CODE.DEVICE_NOT_FOUND: {
            finalError = true;
            log.warn(`404 Device Not Found`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.NOT_FOUND, 'Webex Calling is unable to find your device. Sign out, then sign back in', RegistrationStatus.INACTIVE, lineError);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
            break;
        }
        case ERROR_CODE.TOO_MANY_REQUESTS: {
            log.warn(`429 Too Many Requests`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.TOO_MANY_REQUESTS, 'Server is handling too many request at the time. Wait a moment and try again', RegistrationStatus.INACTIVE, lineError);
            const caller = loggerContext.method || 'handleErrors';
            if (retry429Cb && err.headers) {
                const retryAfter = Number(err.headers['retry-after']);
                await retry429Cb(retryAfter, caller);
            }
            shouldDisconnect = false;
            break;
        }
        case ERROR_CODE.INTERNAL_SERVER_ERROR: {
            log.warn(`500 Internal Server Error`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.SERVER_ERROR, 'An unknown error occurred while placing the request. Wait a moment and try again.', RegistrationStatus.INACTIVE, lineError);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
            break;
        }
        case ERROR_CODE.SERVICE_UNAVAILABLE: {
            log.warn(`503 Service Unavailable`, loggerContext);
            updateLineErrorContext(loggerContext, ERROR_TYPE.SERVICE_UNAVAILABLE, 'An error occurred on the server while processing the request. Wait a moment and try again.', RegistrationStatus.INACTIVE, lineError);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
            break;
        }
        case ERROR_CODE.FORBIDDEN: {
            log.warn(`403 Forbidden`, loggerContext);
            const errorBody = err.body;
            if (!errorBody) {
                log.warn('Error response has no body, throwing default error', loggerContext);
                updateLineErrorContext(loggerContext, ERROR_TYPE.FORBIDDEN_ERROR, 'An unauthorized action has been received. This action has been blocked. Please contact the administrator if this persists.', RegistrationStatus.INACTIVE, lineError);
                emitterCb(lineError, finalError);
                shouldDisconnect = serverCount > 1;
                return { finalError, shouldDisconnect };
            }
            const code = Number(errorBody.errorCode);
            log.warn(`Error code found : ${code}`, loggerContext);
            switch (code) {
                case DEVICE_ERROR_CODE.DEVICE_LIMIT_EXCEEDED: {
                    const errorMessage = 'User device limit exceeded';
                    log.warn(errorMessage, loggerContext);
                    if (restoreRegCb) {
                        const caller = loggerContext.method || 'handleErrors';
                        await restoreRegCb(errorBody, caller);
                    }
                    shouldDisconnect = false;
                    break;
                }
                case DEVICE_ERROR_CODE.DEVICE_CREATION_DISABLED: {
                    const errorMessage = 'User is not configured for WebRTC calling. Please contact the administrator to resolve this issue.';
                    finalError = true;
                    updateLineErrorContext(loggerContext, ERROR_TYPE.FORBIDDEN_ERROR, errorMessage, RegistrationStatus.INACTIVE, lineError);
                    log.warn(errorMessage, loggerContext);
                    emitterCb(lineError, true);
                    shouldDisconnect = true;
                    break;
                }
                case DEVICE_ERROR_CODE.DEVICE_CREATION_FAILED: {
                    const errorMessage = 'An unknown error occurred while provisioning the device. Wait a moment and try again.';
                    updateLineErrorContext(loggerContext, ERROR_TYPE.FORBIDDEN_ERROR, errorMessage, RegistrationStatus.INACTIVE, lineError);
                    log.warn(errorMessage, loggerContext);
                    emitterCb(lineError, finalError);
                    shouldDisconnect = serverCount > 1;
                    break;
                }
                default: {
                    const errorMessage = 'An unknown error occurred. Wait a moment and try again. Please contact the administrator if the problem persists.';
                    updateLineErrorContext(loggerContext, ERROR_TYPE.FORBIDDEN_ERROR, errorMessage, RegistrationStatus.INACTIVE, lineError);
                    log.warn(errorMessage, loggerContext);
                    emitterCb(lineError, finalError);
                    shouldDisconnect = serverCount > 1;
                }
            }
            break;
        }
        default: {
            updateLineErrorContext(loggerContext, ERROR_TYPE.DEFAULT, 'Unknown error', RegistrationStatus.INACTIVE, lineError);
            log.warn(`Unknown Error`, loggerContext);
            emitterCb(lineError, finalError);
            shouldDisconnect = serverCount > 1;
        }
    }
    return { finalError, shouldDisconnect };
}
export async function handleCallingClientErrors(err, emitterCb, loggerContext) {
    const clientError = createClientError('', {}, ERROR_TYPE.DEFAULT, RegistrationStatus.INACTIVE);
    const errorCode = Number(err.statusCode);
    let finalError = false;
    log.warn(`Status code: -> ${errorCode}`, loggerContext);
    switch (errorCode) {
        case ERROR_CODE.UNAUTHORIZED: {
            finalError = true;
            log.warn(`401 Unauthorized`, loggerContext);
            updateErrorContext(loggerContext, ERROR_TYPE.TOKEN_ERROR, 'User is unauthorized due to an expired token.', clientError);
            emitterCb(clientError, finalError);
            break;
        }
        case ERROR_CODE.INTERNAL_SERVER_ERROR: {
            log.warn(`500 Internal Server Error`, loggerContext);
            updateErrorContext(loggerContext, ERROR_TYPE.SERVER_ERROR, 'An unknown error occurred while placing the request. Wait a moment and try again.', clientError);
            emitterCb(clientError, finalError);
            break;
        }
        default: {
            updateErrorContext(loggerContext, ERROR_TYPE.DEFAULT, 'Unknown error', clientError);
            log.warn(`Unknown Error`, loggerContext);
            emitterCb(clientError, finalError);
        }
    }
    await uploadLogs();
    return finalError;
}
export async function handleCallErrors(emitterCb, errorLayer, retryCb, correlationId, err, caller, file) {
    let abort = false;
    const loggerContext = {
        file,
        method: caller,
    };
    const callError = createCallError('', loggerContext, ERROR_TYPE.DEFAULT, '', errorLayer);
    const errorCode = Number(err.statusCode);
    log.warn(`Status code: ->${errorCode}`, loggerContext);
    const isKeepalive = caller === METHODS.HANDLE_CALL_ESTABLISHED;
    switch (errorCode) {
        case ERROR_CODE.UNAUTHORIZED: {
            log.warn(`401 Unauthorized`, loggerContext);
            updateCallErrorContext(loggerContext, ERROR_TYPE.TOKEN_ERROR, 'User is unauthorized due to an expired token. Sign out, then sign back in.', correlationId, callError);
            emitterCb(callError);
            if (isKeepalive) {
                abort = true;
            }
            break;
        }
        case ERROR_CODE.FORBIDDEN:
            if (isKeepalive) {
                abort = true;
                updateCallErrorContext(loggerContext, ERROR_TYPE.FORBIDDEN_ERROR, 'An unauthorized action has been received. This action has been blocked. Please contact the administrator if this persists.', correlationId, callError);
                emitterCb(callError);
                break;
            }
        case ERROR_CODE.SERVICE_UNAVAILABLE: {
            const errorBody = err.body;
            if (!errorBody) {
                log.warn('Error response has no body, throwing default error', loggerContext);
                updateCallErrorContext(loggerContext, err.statusCode === 403 ? ERROR_TYPE.FORBIDDEN_ERROR : ERROR_TYPE.SERVICE_UNAVAILABLE, err.statusCode === 403
                    ? 'An unauthorized action has been received. This action has been blocked. Please contact the administrator if this persists.'
                    : 'An error occurred on the server while processing the request. Wait a moment and try again.', correlationId, callError);
                emitterCb(callError);
                return abort;
            }
            if (err.headers && 'retry-after' in err.headers && retryCb) {
                const retryInterval = Number(err.headers['retry-after']);
                log.warn(`Retry Interval received: ${retryInterval}`, loggerContext);
                retryCb(retryInterval);
                return abort;
            }
            if (isKeepalive) {
                retryCb(DEFAULT_KEEPALIVE_INTERVAL);
            }
            const code = Number(errorBody.errorCode);
            let message;
            switch (code) {
                case CALL_ERROR_CODE.INVALID_STATUS_UPDATE: {
                    message =
                        'An invalid status update has been received for the call. Wait a moment and try again.';
                    break;
                }
                case CALL_ERROR_CODE.DEVICE_NOT_REGISTERED: {
                    message =
                        'The client has unregistered. Please wait for the client to register before attempting the call. If error persists, sign out, sign back in and attempt the call.';
                    break;
                }
                case CALL_ERROR_CODE.CALL_NOT_FOUND: {
                    message = 'Call is not found on the server. Wait a moment and try again.';
                    break;
                }
                case CALL_ERROR_CODE.ERROR_PROCESSING: {
                    message =
                        'An error occurred while processing the call on the server. Wait a moment and try again.';
                    break;
                }
                case CALL_ERROR_CODE.USER_BUSY: {
                    message = 'Called user is busy.';
                    break;
                }
                case CALL_ERROR_CODE.PARSING_ERROR: {
                    message =
                        'An error occurred while parsing the provided information. Wait a moment and try again.';
                    break;
                }
                case CALL_ERROR_CODE.NOT_ACCEPTABLE: {
                    message =
                        'An error occurred on the server while accepting the call. Wait a moment and try again. Please contact the administrator if this persists.';
                    break;
                }
                case CALL_ERROR_CODE.CALL_REJECTED: {
                    message =
                        'Call rejected by the server. Wait a moment and try again. Please contact the administrator if this persists.';
                    break;
                }
                case CALL_ERROR_CODE.NOT_AVAILABLE: {
                    message =
                        'Calling services not available. Wait a moment and try again. Please contact the administrator if this persists.';
                    break;
                }
                default: {
                    message = 'An unknown error occurred. Wait a moment and try again.';
                }
            }
            updateCallErrorContext(loggerContext, err.statusCode === 403 ? ERROR_TYPE.FORBIDDEN_ERROR : ERROR_TYPE.SERVICE_UNAVAILABLE, message, correlationId, callError);
            emitterCb(callError);
            break;
        }
        case ERROR_CODE.DEVICE_NOT_FOUND: {
            log.warn(`404 Call Not Found`, loggerContext);
            updateCallErrorContext(loggerContext, ERROR_TYPE.NOT_FOUND, 'Call is no longer active. Wait a moment and try again.', correlationId, callError);
            emitterCb(callError);
            if (isKeepalive) {
                abort = true;
            }
            break;
        }
        case ERROR_CODE.INTERNAL_SERVER_ERROR: {
            log.warn(`500 Internal Server Error`, loggerContext);
            updateCallErrorContext(loggerContext, ERROR_TYPE.SERVER_ERROR, 'An unknown error occurred in the call. Wait a moment and try again.', correlationId, callError);
            emitterCb(callError);
            if (isKeepalive && retryCb) {
                const retryInterval = err.headers && 'retry-after' in err.headers
                    ? Number(err.headers['retry-after'])
                    : DEFAULT_KEEPALIVE_INTERVAL;
                retryCb(retryInterval);
            }
            break;
        }
        default: {
            log.warn(`Unknown Error`, loggerContext);
            updateCallErrorContext(loggerContext, ERROR_TYPE.DEFAULT, 'An unknown error occurred in the call.', correlationId, callError);
            emitterCb(callError);
        }
    }
    return abort;
}
export async function serviceErrorCodeHandler(err, loggerContext) {
    const errorCode = Number(err.statusCode);
    const failureMessage = 'FAILURE';
    switch (errorCode) {
        case ERROR_CODE.BAD_REQUEST: {
            log.warn(`400 Bad request`, loggerContext);
            const errorDetails = {
                statusCode: 400,
                data: {
                    error: '400 Bad request',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.UNAUTHORIZED: {
            log.warn(`401 User is unauthorised, possible token expiry`, loggerContext);
            const errorDetails = {
                statusCode: 401,
                data: {
                    error: 'User is unauthorised, possible token expiry',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.FORBIDDEN: {
            log.warn(`403 User request is forbidden`, loggerContext);
            const errorDetails = {
                statusCode: 403,
                data: {
                    error: 'User request is forbidden',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.DEVICE_NOT_FOUND: {
            log.warn(`404 User info not found`, loggerContext);
            const errorDetails = {
                statusCode: 404,
                data: {
                    error: 'User info not found',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.REQUEST_TIMEOUT: {
            log.warn(`408 Request to the server timedout`, loggerContext);
            const errorDetails = {
                statusCode: 408,
                data: {
                    error: 'Request to the server timedout',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.NOT_IMPLEMENTED: {
            log.warn(`501 Not Implemented error occurred`, loggerContext);
            const errorDetails = {
                statusCode: 501,
                data: {
                    error: 'Method is not implemented at the backend',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.INTERNAL_SERVER_ERROR: {
            log.warn(`500 Internal server error occurred`, loggerContext);
            const errorDetails = {
                statusCode: 500,
                data: {
                    error: 'Internal server error occurred',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        case ERROR_CODE.SERVICE_UNAVAILABLE: {
            log.warn(`503 Unable to establish a connection with the server`, loggerContext);
            const errorDetails = {
                statusCode: 503,
                data: {
                    error: 'Unable to establish a connection with the server',
                },
                message: failureMessage,
            };
            return errorDetails;
        }
        default: {
            log.warn(`${errorCode || 422} Exception has occurred`, loggerContext);
            const errorDetails = {
                statusCode: errorCode || 422,
                data: {
                    error: `${errorCode || 422} Exception has occurred`,
                },
                message: failureMessage,
            };
            return errorDetails;
        }
    }
}
export function parseMediaQualityStatistics(stats) {
    if (!stats || navigator.userAgent.indexOf('Firefox') !== -1) {
        log.info('RTCStatsReport is null, adding dummy stats', {
            file: UTILS_FILE,
            method: 'parseMediaQualityStatistics',
        });
        return DUMMY_METRICS;
    }
    try {
        let type;
        let id;
        let inboundCodec;
        let outboundCodec;
        const localCandidates = {};
        const candidatePairs = {};
        const codecList = {};
        const rxStat = {};
        const txStat = {};
        const vqPayload = {};
        let jitterBufferDelay = 0;
        let jitterBufferEmittedCount = 0;
        let totalRoundTripTime = 0;
        let roundTripTimeMeasurements = 0;
        let selectedPair = '';
        let bitRate = 0;
        vqPayload.maxJitter = 0;
        vqPayload.VoPktSizeMs = 20;
        stats.forEach((report) => {
            Object.keys(report).forEach((statName) => {
                if (statName !== TIMESTAMP) {
                    if (!type || statName === TYPE) {
                        type = report[statName];
                    }
                    else if (!id || statName === MEDIA_ID) {
                        id = report[statName];
                    }
                    else if (id && id.indexOf(RTC_ICE_CANDIDATE_PAIR) !== -1) {
                        if (statName === LOCAL_CANDIDATE_ID) {
                            candidatePairs[id] = report[statName];
                        }
                    }
                    else if (id && id.indexOf(RTC_ICE_CANDIDATE) !== -1) {
                        if (statName === NETWORK_TYPE) {
                            localCandidates[id] = report[statName];
                        }
                    }
                    else if (id &&
                        (id.indexOf(INBOUND_CODEC_MATCH) !== -1 ||
                            id.indexOf(OUTBOUND_CODEC_MATCH) !== -1 ||
                            id.indexOf(RTC_CODEC) !== -1)) {
                        if (statName === MIME_TYPE) {
                            codecList[id] = report[statName];
                        }
                    }
                    else if (type && type === REMOTE_INBOUND_RTP) {
                        switch (statName) {
                            case TOTAL_ROUND_TRIP_TIME: {
                                totalRoundTripTime = report[statName];
                                break;
                            }
                            case ROUND_TRIP_TIME_MEASUREMENTS: {
                                roundTripTimeMeasurements = report[statName];
                                break;
                            }
                            default: {
                            }
                        }
                    }
                    else if (type && type === INBOUND_RTP) {
                        switch (statName) {
                            case CODEC_ID: {
                                inboundCodec = report[statName];
                                break;
                            }
                            case PACKETS_RECEIVED: {
                                rxStat.Pkt = report[statName];
                                break;
                            }
                            case BYTES_RECEIVED: {
                                rxStat.Oct = report[statName];
                                break;
                            }
                            case PACKETS_DISCARDED: {
                                rxStat.LatePkt = report[statName];
                                break;
                            }
                            case PACKETS_LOST: {
                                rxStat.LostPkt = report[statName];
                                break;
                            }
                            case JITTER_BUFFER_DELAY: {
                                jitterBufferDelay = report[statName];
                                break;
                            }
                            case JITTER_BUFFER_EMITTED_COUNT: {
                                jitterBufferEmittedCount = report[statName];
                                break;
                            }
                            default: {
                            }
                        }
                    }
                    else if (type && type === TRANSPORT) {
                        switch (statName) {
                            case SELECTED_CANDIDATE_PAIR_ID: {
                                selectedPair = report[statName];
                                break;
                            }
                            default: {
                            }
                        }
                    }
                    else if (type && type === OUTBOUND_RTP) {
                        switch (statName) {
                            case CODEC_ID: {
                                outboundCodec = report[statName];
                                break;
                            }
                            case PACKETS_SENT: {
                                txStat.Pkt = report[statName];
                                break;
                            }
                            case BYTES_SENT: {
                                txStat.Oct = report[statName];
                                break;
                            }
                            case TARGET_BIT_RATE: {
                                bitRate = report[statName];
                                break;
                            }
                            default: {
                            }
                        }
                    }
                    else if (type && type === MEDIA_SOURCE) {
                        switch (statName) {
                            case TOTAL_SAMPLES_DURATION: {
                                rxStat.Dur = report[statName];
                                txStat.Dur = report[statName];
                                break;
                            }
                            default: {
                            }
                        }
                    }
                }
            });
        });
        if (roundTripTimeMeasurements !== 0) {
            vqPayload.VoOneWayDelayMs = totalRoundTripTime / (2 * roundTripTimeMeasurements);
        }
        else {
            vqPayload.VoOneWayDelayMs = 0;
        }
        vqPayload.hwType = `${platform.os}/${platform.name}-${platform.version}`;
        vqPayload.networkType = localCandidates[candidatePairs[selectedPair]];
        rxStat.AvgJit = jitterBufferDelay / jitterBufferEmittedCount;
        vqPayload.VoRxCodec = codecList[inboundCodec].split('/')[1];
        const txVqPayload = {};
        txVqPayload.VoTxCodec = codecList[outboundCodec].split('/')[1];
        txVqPayload.rtpBitRate = bitRate;
        const byeStats = {};
        rxStat.VQMetrics = vqPayload;
        txStat.VQMetrics = txVqPayload;
        byeStats[RTP_RX_STAT] = rxStat;
        byeStats[RTP_TX_STAT] = txStat;
        log.log(JSON.stringify(byeStats), { file: UTILS_FILE, method: 'parseMediaQualityStatistics' });
        return byeStats;
    }
    catch (err) {
        log.warn(`Caught error while parsing RTP stats, ${err}`, {
            file: UTILS_FILE,
            method: 'parseMediaQualityStatistics',
        });
        return DUMMY_METRICS;
    }
}
export const waitForMsecs = (msec) => new Promise((resolve) => {
    setTimeout(resolve, msec);
});
export function getCallingBackEnd(webex) {
    const entModels = webex.internal.device.features.entitlement.models;
    let callingBackend;
    if (webex.internal.device.callingBehavior === NATIVE_WEBEX_TEAMS_CALLING) {
        for (let i = 0; i < entModels.length; i += 1) {
            if (entModels[i][VALUES][KEY] === ENTITLEMENT_BASIC ||
                entModels[i][VALUES][KEY] === ENTITLEMENT_STANDARD) {
                callingBackend = CALLING_BACKEND.WXC;
                break;
            }
            else if (entModels[i][VALUES][KEY] === ENTITLEMENT_BROADWORKS_CONN) {
                callingBackend = CALLING_BACKEND.BWRKS;
                break;
            }
        }
    }
    else if (webex.internal.device.callingBehavior === NATIVE_SIP_CALL_TO_UCM) {
        callingBackend = CALLING_BACKEND.UCM;
    }
    else {
        callingBackend = CALLING_BACKEND.INVALID;
    }
    return callingBackend;
}
export async function getXsiActionEndpoint(webex, loggerContext, callingBackend) {
    try {
        switch (callingBackend) {
            case CALLING_BACKEND.WXC: {
                const userIdResponse = await webex.request({
                    uri: `${webex.internal.services._serviceUrls.hydra}/${XSI_ACTION_ENDPOINT_ORG_URL_PARAM}`,
                    method: HTTP_METHODS.GET,
                });
                log.log(`Response code: ${userIdResponse.statusCode}, Response trackingid: ${userIdResponse?.headers?.trackingid}`, {
                    file: UTILS_FILE,
                    method: 'getXsiActionEndpoint',
                });
                const response = userIdResponse.body;
                const xsiEndpoint = response[ITEMS][0][XSI_ACTION_ENDPOINT];
                return xsiEndpoint;
            }
            case CALLING_BACKEND.BWRKS: {
                const bwTokenResponse = await webex.request({
                    uri: `${webex.internal.services._serviceUrls.wdm}/${DEVICES}`,
                    method: HTTP_METHODS.GET,
                });
                log.log(`Response code: ${bwTokenResponse.statusCode}, Response trackingid: ${bwTokenResponse?.headers?.trackingid}`, {
                    file: UTILS_FILE,
                    method: 'getXsiActionEndpoint',
                });
                const response = bwTokenResponse.body;
                let xsiEndpoint = response[DEVICES][0][SETTINGS][BW_XSI_URL];
                const xsiUrl = response[DEVICES][0][SETTINGS][BW_XSI_URL];
                if (xsiUrl.endsWith(BW_XSI_ENDPOINT_VERSION)) {
                    xsiEndpoint = xsiUrl.slice(0, -5);
                }
                else if (xsiUrl.endsWith(BW_XSI_ENDPOINT_VERSION_WITH_SLASH)) {
                    xsiEndpoint = xsiUrl.slice(0, -6);
                }
                return xsiEndpoint;
            }
            default: {
                throw new Error('Calling backend is not identified, exiting....');
            }
        }
    }
    catch (err) {
        const errorInfo = err;
        serviceErrorCodeHandler(errorInfo, loggerContext);
        return errorInfo;
    }
}
export function getVgActionEndpoint(webex, callingBackend) {
    try {
        if (callingBackend && callingBackend === CALLING_BACKEND.UCM) {
            return webex.internal.services._serviceUrls['ucmgmt-gateway'];
        }
        throw new Error('Calling backend is not identified, exiting....');
    }
    catch (err) {
        const errorInfo = err;
        return errorInfo;
    }
}
export function getSortedVoicemailList(voiceMessageList, sortOrder) {
    if (sortOrder === SORT.DESC) {
        voiceMessageList.sort((voiceMail, nextVoiceMail) => nextVoiceMail[TIME][PLACEHOLDER_KEY] - voiceMail[TIME][PLACEHOLDER_KEY]);
    }
    else if (sortOrder === SORT.ASC) {
        voiceMessageList.sort((voiceMail, nextVoiceMail) => voiceMail[TIME][PLACEHOLDER_KEY] - nextVoiceMail[TIME][PLACEHOLDER_KEY]);
    }
    return voiceMessageList;
}
export async function scimQuery(filter) {
    log.info(`Starting resolution for filter:- ${filter}`, {
        file: UTILS_FILE,
        method: 'scimQuery',
    });
    const sdkConnector = SDKConnector;
    const webex = sdkConnector.getWebex();
    const isProd = !webex.internal.device.url.includes('-int');
    const webexHost = isProd ? WEBEX_API_PROD : WEBEX_API_BTS;
    const scimUrl = `${webexHost}/${IDENTITY_ENDPOINT_RESOURCE}/${SCIM_ENDPOINT_RESOURCE}/${webex.internal.device.orgId}/${SCIM_USER_FILTER}`;
    const query = scimUrl + encodeURIComponent(filter);
    const response = await webex.request({
        uri: query,
        method: HTTP_METHODS.GET,
        headers: {
            [CISCO_DEVICE_URL]: webex.internal.device.url,
            [SPARK_USER_AGENT]: CALLING_USER_AGENT,
        },
    });
    log.log(`Response code: ${response.statusCode}, Response trackingid: ${response?.headers?.trackingid}`, {
        file: UTILS_FILE,
        method: 'scimQuery',
    });
    return response;
}
export async function resolveCallerIdDisplay(filter) {
    let resolution;
    const displayResult = {};
    try {
        const response = await scimQuery(filter);
        resolution = response.body;
        log.info(`Number of records found for this user :- ${resolution.totalResults}`, {
            file: UTILS_FILE,
            method: 'resolveCallerIdDisplay',
        });
    }
    catch (err) {
        const res = err;
        log.warn(`Error response: - ${res.statusCode}`, {
            file: UTILS_FILE,
            method: 'resolveCallerIdDisplay',
        });
    }
    if (resolution?.totalResults && resolution.totalResults > 0) {
        const scimResource = resolution.Resources[0];
        displayResult.name = scimResource.displayName;
        const numberObj = scimResource.phoneNumbers?.find((num) => num.primary) ||
            scimResource.phoneNumbers?.find((num) => num.type.toLowerCase() === 'work');
        if (numberObj) {
            displayResult.num = numberObj.value;
        }
        else if (scimResource.phoneNumbers && scimResource.phoneNumbers.length > 0) {
            log.info('Failure to resolve caller information. Setting number as caller ID', {
                file: UTILS_FILE,
                method: 'resolveCallerIdDisplay',
            });
            displayResult.num = scimResource.phoneNumbers[0].value;
        }
        const photo = scimResource.photos?.find((photo) => photo.type === 'thumbnail');
        displayResult.avatarSrc = photo ? photo.value : 'unknown';
        displayResult.id = scimResource.id;
    }
    return displayResult;
}
export async function resolveCallerIdByName(name) {
    const displayResult = {};
    const sdkConnector = SDKConnector;
    const webex = sdkConnector.getWebex();
    const searchDirectory = (query) => {
        return webex.people.list({
            displayName: query,
        });
    };
    await searchDirectory(name).then((results) => {
        log.info(`DS Result: ${results}`, {
            file: UTILS_FILE,
            method: 'resolveCallerIdByName',
        });
        if (results && results.items && results.items.length > 0) {
            const resolution = results.items[0];
            displayResult.name = resolution.displayName;
            const id = Buffer.from(resolution.id, 'base64').toString('binary');
            displayResult.id = id.split('/').pop();
            const numObj = resolution.phoneNumbers.find((num) => num.type === 'work');
            if (numObj) {
                displayResult.num = numObj.value;
            }
            else if (resolution.phoneNumbers.length > 0) {
                displayResult.num = resolution.phoneNumbers[0].value;
            }
            displayResult.avatarSrc = resolution.avatar;
            log.info(`Extracted details:- name: ${displayResult.name} , number: ${displayResult.num}, photo: ${displayResult.avatarSrc}, id: ${displayResult.id}`, {
                file: UTILS_FILE,
                method: 'resolveCallerIdByName',
            });
        }
    });
    return displayResult;
}
export async function resolveContact(callingPartyInfo) {
    if (callingPartyInfo.userExternalId && callingPartyInfo.userExternalId.$) {
        return resolveCallerIdDisplay(`id eq "${callingPartyInfo.userExternalId.$}"`);
    }
    if (callingPartyInfo.name && callingPartyInfo.name.$) {
        return resolveCallerIdByName(callingPartyInfo.name.$);
    }
    return null;
}
export function storeVoicemailList(context, voiceMessageList) {
    const vmEncodedList = Buffer.from(JSON.stringify(voiceMessageList), 'utf8').toString('base64');
    sessionStorage.setItem(context, vmEncodedList.toString());
}
export function fetchVoicemailList(context, offset, offsetLimit, loggerContext) {
    let moreVoicemails = false;
    let requiredVoicemailList = [];
    try {
        const voicemailList = JSON.parse(Buffer.from(sessionStorage.getItem(context), 'base64').toString('utf8'));
        log.info(`Length of voicemail list:  ${voicemailList.length}`, loggerContext);
        if (voicemailList.length > offset + offsetLimit) {
            moreVoicemails = true;
        }
        requiredVoicemailList = voicemailList.slice(offset, offset + offsetLimit);
    }
    catch (err) {
        log.warn(`Caught exception while fetching voicemail from storage. ${err}`, loggerContext);
        const errorInfo = err;
        serviceErrorCodeHandler(errorInfo, {});
    }
    return { messages: requiredVoicemailList, moreVMAvailable: moreVoicemails };
}
export function inferIdFromUuid(id, decodeType) {
    return Buffer.from(`${INFER_ID_CONSTANT}/${decodeType}/${id}`, 'binary').toString('base64');
}
function isValidServiceIndicator(indicator) {
    return Object.values(ServiceIndicator).some((v) => v === indicator);
}
export function isValidServiceDomain(serviceData) {
    const regexp = /^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/i;
    const { domain } = serviceData;
    if (!domain) {
        return (serviceData.indicator === ServiceIndicator.CALLING ||
            serviceData.indicator === ServiceIndicator.GUEST_CALLING);
    }
    return regexp.test(domain);
}
export function validateServiceData(serviceData) {
    const allowedValues = Object.values(ServiceIndicator);
    const formattedValues = allowedValues.join(', ').replace(/,([^,]*)$/, ' and$1');
    if (!isValidServiceIndicator(serviceData.indicator)) {
        throw new Error(`Invalid service indicator, Allowed values are: ${formattedValues}`);
    }
}
export function modifySdpForIPv4(sdp) {
    try {
        sdp = sdp.replace(/\r\n|\r/g, '\n');
        sdp = sdp.replace(/^[ \t]+/gm, '');
        const ipv6CLineMatches = sdp.match(/c=IN IP6 [\da-f:.]+/gi) || [];
        const hasIPv6CLine = ipv6CLineMatches.length > 0;
        if (hasIPv6CLine) {
            log.info('Modifying SDP for IPv4 compatibility', {
                file: UTILS_FILE,
                method: 'modifySdpForIPv4',
            });
            const ipv4CandidateMatch = sdp.match(/a=candidate:\d+ \d+ \w+ \d+ ([\d.]+) \d+ typ \w+/);
            const ipv4Address = ipv4CandidateMatch?.[1] || '192.1.1.1';
            sdp = sdp.replace(/c=IN IP6 [\da-f:.]+/gi, `c=IN IP4 ${ipv4Address}`);
            if (!ipv4CandidateMatch) {
                let ipv4CandidateAdded = false;
                sdp = sdp.replace(/(a=candidate:(\d+) (\d+) (\w+) (\d+) ([\da-f:.]+) (\d+) typ (\w+)[^\n]*)/g, (match, full, foundation, componentId, transport, priority, connectionAddress, port, candidateType) => {
                    if (!ipv4CandidateAdded && connectionAddress.includes(':')) {
                        ipv4CandidateAdded = true;
                        const newFoundation = (parseInt(foundation, 10) + 1).toString();
                        return (`${full}\n` +
                            `a=candidate:${newFoundation} ${componentId} ${transport} ${priority} ${ipv4Address} ${port} typ ${candidateType} generation 0 network-id 1 network-cost 10`);
                    }
                    return match;
                });
            }
        }
        return sdp;
    }
    catch (error) {
        log.warn(`Error modifying SDP for IPv4 compatibility: ${error}`, {
            file: UTILS_FILE,
            method: 'modifySdpForIPv4',
        });
        return sdp;
    }
}
export async function uploadLogs(metaData = {}, throwError = false) {
    const webex = SDKConnector.getWebex();
    const feedbackId = uuid();
    try {
        const response = await webex.internal.support.submitLogs({ ...metaData, feedbackId }, undefined, { type: 'diff' });
        log.info(`Logs uploaded successfully with feedbackId: ${feedbackId}`, {
            file: UTILS_FILE,
            method: 'uploadLogs',
        });
        getMetricManager().submitUploadLogsMetric(METRIC_EVENT.UPLOAD_LOGS_SUCCESS, UPLOAD_LOGS_ACTION, METRIC_TYPE.BEHAVIORAL, response?.trackingid, feedbackId, metaData?.correlationId, undefined, metaData?.callId, metaData?.broadworksCorrelationInfo);
        return {
            trackingid: response.trackingid,
            ...(response.url ? { url: response.url } : {}),
            ...(response.userId ? { userId: response.userId } : {}),
            ...(response.correlationId ? { correlationId: response.correlationId } : {}),
            ...(metaData?.broadworksCorrelationInfo
                ? { broadworksCorrelationInfo: metaData?.broadworksCorrelationInfo }
                : {}),
            feedbackId,
        };
    }
    catch (error) {
        const errorLog = new Error(`Failed to upload Logs ${JSON.stringify(error)}`);
        log.error(errorLog.message, {
            file: UTILS_FILE,
            method: 'uploadLogs',
        });
        getMetricManager().submitUploadLogsMetric(METRIC_EVENT.UPLOAD_LOGS_FAILED, UPLOAD_LOGS_ACTION, METRIC_TYPE.BEHAVIORAL, undefined, feedbackId, metaData?.correlationId, errorLog.message, metaData?.callId, metaData?.broadworksCorrelationInfo);
        if (throwError) {
            throw error;
        }
        return undefined;
    }
}
export function normalizeMobiusUris(urls) {
    return urls.map((url) => (!url.endsWith('/') ? `${url}/` : url));
}
