#import "RNInCallManager.h"
#import "RTKLogger.h"
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTUtils.h>

//static BOOL const automatic = YES;

@implementation RNInCallManager

{
        
    UIDevice *_currentDevice;
    bool hasListeners;

    AVAudioSession *_audioSession;
    BOOL _isUpdatingAudioRoute;

    //BOOL isProximitySupported;
    BOOL _proximityIsNear;

    // --- tags to indicating which observer has added
    BOOL _isProximityRegistered;
    BOOL _isAudioSessionInterruptionRegistered;
    BOOL _isAudioSessionRouteChangeRegistered;
    BOOL _isAudioSessionMediaServicesWereLostRegistered;
    BOOL _isAudioSessionMediaServicesWereResetRegistered;
    BOOL _isAudioSessionSilenceSecondaryAudioHintRegistered;

    // -- notification observers
    id _proximityObserver;
    id _audioSessionInterruptionObserver;
    id _audioSessionRouteChangeObserver;
    id _audioSessionMediaServicesWereLostObserver;
    id _audioSessionMediaServicesWereResetObserver;
    id _audioSessionSilenceSecondaryAudioHintObserver;

    NSString *_incallAudioMode;
    NSString *_incallAudioCategory;
    NSString *_origAudioCategory;
    NSString *_origAudioMode;
    NSString *_userSelectedAudioRoute;
    NSString *_currentAudioRoute;
    BOOL _audioSessionInitialized;
    int _forceSpeakerOn;
    int _turnBluetoothOn;
}

- (BOOL)canSendEvents {
    return self.bridge != nil;
}

// Will be called when this module's first listener is added.
-(void)startObserving {
    hasListeners = YES;
    // Set up any upstream listeners or background tasks as necessary
}

// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
    hasListeners = NO;
    // Remove upstream listeners, stop unnecessary background tasks
}

+ (BOOL)requiresMainQueueSetup
{
    return NO;
}

RCT_EXPORT_MODULE(InCallManager)

- (instancetype)init
{
    if (self = [super init]) {
        _currentDevice = [UIDevice currentDevice];
        _audioSession = [AVAudioSession sharedInstance];

        _proximityIsNear = NO;

        _isProximityRegistered = NO;
        _isAudioSessionInterruptionRegistered = NO;
        _isAudioSessionRouteChangeRegistered = NO;
        _isAudioSessionMediaServicesWereLostRegistered = NO;
        _isAudioSessionMediaServicesWereResetRegistered = NO;
        _isAudioSessionSilenceSecondaryAudioHintRegistered = NO;

        _proximityObserver = nil;
        _audioSessionInterruptionObserver = nil;
        _audioSessionRouteChangeObserver = nil;
        _audioSessionMediaServicesWereLostObserver = nil;
        _audioSessionMediaServicesWereResetObserver = nil;
        _audioSessionSilenceSecondaryAudioHintObserver = nil;

        _incallAudioMode = AVAudioSessionModeVideoChat;
        _incallAudioCategory = AVAudioSessionCategoryPlayAndRecord;
        _origAudioCategory = nil;
        _origAudioMode = nil;
        _audioSessionInitialized = NO;
        _forceSpeakerOn = 0;
        _turnBluetoothOn = 0;
        _userSelectedAudioRoute = nil;
        _currentAudioRoute = nil;
        _isUpdatingAudioRoute = NO;

        [RTKLogger debug:@"RNInCallManager.init(): initialized"];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self stop:@""];
}

//Supported events
- (NSArray<NSString *> *)supportedEvents
{
    return @[@"Proximity",
             @"WiredHeadset",
             @"onAudioDeviceChanged",
             @"Bluetooth"];
}

RCT_EXPORT_METHOD(start)
{
    [RTKLogger debug:@"Starting InCall manager"];
    if (_audioSessionInitialized) {
        return;
    }
    
    // Default to video call behavior (speaker)
    _incallAudioMode = AVAudioSessionModeVideoChat;
    
    [RTKLogger debug:@"RNInCallManager.start() start InCallManager. mode=%@", _incallAudioMode];
    [self storeOriginalAudioSetup];
    
    // Default to speaker for video calls
    _forceSpeakerOn = 1;
    
    [self startAudioSessionNotification];
    
    // Always set category with AllowBluetooth to keep Bluetooth visible
    [self audioSessionSetCategory:_incallAudioCategory
                          options:AVAudioSessionCategoryOptionAllowBluetooth
                       callerMemo:NSStringFromSelector(_cmd)];
    [self audioSessionSetMode:_incallAudioMode
                   callerMemo:NSStringFromSelector(_cmd)];
    [self audioSessionSetActive:YES
                        options:0
                     callerMemo:NSStringFromSelector(_cmd)];

    // Route to speaker by default
    // Don't call routeAudioFromSpeakerphone() to avoid setting category twice
    NSError *error = nil;
    [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
    if (error) {
        [RTKLogger error:@"Failed to route to speaker on start: %@", error];
    } else {
        [RTKLogger debug:@"Routed to speaker on start (Bluetooth stays visible)"];
    }
    
    // Send initial device event
    [self sendAudioDeviceEvent];

    // Give iOS time to discover Bluetooth devices and send initial device list
    // This is needed for setup screens where audio tracks aren't acquired yet
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), 
                   dispatch_get_main_queue(), ^{
        [RTKLogger debug:@"Sending delayed audio device event for setup screen"];
        [self sendAudioDeviceEvent];
    });

    [self setKeepScreenOn:YES];
    _audioSessionInitialized = YES;
    
    [RTKLogger debug:@"Audio Session Initialized with default speaker routing"];
}

RCT_EXPORT_METHOD(setVoiceCallMode)
{
    [RTKLogger debug:@"Setting voice call mode (earpiece default)"];
    
    // Switch to audio call behavior (earpiece)
    _incallAudioMode = AVAudioSessionModeVoiceChat;
    _forceSpeakerOn = 0;
    
    // Apply the mode change to the audio session
    [self audioSessionSetMode:_incallAudioMode
                   callerMemo:NSStringFromSelector(_cmd)];
    
    // Route to earpiece
    [self routeAudioFromEarpiece];
    
    [RTKLogger debug:@"Voice call mode set - audio routed to earpiece"];
}

RCT_EXPORT_METHOD(chooseAudioRoute: (NSString *)audioRoute  Promise:(RCTPromiseResolveBlock)resolve
                                 reject:(RCTPromiseRejectBlock)reject) {
    [RTKLogger debug:@"choseAudio Route Called"];
    [RTKLogger debug:@"Selected Audio Route: %@, Current Route: %@", audioRoute, _currentAudioRoute];
    BOOL success = NO;
//    SPEAKER_PHONE,
//    WIRED_HEADSET,
//    EARPIECE,
//    BLUETOOTH,
//    NONE
// TODO: Create enums for AudioRoute
    // This ensures we override any iOS auto-routing behavior
    if ([audioRoute  isEqual: @"SPEAKER_PHONE"] || [audioRoute isEqualToString:@"speaker"]) {
        _forceSpeakerOn = 1;
        _userSelectedAudioRoute = @"speaker";
        success = [self routeAudioFromSpeakerphone];
    } else if ([audioRoute isEqualToString:@"WIRED_HEADSET"] || [audioRoute isEqualToString:@"wired"]) {
        _forceSpeakerOn = 0;
        _userSelectedAudioRoute = @"wired";
        success = [self routeAudioFromEarpiece];
    } else if ([audioRoute  isEqual: @"EARPIECE"] || [audioRoute isEqualToString:@"earpiece"]) {
        _forceSpeakerOn = 0;
        _userSelectedAudioRoute = @"earpiece";
        success = [self routeAudioFromEarpiece];
    } else if ([audioRoute isEqualToString:@"BLUETOOTH"] || [audioRoute isEqualToString:@"bluetooth"]) {
        _forceSpeakerOn = 0;
        _userSelectedAudioRoute = @"bluetooth";
        success = [self routeAudioFromBluetooth];
    }
    
    if (success) {
        _currentAudioRoute = _userSelectedAudioRoute;
        // Send event to notify JavaScript of the route change
        // This ensures immediate feedback even before iOS route change notifications fire
        [self sendAudioDeviceEvent];
        resolve(@"🎉 Audio Route sucessfully changed");
    } else {
        reject(@"😭 error_code", @"getAudioUriJS() failed in chooseAudioRoute", RCTErrorWithMessage(@"Failed to change audio route"));
    }
}

- (void)updateAudioDeviceState:(NSString *)audioDevice {
    // check if route exist and value isn't null
    return;
}

RCT_EXPORT_METHOD(stop:(NSString *)busytoneUriType)
{
    if (!_audioSessionInitialized) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stop(): stop InCallManager"];
    [self restoreOriginalAudioSetup];
    [self stopProximitySensor];
    [self audioSessionSetActive:NO
                        options:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
                     callerMemo:NSStringFromSelector(_cmd)];
    [self setKeepScreenOn:NO];
    [self stopAudioSessionNotification];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    _forceSpeakerOn = 0;
    _audioSessionInitialized = NO;
}

RCT_EXPORT_METHOD(turnScreenOn)
{
    [RTKLogger debug:@"RNInCallManager.turnScreenOn(): ios doesn't support turnScreenOn()"];
}

RCT_EXPORT_METHOD(turnScreenOff)
{
    [RTKLogger debug:@"RNInCallManager.turnScreenOff(): ios doesn't support turnScreenOff()"];
}

RCT_EXPORT_METHOD(setFlashOn:(BOOL)enable
                  brightness:(nonnull NSNumber *)brightness)
{
    if ([AVCaptureDevice class]) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        if (device.hasTorch && device.position == AVCaptureDevicePositionBack) {
            @try {
                [device lockForConfiguration:nil];

                if (enable) {
                    [device setTorchMode:AVCaptureTorchModeOn];
                } else {
                    [device setTorchMode:AVCaptureTorchModeOff];
                }

                [device unlockForConfiguration];
            } @catch (NSException *e) {}
        }
    }
}

RCT_EXPORT_METHOD(setKeepScreenOn:(BOOL)enable)
{
    [RTKLogger debug:@"RNInCallManager.setKeepScreenOn(): enable: %@", enable ? @"YES" : @"NO"];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] setIdleTimerDisabled:enable];
    });
}



RCT_EXPORT_METHOD(setSpeakerphoneOn:(BOOL)enable)
{
    if(!enable){
        [self routeAudioFromEarpiece];
    } else {
        [self routeAudioFromSpeakerphone];
    }
}

- (BOOL)routeAudioFromBluetooth {
    BOOL success;
    NSError *error = nil;
    _audioSession = [AVAudioSession sharedInstance];
    
    [RTKLogger debug:@"Routing audio to Bluetooth"];
    [RTKLogger debug:@"Current route before: %@", [_audioSession currentRoute]];
    
    // Switch back to VideoChat mode if we were in VoiceChat mode (from earpiece)
    // VideoChat mode works fine with Bluetooth
    if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
        [self audioSessionSetMode:AVAudioSessionModeVideoChat
                       callerMemo:NSStringFromSelector(_cmd)];
        _incallAudioMode = AVAudioSessionModeVideoChat;
        [RTKLogger debug:@"Switched back to VideoChat mode for Bluetooth routing"];
    }
    
    // Re-enable Bluetooth routing (already enabled, but ensure it's set)
    [self audioSessionSetCategory:_incallAudioCategory
                          options:AVAudioSessionCategoryOptionAllowBluetooth
                       callerMemo:NSStringFromSelector(_cmd)];
    [RTKLogger debug:@"Bluetooth routing enabled"];

    // Clear audio port override first to allow Bluetooth routing
    [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
    if (error) {
        [RTKLogger error:@"Failed to clear output port override: %@", error];
        return false;
    } else {
        [RTKLogger debug:@"Cleared output port override"];
    }
    
    // Find and set Bluetooth as preferred input
    for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
        if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
            [[input portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
            [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
            
            @try {
                [RTKLogger debug:@"Setting preferred input to Bluetooth: %@", input.portName];
                
                // Setting Bluetooth as preferred input automatically routes output to Bluetooth too
                success = [_audioSession setPreferredInput:input error:&error];
                if (!success || error) {
                    [RTKLogger error:@"Unable to set Bluetooth as preferred input: %@", error];
                    return false;
                }
                
                [RTKLogger debug:@"Set preferred input successfully"];
                
                // Verify the route changed
                AVAudioSessionRouteDescription* newRoute = [_audioSession currentRoute];
                [RTKLogger debug:@"Current route after: %@", newRoute];
                
                BOOL isRoutedToBluetooth = NO;
                for (AVAudioSessionPortDescription* output in newRoute.outputs) {
                    if ([[output portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                        [[output portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
                        [[output portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
                        isRoutedToBluetooth = YES;
                        [RTKLogger debug:@"✅ Successfully routed audio to Bluetooth: %@", output.portName];
                        break;
                    }
                }
                
                if (!isRoutedToBluetooth) {
                    [RTKLogger warning:@"⚠️ setPreferredInput succeeded but route didn't change to Bluetooth"];
                }
                
                return true;
            } @catch (NSException *e) {
                [RTKLogger error:@"Exception setting Bluetooth routing: %@", e.reason];
                return false;
            }
        }
    }
    
    [RTKLogger warning:@"No Bluetooth device found in available inputs"];
    return false;
}



- (BOOL)routeAudioFromEarpiece {
    NSError *error = nil;
    [RTKLogger debug:@"Routing audio via Earpiece"];
    @try {
        // VideoChat mode defaults to speaker even with overrideOutputAudioPort:None
        if (![_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
            [self audioSessionSetMode:AVAudioSessionModeVoiceChat
                           callerMemo:NSStringFromSelector(_cmd)];
            _incallAudioMode = AVAudioSessionModeVoiceChat;
            [RTKLogger debug:@"Switched to VoiceChat mode for earpiece routing"];
        }
        
        // Always keep Bluetooth enabled in category so it stays visible
        // Check current category options to see if we need to update
        AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
        BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
        
        if (!hasAllowBluetooth) {
            [RTKLogger warning:@"Bluetooth was disabled! Re-enabling it now"];
            [self audioSessionSetCategory:_incallAudioCategory
                                  options:AVAudioSessionCategoryOptionAllowBluetooth
                               callerMemo:NSStringFromSelector(_cmd)];
        } else {
            [RTKLogger debug:@"Bluetooth already enabled in category options"];
        }

        // Override to None routes to earpiece in VoiceChat mode
        // Note: We do NOT change preferred input - if Bluetooth mic is active, it stays active
        [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
        if (error) {
            [RTKLogger error:@"Port override failed due to: %@", error];
            return false;
        }
        
        [RTKLogger debug:@"Successfully routed audio to Earpiece (Bluetooth remains visible)"];
        return true;
    } @catch (NSException *e) {
        [RTKLogger error:@"Error occurred while routing audio via Earpiece: %@", e.reason];
        return false;
    }
}

- (BOOL)routeAudioFromSpeakerphone
{
    [RTKLogger debug:@"Routing audio via Loudspeaker"];
    NSError *error = nil;
    @try {
        // Switch back to VideoChat mode if we were in VoiceChat mode (from earpiece)
        // VideoChat mode is better for speaker as it's optimized for video calls
        if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
            [self audioSessionSetMode:AVAudioSessionModeVideoChat
                           callerMemo:NSStringFromSelector(_cmd)];
            _incallAudioMode = AVAudioSessionModeVideoChat;
            [RTKLogger debug:@"Switched back to VideoChat mode for speaker routing"];
        }
        
        // Always keep Bluetooth enabled in category so it stays visible
        // Check current category options to see if we need to update
        AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
        BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
        
        if (!hasAllowBluetooth) {
            [RTKLogger warning:@"Bluetooth was disabled! Re-enabling it now"];
            [self audioSessionSetCategory:_incallAudioCategory
                                  options:AVAudioSessionCategoryOptionAllowBluetooth
                               callerMemo:NSStringFromSelector(_cmd)];
        } else {
            [RTKLogger debug:@"Bluetooth already enabled in category options"];
        }
        
        // Override to Speaker - this forces output to speaker
        // Note: We do NOT change preferred input - if Bluetooth mic is active, it stays active
        [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
        if (error) {
            [RTKLogger error:@"Port override failed due to: %@", error];
            return false;
        }
        
        // Verify the route actually changed to speaker
        AVAudioSessionRouteDescription* currentRoute = [_audioSession currentRoute];
        BOOL isRoutedToSpeaker = NO;
        
        for (AVAudioSessionPortDescription* output in currentRoute.outputs) {
            if ([[output portType] isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
                isRoutedToSpeaker = YES;
                [RTKLogger debug:@"✅ Successfully routed audio to Speaker: %@", output.portName];
                break;
            }
        }
        
        if (!isRoutedToSpeaker) {
            [RTKLogger warning:@"⚠️ Speaker override didn't take effect immediately, current route: %@", currentRoute.outputs];
            // iOS might route to speaker after a delay, or it might auto-route to Bluetooth
            // The handleNewDeviceAvailable will detect if iOS routes back to Bluetooth
        }
        
        return true;
    } @catch (NSException *e) {
        [RTKLogger error:@"Error occurred while routing audio via Loudspeaker: %@", e.reason];
        return false;
    }
}

RCT_EXPORT_METHOD(setForceSpeakerphoneOn:(int)flag)
{
    _forceSpeakerOn = flag;
    [RTKLogger debug:@"RNInCallManager.setForceSpeakerphoneOn(): flag: %d", flag];
    [self updateAudioRoute:YES];
}

RCT_EXPORT_METHOD(setMicrophoneMute:(BOOL)enable)
{
    [RTKLogger debug:@"RNInCallManager.setMicrophoneMute(): ios doesn't support setMicrophoneMute()"];
}

RCT_EXPORT_METHOD(getIsWiredHeadsetPluggedIn:(RCTPromiseResolveBlock)resolve
                                      reject:(RCTPromiseRejectBlock)reject)
{
    BOOL wiredHeadsetPluggedIn = [self isWiredHeadsetPluggedIn];
    resolve(wiredHeadsetPluggedIn ? @YES : @NO);
}

RCT_EXPORT_METHOD(getAvailableAudioDevices:(RCTPromiseResolveBlock)resolve
                                    reject:(RCTPromiseRejectBlock)reject)
{
    NSArray<NSString *> *devices = [self getAvailableAudioDeviceList];
    resolve(devices);
}

RCT_EXPORT_METHOD(getCurrentAudioDevice:(RCTPromiseResolveBlock)resolve
                                reject:(RCTPromiseRejectBlock)reject)
{
    NSString *device = [self getSelectedAudioDevice];
    resolve(device);
}

RCT_EXPORT_METHOD(forceUpdateAudioDevices)
{
    [RTKLogger debug:@"RNInCallManager.forceUpdateAudioDevices(): Manually triggering audio device update"];
    [self sendAudioDeviceEvent];
}


RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
                                      reject:(RCTPromiseRejectBlock) reject) 
{
    [RTKLogger debug:@"Checking if Bluetooth device is connected"];
    NSMutableArray *devices = [NSMutableArray array];

    // Read the current state without changing anything
    AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
    
    // Check current route outputs for Bluetooth
    for (AVAudioSessionPortDescription* desc in [route outputs]) {
        if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
            [[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
            [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
            [devices addObject:[desc portName]];
        }
    }

    // Check current route inputs for Bluetooth
    if (devices.count == 0) {
        for (AVAudioSessionPortDescription* desc in [route inputs]) {
            if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
                [devices addObject:[desc portName]];
            }
        }
    }
    
    // Check availableInputs for connected but not currently routed Bluetooth devices
    if (devices.count == 0) {
        for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
            if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
                [devices addObject:[input portName]];
            }
        }
    }
    
    if (!devices || !devices.count){
        resolve(@NO);
    } else {
        resolve(@YES);
    }
}

//TODO: Rewrite this function so that we manage bluetooth, wiredHeadset and all the updates from here
- (BOOL)updateAudioRoute:(BOOL)shouldSendEvent
{
    // Prevent re-entrant calls that cause infinite loops
    if (_isUpdatingAudioRoute) {
        [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Already updating, skipping to prevent loop"];
        return NO;
    }
    
    _isUpdatingAudioRoute = YES;
    [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): [Enter] forceSpeakerOn flag=%d category=%@ mode=%@", _forceSpeakerOn, _audioSession.category, _audioSession.mode];
    //self.debugAudioSession()

    //AVAudioSessionPortOverride overrideAudioPort;
    int overrideAudioPort;
    NSString *overrideAudioPortString = @"";
    NSString *audioMode = @"";

    // --- WebRTC native code will change audio mode automatically when established.
    // --- It would have some race condition if we change audio mode with webrtc at the same time.
    // --- So we should not change audio mode as possible as we can. Only when default video call which wants to force speaker off.
    // --- audio: only override speaker on/off; video: should change category if needed and handle proximity sensor. ( because default proximity is off when video call )
    if (_forceSpeakerOn == 1) {
        [RTKLogger debug:@"Force Speaker on"];
        // --- force ON, override speaker only, keep audio mode remain.
        overrideAudioPort = AVAudioSessionPortOverrideSpeaker;
        overrideAudioPortString = @".Speaker";
    }  else if ([_userSelectedAudioRoute isEqualToString:@"earpiece"]) {
        // User explicitly selected earpiece
        [RTKLogger debug:@"User selected Earpiece"];
        overrideAudioPort = AVAudioSessionPortOverrideNone;
        overrideAudioPortString = @".None";
    }  else {
        // Default to speaker when no explicit selection or Bluetooth selected
        [RTKLogger debug:@"Default Audio Route - Speaker"];
        overrideAudioPort = AVAudioSessionPortOverrideSpeaker;
        overrideAudioPortString = @".Speaker";
    }
    //NOTE: Check Current route
    BOOL isCurrentRouteToSpeaker = [self checkAudioRoute:@[AVAudioSessionPortBuiltInSpeaker]
                                               routeType:@"output"];
    BOOL isCurrentRouteToBluetooth = [self checkAudioRoute:@[AVAudioSessionPortBluetoothHFP, AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE]
                                               routeType:@"output"];
    
    // Priority 1: If forceSpeakerOn is set, ALWAYS route to speaker (even if Bluetooth is available)
    if (_forceSpeakerOn == 1) {
        if (!isCurrentRouteToSpeaker) {
            @try {
                [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
                [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Forcing speaker (overriding Bluetooth if present)"];
            } @catch (NSException *e) {
                [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to force speaker: %@", e.reason];
            }
        } else {
            [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Already on speaker, forceSpeakerOn active"];
        }
    }
    // Priority 2: Don't override if user selected Bluetooth (and forceSpeaker is OFF)
    else if ([_userSelectedAudioRoute isEqualToString:@"bluetooth"] && isCurrentRouteToBluetooth) {
        [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): User selected Bluetooth, not overriding"];
    }
    // Priority 3: Route based on overrideAudioPort value
    else {
        // overrideAudioPort is either .Speaker (default/forceSpeaker) or .None (earpiece)
        if (overrideAudioPort == AVAudioSessionPortOverrideSpeaker && !isCurrentRouteToSpeaker && !isCurrentRouteToBluetooth) {
            // Need to route to speaker (and not on Bluetooth)
            @try {
                [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
                [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Routing to speaker"];
            } @catch (NSException *e) {
                [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to route to speaker: %@", e.reason];
            }
        } else if (overrideAudioPort == AVAudioSessionPortOverrideNone && isCurrentRouteToSpeaker) {
            // User selected earpiece, need to switch from speaker to earpiece
            @try {
                [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
                [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Routing to earpiece"];
            } @catch (NSException *e) {
                [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to route to earpiece: %@", e.reason];
            }
        } else {
            [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): No route change needed"];
        }
    }

    [RTKLogger debug:@"RNInCallManager.updateAudioRoute(), sending Event"];
    if(shouldSendEvent) [self sendAudioDeviceEvent];
    
    BOOL didChangeMode = NO;
    if (audioMode.length > 0 && ![_audioSession.mode isEqualToString:audioMode]) {
        [self audioSessionSetMode:audioMode
                       callerMemo:NSStringFromSelector(_cmd)];

        [RTKLogger debug:@"RNInCallManager.updateAudioRoute() audio mode has changed to %@", audioMode];
        didChangeMode = YES;
    } else {
        [RTKLogger debug:@"RNInCallManager.updateAudioRoute() did NOT change audio mode"];
        didChangeMode = NO;
    }
    
    _isUpdatingAudioRoute = NO;
    return didChangeMode;
    //self.debugAudioSession()
}

- (BOOL)checkAudioRoute:(NSArray<NSString *> *)targetPortTypeArray
              routeType:(NSString *)routeType
{
    //TODO: Checking bluetooth route should be called from here
    AVAudioSessionRouteDescription *currentRoute = _audioSession.currentRoute;
    if (currentRoute != nil) {
        NSArray<AVAudioSessionPortDescription *> *routes = [routeType isEqualToString:@"input"]
            ? currentRoute.inputs
            : currentRoute.outputs;
        for (AVAudioSessionPortDescription *portDescription in routes) {
            if ([targetPortTypeArray containsObject:portDescription.portType]) {
                return YES;
            }
        }
    }
    return NO;
}

- (BOOL)isWiredHeadsetPluggedIn
{
    [RTKLogger debug:@"Checking if isWiredHeadset is plugged in or not"];
    // --- only check for a audio device plugged into headset port instead bluetooth/usb/hdmi
    return [self checkAudioRoute:@[AVAudioSessionPortHeadphones]
                       routeType:@"output"]
        || [self checkAudioRoute:@[AVAudioSessionPortHeadsetMic]
                       routeType:@"input"];
}

- (void)audioSessionSetCategory:(NSString *)audioCategory
                        options:(AVAudioSessionCategoryOptions)options
                     callerMemo:(NSString *)callerMemo
{
    @try {
        if (options != 0) {
            [_audioSession setCategory:audioCategory
                           withOptions:options
                                 error:nil];
        } else {
            [_audioSession setCategory:audioCategory
                                 error:nil];
        }
        [RTKLogger debug:@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu success", callerMemo, audioCategory, (unsigned long)options];
    } @catch (NSException *e) {
        [RTKLogger error:@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu fail: %@", callerMemo, audioCategory, (unsigned long)options, e.reason];
    }
}

- (void)audioSessionSetMode:(NSString *)audioMode
                 callerMemo:(NSString *)callerMemo
{
    @try {
        [_audioSession setMode:audioMode error:nil];
        [RTKLogger debug:@"RNInCallManager.%@: audioSession.setMode(%@) success", callerMemo, audioMode];
    } @catch (NSException *e) {
        [RTKLogger error:@"RNInCallManager.%@: audioSession.setMode(%@) fail: %@", callerMemo, audioMode, e.reason];
    }
}

- (void)audioSessionSetActive:(BOOL)audioActive
                   options:(AVAudioSessionSetActiveOptions)options
                   callerMemo:(NSString *)callerMemo
{
    @try {
        if (options != 0) {
            [_audioSession setActive:audioActive
                         withOptions:options
                               error:nil];
        } else {
            [_audioSession setActive:audioActive
                               error:nil];
        }
        [RTKLogger debug:@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu success", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options];
    } @catch (NSException *e) {
        [RTKLogger error:@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu fail: %@", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options, e.reason];
    }
}

- (void)storeOriginalAudioSetup
{
    [RTKLogger debug:@"RNInCallManager.storeOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode];
    _origAudioCategory = _audioSession.category;
    _origAudioMode = _audioSession.mode;
}

- (void)restoreOriginalAudioSetup
{
    [RTKLogger debug:@"RNInCallManager.restoreOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode];
    [self audioSessionSetCategory:_origAudioCategory
                          options:0
                       callerMemo:NSStringFromSelector(_cmd)];
    [self audioSessionSetMode:_origAudioMode
                   callerMemo:NSStringFromSelector(_cmd)];
}

- (void)startProximitySensor
{
    if (_isProximityRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.startProximitySensor()"];
    dispatch_async(dispatch_get_main_queue(), ^{
        self->_currentDevice.proximityMonitoringEnabled = YES;
    });

    // --- in case it didn't deallocate when ViewDidUnload
    [self stopObserve:_proximityObserver
                 name:UIDeviceProximityStateDidChangeNotification
               object:nil];

    _proximityObserver = [self startObserve:UIDeviceProximityStateDidChangeNotification
                                     object:_currentDevice
                                      queue: nil
                                      block:^(NSNotification *notification) {
        BOOL state = self->_currentDevice.proximityState;
        if (state != self->_proximityIsNear) {
            [RTKLogger debug:@"RNInCallManager.UIDeviceProximityStateDidChangeNotification(): isNear: %@", state ? @"YES" : @"NO"];
            self->_proximityIsNear = state;
            @try {
                if (self->hasListeners && [self canSendEvents]) {
                    [self sendEventWithName:@"Proximity" body:@{@"isNear": state ? @YES : @NO}];
                }
            } @catch (NSException *exception) {
                [RTKLogger error:@"Error sending event 'Proximity': %@", exception.reason];
            }
        }
    }];

    _isProximityRegistered = YES;
}

- (void)stopProximitySensor
{
    if (!_isProximityRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stopProximitySensor()"];
    dispatch_async(dispatch_get_main_queue(), ^{
        self->_currentDevice.proximityMonitoringEnabled = NO;
    });

    // --- remove all no matter what object
    [self stopObserve:_proximityObserver
                 name:UIDeviceProximityStateDidChangeNotification
               object:nil];

    _isProximityRegistered = NO;
}

- (NSArray<NSString *> *)getAvailableAudioDeviceList
{
    NSMutableArray<NSString *> *devices = [NSMutableArray arrayWithObjects:@"speaker", @"earpiece", nil];
    
    // Check for wired headset
    if ([self checkAudioRoute:@[AVAudioSessionPortHeadsetMic] routeType:@"input"] ||
        [self checkAudioRoute:@[AVAudioSessionPortHeadphones] routeType:@"output"]) {
        [devices addObject:@"wired"];
    }
    
    // Check for Bluetooth in current route outputs
    AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
    BOOL bluetoothFound = NO;
    
    for (AVAudioSessionPortDescription* desc in [route outputs]) {
        if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
            [[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
            [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
            [devices addObject:@"bluetooth"];
            bluetoothFound = YES;
            [RTKLogger debug:@"Bluetooth found in outputs: %@", [desc portName]];
            break;
        }
    }
    
    // Check for Bluetooth in current route inputs (HFP devices appear here)
    if (!bluetoothFound) {
        for (AVAudioSessionPortDescription* desc in [route inputs]) {
            if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
                [devices addObject:@"bluetooth"];
                bluetoothFound = YES;
                [RTKLogger debug:@"Bluetooth found in inputs: %@", [desc portName]];
                break;
            }
        }
    }
    
    // Check availableInputs for connected but not currently routed Bluetooth devices
    if (!bluetoothFound) {
        for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
            if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
                [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
                [devices addObject:@"bluetooth"];
                bluetoothFound = YES;
                [RTKLogger debug:@"Bluetooth found in availableInputs: %@", [input portName]];
                break;
            }
        }
    }
    
    [RTKLogger debug:@"Available audio devices: %@", devices];
    return devices;
}

- (NSString *)getSelectedAudioDevice
{
    AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
    
    // Check current output route
    for (AVAudioSessionPortDescription* desc in [route outputs]) {
        NSString *portType = [desc portType];
        
        if ([portType isEqualToString:AVAudioSessionPortBluetoothHFP] ||
            [portType isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
            [portType isEqualToString:AVAudioSessionPortBluetoothLE]) {
            return @"bluetooth";
        } else if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
            return @"wired";
        } else if ([portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
            return @"speaker";
        } else if ([portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
            return @"earpiece";
        }
    }
    
    // Check input route for wired headset mic
    for (AVAudioSessionPortDescription* desc in [route inputs]) {
        if ([[desc portType] isEqualToString:AVAudioSessionPortHeadsetMic]) {
            return @"wired";
        }
    }
    
    // Default fallback
    return @"speaker";
}

- (void)sendAudioDeviceEvent
{
    @try {
        if (self->hasListeners && [self canSendEvents]) {
            // Log current audio session state for debugging
            AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
            BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
            [RTKLogger debug:@"Category: %@, Options: %lu, AllowBluetooth: %@", 
                            _audioSession.category, 
                            (unsigned long)currentOptions,
                            hasAllowBluetooth ? @"YES" : @"NO"];
            
            AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
            [RTKLogger debug:@"Current route outputs: %@", route.outputs];
            [RTKLogger debug:@"Current route inputs: %@", route.inputs];
            [RTKLogger debug:@"Available inputs: %@", [_audioSession availableInputs]];
            
            NSArray<NSString *> *availableDevices = [self getAvailableAudioDeviceList];
            NSString *selectedDevice = [self getSelectedAudioDevice];
            [RTKLogger debug:@"available: %@, selected: %@", availableDevices, selectedDevice];
            
            // Convert array to JSON string format
            NSError *error;
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:availableDevices
                                                               options:0
                                                                 error:&error];
            [RTKLogger debug:@"JSON: %@", jsonData];
            NSString *deviceListString = @"[speaker, earpiece]";
            if (jsonData && !error) {
                deviceListString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            }
            
            [self sendEventWithName:@"onAudioDeviceChanged"
                               body:@{
                                   @"availableAudioDeviceList": deviceListString,
                                   @"selectedAudioDevice": selectedDevice,
                               }];
            
            [RTKLogger debug:@"Sent audio device event - available: %@, selected: %@", deviceListString, selectedDevice];
        }
    } @catch (NSException *exception) {
        [RTKLogger error:@"Error sending audio device event: %@", exception.reason];
    }
}

- (void)handleNewDeviceAvailable
{
    // Get the actual current route from iOS
    NSString *currentRoute = [self getSelectedAudioDevice];
    
    [RTKLogger debug:@"handleNewDeviceAvailable: iOS routed to '%@', user selected '%@'", 
                    currentRoute, 
                    _userSelectedAudioRoute ?: @"(none)"];
    
    // Update our state to match iOS's decision
    _userSelectedAudioRoute = currentRoute;
    _currentAudioRoute = currentRoute;
    
    _forceSpeakerOn = 0;
    
    // Earpiece requires VoiceChat mode, others work with VideoChat mode
    if ([currentRoute isEqualToString:@"earpiece"]) {
        // iOS routed to earpiece - ensure we're in VoiceChat mode
        if (![_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
            [self audioSessionSetMode:AVAudioSessionModeVoiceChat
                           callerMemo:NSStringFromSelector(_cmd)];
            _incallAudioMode = AVAudioSessionModeVoiceChat;
            [RTKLogger debug:@"Switched to VoiceChat mode for earpiece"];
        }
    } else {
        // iOS routed to speaker/bluetooth/wired - ensure we're in VideoChat mode
        if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
            [self audioSessionSetMode:AVAudioSessionModeVideoChat
                           callerMemo:NSStringFromSelector(_cmd)];
            _incallAudioMode = AVAudioSessionModeVideoChat;
            [RTKLogger debug:@"Switched back to VideoChat mode for non-earpiece device"];
        }
    }
    
    // Log what device iOS routed to
    if ([self checkAudioRoute:@[AVAudioSessionPortHeadsetMic] routeType:@"input"]) {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): HeadsetMic - synced state to 'wired'"];
    } else if ([self checkAudioRoute:@[AVAudioSessionPortHeadphones] routeType:@"output"]) {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): Headphones - synced state to 'wired'"];
    } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothHFP] routeType:@"output"]) {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothHFP - synced state to 'bluetooth'"];
    } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothA2DP] routeType:@"output"]) {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothA2DP - synced state to 'bluetooth'"];
    } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothLE] routeType:@"output"]) {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothLE - synced state to 'bluetooth'"];
    } else {
        [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): Other device - synced state to '%@'", currentRoute];
    }
}

- (void)startAudioSessionNotification
{
    [RTKLogger debug:@"RNInCallManager.startAudioSessionNotification() starting..."];
    [self startAudioSessionInterruptionNotification];
    [self startAudioSessionRouteChangeNotification];
    [self startAudioSessionMediaServicesWereLostNotification];
    [self startAudioSessionMediaServicesWereResetNotification];
    [self startAudioSessionSilenceSecondaryAudioHintNotification];
}

- (void)stopAudioSessionNotification
{
    [RTKLogger debug:@"RNInCallManager.startAudioSessionNotification() stopping..."];
    [self stopAudioSessionInterruptionNotification];
    [self stopAudioSessionRouteChangeNotification];
    [self stopAudioSessionMediaServicesWereLostNotification];
    [self stopAudioSessionMediaServicesWereResetNotification];
    [self stopAudioSessionSilenceSecondaryAudioHintNotification];
}

- (void)startAudioSessionInterruptionNotification
{
    if (_isAudioSessionInterruptionRegistered) {
        return;
    }
    [RTKLogger debug:@"RNInCallManager.startAudioSessionInterruptionNotification()"];

    // --- in case it didn't deallocate when ViewDidUnload
    [self stopObserve:_audioSessionInterruptionObserver
                 name:AVAudioSessionInterruptionNotification
               object:nil];

    _audioSessionInterruptionObserver = [self startObserve:AVAudioSessionInterruptionNotification
                                                    object:nil
                                                     queue:nil
                                                     block:^(NSNotification *notification) {
        if (notification.userInfo == nil
                || ![notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
            return;
        }

        //NSUInteger rawValue = notification.userInfo[AVAudioSessionInterruptionTypeKey].unsignedIntegerValue;
        NSNumber *interruptType = [notification.userInfo objectForKey:@"AVAudioSessionInterruptionTypeKey"];
        if ([interruptType unsignedIntegerValue] == AVAudioSessionInterruptionTypeBegan) {
            [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Began"];
            [self updateAudioRoute:YES];
        } else if ([interruptType unsignedIntegerValue] == AVAudioSessionInterruptionTypeEnded) {
            [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Ended"];
        } else {
            [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Unknown Value"];
        }
        //NSLog(@"RNInCallManager.AudioSessionInterruptionNotification: could not resolve notification");
    }];

    _isAudioSessionInterruptionRegistered = YES;
}

- (void)stopAudioSessionInterruptionNotification
{
    if (!_isAudioSessionInterruptionRegistered) {
        return;
    }
    [RTKLogger debug:@"RNInCallManager.stopAudioSessionInterruptionNotification()"];
    // --- remove all no matter what object
    [self stopObserve:_audioSessionInterruptionObserver
                 name:AVAudioSessionInterruptionNotification
               object: nil];
    _isAudioSessionInterruptionRegistered = NO;
}

- (void)startAudioSessionRouteChangeNotification
{
        if (_isAudioSessionRouteChangeRegistered) {
            return;
        }

        [RTKLogger debug:@"RNInCallManager.startAudioSessionRouteChangeNotification()"];

        // --- in case it didn't deallocate when ViewDidUnload
        [self stopObserve:_audioSessionRouteChangeObserver
                     name: AVAudioSessionRouteChangeNotification
                   object: nil];
        //NOTE: On Audio Change this function is called
        _audioSessionRouteChangeObserver = [self startObserve:AVAudioSessionRouteChangeNotification
                                                       object: nil
                                                        queue: nil
                                                        block:^(NSNotification *notification) {
            // [RTKLogger debug:@"Audio Session changed, user route %@ and %@ current route", _userSelectedAudioRoute, self->_audioSession.currentRoute];
            if (notification.userInfo == nil
                    || ![notification.name isEqualToString:AVAudioSessionRouteChangeNotification]) {
                return;
            }

            NSNumber *routeChangeType = [notification.userInfo objectForKey:@"AVAudioSessionRouteChangeReasonKey"];
            NSUInteger routeChangeTypeValue = [routeChangeType unsignedIntegerValue];
            
            switch (routeChangeTypeValue) {
                case AVAudioSessionRouteChangeReasonUnknown:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Unknown"];
                    break;
                case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: NewDeviceAvailable"];
                    
                    // Get what iOS routed to
                    NSString *currentRoute = [self getSelectedAudioDevice];
                    [RTKLogger debug:@"iOS auto-routed to '%@', user selected '%@'", currentRoute, _userSelectedAudioRoute ?: @"(none)"];
                    
                    // If user explicitly selected a route, don't let iOS override it
                    if (_userSelectedAudioRoute != nil && ![currentRoute isEqualToString:_userSelectedAudioRoute]) {
                        [RTKLogger warning:@"⚠️ iOS tried to auto-route to '%@' but user selected '%@'. Re-asserting user's choice...", currentRoute, _userSelectedAudioRoute];
                        
                        // Re-assert the user's choice immediately
                        if ([_userSelectedAudioRoute isEqualToString:@"speaker"]) {
                            [self routeAudioFromSpeakerphone];
                        } else if ([_userSelectedAudioRoute isEqualToString:@"earpiece"]) {
                            [self routeAudioFromEarpiece];
                        } else if ([_userSelectedAudioRoute isEqualToString:@"bluetooth"]) {
                            [self routeAudioFromBluetooth];
                        }
                    } else {
                        // No user selection, accept iOS's routing decision
                        [self handleNewDeviceAvailable];
                    }
                    
                    [self sendAudioDeviceEvent];
                    break;
                }
                case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: OldDeviceUnavailable"];
                    [self updateAudioRoute:YES];
                    break;
                case AVAudioSessionRouteChangeReasonCategoryChange:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: CategoryChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode];
                    // Only send event, don't trigger route changes to avoid loops
                    [self sendAudioDeviceEvent];
                    break;
                case AVAudioSessionRouteChangeReasonOverride:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Override - Skipping event"];
                    // Skip sending event for Override notifications
                    // These fire during transitions and can report incorrect routes
                    // Actual route changes will be caught by NewDeviceAvailable/OldDeviceUnavailable
                    break;
                case AVAudioSessionRouteChangeReasonWakeFromSleep:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: WakeFromSleep"];
                    break;
                case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: NoSuitableRouteForCategory"];
                    break;
                case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: RouteConfigurationChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode];
                    
                    // WebRTC/Video capture may change category and disable Bluetooth
                    // Re-enable it if it was disabled
                    AVAudioSessionCategoryOptions currentOptions = [self->_audioSession categoryOptions];
                    BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
                    
                    if (!hasAllowBluetooth && self->_audioSessionInitialized) {
                        [RTKLogger warning:@"⚠️ WebRTC/System disabled Bluetooth! Re-enabling it now"];
                        [self audioSessionSetCategory:self->_incallAudioCategory
                                              options:AVAudioSessionCategoryOptionAllowBluetooth
                                           callerMemo:@"RouteConfigurationChange"];
                        
                        // Ensure we're in VideoChat mode after WebRTC changes
                        if (![self->_audioSession.mode isEqualToString:AVAudioSessionModeVideoChat]) {
                            [self audioSessionSetMode:AVAudioSessionModeVideoChat
                                           callerMemo:@"RouteConfigurationChange"];
                            self->_incallAudioMode = AVAudioSessionModeVideoChat;
                        }
                        
                        [RTKLogger debug:@"✅ Re-enabled Bluetooth after WebRTC changed category"];
                    }
                    
                    // Send event to update device list
                    [self sendAudioDeviceEvent];
                    break;
                default:
                    [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Unknown Value"];
                    break;
            }

            NSNumber *silenceSecondaryAudioHintType = [notification.userInfo objectForKey:@"AVAudioSessionSilenceSecondaryAudioHintTypeKey"];
            NSUInteger silenceSecondaryAudioHintTypeValue = [silenceSecondaryAudioHintType unsignedIntegerValue];
        }];

        _isAudioSessionRouteChangeRegistered = YES;
}

- (void)stopAudioSessionRouteChangeNotification
{
    if (!_isAudioSessionRouteChangeRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stopAudioSessionRouteChangeNotification()"];
    // --- remove all no matter what object
    [self stopObserve:_audioSessionRouteChangeObserver
                 name:AVAudioSessionRouteChangeNotification
               object:nil];
    _isAudioSessionRouteChangeRegistered = NO;
}

- (void)startAudioSessionMediaServicesWereLostNotification
{
    if (_isAudioSessionMediaServicesWereLostRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.startAudioSessionMediaServicesWereLostNotification()"];

    // --- in case it didn't deallocate when ViewDidUnload
    [self stopObserve:_audioSessionMediaServicesWereLostObserver
                 name:AVAudioSessionMediaServicesWereLostNotification
               object:nil];

    _audioSessionMediaServicesWereLostObserver = [self startObserve:AVAudioSessionMediaServicesWereLostNotification
                                                             object:nil
                                                              queue:nil
                                                              block:^(NSNotification *notification) {
        // --- This notification has no userInfo dictionary.
        [RTKLogger debug:@"RNInCallManager.AudioSessionMediaServicesWereLostNotification: Media Services Were Lost"];
    }];

    _isAudioSessionMediaServicesWereLostRegistered = YES;
}

- (void)stopAudioSessionMediaServicesWereLostNotification
{
    if (!_isAudioSessionMediaServicesWereLostRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stopAudioSessionMediaServicesWereLostNotification()"];

    // --- remove all no matter what object
    [self stopObserve:_audioSessionMediaServicesWereLostObserver
                 name:AVAudioSessionMediaServicesWereLostNotification
               object:nil];

    _isAudioSessionMediaServicesWereLostRegistered = NO;
}

- (void)startAudioSessionMediaServicesWereResetNotification
{
    if (_isAudioSessionMediaServicesWereResetRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.startAudioSessionMediaServicesWereResetNotification()"];

    // --- in case it didn't deallocate when ViewDidUnload
    [self stopObserve:_audioSessionMediaServicesWereResetObserver
                 name:AVAudioSessionMediaServicesWereResetNotification
               object:nil];

    _audioSessionMediaServicesWereResetObserver = [self startObserve:AVAudioSessionMediaServicesWereResetNotification
                                                              object:nil
                                                               queue:nil
                                                               block:^(NSNotification *notification) {
        // --- This notification has no userInfo dictionary.
        [RTKLogger debug:@"RNInCallManager.AudioSessionMediaServicesWereResetNotification: Media Services Were Reset"];
    }];

    _isAudioSessionMediaServicesWereResetRegistered = YES;
}

- (void)stopAudioSessionMediaServicesWereResetNotification
{
    if (!_isAudioSessionMediaServicesWereResetRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stopAudioSessionMediaServicesWereResetNotification()"];

    // --- remove all no matter what object
    [self stopObserve:_audioSessionMediaServicesWereResetObserver
                 name:AVAudioSessionMediaServicesWereResetNotification
               object:nil];

    _isAudioSessionMediaServicesWereResetRegistered = NO;
}

- (void)startAudioSessionSilenceSecondaryAudioHintNotification
{
    if (_isAudioSessionSilenceSecondaryAudioHintRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.startAudioSessionSilenceSecondaryAudioHintNotification()"];

    // --- in case it didn't deallocate when ViewDidUnload
    [self stopObserve:_audioSessionSilenceSecondaryAudioHintObserver
                 name:AVAudioSessionSilenceSecondaryAudioHintNotification
               object:nil];

    _audioSessionSilenceSecondaryAudioHintObserver = [self startObserve:AVAudioSessionSilenceSecondaryAudioHintNotification
                                                                 object:nil
                                                                  queue:nil
                                                                  block:^(NSNotification *notification) {
        if (notification.userInfo == nil
                || ![notification.name isEqualToString:AVAudioSessionSilenceSecondaryAudioHintNotification]) {
            return;
        }

        NSNumber *silenceSecondaryAudioHintType = [notification.userInfo objectForKey:@"AVAudioSessionSilenceSecondaryAudioHintTypeKey"];
        NSUInteger silenceSecondaryAudioHintTypeValue = [silenceSecondaryAudioHintType unsignedIntegerValue];
        switch (silenceSecondaryAudioHintTypeValue) {
            case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
                [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Begin"];
                break;
            case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
                [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: End"];
                break;
            default:
                [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Unknow Value"];
                break;
        }
    }];
    _isAudioSessionSilenceSecondaryAudioHintRegistered = YES;
}

- (void)stopAudioSessionSilenceSecondaryAudioHintNotification
{
    if (!_isAudioSessionSilenceSecondaryAudioHintRegistered) {
        return;
    }

    [RTKLogger debug:@"RNInCallManager.stopAudioSessionSilenceSecondaryAudioHintNotification()"];
    // --- remove all no matter what object
    [self stopObserve:_audioSessionSilenceSecondaryAudioHintObserver
                 name:AVAudioSessionSilenceSecondaryAudioHintNotification
               object:nil];

    _isAudioSessionSilenceSecondaryAudioHintRegistered = NO;
}

- (id)startObserve:(NSString *)name
            object:(id)object
             queue:(NSOperationQueue *)queue
             block:(void (^)(NSNotification *))block
{
    return [[NSNotificationCenter defaultCenter] addObserverForName:name
                                               object:object
                                                queue:queue
                                           usingBlock:block];
}

- (void)stopObserve:(id)observer
             name:(NSString *)name
           object:(id)object
{
    if (observer == nil) return;
    [[NSNotificationCenter defaultCenter] removeObserver:observer
                                                    name:name
                                                  object:object];
}

@end
