//
//  Hmssdk.swift
//  HMSSDK
//
//  Copyright © 2023 100ms. All rights reserved.
//

import Foundation
import HMSSDK
import ReplayKit
import AVKit
import SwiftUI

class HMSRNSDK: NSObject, HMSUpdateListener, HMSPreviewListener {

    var hms: HMSSDK?
    var interactivity: HMSRNInteractivityCenter?

    var delegate: HMSManager?
    var id: String = "12345"

    private var recentRoleChangeRequest: HMSRoleChangeRequest?
    internal var previewForRoleTracks: [HMSTrack]?
    private var reconnectingStage: Bool = false
    private var preferredExtension: String?
    private var systemBroadcastPicker: RPSystemBroadcastPickerView?
    private var startScreenshareResolve: RCTPromiseResolveBlock?
    private var stopScreenshareResolve: RCTPromiseResolveBlock?
    private var isScreenShared: Bool? = false
    private var previewInProgress = false
    private var networkQualityUpdatesAttached = false
    internal var eventsEnableStatus: [String: Bool] = [:]
    private var sessionStore: HMSSessionStore?
    private var sessionStoreChangeObservers = [String: NSObjectProtocol]()
    private var peerListIterators = [String: HMSPeerListIterator]()
    private var roomMutedLocally = false
    private var noiseCancellationPlugin: HMSNoiseCancellationPlugin?
    private var videoFilterPlugin: HMSVideoFilterPlugin?
    private var virtualBackgroundPlugin: HMSVideoPlugin?

    // MARK: - Setup
    init(data: NSDictionary?, delegate manager: HMSManager?, uid id: String) {
        super.init()
        preferredExtension = data?.value(forKey: "preferredExtension") as? String
        self.delegate = manager
        self.id = id
        DispatchQueue.main.async { [weak self] in
            var noiseCancellationPlugin: HMSNoiseCancellationPlugin?
            var videoFilterPlugin: HMSVideoFilterPlugin?
            var virtualBackgroundPlugin: HMSVideoPlugin?

            self?.hms = HMSSDK.build { sdk in
                sdk.appGroup = data?.value(forKey: "appGroup") as? String
                sdk.frameworkInfo = HMSHelper.getFrameworkInfo(data?.value(forKey: "frameworkInfo") as? NSDictionary)
                let trackSettingsDict = data?.value(forKey: "trackSettings") as? NSDictionary

                // Video track settings
                let videoSettingsDict = trackSettingsDict?.value(forKey: "video") as? NSDictionary
                let videoPluginDict = videoSettingsDict?.value(forKey: "videoPlugin") as? NSDictionary
                let videoPlugin = HMSHelper.getHMSVideoPlugin(videoPluginDict)

                if let asVideoFilterPlugin = videoPlugin as? HMSVideoFilterPlugin {
                    videoFilterPlugin = asVideoFilterPlugin
                } else if #available(iOS 15.0, *) {
                    let asVirtualBGPlugin = videoPlugin as? HMSVirtualBackgroundPlugin
                    virtualBackgroundPlugin = asVirtualBGPlugin
                }

                let videoSettings = HMSHelper.getLocalVideoSettings(videoSettingsDict, videoPlugin)

                // Audio track settings
                let audioSettingsDict = trackSettingsDict?.value(forKey: "audio") as? NSDictionary

                let value = audioSettingsDict?.value(forKey: "noiseCancellationPlugin") as? NSDictionary
                noiseCancellationPlugin = HMSHelper.getHMSNoiseCancellationPlugin(value)

                let audioSettings = HMSHelper.getLocalAudioSettings(audioSettingsDict, noiseCancellationPlugin, sdk, self?.delegate, id)

                // Track Settings
                sdk.trackSettings = HMSTrackSettings(videoSettings: videoSettings, audioSettings: audioSettings)
            }
            self?.noiseCancellationPlugin = noiseCancellationPlugin
            self?.virtualBackgroundPlugin = virtualBackgroundPlugin
            self?.videoFilterPlugin = videoFilterPlugin
            if let hms = self?.hms {
                self?.interactivity = HMSRNInteractivityCenter(
                    hmssdk: hms,
                    hmsrnsdk: self
                )
            }

            NotificationCenter.default.addObserver(forName: UIApplication.willTerminateNotification,
                                                   object: nil,
                                                   queue: .main) { [weak self] _ in
                if self?.hms?.room != nil {
                    self?.hms?.leave()
                }
            }
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    // MARK: - Prebuilt

    func getRoomLayout(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let token = data["authToken"] as? String else {
            reject?("40000", "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["authToken"]), nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else { return }

             if let endPoint = data["endpoint"] as? String, endPoint.contains("mockable") || endPoint.contains("nonprod") {
                  UserDefaults.standard.set(endPoint, forKey: "HMSRoomLayoutEndpointOverride")
             } else {
                   UserDefaults.standard.removeObject(forKey: "HMSRoomLayoutEndpointOverride")
             }

            strongSelf.hms?.getRoomLayout(using: token) { layout, error in

                if let rawData = layout?.rawData {
                    let jsonString = String(decoding: rawData, as: UTF8.self)
                    resolve?(jsonString)
                    return
                }

                let errorMessage = "\(#function) Could not parse Room Layout for Token: \(token), error: \(error?.localizedDescription ?? "Could not fetch the error")"
                reject?("40000", errorMessage, nil)
            }
        }
    }

    // MARK: - HMS SDK Actions

    func preview(_ credentials: NSDictionary) {

        guard !previewInProgress else {
            if eventsEnableStatus[HMSConstants.ON_ERROR] == true {
                delegate?.emitEvent(HMSConstants.ON_ERROR, ["error": ["code": 5000, "description": "Preview is in progress", "isTerminal": false, "canRetry": true, "params": ["function": #function]] as [String: Any], "id": id])
            }
            return
        }

        guard let authToken = credentials.value(forKey: "authToken") as? String,
              let user = credentials.value(forKey: "username") as? String
        else {
            let errorMessage = "preview: " + HMSHelper.getUnavailableRequiredKey(credentials, ["authToken", "username"])
            emitRequiredKeysError(errorMessage)
            return
        }

        let metadata = credentials.value(forKey: "metadata") as? String

        let endpoint = credentials.value(forKey: "endpoint") as? String

        let captureNetworkQualityInPreview = credentials.value(forKey: "captureNetworkQualityInPreview") as? Bool ?? false

        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else { return }

            let config = HMSConfig(userName: user,
                                   authToken: authToken,
                                   metadata: metadata,
                                   endpoint: endpoint,
                                   captureNetworkQualityInPreview: captureNetworkQualityInPreview)

            strongSelf.hms?.preview(config: config, delegate: strongSelf)

            strongSelf.previewInProgress = true
        }
    }

    func previewForRole(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let role = data.value(forKey: "role") as? String
        else {
            let errorMessage = "previewForRole: " + HMSHelper.getUnavailableRequiredKey(data, ["role"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let roleObj = HMSHelper.getRoleFromRoleName(role, roles: hms?.roles)

        if let extractedRole = roleObj {
            DispatchQueue.main.async { [weak self] in
                self?.hms?.preview(role: extractedRole) { tracks, error in
                    if error != nil {
                        reject?(error?.localizedDescription, error?.localizedDescription, nil)
                        return
                    }

                    self?.previewForRoleTracks = tracks

                    let decodedTracks = HMSDecoder.getAllTracks(tracks ?? [])

                    resolve?(["success": true, "tracks": decodedTracks] as [String: Any])
                }
            }
        }
    }

    func cancelPreview(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        DispatchQueue.main.async { [weak self] in
            self?.hms?.cancelPreview()
            self?.previewForRoleTracks = nil
            resolve?(["success": true])
        }
    }

    func join(_ credentials: NSDictionary) {

        guard !previewInProgress else {
            if eventsEnableStatus[HMSConstants.ON_ERROR] == true {
                delegate?.emitEvent(HMSConstants.ON_ERROR, ["error": ["code": 5000, "description": "Preview is in progress", "isTerminal": false, "canRetry": true, "params": ["function": #function]] as [String: Any], "id": id])
            }
            return
        }

        guard let authToken = credentials.value(forKey: "authToken") as? String,
              let user = credentials.value(forKey: "username") as? String
        else {
            let errorMessage = "join: " + HMSHelper.getUnavailableRequiredKey(credentials, ["authToken", "username"])
            emitRequiredKeysError(errorMessage)
            return
        }
        reconnectingStage = false
        let metadata = credentials.value(forKey: "metadata") as? String
        let captureNetworkQualityInPreview = credentials.value(forKey: "captureNetworkQualityInPreview") as? Bool ?? false

        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else { return }

            let config = HMSConfig(userName: user,
                                   authToken: authToken,
                                   metadata: metadata,
                                   endpoint: credentials.value(forKey: "endpoint") as? String,
                                   captureNetworkQualityInPreview: captureNetworkQualityInPreview)

            strongSelf.hms?.join(config: config, delegate: strongSelf)
        }
    }

    func getAuthTokenByRoomCode(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let roomCode = data.value(forKey: "roomCode") as? String
        else {
            let errorMessage = "getAuthTokenByRoomCode: " + HMSHelper.getUnavailableRequiredKey(data, ["roomCode"])
            reject?("40000", errorMessage, nil)
            return
        }
        let userId = data.value(forKey: "userId") as? String? ?? nil
        let endpoint = data.value(forKey: "endpoint") as? String? ?? nil

        // This is to make the QA links work
        if endpoint != nil && endpoint!.contains("nonprod") {
            UserDefaults.standard.set(endpoint, forKey: "HMSAuthTokenEndpointOverride")
        } else {
            UserDefaults.standard.removeObject(forKey: "HMSAuthTokenEndpointOverride")
        }

        DispatchQueue.main.async { [weak self] in
            self?.hms?.getAuthTokenByRoomCode(roomCode, userID: userId) { token, error in
                // error occurred
                if let error = error as? NSError {
                    reject?(String(error.code), error.localizedDescription, nil)
                    return
                }
                // no error and token is valid
                else if token != nil {
                    resolve?(token)
                    return
                }
                // no error but token is null
                else {
                    reject?("50000", "token is null", nil)
                    return
                }
            }
        }
    }

    func setLocalMute(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let isMute = data.value(forKey: "isMute") as? Bool
        else {
            let errorMessage = "setLocalMute: " + HMSHelper.getUnavailableRequiredKey(data, ["isMute"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            if let audioTrack = self?.hms?.localPeer?.localAudioTrack() {
                audioTrack.setMute(isMute)
                resolve?(true)
            } else if let tracks = self?.previewForRoleTracks, let audioTrack = tracks.first(where: { $0.kind == HMSTrackKind.audio }) as? HMSLocalAudioTrack {
                audioTrack.setMute(isMute)
                resolve?(true)
            } else {
                let errorMessage = "setLocalMute: No local audio track available for setting mute state."
                print(#function, errorMessage)
                reject?(errorMessage, errorMessage, nil)
            }
        }
    }

    func setLocalVideoMute(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let isMute = data.value(forKey: "isMute") as? Bool
        else {
            let errorMessage = "setLocalVideoMute: " + HMSHelper.getUnavailableRequiredKey(data, ["isMute"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            if let videoTrack = self?.hms?.localPeer?.localVideoTrack() {
                videoTrack.setMute(isMute)
                resolve?(true)
            } else if let tracks = self?.previewForRoleTracks, let videoTrack = tracks.first(where: { $0.kind == HMSTrackKind.video }) as? HMSLocalVideoTrack {
                videoTrack.setMute(isMute)
                resolve?(true)
            } else {
                let errorMessage = "setLocalVideoMute: No local video track available for setting mute state."
                print(#function, errorMessage)
                reject?(errorMessage, errorMessage, nil)
            }
        }
    }

    func switchCamera() {
        DispatchQueue.main.async { [weak self] in
            if let localVideoTrack = self?.hms?.localPeer?.localVideoTrack() {
                localVideoTrack.switchCamera()
            } else if let tracks = self?.previewForRoleTracks {
                if let videoTrack = tracks.first(where: { $0.kind == HMSTrackKind.video }) as? HMSLocalVideoTrack {
                    videoTrack.switchCamera()
                }
            } else {
                print(#function, "No local video track available for switching camera.")
            }
        }
    }

    func leave(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else {
                let errorMessage = "\(#function): Could not find reference to self while executing Room leave"
                print(#function, errorMessage)
                reject?(errorMessage, errorMessage, nil)
                return
            }

            strongSelf.hms?.leave { [weak self] success, error in

                guard let strongSelf = self else {
                    let errorMessage = "\(#function): Could not find reference to self when callback is received while executing Room leave"
                    print(#function, errorMessage)
                    reject?(errorMessage, errorMessage, nil)
                    return
                }

                if let nonnilError = error {
                    reject?(nonnilError.localizedDescription, nonnilError.localizedDescription, nil)
                } else if success {
                    resolve?(["success": success])
                    strongSelf.cleanup() // resetting states and doing data cleanup
                } else {
                    reject?("error in leave", "error in leave", nil)
                }
            }
        }
    }

    func sendBroadcastMessage(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let message = data.value(forKey: "message") as? String
        else {
            let errorMessage = "sendBroadcastMessage: " + HMSHelper.getUnavailableRequiredKey(data, ["message"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let type = data.value(forKey: "type") as? String ?? "chat"

        DispatchQueue.main.async { [weak self] in
            self?.hms?.sendBroadcastMessage(type: type, message: message, completion: { message, error in
                if error == nil {
                    resolve?(["messageId": message?.messageID ?? "", "data": ["sender": message?.sender?.name ?? "", "message": message?.message ?? "", "type": message?.type]] as [String: Any])
                    return
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                    return
                }
            })
        }
    }

    func sendGroupMessage(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let message = data.value(forKey: "message") as? String,
              let targetedRoles = data.value(forKey: "roles") as? [String]
        else {
            let errorMessage = "sendGroupMessage: " + HMSHelper.getUnavailableRequiredKey(data, ["message", "roles"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let type = data.value(forKey: "type") as? String ?? "chat"
        DispatchQueue.main.async { [weak self] in
            let encodedTargetedRoles = HMSHelper.getRolesFromRoleNames(targetedRoles, roles: self?.hms?.roles)
            self?.hms?.sendGroupMessage(type: type, message: message, roles: encodedTargetedRoles, completion: { message, error in
                if error == nil {
                    resolve?(["messageId": message?.messageID ?? "", "data": ["sender": message?.sender?.name ?? "", "message": message?.message ?? "", "type": message?.type]] as [String: Any])
                    return
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                    return
                }
            })
        }
    }

    func sendDirectMessage(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let message = data.value(forKey: "message") as? String,
              let peerId = data.value(forKey: "peerId") as? String
        else {
            let errorMessage = "sendDirectMessage: " + HMSHelper.getUnavailableRequiredKey(data, ["message", "peerId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let type = data.value(forKey: "type") as? String ?? "chat"
        DispatchQueue.main.async { [weak self] in
            guard let peer = HMSHelper.getPeerFromPeerId(peerId, remotePeers: self?.hms?.remotePeers) else { return }

            self?.hms?.sendDirectMessage(type: type, message: message, peer: peer, completion: { message, error in
                if error == nil {
                    resolve?(["messageId": message?.messageID ?? "", "data": ["sender": message?.sender?.name ?? "", "message": message?.message ?? "", "type": message?.type]] as [String: Any])
                    return
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                    return
                }
            })
        }
    }

    func acceptRoleChange(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        DispatchQueue.main.async { [weak self] in

            guard let request = self?.recentRoleChangeRequest
            else {
                let errorMessage = "acceptRoleChange: recentRoleChangeRequest not found"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            self?.hms?.accept(changeRole: request, completion: { success, error in
                if success {
                    resolve?(["success": success])
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            })
            self?.recentRoleChangeRequest = nil
            self?.previewForRoleTracks = nil
        }
    }

    func changeRole(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let peerId = data.value(forKey: "peerId") as? String,
              let role = data.value(forKey: "role") as? String
        else {
            let errorMessage = "changeRole: " + HMSHelper.getUnavailableRequiredKey(data, ["peerId", "role"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let force = data.value(forKey: "force") as? Bool ?? false

        DispatchQueue.main.async { [weak self] in
            guard let peer = HMSHelper.getPeerFromPeerId(peerId, remotePeers: self?.hms?.remotePeers, localPeer: self?.hms?.localPeer)
            else {
                reject?("PEER_NOT_FOUND", "PEER_NOT_FOUND", nil)
                return
            }

            guard let role = HMSHelper.getRoleFromRoleName(role, roles: self?.hms?.roles)
            else {
                reject?("ROLE_NOT_FOUND", "ROLE_NOT_FOUND", nil)
                return
            }

            self?.hms?.changeRole(for: peer, to: role, force: force, completion: { success, error in
                if success {
                    resolve?(["success": success])
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            })
        }
    }

    func changeRolesOfAllPeers(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let toRoleString = data.object(forKey: "toRole") as? String
        else {
            let errorMessage = "changeRolesOfAllPeers: " + HMSHelper.getUnavailableRequiredKey(data, ["toRole"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let toRole = HMSHelper.getRoleFromRoleName(toRoleString, roles: self?.hms?.roles) else {
                let errorMessage = "changeRolesOfAllPeers: " + HMSHelper.getUnavailableRequiredKey(data, ["toRole"])
                reject?(errorMessage, errorMessage, nil)
                return
            }

            var limitToRoles: [HMSRole]?

            if let ofRoleNames = data.object(forKey: "ofRoles") as? [String] {
                limitToRoles = self?.hms?.roles.filter { ofRoleNames.contains($0.name) }
            }

            self?.hms?.changeRolesOfAllPeers(to: toRole, limitToRoles: limitToRoles) { success, error in
                if success {
                    resolve?(["success": success])
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            }
        }
    }

    func changeTrackState(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "changeTrackState: " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let mute = data.value(forKey: "mute") as? Bool ?? true

        DispatchQueue.main.async { [weak self] in
            guard let remotePeers = self?.hms?.remotePeers,
                  let track = HMSHelper.getTrackFromTrackId(trackId, remotePeers)
            else {
                reject?("TRACK_NOT_FOUND", "TRACK_NOT_FOUND", nil)
                return
            }

            self?.hms?.changeTrackState(for: track, mute: mute, completion: { success, error in
                if success {
                    resolve?(["success": success])
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            })
        }
    }

    func changeTrackStateForRoles(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let mute = data.value(forKey: "mute") as? Bool
        else {
            let errorMessage = "changeTrackStateForRoles: " + HMSHelper.getUnavailableRequiredKey(data, ["mute"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        let source = data.value(forKey: "source") as? String
        let targetedRoles = data.value(forKey: "roles") as? [String]
        let type = data.value(forKey: "type") as? String

        var decodeType: HMSTrackKind?
        if  type != nil {
            if  type == "AUDIO" {
                decodeType = HMSTrackKind.audio
            } else {
                decodeType = HMSTrackKind.video
            }
        }

        DispatchQueue.main.async { [weak self] in
            let encodedTargetedRoles = HMSHelper.getRolesFromRoleNames(targetedRoles, roles: self?.hms?.roles)
            self?.hms?.changeTrackState(mute: mute, for: decodeType, source: source, roles: encodedTargetedRoles, completion: { success, error in
                if success {
                    resolve?(["success": success])
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            })
        }
    }

    func isMute(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "isMute: " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else { return }
            guard let localPeer = strongSelf.hms?.localPeer,
                let localTrack = HMSHelper.getLocalTrackFromTrackId(trackId, localPeer: localPeer)
            else {
                guard let remotePeers = strongSelf.hms?.remotePeers,
                    let track = HMSHelper.getTrackFromTrackId(trackId, remotePeers)
                else {
                    reject?("Track not found", "Track not found", nil)
                    return
                }
                let mute = track.isMute()
                resolve?(mute)
                return
            }
            let mute = localTrack.isMute()
            resolve?(mute)
        }
    }

    func removePeer(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let peerId = data.value(forKey: "peerId") as? String
        else {
            let errorMessage = "removePeer: " + HMSHelper.getUnavailableRequiredKey(data, ["peerId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let reason = data.value(forKey: "reason") as? String

        HMSHelper.getRemotePeerFromPeerId(peerId, hmsSDK: self.hms) { peer in

            DispatchQueue.main.async { [weak self] in
                guard let nonnilPeer = peer else {
                    reject?("PEER_NOT_FOUND", "PEER_NOT_FOUND", nil)
                    return
                }

                self?.hms?.removePeer(nonnilPeer, reason: reason ?? "Removed from room", completion: { success, error in
                    if success {
                        resolve?(["success": success])
                    } else {
                        reject?(error?.localizedDescription, error?.localizedDescription, nil)
                    }
                })
            }
        }
    }

    func endRoom(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let lock = data.value(forKey: "lock") as? Bool,
                let reason = data.value(forKey: "reason") as? String
        else {
            let errorMessage = "endRoom: " + HMSHelper.getUnavailableRequiredKey(data, ["lock", "reason"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            self?.hms?.endRoom(lock: lock, reason: reason, completion: { success, error in
                if success {
                    resolve?(["success": success])
                    self?.cleanup() // resetting states and doing data cleanup
                } else {
                    reject?(error?.localizedDescription, error?.localizedDescription, nil)
                }
            })
        }
    }

    func isPlaybackAllowed(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "isPlaybackAllowed: " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        DispatchQueue.main.async { [weak self] in
            guard let remotePeers = self?.hms?.remotePeers
            else {
                reject?("REMOTE_PEERS_NOT_FOUND", "REMOTE_PEERS_NOT_FOUND", nil)
                return
            }
            let remoteAudioTrack = HMSHelper.getRemoteAudioTrackFromTrackId(trackId, remotePeers)
            let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)
            if remoteAudioTrack != nil {
                let isPlaybackAllowed = remoteAudioTrack?.isPlaybackAllowed()
                resolve?(isPlaybackAllowed)
                return
            } else if remoteVideoTrack != nil {
                let isPlaybackAllowed = remoteVideoTrack?.isPlaybackAllowed()
                resolve?(isPlaybackAllowed)
                return
            } else {
                reject?("TRACK_NOT_FOUND", "TRACK_NOT_FOUND", nil)
                return
            }
        }
    }

    func setPlaybackAllowed(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String,
              let playbackAllowed = data.value(forKey: "playbackAllowed") as? Bool
        else {
            let errorMessage = "setPlaybackAllowed: " + HMSHelper.getUnavailableRequiredKey(data, ["trackId", "playbackAllowed"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            guard let remotePeers = self?.hms?.remotePeers
            else {
                let errorMessage = "setPlaybackAllowed: No remote peers found"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            let remoteAudioTrack = HMSHelper.getRemoteAudioTrackFromTrackId(trackId, remotePeers)
            let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)

            if remoteAudioTrack != nil {
                remoteAudioTrack?.setPlaybackAllowed(playbackAllowed)
                resolve?(true)
            } else if remoteVideoTrack != nil {
                remoteVideoTrack?.setPlaybackAllowed(playbackAllowed)
                resolve?(true)
            } else {
                let errorMessage = "setPlaybackAllowed: No remote audio or video track to set playback"
                reject?(errorMessage, errorMessage, nil)
            }
        }
    }

    func changeMetadata(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let metadata = data.value(forKey: "metadata") as? String
        else {
            let errorMessage = "changeMetadata: " + HMSHelper.getUnavailableRequiredKey(data, ["metadata"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        hms?.change(metadata: metadata, completion: { success, error in
            if success {
                resolve?(["success": success])
                return
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
        })
    }

    func setVolume(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String,
              let volume = data.value(forKey: "volume") as? Double
        else {
            let errorMessage = "setVolume: " + HMSHelper.getUnavailableRequiredKey(data, ["trackId", "volume"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            guard let strongSelf = self else { return }
            let remotePeers = strongSelf.hms?.remotePeers

            let remoteAudioTrack = HMSHelper.getRemoteAudioAuxiliaryTrackFromTrackId(trackId, remotePeers)

            if remoteAudioTrack != nil {
                remoteAudioTrack?.setVolume(volume)
                resolve?(nil)
            } else {
                let errorMessage = "setVolume: No remote audio track not available"
                reject?(errorMessage, errorMessage, nil)
            }
        }
    }

    func switchAudioOutput(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let audioDevice = data["audioDevice"] as? String else {
            let errorMessage = "\(#function)" + HMSHelper.getUnavailableRequiredKey(data, ["audioDevice"])
            reject?("6004", errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            let outputDevice: HMSAudioOutputDevice
            switch audioDevice {
            case "EARPIECE":
                outputDevice = HMSAudioOutputDevice.earpiece
            default:
                outputDevice = HMSAudioOutputDevice.speaker
            }
            do {
                try self?.hms?.switchAudioOutput(to: outputDevice)
                resolve?(nil)
            } catch let error {
                reject?("6004", error.localizedDescription, nil)
            }
        }
    }

    func startRTMPOrRecording(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let record = data.value(forKey: "record") as? Bool
        else {
            let errorMessage = "startRTMPOrRecording: " + HMSHelper.getUnavailableRequiredKey(data, ["record"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        let meetingNullableString = data.value(forKey: "meetingURL") as? String

        let rtmpStrings = data.value(forKey: "rtmpURLs") as? [String]

        var meetingUrl: URL?
        if let meetingString = meetingNullableString, !meetingString.isEmpty {
            if let meetLink = URL(string: meetingString) {
                meetingUrl = meetLink
            } else {
                reject?("Invalid meeting url passed", "Invalid meeting url passed", nil)
                return
            }
        }

        let URLs = HMSHelper.getRtmpUrls(rtmpStrings)

        let config = HMSRTMPConfig(meetingURL: meetingUrl, rtmpURLs: URLs, record: record)
        hms?.startRTMPOrRecording(config: config, completion: { success, error in
            if success {
                resolve?(["success": success])
                return
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
        })
    }

    func stopRtmpAndRecording(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        hms?.stopRTMPAndRecording(completion: { success, error in
            if success {
                resolve?(["success": success])
                return
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
        })
    }

    // MARK: - HLS Streaming

    func startHLSStreaming(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        let recordConfig = HMSHelper.getHlsRecordingConfig(data.value(forKey: "hlsRecordingConfig") as? NSDictionary)
        let hlsMeetingUrlVariant = HMSHelper.getHMSHLSMeetingURLVariants(data.value(forKey: "meetingURLVariants") as? [[String: Any]])
        var config: HMSHLSConfig?
        if !hlsMeetingUrlVariant.isEmpty || recordConfig !== nil {
            config = HMSHLSConfig(variants: hlsMeetingUrlVariant, recording: recordConfig)
        }

        hms?.startHLSStreaming(config: config, completion: { success, error in
            if success {
                resolve?(["success": success])
                return
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
        })
    }

    func stopHLSStreaming(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        hms?.stopHLSStreaming(config: nil, completion: { success, error in
            if success {
                resolve?(["success": success])
                return
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
        })
    }

    func sendHLSTimedMetadata(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let metadataArrayList = data["metadata"] as? [NSDictionary] else {
            let errorMessage = "\(#function) metadata for sendHLSTimedMetadata was not found"
            reject?("6004", errorMessage, nil)
            return
        }

        let metadata = metadataArrayList.compactMap { (dict: NSDictionary) -> HMSHLSTimedMetadata? in
            guard let payload = dict["payload"] as? String else {
               return nil
            }
            if let duration = dict["duration"] as? Int {
                return HMSHLSTimedMetadata(payload: payload, duration: duration)
            } else {
                return HMSHLSTimedMetadata(payload: payload)
            }
        }

        DispatchQueue.main.async { [weak self] in
            self?.hms?.sendHLSTimedMetadata(metadata) { success, error in
                if let error = error as? HMSError {
                    print(#function, "Unable to send metadata: \(error)")
                    reject?("6004", error.localizedDescription, nil)
                }

                resolve?(success)
            }
        }
    }

    // MARK: -

    func changeName(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let name = data.value(forKey: "name") as? String
        else {
            let errorMessage = "changeName: " + HMSHelper.getUnavailableRequiredKey(data, ["name"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        hms?.change(name: name) { success, error in
            if success {
                resolve?(["success": success])
            } else {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
            }
        }
    }

    func remoteMuteAllAudio(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        let allAudioTracks = HMSUtilities.getAllAudioTracks(in: (self.hms?.room)!!)
        var customError: Error?
        for audioTrack in allAudioTracks {
            self.hms?.changeTrackState(for: audioTrack, mute: true, completion: { success, error in
                if success {
                } else {
                    customError = error
                }
            })
        }
        if customError == nil {
            resolve?(["success": true])
        } else {
            reject?(customError?.localizedDescription, customError?.localizedDescription, nil)
        }
    }

    func setPlaybackForAllAudio(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let mute = data.value(forKey: "mute") as? Bool
        else {
            let errorMessage = "setPlaybackForAllAudio: " + HMSHelper.getUnavailableRequiredKey(data, ["mute"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in
            if let self = self, let room = self.hms?.room {
                self.roomMutedLocally = mute

                let audioTracks = HMSUtilities.getAllAudioTracks(in: room)

                audioTracks.forEach { audioTrack in
                    if audioTrack is HMSRemoteAudioTrack {
                        (audioTrack as! HMSRemoteAudioTrack).setPlaybackAllowed(!mute)
                    }
                }
                resolve?(true)
            } else {
                let errorMessage = "setPlaybackForAllAudio: HMSSDK instance not available"
                reject?(errorMessage, errorMessage, nil)
            }
        }
    }

    func startScreenshare(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let preferredExtension = preferredExtension else {
            reject?("Could not start screen share, preferredExtension not passed in Build method", "Could not start screen share, preferredExtension not passed in Build method", nil)
            return
        }
        DispatchQueue.main.async { [weak self] in
            if self?.systemBroadcastPicker == nil {
                self?.systemBroadcastPicker = RPSystemBroadcastPickerView()
                self?.systemBroadcastPicker!.preferredExtension = preferredExtension
                self?.systemBroadcastPicker!.showsMicrophoneButton = false
            }

            for view in self!.systemBroadcastPicker!.subviews {
                if let button = view as? UIButton {
                    button.sendActions(for: .allEvents)
                }
            }
            self?.startScreenshareResolve = resolve
        }
    }

    func stopScreenshare(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let preferredExtension = preferredExtension else {
            reject?("Could not start screen share, preferredExtension not passed in Build method", "Could not start screen share, preferredExtension not passed in Build method", nil)
            return
        }
        DispatchQueue.main.async { [weak self] in
            if self?.systemBroadcastPicker == nil {
                self?.systemBroadcastPicker = RPSystemBroadcastPickerView()
                self?.systemBroadcastPicker!.preferredExtension = preferredExtension
                self?.systemBroadcastPicker!.showsMicrophoneButton = false
            }

            for view in self!.systemBroadcastPicker!.subviews {
                if let button = view as? UIButton {
                    button.sendActions(for: .allEvents)
                }
            }
            self?.stopScreenshareResolve = resolve
        }
    }

    func isScreenShared(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        resolve?(isScreenShared)
    }

    func raiseLocalPeerHand(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        self.hms?.raiseLocalPeerHand { success, error in
            if error != nil {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
            resolve?(success)
        }
    }

    func lowerLocalPeerHand(_ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        self.hms?.lowerLocalPeerHand { success, error in
            if error != nil {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
            resolve?(success)
        }
    }

    func lowerRemotePeerHand(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let peerId = data.value(forKey: "peerId") as? String else {
            let errorMessage = "lowerRemotePeerHand: " + HMSHelper.getUnavailableRequiredKey(data, ["peerId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        guard let remotePeer = HMSHelper.getPeerFromPeerId(peerId, remotePeers: self.hms?.remotePeers) else {
            let errorMessage = "lowerRemotePeerHand: Could not find remote peer with peerID - " + peerId
            reject?(errorMessage, errorMessage, nil)
            return
        }

        self.hms?.lowerRemotePeerHand(remotePeer) { success, error in
            if error != nil {
                reject?(error?.localizedDescription, error?.localizedDescription, nil)
                return
            }
            resolve?(success)
        }
    }

    func playAudioShare(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let fileUrl = data.value(forKey: "fileUrl") as? String,
              let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "playAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode", "fileUrl"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        let loops = data.value(forKey: "loops") as? Bool ?? false
        let interrupts = data.value(forKey: "interrupts") as? Bool ?? false
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            if let url = URL(string: fileUrl) {
                do {
                    try audioFilePlayerNode.play(fileUrl: url, loops: loops, interrupts: interrupts)
                    resolve?(["success": true])
                } catch {
                    reject?(error.localizedDescription, error.localizedDescription, nil)
                }
            } else {
                reject?("Incorrect URL", "Incorrect URL", nil)
            }
        } else {
            reject?("AudioFilePlayerNode not found", "AudioFilePlayerNode not found", nil)
        }
    }

    func setAudioShareVolume(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let volume = data.value(forKey: "volume") as? NSNumber,
              let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "setAudioShareVolume: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode", "volume"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioMicNode = playerNode as? HMSMicNode {
            audioMicNode.volume = volume.floatValue
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            audioFilePlayerNode.volume = volume.floatValue
        }
        resolve?(true)
    }

    func stopAudioShare(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "stopAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            audioFilePlayerNode.stop()
            resolve?(true)
        } else {
            let errorMessage = "stopAudioShare: HMSAudioFilePlayerNode not available!"
            reject?(errorMessage, errorMessage, nil)
        }
    }

    func resumeAudioShare(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "resumeAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            do {
                try audioFilePlayerNode.resume()
                resolve?(true)
            } catch {
                reject?(error.localizedDescription, error.localizedDescription, nil)
            }
        } else {
            let errorMessage = "resumeAudioShare: HMSAudioFilePlayerNode not available!"
            reject?(errorMessage, errorMessage, nil)
        }
    }

    func pauseAudioShare(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "pauseAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            audioFilePlayerNode.pause()
            resolve?(true)
        } else {
            let errorMessage = "pauseAudioShare: HMSAudioFilePlayerNode not available!"
            reject?(errorMessage, errorMessage, nil)
        }
    }

    func audioShareIsPlaying(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "pauseAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            resolve?(audioFilePlayerNode.isPlaying)
        } else {
            reject?("AudioFilePlayerNode not found", "AudioFilePlayerNode not found", nil)
        }
    }

    func audioShareCurrentTime(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "pauseAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            resolve?(audioFilePlayerNode.currentTime)
        } else {
            reject?("AudioFilePlayerNode not found", "AudioFilePlayerNode not found", nil)
        }
    }

    func audioShareDuration(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let audioNodeName = data.value(forKey: "audioNode") as? String,
              let audioMixerSourceMap = HMSHelper.getAudioMixerSourceMap(),
              let playerNode = audioMixerSourceMap[audioNodeName]
        else {
            let errorMessage = "pauseAudioShare: " + HMSHelper.getUnavailableRequiredKey(data, ["audioNode"])
            reject?(errorMessage, errorMessage, nil)
            return
        }
        if let audioFilePlayerNode = playerNode as? HMSAudioFilePlayerNode {
            resolve?(audioFilePlayerNode.duration)
        } else {
            reject?("AudioFilePlayerNode not found", "AudioFilePlayerNode not found", nil)
        }
    }

    func enableNetworkQualityUpdates() {
        networkQualityUpdatesAttached = true
    }

    func disableNetworkQualityUpdates() {
        networkQualityUpdatesAttached = false
    }

    func enableEvent(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let eventType = data.value(forKey: "eventType") as? String else {
            let errorMessage = "enableEvent: " + HMSHelper.getUnavailableRequiredKey(data, ["eventType"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        eventsEnableStatus[eventType] = true
        resolve?(["success": true] as [String: Any])
    }

    func disableEvent(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let eventType = data.value(forKey: "eventType") as? String else {
            let errorMessage = "disableEvent: " + HMSHelper.getUnavailableRequiredKey(data, ["eventType"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        eventsEnableStatus[eventType] = false
        resolve?(["success": true, "message": "function call executed successfully"] as [String: Any])
    }

    func restrictData(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let roleName = data.value(forKey: "roleName") as? String else {
            let errorMessage = "restrictData: " + HMSHelper.getUnavailableRequiredKey(data, ["roleName"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        HMSDecoder.setRestrictRoleData(roleName, true)
        resolve?(true)
    }

    // MARK: - HMS SDK Get APIs
    func getRoom(_ resolve: RCTPromiseResolveBlock?) {
        let roomData = HMSDecoder.getHmsRoom(hms?.room)

        resolve?(roomData)
    }

    func getLocalPeer(_ resolve: RCTPromiseResolveBlock?) {
        let localPeer = HMSDecoder.getHmsLocalPeer(hms?.localPeer)

        resolve?(localPeer)
    }

    func getRemotePeers(_ resolve: RCTPromiseResolveBlock?) {
        let remotePeers = HMSDecoder.getHmsRemotePeers(hms?.remotePeers)

        resolve?(remotePeers)
    }

    func getRoles(_ resolve: RCTPromiseResolveBlock?) {
        let roles = HMSDecoder.getAllRoles(hms?.roles)

        resolve?(roles)
    }

    func getPeerProperty(_ data: NSDictionary) -> [AnyHashable: Any]? {
        guard let property = data.value(forKey: "property") as? String else {
            return nil
        }

        guard let peerId = data.value(forKey: "peerId") as? String else {
            return nil
        }

        guard let room = hms?.room, let peer = HMSUtilities.getPeer(for: peerId, in: room) else {
            return nil
        }

        switch property {
            case "name":
                return ["name": peer.name]
            case "isLocal":
                return ["isLocal": peer.isLocal]
            case "type":
                return ["type": HMSDecoder.getPeerType(type: peer.type)]
            case "networkQuality":
                if peer.networkQuality != nil {
                    return ["networkQuality": HMSDecoder.getHmsNetworkQuality(peer.networkQuality)]
                } else {
                    return nil
                }
            case "metadata":
                return ["metadata": peer.metadata ?? ""]
            case "isHandRaised":
                return ["isHandRaised": peer.isHandRaised]
            case "role":
                return ["role": HMSDecoder.getHmsRole(peer.role)]
            case "customerUserID":
                return ["customerUserID": peer.customerUserID ?? ""]
            case "audioTrack":
                if peer.audioTrack != nil {
                    return ["audioTrack": HMSDecoder.getHmsAudioTrack(peer.audioTrack)]
                } else {
                    return nil
                }
            case "videoTrack":
                if peer.videoTrack != nil {
                    return ["videoTrack": HMSDecoder.getHmsVideoTrack(peer.videoTrack)]
                } else {
                    return nil
                }
            case "auxiliaryTracks":
                if let auxTracks = peer.auxiliaryTracks, auxTracks.count > 0 {
                    return ["auxiliaryTracks": HMSDecoder.getAllTracks(auxTracks)]
                } else {
                    return nil
                }
            default:
                return nil
        }
    }

    func getRoomProperty(_ data: NSDictionary) -> [AnyHashable: Any]? {
        guard let property = data.value(forKey: "property") as? String else {
            return nil
        }

        guard let hmsRoom = hms?.room else {
            return nil
        }

        switch property {
            case "sessionId":
                return ["sessionId": hmsRoom.sessionID ?? ""]
            case "name":
                return ["name": hmsRoom.name ?? ""]

            case "metaData":
                return ["metaData": hmsRoom.metaData ?? ""]

            case "peerCount":
                return ["peerCount": hmsRoom.peerCount as Any]

            case "peers":
                var peers = [[String: Any]]()
                for peer in hmsRoom.peers {
                    let parsedPeer = HMSDecoder.getHmsPeerSubset(peer)
                    peers.append(parsedPeer)
                }
                return ["peers": peers]

            case "localPeer":
                return ["localPeer": HMSDecoder.getHmsLocalPeer(hms?.localPeer)]

            case "browserRecordingState":
                return ["browserRecordingState": HMSDecoder.getBrowserRecordingState(hmsRoom.browserRecordingState)]

            case "rtmpHMSRtmpStreamingState":
                return ["rtmpHMSRtmpStreamingState": HMSDecoder.getRtmpStreamingState(hmsRoom.rtmpStreamingState)]

            case "serverRecordingState":
                return ["serverRecordingState": HMSDecoder.getServerRecordingState(hmsRoom.serverRecordingState)]

            case "hlsStreamingState":
                return ["hlsStreamingState": HMSDecoder.getHlsStreamingState(hmsRoom.hlsStreamingState)]

            case "hlsRecordingState":
                return ["hlsRecordingState": HMSDecoder.getHlsRecordingState(hmsRoom.hlsRecordingState)]

            case "isLargeRoom":
                return ["isLargeRoom": hmsRoom.isLarge]

            case "transcriptions":
                return ["transcriptions": HMSDecoder.getTranscriptionStates(hmsRoom.transcriptionStates)]

            default:
                return nil
        }
    }

    func getRemoteVideoTrackFromTrackId(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let self = self,
                  let remotePeers = self.hms?.remotePeers,
                  let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)
            else {
                let errorMessage = "\(#function) " + "TRACK_NOT_FOUND"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            resolve?(HMSDecoder.getHMSRemoteVideoTrack(remoteVideoTrack))
        }
    }

    func getRemoteAudioTrackFromTrackId(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let self = self,
                  let remotePeers = self.hms?.remotePeers,
                  let remoteAudioTrack = HMSHelper.getRemoteAudioTrackFromTrackId(trackId, remotePeers)
            else {
                let errorMessage = "\(#function) " + "TRACK_NOT_FOUND"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            resolve?(HMSDecoder.getHMSRemoteAudioTrack(remoteAudioTrack))
        }
    }

    // MARK: - HMS SDK Delegate Callbacks
    func on(join room: HMSRoom) {
        if eventsEnableStatus[HMSConstants.ON_JOIN] != true {
            return
        }
        let roomData = HMSDecoder.getHmsRoomSubset(room)
        self.delegate?.emitEvent(HMSConstants.ON_JOIN, ["event": HMSConstants.ON_JOIN, "id": self.id, "room": roomData])
    }

    func onPreview(room: HMSRoom, localTracks: [HMSTrack]) {
        previewInProgress = false
        if eventsEnableStatus[HMSConstants.ON_PREVIEW] != true {
            return
        }
        let previewTracks = HMSDecoder.getPreviewTracks(localTracks)
        let hmsRoom = HMSDecoder.getHmsRoomSubset(room)

        self.delegate?.emitEvent(HMSConstants.ON_PREVIEW, ["event": HMSConstants.ON_PREVIEW, "id": self.id, "room": hmsRoom, "previewTracks": previewTracks])
    }

    func on(room: HMSRoom, update: HMSRoomUpdate) {
        if eventsEnableStatus[HMSConstants.ON_ROOM_UPDATE] != true {
            return
        }

        let roomData = HMSDecoder.getHmsRoomSubset(room, update)
        let type = getString(from: update)

        self.delegate?.emitEvent(HMSConstants.ON_ROOM_UPDATE, ["event": HMSConstants.ON_ROOM_UPDATE, "id": self.id, "type": type, "room": roomData])
    }

    func onPeerListUpdate(added: [HMSPeer], removed: [HMSPeer]) {
        if eventsEnableStatus["ON_PEER_LIST_UPDATED"] != true {
            return
        }
        var addedPeers = [[String: Any]]()
        var removedPeers = [[String: Any]]()

        for peer in added {
            addedPeers.append(HMSDecoder.getHmsPeerSubset(peer))
        }

        for peer in removed {
            removedPeers.append(HMSDecoder.getHmsPeerSubset(peer))
        }

        self.delegate?.emitEvent(HMSConstants.ON_PEER_LIST_UPDATED, ["event": HMSConstants.ON_PEER_LIST_UPDATED, "id": self.id, "addedPeers": addedPeers, "removedPeers": removedPeers])
    }

    func on(peer: HMSPeer, update: HMSPeerUpdate) {

        guard let isPeerUpdateEnabled = eventsEnableStatus[HMSConstants.ON_PEER_UPDATE],
                isPeerUpdateEnabled
        else { return }

        if !networkQualityUpdatesAttached && update == .networkQualityUpdated {
            return
        }

        let hmsPeer = HMSDecoder.getHmsPeerSubsetForPeerUpdateEvent(peer, update)

        self.delegate?.emitEvent(HMSConstants.ON_PEER_UPDATE, hmsPeer)
    }

    func on(track: HMSTrack, update: HMSTrackUpdate, for peer: HMSPeer) {
        if peer.isLocal && track.source.uppercased() == "SCREEN" && track.kind == HMSTrackKind.video {
            if update == .trackAdded {
                isScreenShared = true
                startScreenshareResolve?(["success": true])
                startScreenshareResolve = nil
            } else if update == .trackRemoved {
                isScreenShared = false
                stopScreenshareResolve?(["success": true])
                stopScreenshareResolve = nil
            }
        }

        if roomMutedLocally && update == .trackAdded && track.kind == .audio  && !peer.isLocal, let room = self.hms?.room {
            if let audioTrack = HMSUtilities.getAudioTrack(for: track.trackId, in: room) {
                if audioTrack is HMSRemoteAudioTrack {
                    (audioTrack as! HMSRemoteAudioTrack).setPlaybackAllowed(!roomMutedLocally)
                }
            }
        }

        if #available(iOS 15.0, *),
            useActiveSpeakerInPIP,
            let controller = pipController,
            controller.isPictureInPictureActive,
            track.kind == .video,
            update == .trackRemoved,
            pipModel?.track == track {

            pipModel?.text = hms?.localPeer?.name
            pipModel?.track = nil
        }

        if eventsEnableStatus[HMSConstants.ON_TRACK_UPDATE] != true {
            return
        }

        let type = getString(from: update)
        let hmsPeer = HMSDecoder.getHmsPeerSubset(peer)
        let hmsTrack = HMSDecoder.getHmsTrack(track)

        self.delegate?.emitEvent(HMSConstants.ON_TRACK_UPDATE, ["event": HMSConstants.ON_TRACK_UPDATE, "id": self.id, "type": type, "peer": hmsPeer, "track": hmsTrack])
    }

    func on(error: Error) {
        if previewInProgress { previewInProgress = false }
        if eventsEnableStatus[HMSConstants.ON_ERROR] != true {
            return
        }
        self.delegate?.emitEvent(HMSConstants.ON_ERROR, ["error": HMSDecoder.getError(error), "id": id])
    }

    func on(message: HMSMessage) {
        if eventsEnableStatus[HMSConstants.ON_MESSAGE] != true {
            return
        }
        self.delegate?.emitEvent(HMSConstants.ON_MESSAGE, ["event": HMSConstants.ON_MESSAGE, "id": self.id, "sender": HMSDecoder.getHmsPeerSubset(message.sender), "recipient": HMSDecoder.getHmsMessageRecipient(message.recipient), "time": message.time.timeIntervalSince1970 * 1000, "message": message.message, "messageId": message.messageID, "type": message.type])
    }

    func on(updated speakers: [HMSSpeaker]) {

        if #available(iOS 15.0, *),
           useActiveSpeakerInPIP,
           let controller = pipController,
           controller.isPictureInPictureActive,
           let peer = speakers.first?.peer,
           let track = peer.videoTrack {

            if track.isMute() {
                pipModel?.text = peer.name
                pipModel?.track = nil
            } else {
                if peer.isLocal {
                    if #available(iOS 16.0, *) {
                        if AVCaptureSession().isMultitaskingCameraAccessSupported {
                            pipModel?.text = nil
                            pipModel?.track = track
                            return
                        }
                    }
                    pipModel?.text = peer.name
                    pipModel?.track = nil
                } else {
                    pipModel?.text = nil
                    pipModel?.track = track
                }
            }
        }

        if eventsEnableStatus[HMSConstants.ON_SPEAKER] != true {
            return
        }
        var speakerPeerIds: [[String: Any]] = []
        for speaker in speakers {
            speakerPeerIds.append(["peer": HMSDecoder.getHmsPeerSubset(speaker.peer), "level": speaker.level, "track": HMSDecoder.getHmsTrack(speaker.track)])
        }
        self.delegate?.emitEvent(HMSConstants.ON_SPEAKER, ["event": HMSConstants.ON_SPEAKER, "id": self.id, "speakers": speakerPeerIds])
    }

    func onReconnecting() {
        reconnectingStage = true
        if eventsEnableStatus[HMSConstants.RECONNECTING] != true {
            return
        }
        self.delegate?.emitEvent(HMSConstants.RECONNECTING, ["event": HMSConstants.RECONNECTING, "error": ["code": 1003, "description": "Network connection lost ", "isTerminal": false, "canRetry": true] as [String: Any], "id": self.id ])
    }

    func onReconnected() {
        reconnectingStage = false
        if eventsEnableStatus[HMSConstants.RECONNECTED] != true {
            return
        }
        self.delegate?.emitEvent(HMSConstants.RECONNECTED, ["event": HMSConstants.RECONNECTED, "id": self.id ])
    }

    func on(roleChangeRequest: HMSRoleChangeRequest) {
        recentRoleChangeRequest = roleChangeRequest
        if eventsEnableStatus[HMSConstants.ON_ROLE_CHANGE_REQUEST] != true {
            return
        }
        let decodedRoleChangeRequest = HMSDecoder.getHmsRoleChangeRequest(roleChangeRequest, self.id)
        self.delegate?.emitEvent(HMSConstants.ON_ROLE_CHANGE_REQUEST, decodedRoleChangeRequest)
    }

    func on(changeTrackStateRequest: HMSChangeTrackStateRequest) {
        if eventsEnableStatus[HMSConstants.ON_CHANGE_TRACK_STATE_REQUEST] != true {
            return
        }
        let decodedChangeTrackStateRequest = HMSDecoder.getHmsChangeTrackStateRequest(changeTrackStateRequest, id)
        delegate?.emitEvent(HMSConstants.ON_CHANGE_TRACK_STATE_REQUEST, decodedChangeTrackStateRequest)
    }

    func on(removedFromRoom notification: HMSRemovedFromRoomNotification) {

        if eventsEnableStatus[HMSConstants.ON_REMOVED_FROM_ROOM] != true {
            self.cleanup() // resetting states and doing data cleanup
            return
        }

        let requestedBy = notification.requestedBy as HMSPeer?
        var decodedRequestedBy: [String: Any]?
        if let requested = requestedBy {
            decodedRequestedBy = HMSDecoder.getHmsPeerSubset(requested)
        }
        let reason = notification.reason
        let roomEnded = notification.roomEnded
        self.delegate?.emitEvent(HMSConstants.ON_REMOVED_FROM_ROOM, ["event": HMSConstants.ON_REMOVED_FROM_ROOM, "id": self.id, "requestedBy": decodedRequestedBy as Any, "reason": reason, "roomEnded": roomEnded ])

        self.cleanup() // resetting states and doing data cleanup
    }

    func on(sessionStoreAvailable store: HMSSessionStore) {
        self.sessionStore = store
        if eventsEnableStatus[HMSConstants.ON_SESSION_STORE_AVAILABLE] != true {
            return
        }
        self.delegate?.emitEvent(HMSConstants.ON_SESSION_STORE_AVAILABLE, ["id": self.id])
    }

    func on(rtcStats: HMSRTCStatsReport) {
        if eventsEnableStatus[HMSConstants.ON_RTC_STATS] != true {
            return
        }
        let video = HMSDecoder.getHMSRTCStats(rtcStats.video) // [bitrateReceived, bitrateSent, bytesReceived, bytesSent, packetsLost, packetsReceived, roundTripTime]
        let audio = HMSDecoder.getHMSRTCStats(rtcStats.audio) // [bitrateReceived, bitrateSent, bytesReceived, bytesSent, packetsLost, packetsReceived, roundTripTime]
        let combined = HMSDecoder.getHMSRTCStats(rtcStats.combined) // [bitrateReceived, bitrateSent, bytesReceived, bytesSent, packetsLost, packetsReceived, roundTripTime]

        self.delegate?.emitEvent(HMSConstants.ON_RTC_STATS, ["video": video, "audio": audio, "combined": combined, "id": self.id])
    }

    func on(localAudioStats: HMSLocalAudioStats, track: HMSAudioTrack, peer: HMSPeer) {
        if eventsEnableStatus[HMSConstants.ON_LOCAL_AUDIO_STATS] != true {
            return
        }
        let localStats = HMSDecoder.getLocalAudioStats(localAudioStats) // [bitrate, bytesSent, roundTripTime]
        let localTrack = HMSDecoder.getHmsAudioTrack(track)
        let decodedPeer = HMSDecoder.getHmsPeerSubset(peer)

        self.delegate?.emitEvent(HMSConstants.ON_LOCAL_AUDIO_STATS, ["localAudioStats": localStats, "track": localTrack, "peer": decodedPeer, "id": self.id])
    }

    func on(localVideoStats: [HMSLocalVideoStats], track: HMSVideoTrack, peer: HMSPeer) { // DOUBT: HMSLocalVideoTrack instead of HMSVideoTrack?
        if eventsEnableStatus[HMSConstants.ON_LOCAL_VIDEO_STATS] != true {
            return
        }
        let localStats = HMSDecoder.getLocalVideoStats(localVideoStats) // List<[bitrate, bytesSent, roundTripTime, frameRate, resolution, layer]>
        let decodedPeer = HMSDecoder.getHmsPeerSubset(peer)
        let localTrack = HMSDecoder.getHmsVideoTrack(track)

        self.delegate?.emitEvent(HMSConstants.ON_LOCAL_VIDEO_STATS, ["localVideoStats": localStats, "track": localTrack, "peer": decodedPeer, "id": self.id])
    }

    func on(remoteAudioStats: HMSRemoteAudioStats, track: HMSAudioTrack, peer: HMSPeer) {
        if eventsEnableStatus[HMSConstants.ON_REMOTE_AUDIO_STATS] != true {
            return
        }
        let remoteStats = HMSDecoder.getRemoteAudioStats(remoteAudioStats) // [bitrate, bytesReceived, jitter, packetsLost, packetsReceived]
        let remoteTrack = HMSDecoder.getHmsAudioTrack(track)
        let decodedPeer = HMSDecoder.getHmsPeerSubset(peer)

        self.delegate?.emitEvent(HMSConstants.ON_REMOTE_AUDIO_STATS, ["remoteAudioStats": remoteStats, "track": remoteTrack, "peer": decodedPeer, "id": self.id])
    }

    func on(remoteVideoStats: HMSRemoteVideoStats, track: HMSVideoTrack, peer: HMSPeer) {
        if eventsEnableStatus[HMSConstants.ON_REMOTE_VIDEO_STATS] != true {
            return
        }
        let remoteStats = HMSDecoder.getRemoteVideoStats(remoteVideoStats) // [bitrate, bytesReceived, frameRate, jitter, packetsLost, packetsReceived, resolution]
        let decodedPeer = HMSDecoder.getHmsPeerSubset(peer)
        let remoteTrack = HMSDecoder.getHmsVideoTrack(track)

        self.delegate?.emitEvent(HMSConstants.ON_REMOTE_VIDEO_STATS, ["remoteVideoStats": remoteStats, "track": remoteTrack, "peer": decodedPeer, "id": self.id])
    }

    func on(transcripts: HMSTranscripts) {
        if eventsEnableStatus[HMSConstants.ON_TRANSCRIPTS] != true {
            return
        }
        let transcriptsArray = HMSDecoder.getHmsTranscripts(transcripts.transcripts)

        self.delegate?.emitEvent(HMSConstants.ON_TRANSCRIPTS, ["id": self.id, "transcripts": transcriptsArray])
    }

    // MARK: - Simulcast

    func getVideoTrackLayerDefinition(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let self = self,
                  let remotePeers = self.hms?.remotePeers,
                  let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)
            else {
                let errorMessage = "\(#function) " + "TRACK_NOT_FOUND"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            guard let layerDefinitions = remoteVideoTrack.layerDefinitions
            else {
                let errorMessage = "\(#function) " + "layer definitions not available for track: '\(trackId)' !"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            let parsedLayerDefinitions = HMSDecoder.getSimulcastLayerDefinitions(for: layerDefinitions)

            resolve?(parsedLayerDefinitions)
        }
    }

    func getVideoTrackLayer(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        guard let trackId = data.value(forKey: "trackId") as? String
        else {
            let errorMessage = "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["trackId"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let self = self,
                  let remotePeers = self.hms?.remotePeers,
                  let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)
            else {
                let errorMessage = "\(#function) " + "TRACK_NOT_FOUND"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            let parsedLayer = HMSDecoder.getString(from: remoteVideoTrack.layer)

            resolve?(parsedLayer)
        }
    }

    func setVideoTrackLayer(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let trackId = data.value(forKey: "trackId") as? String,
              let layer = data.value(forKey: "layer") as? String
        else {
            let errorMessage = "\(#function) " + HMSHelper.getUnavailableRequiredKey(data, ["trackId", "layer"])
            reject?(errorMessage, errorMessage, nil)
            return
        }

        DispatchQueue.main.async { [weak self] in

            guard let self = self,
                  let remotePeers = self.hms?.remotePeers,
                  let remoteVideoTrack = HMSHelper.getRemoteVideoTrackFromTrackId(trackId, remotePeers)
            else {
                let errorMessage = "\(#function) " + "TRACK_NOT_FOUND"
                reject?(errorMessage, errorMessage, nil)
                return
            }

            switch layer.uppercased() {
            case "LOW":
                remoteVideoTrack.layer = .low
            case "MEDIUM":
                remoteVideoTrack.layer = .mid
            default:
                remoteVideoTrack.layer = .high
            }

            resolve?(true)
        }
    }

    // MARK: - Advanced Camera Controls

    func captureImageAtMaxSupportedResolution(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        let withFlash = data["flash"] as? Bool ?? false

        DispatchQueue.main.async { [weak self] in

            guard let localPeer = self?.hms?.localPeer else {
                let errorMessage = "\(#function) An instance of Local Peer could not be found. Please check if a Room is joined."
                reject?("6004", errorMessage, nil)
                return
            }

            guard let localVideoTrack = localPeer.localVideoTrack()
            else {
                let errorMessage = "\(#function) Video Track of Local Peer could not be found. Please check if the Local Peer has permission to publish video & video is unmuted currently."
                reject?("6004", errorMessage, nil)
                return
            }

            localVideoTrack.captureImageAtMaxSupportedResolution(withFlash: withFlash) { image in

                guard let rawImage = image, let capturedImage = rawImage.fixOrientation() else {
                    let errorMessage = "\(#function) Could not capture image of the Local Peer's Video Track."
                    reject?("6004", errorMessage, nil)
                    return
                }

                guard let data = capturedImage.pngData() else {
                    let errorMessage = "\(#function) Could not compress image of the Local Peer's Video Track to png data."
                    reject?("6004", errorMessage, nil)
                    return
                }

                let filePath = HMSRNSDK.getDocumentsDirectory().appendingPathComponent("hms_\(HMSRNSDK.getTimeStamp()).png")

                do {
                    try data.write(to: filePath)

                    resolve?(filePath.relativePath)
                } catch let error {
                    let errorMessage = "\(#function) Could not write to disk the image data  of the Local Peer's Video Track. \(error.localizedDescription)"
                    reject?("6004", errorMessage, nil)
                    return
                }
            }
        }
    }

    // MARK: - Session Store

    func getSessionMetadataForKey(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        DispatchQueue.main.async { [weak self] in

            guard let store = self?.sessionStore
            else {
                let errorMessage = "\(#function) Session Store is null"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let data = data as? [AnyHashable: Any],
                let key = data["key"] as? String
            else {
                let errorMessage = "\(#function) Key to be fetched from Session Store is null." + HMSHelper.getUnavailableRequiredKey(data, ["key"])
                reject?("6004", errorMessage, nil)
                return
            }

            store.object(forKey: key) { value, error in

                if let error = error {
                    let errorMessage = "\(#function) Error in fetching key: \(key) from Session Store. Error: \(error.localizedDescription)"
                    reject?("6004", errorMessage, nil)
                    return
                }

                switch value {
                case .none:
                    resolve?(nil)
                case let boolValue as Bool:
                    resolve?(boolValue)
                case let doubleValue as Double:
                    resolve?(doubleValue)
                case let dictValue as [AnyHashable: Any]:
                    resolve?(dictValue)
                case let arrayValue as [Any]:
                    resolve?(arrayValue)
                case let stringValue as String:
                    resolve?(stringValue)
                default:
                    let errorMessage = "\(#function) Session Store value for the key: \(key) is not of valid type. Value: \(String(describing: value))"
                    reject?("6004", errorMessage, nil)
                }
            }
        }
    }

    func setSessionMetadataForKey(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        DispatchQueue.main.async { [weak self] in

            guard let store = self?.sessionStore
            else {
                let errorMessage = "\(#function) Session Store is null"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let data = data as? [AnyHashable: Any],
                let key = data["key"] as? String
            else {
                let errorMessage = "\(#function) Key for the object to be set in Session Store is null." + HMSHelper.getUnavailableRequiredKey(data, ["key"])
                reject?("6004", errorMessage, nil)
                return
            }

            let valueToBeSet = data["value"] as Any

            store.set(valueToBeSet, forKey: key) { _, error in

                if let error = error {
                    reject?("6004", error.localizedDescription, nil)
                    return
                }
                resolve?(["success": true])
            }
        }
    }

    func addKeyChangeListener(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        DispatchQueue.main.async { [weak self] in

            guard let store = self?.sessionStore
            else {
                let errorMessage = "\(#function) Session Store is null"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let data = data as? [AnyHashable: Any]
            else {
                let errorMessage = "\(#function) No arguments passed which can be attached to Key Change Listener on the Session Store."
                reject?("6004", errorMessage, nil)
                return
            }

            guard let keys = data["keys"] as? [String]
            else {
                let errorMessage = "\(#function) No keys passed which can be attached to Key Change Listener on the Session Store. Available arguments: \(data)"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let uniqueId = data["uniqueId"] as? String
            else {
                let errorMessage = "\(#function) No uniqueId passed which can be used to attach Key Change Listener on the Session Store. Available arguments: \(data)"
                reject?("6004", errorMessage, nil)
                return
            }

            store.observeChanges(forKeys: keys, changeObserver: { [weak self] key, value in

                var data = [String: Any]()

                data["id"] = self?.id

                data["key"] = key

                switch value {
                case .none:
                    data["value"] = nil
                case let boolValue as Bool:
                    data["value"] = boolValue
                case let doubleValue as Double:
                    data["value"] = doubleValue
                case let dictValue as [AnyHashable: Any]:
                    data["value"] = dictValue
                case let arrayValue as [Any]:
                    data["value"] = arrayValue
                case let stringValue as String:
                    data["value"] = stringValue
                default:
                    let message = "\(#function) Session Store value for the key: \(key) is not of valid type. Value: \(String(describing: value))"
                    print(message)
                    data["value"] = nil
                }
                self?.delegate?.emitEvent(HMSConstants.ON_SESSION_STORE_CHANGED, data)

            }) { [weak self] observer, error in

                    if let error = error {
                        let errorMessage = "\(#function) Error in observing changes for key: \(keys) in the Session Store. Error: \(error.localizedDescription)"
                        reject?("6004", errorMessage, nil)
                        return
                    }

                    guard let observer = observer
                    else {
                        let errorMessage = "\(#function) Unknown Error in observing changes for key: \(keys) in the Session Store."
                        reject?("6004", errorMessage, nil)
                        return
                    }

                    guard let self = self
                    else {
                        let errorMessage = "\(#function) Could not find self instance while observing changes for key: \(keys) in the Session Store."
                        reject?("6004", errorMessage, nil)
                        return
                    }

                    self.sessionStoreChangeObservers[uniqueId] = observer

                    resolve?(true)
                }
        }
    }

    func removeKeyChangeListener(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {

        DispatchQueue.main.async { [weak self] in

            guard let store = self?.sessionStore
            else {
                let errorMessage = "\(#function) Session Store is null"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let data = data as? [AnyHashable: Any]
            else {
                let errorMessage = "\(#function) No arguments passed which can be used to remove Key Change Listener from the Session Store."
                reject?("6004", errorMessage, nil)
                return
            }

            guard let uniqueId = data["uniqueId"] as? String
            else {
                let errorMessage = "\(#function) No uniqueId passed which can be used to remove Key Change Listener from the Session Store. Available arguments: \(data)"
                reject?("6004", errorMessage, nil)
                return
            }

            guard let observerToBeRemoved = self?.sessionStoreChangeObservers[uniqueId]
            else {
                let errorMessage = "\(#function) No listener found to remove for the uniqueId passed. Available arguments: \(data)"
                reject?("6004", errorMessage, nil)
                return
            }

            self?.sessionStoreChangeObservers.removeValue(forKey: uniqueId)

            store.removeObserver(observerToBeRemoved)

            resolve?(true)
        }
    }

    // MARK: - Peer List Iterator

    func getPeerListIterator(_ data: NSDictionary) -> [AnyHashable: Any]? {
        guard let uniqueId = data["uniqueId"] as? String else {
            print("Error in getPeerListIterator: uniqueId is not available")
            return nil
        }

        guard let hmssdk = hms else {
            print("Error in getPeerListIterator: HMSSDK is not available")
            return nil
        }

        var peerListIterator: HMSPeerListIterator

        if let peerListIteratorOptions = HMSHelper.getPeerListIteratorOptions(data) {
            peerListIterator = hmssdk.getPeerListIterator(options: peerListIteratorOptions)
        } else {
            peerListIterator = hmssdk.getPeerListIterator()
        }

        peerListIterators[uniqueId] = peerListIterator

        return [
            "success": true,
            "uniqueId": uniqueId,
            "totalCount": peerListIterator.totalCount
        ]
    }

    func peerListIteratorHasNext(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let uniqueId = data["uniqueId"] as? String else {
            let errorMessage = "\(#function) uniqueId is not available"
            reject?("6004", errorMessage, nil)
            return
        }

        guard let peerListIterator = peerListIterators[uniqueId] else {
            let errorMessage = "\(#function) HMSPeerListIterator is not available for uniqueId - \(uniqueId)"
            reject?("6004", errorMessage, nil)
            return
        }

        resolve?(peerListIterator.hasNext)
    }

    func peerListIteratorNext(_ data: NSDictionary, _ resolve: RCTPromiseResolveBlock?, _ reject: RCTPromiseRejectBlock?) {
        guard let uniqueId = data["uniqueId"] as? String else {
            let errorMessage = "\(#function) uniqueId is not available"
            reject?("6004", errorMessage, nil)
            return
        }

        guard let peerListIterator = peerListIterators[uniqueId] else {
            let errorMessage = "\(#function) HMSPeerListIterator is not available for uniqueId - \(uniqueId)"
            reject?("6004", errorMessage, nil)
            return
        }

        peerListIterator.next { peers, error in
            if let nonnilError = error {
                reject?("6004", nonnilError.localizedDescription, nil)
                return
            }

            if let nonnilPeers = peers {
                var data = [[String: Any]]()
                for peer in nonnilPeers {
                    data.append(HMSDecoder.getHmsPeer(peer))
                }
                resolve?(["totalCount": peerListIterator.totalCount, "peers": data])
            } else {
                let errorMessage = "\(#function) peers is nil"
                reject?("6004", errorMessage, nil)
            }
        }
    }

    // MARK: - Noise Cancellation Plugin Functions

    func enableNoiseCancellationPlugin(_ data: NSDictionary,
                                       _ resolve: RCTPromiseResolveBlock?,
                                       _ reject: RCTPromiseRejectBlock?) {

        guard let noiseCancellationPlugin = self.noiseCancellationPlugin else {
            let errorMessage = "\(#function) noiseCancellationPlugin instance is not available!"
            reject?("6004", errorMessage, nil)
            return
        }
        do {
            try noiseCancellationPlugin.enable()
            resolve?(true)
        } catch {
            reject?("6004", error.localizedDescription, nil)
        }
    }

    func disableNoiseCancellationPlugin(_ data: NSDictionary,
                                        _ resolve: RCTPromiseResolveBlock?,
                                        _ reject: RCTPromiseRejectBlock?) {

        guard let noiseCancellationPlugin = self.noiseCancellationPlugin else {
            let errorMessage = "\(#function) noiseCancellationPlugin instance is not available!"
            reject?("6004", errorMessage, nil)
            return
        }
        do {
            try noiseCancellationPlugin.disable()
            resolve?(true)
        } catch {
            reject?("6004", error.localizedDescription, nil)
        }
    }

    func isNoiseCancellationPluginEnabled(_ data: NSDictionary,
                                          _ resolve: RCTPromiseResolveBlock?,
                                          _ reject: RCTPromiseRejectBlock?) {

        guard let noiseCancellationPlugin = self.noiseCancellationPlugin else {
            let errorMessage = "\(#function) noiseCancellationPlugin instance is not available!"
            reject?("6004", errorMessage, nil)
            return
        }
        let isEnabled = noiseCancellationPlugin.isEnabled()
        resolve?(isEnabled)
    }

    func isNoiseCancellationPluginAvailable(_ data: NSDictionary,
                                            _ resolve: RCTPromiseResolveBlock?,
                                            _ reject: RCTPromiseRejectBlock?) {

        guard let noiseCancellationPlugin = self.noiseCancellationPlugin else {
            let errorMessage = "\(#function) noiseCancellationPlugin instance is not available!"
            reject?("6004", errorMessage, nil)
            return
        }
        let isAvailable = noiseCancellationPlugin.isNoiseCancellationAvailable
        resolve?(isAvailable)
    }

    // MARK: - Video Plugins Functions

    func enableVideoPlugin(_ data: NSDictionary,
                           _ resolve: RCTPromiseResolveBlock?,
                           _ reject: RCTPromiseRejectBlock?) {
        guard let videoPluginType = data.value(forKey: "type") as? String else {
            let errorMessage = "\(#function) HMSVideoPlugin type not passed!"
            reject?("6004", errorMessage, nil)
            return
        }
        switch videoPluginType {
        case "HMSVirtualBackgroundPlugin":
            if #available(iOS 15.0, *) {
                guard let virtualBackgroundPlugin = self.virtualBackgroundPlugin as? HMSVirtualBackgroundPlugin else {
                    let errorMessage = "\(#function) Unable to cast `var virtualBackgroundPlugin` to type `HMSVirtualBackgroundPlugin`, It is \(String(describing: virtualBackgroundPlugin))"
                    reject?("6004", errorMessage, nil)
                    return
                }
                virtualBackgroundPlugin.activate()
                resolve?(true)
            } else {
                let errorMessage = "\(#function) HMSVirtualBackgroundPlugin not available below iOS 15.0"
                reject?("6004", errorMessage, nil)
                return
            }
        case "HMSVideoFilterPlugin":
            guard let videoFilterPlugin = self.videoFilterPlugin else {
                let errorMessage = "\(#function) `videoFilterPlugin` is `nil`, Make sure you are passing `HMSVideoFilterPlugin` instance to `videoTrackSettings` in `HMSSDK.build`"
                reject?("6004", errorMessage, nil)
                return
            }
            videoFilterPlugin.activate()
            resolve?(true)
        default:
            let errorMessage = "\(#function) Unknown HMSVideoPlugin type passed!"
            reject?("6004", errorMessage, nil)
            return
        }
    }

    func disableVideoPlugin(_ data: NSDictionary,
                            _ resolve: RCTPromiseResolveBlock?,
                            _ reject: RCTPromiseRejectBlock?) {
        guard let videoPluginType = data.value(forKey: "type") as? String else {
            let errorMessage = "\(#function) HMSVideoPlugin `type` not passed!"
            reject?("6004", errorMessage, nil)
            return
        }
        switch videoPluginType {
        case "HMSVirtualBackgroundPlugin":
            if #available(iOS 15.0, *) {
                guard let virtualBackgroundPlugin = self.virtualBackgroundPlugin as? HMSVirtualBackgroundPlugin else {
                    let errorMessage = "\(#function) Unable to cast `var virtualBackgroundPlugin` to type `HMSVirtualBackgroundPlugin`, It is \(String(describing: virtualBackgroundPlugin))"
                    reject?("6004", errorMessage, nil)
                    return
                }
                virtualBackgroundPlugin.deactivate()
                resolve?(true)
            } else {
                let errorMessage = "\(#function) HMSVirtualBackgroundPlugin not available below iOS 15.0"
                reject?("6004", errorMessage, nil)
                return
            }
        case "HMSVideoFilterPlugin":
            guard let videoFilterPlugin = self.videoFilterPlugin else {
                let errorMessage = "\(#function) `videoFilterPlugin` is `nil`, Make sure you are passing `HMSVideoFilterPlugin` instance to `videoTrackSettings` in `HMSSDK.build`"
                reject?("6004", errorMessage, nil)
                return
            }
            videoFilterPlugin.deactivate()
            resolve?(true)
        default:
            let errorMessage = "\(#function) Unknown HMSVideoPlugin `type` passed!"
            reject?("6004", errorMessage, nil)
            return
        }
    }

    func changeVirtualBackground(_ data: NSDictionary,
                            _ resolve: RCTPromiseResolveBlock?,
                            _ reject: RCTPromiseRejectBlock?) {
        if #available(iOS 15.0, *) {
            guard let backgroundDict = data.value(forKey: "background") as? NSDictionary
            else {
                let errorMessage = "\(#function) No background object passed!"
                reject?("6004", errorMessage, nil)
                return
            }
            guard let backgroundType = backgroundDict.value(forKey: "type") as? String
            else {
                let errorMessage = "\(#function) No background `type` passed!"
                reject?("6004", errorMessage, nil)
                return
            }
            guard let virtualBackgroundPlugin = self.virtualBackgroundPlugin as? HMSVirtualBackgroundPlugin else {
                let errorMessage = "\(#function) Unable to cast `var virtualBackgroundPlugin` to type `HMSVirtualBackgroundPlugin`, It is \(String(describing: virtualBackgroundPlugin))"
                reject?("6004", errorMessage, nil)
                return
            }
            switch backgroundType {
            case "blur":
                virtualBackgroundPlugin.backgroundImage = nil
                resolve?(true)
            case "image":
                guard let imageSource = backgroundDict.value(forKey: "source") as? NSDictionary else {
                    let errorMessage = "\(#function) No background `source` passed for image!"
                    reject?("6004", errorMessage, nil)
                    return
                }

                // Check if it's a remote URL
                if let uriString = imageSource.value(forKey: "uri") as? String,
                   (uriString.hasPrefix("http://") || uriString.hasPrefix("https://")),
                   let url = URL(string: uriString) {
                    // Download remote image
                    URLSession.shared.dataTask(with: url) { data, response, error in
                        DispatchQueue.main.async {
                            if let error = error {
                                let errorMessage = "\(#function) Failed to download image: \(error.localizedDescription)"
                                reject?("6004", errorMessage, nil)
                                return
                            }
                            guard let data = data, let image = UIImage(data: data) else {
                                let errorMessage = "\(#function) Unable to create `UIImage` from downloaded data!"
                                reject?("6004", errorMessage, nil)
                                return
                            }
                            virtualBackgroundPlugin.backgroundImage = image
                            resolve?(true)
                        }
                    }.resume()
                } else {
                    // Use RCTConvert for local files and bundled resources
                    DispatchQueue.main.async {
                        guard let image = RCTConvert.uiImage(imageSource) else {
                            let errorMessage = "\(#function) Unable to create `UIImage` from given background `source` object!"
                            reject?("6004", errorMessage, nil)
                            return
                        }
                        virtualBackgroundPlugin.backgroundImage = image
                        resolve?(true)
                    }
                }
            default:
                let errorMessage = "\(#function) Unknown background `type` passed!"
                reject?("6004", errorMessage, nil)
                return
            }
        } else {
            let errorMessage = "\(#function) HMSVirtualBackgroundPlugin not available below iOS 15.0"
            reject?("6004", errorMessage, nil)
            return
        }
    }

    func setVideoFilterParameter(_ data: NSDictionary,
                                 _ resolve: RCTPromiseResolveBlock?,
                                 _ reject: RCTPromiseRejectBlock?) {
        guard let videoFilterPlugin = self.videoFilterPlugin else {
            let errorMessage = "\(#function) `videoFilterPlugin` is `nil`, Make sure you are passing `HMSVideoFilterPlugin` instance to `videoTrackSettings` in `HMSSDK.build`"
            reject?("6004", errorMessage, nil)
            return
        }
        guard let filterType = data.value(forKey: "filter") as? String else {
            let errorMessage = "\(#function) `filter` property not passed!"
            reject?("6004", errorMessage, nil)
            return
        }
        guard let filterValue = data.value(forKey: "value") as? NSNumber else {
            let errorMessage = "\(#function) `value` property not passed!"
            reject?("6004", errorMessage, nil)
            return
        }
        switch filterType {
        case "brightness":
            videoFilterPlugin.brightness = CGFloat(truncating: filterValue)
        case "contrast":
            videoFilterPlugin.contrast = CGFloat(truncating: filterValue)
        case "exposure":
            videoFilterPlugin.exposure = CGFloat(truncating: filterValue)
        case "hue":
            videoFilterPlugin.hue = CGFloat(truncating: filterValue)
        case "redness":
            videoFilterPlugin.redness = CGFloat(truncating: filterValue)
        case "saturation":
            videoFilterPlugin.saturation = CGFloat(truncating: filterValue)
        case "sharpness":
            videoFilterPlugin.sharpness = CGFloat(truncating: filterValue)
        case "smoothness":
            videoFilterPlugin.smoothness = CGFloat(truncating: filterValue)
        default:
            let errorMessage = "\(#function) Unknown `filter` type passed!"
            reject?("6004", errorMessage, nil)
            return
        }
        resolve?(true)
    }

    // MARK: - WebRTC Transcriptions

    func handleRealTimeTranscription(_ data: NSDictionary,
                                     _ resolve: RCTPromiseResolveBlock?,
                                     _ reject: RCTPromiseRejectBlock?) {
        guard let action = data.value(forKey: "action") as? String else {
            reject?("\(#function): `action` key not passed", "\(#function): `action` key not passed", nil)
            return
        }
        switch action {
        case "start":
    startRealTimeTranscription(data, resolve, reject)
            case "stop":
    stopRealTimeTranscription(data, resolve, reject)
        default:
            reject?("\(#function): Unknown `action` key passed", "\(#function): Unknown `action` key passed", nil)
        }
    }

    private func startRealTimeTranscription(_ data: NSDictionary,
                                    _ resolve: RCTPromiseResolveBlock?,
                                    _ reject: RCTPromiseRejectBlock?) {
        guard let hmssdk = hms else {
            reject?("\(#function): HMSSDK not available", "\(#function): HMSSDK not available", nil)
            return
        }
        hmssdk.startTranscription { success, error in
            if let error = error {
                reject?(error.localizedDescription, error.localizedDescription, nil)
                return
            }
            resolve?(success)
        }
    }

    private func stopRealTimeTranscription(_ data: NSDictionary,
                                   _ resolve: RCTPromiseResolveBlock?,
                                   _ reject: RCTPromiseRejectBlock?) {
        guard let hmssdk = hms else {
            reject?("\(#function): HMSSDK not available", "\(#function): HMSSDK not available", nil)
            return
        }
        hmssdk.stopTranscription { success, error in
            if let error = error {
                reject?(error.localizedDescription, error.localizedDescription, nil)
                return
            }
            resolve?(success)
        }
    }

    // MARK: - PIP Mode Support

    internal var _pipVideoCallViewController: Any?

    @available(iOS 15.0, *)
    internal var pipVideoCallViewController: AVPictureInPictureVideoCallViewController? {
        if _pipVideoCallViewController == nil {
            _pipVideoCallViewController = AVPictureInPictureVideoCallViewController()
        }
        return _pipVideoCallViewController as? AVPictureInPictureVideoCallViewController
    }

    internal var _pipModel: Any?

    @available(iOS 15.0, *)
    internal var pipModel: HMSPipModel? {
        if _pipModel == nil {
            _pipModel = HMSPipModel()
        }
        return _pipModel as? HMSPipModel
    }

    internal var pipController: AVPictureInPictureController?

    private var useActiveSpeakerInPIP: Bool = true

    @available(iOS 15.0, *)
    func setPictureInPictureParams(_ data: NSDictionary,
                                   _ resolve: RCTPromiseResolveBlock?,
                                   _ reject: RCTPromiseRejectBlock?) {

        guard AVPictureInPictureController.isPictureInPictureSupported() else {
            let errorMessage = "\(#function) PIP is not supported on this device"
            reject?("6004", errorMessage, nil)
            return
        }

        guard let uiView = UIApplication.shared.keyWindow?.rootViewController?.view else {
            let errorMessage = "\(#function) Failed to setup PIP"
            reject?("6004", errorMessage, nil)
            return
        }

        pipModel?.pipViewEnabled = true

        if let scaleType = data["scaleType"] as? String {
            pipModel?.scaleType = getViewContentMode(scaleType)
        } else {
            pipModel?.scaleType = .scaleAspectFill
        }

        pipModel?.color = .black
        pipModel?.text = hms?.localPeer?.name

        let controller = UIHostingController(rootView: HMSPipView(model: pipModel!))

        pipVideoCallViewController?.view.addConstrained(subview: controller.view)

        if let ratio = data["aspectRatio"] as? [Int], ratio.count == 2 {
            pipVideoCallViewController?.preferredContentSize = CGSize(width: ratio[0], height: ratio[1])
        } else {
            pipVideoCallViewController?.preferredContentSize = CGSize(width: uiView.frame.size.width, height: uiView.frame.size.height)
        }

        guard let pipVideoCallViewController = pipVideoCallViewController else {
            let errorMessage = "\(#function) Failed to setup PIP"
            reject?("6004", errorMessage, nil)
            return
        }

        let pipContentSource = AVPictureInPictureController.ContentSource(activeVideoCallSourceView: uiView, contentViewController: pipVideoCallViewController)

        pipController = AVPictureInPictureController(contentSource: pipContentSource)

        pipController?.delegate = self

        pipController?.canStartPictureInPictureAutomaticallyFromInline = true

        if let autoEnterPIP = data["autoEnterPipMode"] as? Bool {
            pipController?.canStartPictureInPictureAutomaticallyFromInline = autoEnterPIP
        }

        NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification,
                                               object: nil, queue: .main) { [weak self] _ in
            self?.stopPIP(nil, nil)
        }

        resolve?(true)
    }

    func enterPipMode(_ resolve: RCTPromiseResolveBlock?,
                      _ reject: RCTPromiseRejectBlock?) {
        pipController?.startPictureInPicture()
        resolve?(true)
    }

    func stopPIP(_ resolve: RCTPromiseResolveBlock?,
                 _ reject: RCTPromiseRejectBlock?) {
        pipController?.stopPictureInPicture()
        resolve?(nil)
    }

    func disposePIP(_ resolve: RCTPromiseResolveBlock?,
                    _ reject: RCTPromiseRejectBlock?) {
        pipController = nil
        _pipModel = nil
        _pipVideoCallViewController = nil
        NotificationCenter.default.removeObserver(UIApplication.didBecomeActiveNotification)
        resolve?(nil)
    }

    func isPIPActive(_ resolve: RCTPromiseResolveBlock?,
                     _ reject: RCTPromiseRejectBlock?) {
        if pipController != nil && pipController!.isPictureInPictureActive {
            resolve?(true)
        } else {
            resolve?(false)
        }
    }

    /// Change the video track in PIP Mode
    /// - Parameters:
    ///   - data: Data containing the trackId of the video track to be changed
    ///   - resolve: Promise resolve block
    ///   - reject: Promise reject block
    @available(iOS 15.0, *)
    func changeIOSPIPVideoTrack(_ data: NSDictionary,
                                _ resolve: RCTPromiseResolveBlock?,
                                _ reject: RCTPromiseRejectBlock?) {

        guard let trackID = data["trackId"] as? String,
              let room = hms?.room,
              let track = HMSUtilities.getVideoTrack(for: trackID, in: room)
        else {
            let errorMessage = "\(#function) Incorrect data passed for changing track in PIP Mode"
            reject?("6004", errorMessage, nil)
            return
        }

        useActiveSpeakerInPIP = false
        pipModel?.track = track
        resolve?(nil)
    }

    func setActiveSpeakerInIOSPIP(_ data: NSDictionary,
                                  _ resolve: RCTPromiseResolveBlock?,
                                  _ reject: RCTPromiseRejectBlock?) {
        guard let enabled = data["enable"] as? Bool else {
            let errorMessage = "\(#function) Incorrect data passed for setActiveSpeakerInIOSPIP in PIP Mode"
            reject?("6004", errorMessage, nil)
            return
        }

        useActiveSpeakerInPIP = enabled
        resolve?(nil)
    }

    // MARK: - Helper Functions

    // Handle resetting states and data cleanup
    private func cleanup() {
        self.recentRoleChangeRequest = nil
        self.previewForRoleTracks = nil
        self.reconnectingStage = false
        self.preferredExtension = nil
        self.systemBroadcastPicker = nil
        self.startScreenshareResolve = nil
        self.stopScreenshareResolve = nil
        self.isScreenShared = false
        self.previewInProgress = false
        self.networkQualityUpdatesAttached = false
        self.eventsEnableStatus.removeAll()
        self.sessionStore = nil
        self.sessionStoreChangeObservers.removeAll()
        self.peerListIterators.removeAll()
        self.roomMutedLocally = false
        HMSDecoder.clearRestrictDataStates()
    }

    private func getString(from update: HMSPeerUpdate) -> String {
        switch update {
        case .peerJoined:
            return "PEER_JOINED"
        case .peerLeft:
            return "PEER_LEFT"
        case .roleUpdated:
            return "ROLE_CHANGED"
        case .metadataUpdated:
            return "METADATA_CHANGED"
        case .nameUpdated:
            return "NAME_CHANGED"
        case .defaultUpdate:
            return "DEFAULT_UPDATE"
        case .networkQualityUpdated:
            return "NETWORK_QUALITY_UPDATED"
        default:
            return ""
        }
    }

    private func getString(from update: HMSTrackUpdate) -> String {
        switch update {
        case .trackAdded:
            return "TRACK_ADDED"
        case .trackRemoved:
            return "TRACK_REMOVED"
        case .trackMuted:
            return "TRACK_MUTED"
        case .trackUnmuted:
            return "TRACK_UNMUTED"
        case .trackDescriptionChanged:
            return "TRACK_DESCRIPTION_CHANGED"
        case .trackDegraded:
            return "TRACK_DEGRADED"
        case .trackRestored:
            return "TRACK_RESTORED"
        default:
            return ""
        }
    }

    func getString(from update: HMSRoomUpdate) -> String {
        switch update {
        case .roomTypeChanged, .metaDataUpdated, .peerCountUpdated:
            return "ROOM_PEER_COUNT_UPDATED"
        case .browserRecordingStateUpdated:
            return "BROWSER_RECORDING_STATE_UPDATED"
        case .hlsStreamingStateUpdated:
            return "HLS_STREAMING_STATE_UPDATED"
        case .rtmpStreamingStateUpdated:
            return "RTMP_STREAMING_STATE_UPDATED"
        case .serverRecordingStateUpdated:
            return "SERVER_RECORDING_STATE_UPDATED"
        case .hlsRecordingStateUpdated:
            return "HLS_RECORDING_STATE_UPDATED"
        case .transcriptionStateUpdated:
            return "TRANSCRIPTIONS_UPDATED"
        default:
            return ""
        }
    }

    func emitRequiredKeysError(_ error: String) {
        if eventsEnableStatus[HMSConstants.ON_ERROR] != true {
            return
        }
        delegate?.emitEvent(HMSConstants.ON_ERROR, ["error": ["code": 7000, "description": error, "isTerminal": false, "canRetry": true] as [String: Any], "id": id])
    }

    static private func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    static private func getTimeStamp() -> String {
        "\(Date().timeIntervalSince1970)"
    }

    private func getViewContentMode(_ type: String?) -> UIView.ContentMode {
        switch type {
        case "ASPECT_FILL":
            return .scaleAspectFill
        case "ASPECT_FIT":
            return .scaleAspectFit
        case "ASPECT_BALANCED":
            return .center
        default:
            return .scaleAspectFill
        }
    }
}

extension UIImage {
    func fixOrientation() -> UIImage? {
        if self.imageOrientation == UIImage.Orientation.up {
            return self
        }

        UIGraphicsBeginImageContext(self.size)
        self.draw(in: CGRect(origin: .zero, size: self.size))
        let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return normalizedImage
    }
}

extension HMSRNSDK: AVPictureInPictureControllerDelegate {

    public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        print(#function)
    }

    public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {

        if eventsEnableStatus[HMSConstants.ON_PIP_MODE_CHANGED] != true {
            return
        }

        self.delegate?.emitEvent(HMSConstants.ON_PIP_MODE_CHANGED,
                                 ["event": HMSConstants.ON_PIP_MODE_CHANGED,
                                  "id": self.id,
                                  "isInPictureInPictureMode": true])
    }

    public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        print(#function)
    }

    public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        if eventsEnableStatus[HMSConstants.ON_PIP_MODE_CHANGED] != true {
            return
        }

        self.delegate?.emitEvent(HMSConstants.ON_PIP_MODE_CHANGED,
                                 ["event": HMSConstants.ON_PIP_MODE_CHANGED,
                                  "id": self.id,
                                  "isInPictureInPictureMode": false])
    }

    public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
        if eventsEnableStatus[HMSConstants.ON_PIP_MODE_CHANGED] != true {
            return
        }

        self.delegate?.emitEvent(HMSConstants.ON_PIP_MODE_CHANGED,
                                 ["event": HMSConstants.ON_PIP_MODE_CHANGED,
                                  "id": self.id,
                                  "isInPictureInPictureMode": false])
    }

    public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
        print(#function)
    }
}

extension UIView {
    func addConstrained(subview: UIView) {
        addSubview(subview)
        subview.translatesAutoresizingMaskIntoConstraints = false
        subview.topAnchor.constraint(equalTo: topAnchor).isActive = true
        subview.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        subview.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        subview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
}
