//
//  RNBluethManager.m
//  RNBluetoothEscposPrinter
//
//  Created by januslo on 2018/9/28.
//  Copyright © 2018年 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "RNBluetoothManager.h"
#import <CoreBluetooth/CoreBluetooth.h>
@implementation RNBluetoothManager

NSString *EVENT_DEVICE_ALREADY_PAIRED = @"EVENT_DEVICE_ALREADY_PAIRED";
NSString *EVENT_DEVICE_DISCOVER_DONE = @"EVENT_DEVICE_DISCOVER_DONE";
NSString *EVENT_DEVICE_FOUND = @"EVENT_DEVICE_FOUND";
NSString *EVENT_CONNECTION_LOST = @"EVENT_CONNECTION_LOST";
NSString *EVENT_UNABLE_CONNECT=@"EVENT_UNABLE_CONNECT";
NSString *EVENT_CONNECTED=@"EVENT_CONNECTED";
static NSArray<CBUUID *> *supportServices = nil;
static NSDictionary *writeableCharactiscs = nil;
bool hasListeners;
static CBPeripheral *connected;
static CBCharacteristic *cachedWriteCharacteristic = nil; // Cache the writable characteristic
static RNBluetoothManager *instance;
static NSObject<WriteDataToBleDelegate> *writeDataDelegate;// delegate of write data resule;
static NSData *toWrite;
static NSTimer *timer;

-(instancetype)init {
    if (self = [super init]) {
        // Initialize CBCentralManager eagerly so it's ready when methods are called
        // This triggers the centralManagerDidUpdateState callback immediately
        [self centralManager];
        [self initSupportServices];
    }
    return self;
}

+(Boolean)isConnected{
    return !(connected==nil);
}

// Helper method to write data with automatic chunking for BLE MTU limits
+(BOOL)writeDataWithChunking:(NSData *)data 
               toCharacteristic:(CBCharacteristic *)characteristic 
                   onPeripheral:(CBPeripheral *)peripheral {
    CBCharacteristicWriteType writeType = (characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) 
        ? CBCharacteristicWriteWithoutResponse 
        : CBCharacteristicWriteWithResponse;
    
    // BLE has MTU limitations - need to chunk data if it's too large
    // The minimum BLE MTU is 23 bytes, with 3 bytes overhead, leaving 20 bytes usable
    // maximumWriteValueLengthForType often returns incorrect values for WriteWithoutResponse
    // so we force a conservative chunk size
    NSUInteger maxWriteLength = 20; // Safe default that works with all BLE devices
    
    NSUInteger dataLength = [data length];
    
    if(dataLength <= maxWriteLength) {
        // Data fits in one write - no chunking needed
        [peripheral writeValue:data forCharacteristic:characteristic type:writeType];
        return YES;
    } else {
        // Need to chunk the data
        NSLog(@"BLE chunking: %lu bytes -> %lu byte chunks", dataLength, maxWriteLength);
        NSUInteger offset = 0;
        BOOL allSuccess = YES;
        NSUInteger chunkCount = 0;
        
        while(offset < dataLength && allSuccess) {
            NSUInteger chunkSize = MIN(maxWriteLength, dataLength - offset);
            NSData *chunk = [data subdataWithRange:NSMakeRange(offset, chunkSize)];
            
            @try {
                [peripheral writeValue:chunk forCharacteristic:characteristic type:writeType];
                chunkCount++;
                
                // Small delay between chunks to avoid overwhelming the device
                if(writeType == CBCharacteristicWriteWithoutResponse) {
                    [NSThread sleepForTimeInterval:0.02]; // 20ms delay
                }
            }
            @catch(NSException *e) {
                NSLog(@"BLE chunk write error at offset %lu: %@", offset, e);
                allSuccess = NO;
            }
            
            offset += chunkSize;
        }
        
        if(allSuccess) {
            NSLog(@"BLE chunking complete: %lu chunks sent", chunkCount);
        }
        return allSuccess;
    }
}

+(void)writeValue:(NSData *) data withDelegate:(NSObject<WriteDataToBleDelegate> *) delegate
{
    @try{
        writeDataDelegate = delegate;
        toWrite = data;
        connected.delegate = instance;
        
        // If we have a cached writable characteristic, use it directly
        if(cachedWriteCharacteristic && connected){
            // Use helper method to write with chunking
            BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite 
                                                    toCharacteristic:cachedWriteCharacteristic 
                                                        onPeripheral:connected];
            
            if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
            toWrite = nil;
            return;
        }
        
        // No cached characteristic, need to discover services first
        NSLog(@"No cached characteristic, discovering services...");
        // Discover all services (not just SPP which is for Classic BT, not BLE)
        [connected discoverServices:nil];
    }
    @catch(NSException *e){
        NSLog(@"error in writing data to %@,issue:%@",connected,e);
        [writeDataDelegate didWriteDataToBle:false];
    }
}

// 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
}

/**
 * Exports the constants to javascritp.
 **/
- (NSDictionary *)constantsToExport
{
    
    /*
     EVENT_DEVICE_ALREADY_PAIRED    Emits the devices array already paired
     EVENT_DEVICE_DISCOVER_DONE    Emits when the scan done
     EVENT_DEVICE_FOUND    Emits when device found during scan
     EVENT_CONNECTION_LOST    Emits when device connection lost
     EVENT_UNABLE_CONNECT    Emits when error occurs while trying to connect device
     EVENT_CONNECTED    Emits when device connected
     */

    return @{ EVENT_DEVICE_ALREADY_PAIRED: EVENT_DEVICE_ALREADY_PAIRED,
              EVENT_DEVICE_DISCOVER_DONE:EVENT_DEVICE_DISCOVER_DONE,
              EVENT_DEVICE_FOUND:EVENT_DEVICE_FOUND,
              EVENT_CONNECTION_LOST:EVENT_CONNECTION_LOST,
              EVENT_UNABLE_CONNECT:EVENT_UNABLE_CONNECT,
              EVENT_CONNECTED:EVENT_CONNECTED
              };
}
- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();
}

+ (BOOL)requiresMainQueueSetup
{
    return YES;
}

/**
 * Defines the event would be emited.
 **/
- (NSArray<NSString *> *)supportedEvents
{
    return @[EVENT_DEVICE_DISCOVER_DONE,
             EVENT_DEVICE_FOUND,
             EVENT_UNABLE_CONNECT,
             EVENT_CONNECTION_LOST,
             EVENT_CONNECTED,
             EVENT_DEVICE_ALREADY_PAIRED];
}


RCT_EXPORT_MODULE(BluetoothManager);


//isBluetoothEnabled
RCT_EXPORT_METHOD(isBluetoothEnabled:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    CBManagerState state = [self.centralManager  state];
    resolve(state == CBManagerStatePoweredOn?@"true":@"false");//canot pass boolean or int value to resolve directly.
}

//enableBluetooth
RCT_EXPORT_METHOD(enableBluetooth:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    // iOS doesn't allow programmatic Bluetooth enabling
    // Bluetooth must be enabled from Settings by the user
    // Return empty array to match Android's return type (array of paired devices)
    // Since iOS Core Bluetooth doesn't have "paired devices" concept, we return empty array
    resolve(@[]);
}
//disableBluetooth
RCT_EXPORT_METHOD(disableBluetooth:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    resolve(nil);
}
//scanDevices
RCT_EXPORT_METHOD(scanDevices:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    @try{
        if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
            reject(@"BLUETOOTCH_INVALID_STATE",@"BLUETOOTCH_INVALID_STATE",nil);
            return;
        }
        if(self.centralManager.isScanning){
            [self.centralManager stopScan];
        }
        self.scanResolveBlock = resolve;
        self.scanRejectBlock = reject;
        
        // Initialize metadata dictionary if needed
        if(!self.foundDevicesMetadata){
            self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
        }
        
        if(connected && connected.identifier){
            NSString *name = connected.name ? connected.name : @"";
            NSString *address = connected.identifier.UUIDString;
            
            NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
            [deviceInfo setObject:address forKey:@"address"];
            [deviceInfo setObject:name forKey:@"name"];
            [deviceInfo setObject:@(0) forKey:@"rssi"];
            [deviceInfo setObject:@(0) forKey:@"deviceClass"];
            [deviceInfo setObject:@(0) forKey:@"majorDeviceClass"];
            [deviceInfo setObject:@{} forKey:@"advertisementData"];
            
            NSDictionary *peripheralStored = @{address:connected};
            if(!self.foundDevices){
                self.foundDevices = [[NSMutableDictionary alloc] init];
            }
            [self.foundDevices addEntriesFromDictionary:peripheralStored];
            [self.foundDevicesMetadata setObject:deviceInfo forKey:address];
            
            if(hasListeners){
                [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
            }
        }
        [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
        //Callbacks:
        //centralManager:didDiscoverPeripheral:advertisementData:RSSI:
        NSLog(@"Scanning started with services.");
        if(timer && timer.isValid){
            [timer invalidate];
            timer = nil;
        }
        timer = [NSTimer scheduledTimerWithTimeInterval:12 target:self selector:@selector(callStop) userInfo:nil repeats:NO];
    
    }
    @catch(NSException *exception){
        NSLog(@"ERROR IN STARTING SCANE %@",exception);
        reject([exception name],[exception name],nil);
    }
}

//stop scan
RCT_EXPORT_METHOD(stopScan:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    [self callStop];
    resolve(nil);
}

//connect(address)
RCT_EXPORT_METHOD(connect:(NSString *)address
                  findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Trying to connect....%@",address);
    
    // Check if Bluetooth is ready
    if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
        reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready. Please enable Bluetooth and try again.",nil);
        return;
    }
    
    [self callStop];
    if(connected){
        NSString *connectedAddress =connected.identifier.UUIDString;
        if([address isEqualToString:connectedAddress]){
            resolve(nil);
            return;
        }else{
            [self.centralManager cancelPeripheralConnection:connected];
            //Callbacks:
            //entralManager:didDisconnectPeripheral:error:
        }
    }
    CBPeripheral *peripheral = [self.foundDevices objectForKey:address];
    self.connectResolveBlock = resolve;
    self.connectRejectBlock = reject;
    if(peripheral){
          _waitingConnect = address;
          NSLog(@"Trying to connectPeripheral....%@",address);
        [self.centralManager connectPeripheral:peripheral options:nil];
        // Callbacks:
        //    centralManager:didConnectPeripheral:
        //    centralManager:didFailToConnectPeripheral:error:
        
        // Set connection timeout (30 seconds)
        if(timer && timer.isValid){
            [timer invalidate];
            timer = nil;
        }
        timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
    }else{
          //starts the scan.
        _waitingConnect = address;
         NSLog(@"Scan to find ....%@",address);
        [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
        //Callbacks:
        //centralManager:didDiscoverPeripheral:advertisementData:RSSI:
        
        // Set scan + connection timeout (30 seconds)
        if(timer && timer.isValid){
            [timer invalidate];
            timer = nil;
        }
        timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
    }
}
//disconnect(address)
RCT_EXPORT_METHOD(disconnect:(NSString *)address
                  findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Trying to disconnect device: %@",address);
    
    // Check if Bluetooth is ready
    if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
        reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready.",nil);
        return;
    }
    
    if(connected && [connected.identifier.UUIDString isEqualToString:address]){
        [self.centralManager cancelPeripheralConnection:connected];
        // Note: disconnection is async, but we resolve immediately like Android does
        // The didDisconnectPeripheral callback will handle setting connected = nil
        resolve(address);
    } else {
        // Device not connected or different device
        NSLog(@"Device not connected or different device: %@",address);
        resolve(address);
    }
}
//unpaire(address)
RCT_EXPORT_METHOD(unpaire:(NSString *)address
                  findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Trying to unpair/disconnect device: %@",address);
    if(connected && [connected.identifier.UUIDString isEqualToString:address]){
        [self.centralManager cancelPeripheralConnection:connected];
        // Note: Core Bluetooth on iOS doesn't support programmatic unpairing
        // The connection will be cancelled, but the device remains paired at OS level
        // Users must unpair manually from iOS Settings if needed
        connected = nil;
        resolve(address);
    } else {
        // Device not connected, just resolve
        NSLog(@"Device not connected, nothing to unpair: %@",address);
        resolve(address);
    }
}


-(void)handleConnectTimeout{
    NSLog(@"Connection timeout - device not found or unable to connect");
    if(self.centralManager.isScanning){
        [self.centralManager stopScan];
    }
    if(self.connectRejectBlock){
        RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
        rjBlock(@"CONNECTION_TIMEOUT",@"Could not connect to device - timeout after 30 seconds",nil);
        self.connectRejectBlock = nil;
        self.connectResolveBlock = nil;
    }
    _waitingConnect = nil;
    if(timer && timer.isValid){
        [timer invalidate];
        timer = nil;
    }
}

-(void)callStop{
    if(self.centralManager.isScanning){
        [self.centralManager stopScan];
    }

    NSMutableArray *devices = [[NSMutableArray alloc] init];
    for(NSString *key in self.foundDevices){
        NSLog(@"insert found devices:%@ =>%@",key,[self.foundDevices objectForKey:key]);
        NSDictionary *deviceInfo = [self.foundDevicesMetadata objectForKey:key];
        if (deviceInfo) {
            [devices addObject:deviceInfo];
        } else {
            NSString *name = [self.foundDevices objectForKey:key].name;
            if(!name) name = @"";
            [devices addObject:@{@"address":key, @"name":name}];
        }
    }

    NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
    [result setObject:@[] forKey:@"paired"];
    [result setObject:devices forKey:@"found"];

    NSError *error = nil;
    NSData *resultJsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:&error];
    NSString *resultJsonStr = [[NSString alloc] initWithData:resultJsonData encoding:NSUTF8StringEncoding];

    if(hasListeners){
        NSData *foundJsonData = [NSJSONSerialization dataWithJSONObject:devices options:0 error:&error];
        NSString *foundJsonStr = [[NSString alloc] initWithData:foundJsonData encoding:NSUTF8StringEncoding];
        [self sendEventWithName:EVENT_DEVICE_DISCOVER_DONE body:@{@"found":foundJsonStr, @"paired":@"[]"}];
    }
    if(self.scanResolveBlock){
        RCTPromiseResolveBlock rsBlock = self.scanResolveBlock;
        rsBlock(resultJsonStr);
        self.scanResolveBlock = nil;
    }

    if(timer && timer.isValid){
        [timer invalidate];
        timer = nil;
    }
    self.scanRejectBlock = nil;
    self.scanResolveBlock = nil;
}
- (void) initSupportServices
{
    if(!supportServices){
        // Use standard SPP (Serial Port Profile) UUID - same as Android
        CBUUID *spp = [CBUUID UUIDWithString: @"00001101-0000-1000-8000-00805F9B34FB"];
        supportServices = [NSArray arrayWithObject:spp];
        // SPP uses the same UUID for service and characteristic
        writeableCharactiscs = @{spp:@"00001101-0000-1000-8000-00805F9B34FB"};
    }
}

- (CBCentralManager *) centralManager
{
    @synchronized(_centralManager)
    {
        if (!_centralManager)
        {
            if (![CBCentralManager instancesRespondToSelector:@selector(initWithDelegate:queue:options:)])
            {
                //for ios version lowser than 7.0
                self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
            }else
            {
                self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options: nil];
            }
        }
        if(!instance){
            instance = self;
        }
    }
    [self initSupportServices];
    return _centralManager;
}

/**
 * CBCentralManagerDelegate
 **/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    NSString *stateString;
    switch (central.state) {
        case CBManagerStateUnknown:
            stateString = @"Unknown";
            break;
        case CBManagerStateResetting:
            stateString = @"Resetting";
            break;
        case CBManagerStateUnsupported:
            stateString = @"Unsupported";
            break;
        case CBManagerStateUnauthorized:
            stateString = @"Unauthorized";
            break;
        case CBManagerStatePoweredOff:
            stateString = @"PoweredOff";
            break;
        case CBManagerStatePoweredOn:
            stateString = @"PoweredOn";
            break;
        default:
            stateString = @"Unknown";
            break;
    }
    NSLog(@"Bluetooth State Changed: %@ (%ld)", stateString, (long)central.state);
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"did discover peripheral: %@",peripheral);
    
    // Extract device information with enhanced metadata
    NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
    [deviceInfo setObject:peripheral.identifier.UUIDString forKey:@"address"];
    [deviceInfo setObject:(peripheral.name ? peripheral.name : @"") forKey:@"name"];
    
    // Add RSSI for signal strength
    [deviceInfo setObject:RSSI forKey:@"rssi"];
    
    // Try to infer device class from advertising data
    // iOS BLE doesn't expose Classic Bluetooth device class, but we can make educated guesses
    // Build advertisementData dictionary for JS layer
    NSMutableDictionary *advData = [[NSMutableDictionary alloc] init];

    // Service UUIDs
    NSArray *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
    if (serviceUUIDs && serviceUUIDs.count > 0) {
        NSMutableArray *uuidStrings = [[NSMutableArray alloc] init];
        for (CBUUID *uuid in serviceUUIDs) {
            [uuidStrings addObject:uuid.UUIDString];
        }
        [advData setObject:uuidStrings forKey:@"serviceUUIDs"];
    }

    // Local name (may differ from peripheral.name)
    NSString *localName = advertisementData[CBAdvertisementDataLocalNameKey];
    if (localName) {
        [advData setObject:localName forKey:@"localName"];
    }

    // Manufacturer data as base64
    NSData *manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey];
    if (manufacturerData) {
        [advData setObject:[manufacturerData base64EncodedStringWithOptions:0] forKey:@"manufacturerData"];
    }

    // TX power level
    NSNumber *txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey];
    if (txPowerLevel) {
        [advData setObject:txPowerLevel forKey:@"txPowerLevel"];
    }

    // Connectable flag
    NSNumber *isConnectable = advertisementData[CBAdvertisementDataIsConnectable];
    if (isConnectable) {
        [advData setObject:isConnectable forKey:@"isConnectable"];
    }

    [deviceInfo setObject:@(0) forKey:@"deviceClass"];
    [deviceInfo setObject:@(0) forKey:@"majorDeviceClass"];
    [deviceInfo setObject:advData forKey:@"advertisementData"];
    
    // Store the peripheral object for connection
    NSDictionary *peripheralStored = @{peripheral.identifier.UUIDString:peripheral};
    if(!self.foundDevices){
        self.foundDevices = [[NSMutableDictionary alloc] init];
    }
    [self.foundDevices addEntriesFromDictionary:peripheralStored];
    
    // Store device metadata for later retrieval
    if(!self.foundDevicesMetadata){
        self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
    }
    [self.foundDevicesMetadata setObject:deviceInfo forKey:peripheral.identifier.UUIDString];
    
    if(hasListeners){
        [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
    }
    if(_waitingConnect && [_waitingConnect isEqualToString: peripheral.identifier.UUIDString]){
        [self.centralManager connectPeripheral:peripheral options:nil];
        [self callStop];
    }
}

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"did connected: %@",peripheral);
    connected = peripheral;
    NSString *pId = peripheral.identifier.UUIDString;
    
    // Set peripheral delegate to receive service/characteristic callbacks
    peripheral.delegate = self;
    
    if(_waitingConnect && [_waitingConnect isEqualToString: pId] && self.connectResolveBlock){
        // Cancel connection timeout timer if it's running
        if(timer && timer.isValid){
            [timer invalidate];
            timer = nil;
        }
        
        // Pre-discover services to prepare for future write operations
        // This matches Android's behavior where the connection is fully ready after connect
        // Pass nil to discover ALL services (not just SPP which is for Classic BT, not BLE)
        NSLog(@"Pre-discovering all services for faster write operations...");
        [peripheral discoverServices:nil];
        
        // Resolve promise immediately (iOS BLE connects fast, service discovery happens async)
        self.connectResolveBlock(nil);
        _waitingConnect = nil;
        self.connectRejectBlock = nil;
        self.connectResolveBlock = nil;
    }
       NSLog(@"going to emit EVENT_CONNECTED.");
    if(hasListeners){
        [self sendEventWithName:EVENT_CONNECTED body:@{@"device":@{@"name":peripheral.name?peripheral.name:@"",@"address":peripheral.identifier.UUIDString}}];
    }
}

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    // Clear cached characteristic when disconnecting
    cachedWriteCharacteristic = nil;
    
    if(!connected && _waitingConnect && [_waitingConnect isEqualToString:peripheral.identifier.UUIDString]){
        if(self.connectRejectBlock){
            RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
            rjBlock(@"",@"",error);
            self.connectRejectBlock = nil;
            self.connectResolveBlock = nil;
            _waitingConnect=nil;
        }
        connected = nil;
        if(hasListeners){
            [self sendEventWithName:EVENT_UNABLE_CONNECT body:@{@"name":peripheral.name?peripheral.name:@"",@"address":peripheral.identifier.UUIDString}];
        }
    }else{
        connected = nil;
        if(hasListeners){
            [self sendEventWithName:EVENT_CONNECTION_LOST body:nil];
        }
    }
}

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    // Clear cached characteristic when connection fails
    cachedWriteCharacteristic = nil;
    
    if(self.connectRejectBlock){
        RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
        rjBlock(@"",@"",error);
        self.connectRejectBlock = nil;
        self.connectResolveBlock = nil;
        _waitingConnect = nil;
    }
    connected = nil;
    if(hasListeners){
        [self sendEventWithName:EVENT_UNABLE_CONNECT body:@{@"name":peripheral.name?peripheral.name:@"",@"address":peripheral.identifier.UUIDString}];
    }
    }

/**
 * END OF CBCentralManagerDelegate
 **/

/*!
 *  @method peripheral:didDiscoverServices:
 *
 *  @param peripheral    The peripheral providing this information.
 *    @param error        If an error occurred, the cause of the failure.
 *
 *  @discussion            This method returns the result of a @link discoverServices: @/link call. If the service(s) were read successfully, they can be retrieved via
 *                        <i>peripheral</i>'s @link services @/link property.
 *
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error{
    if (error){
        NSLog(@"扫描外设服务出错：%@-> %@", peripheral.name, [error localizedDescription]);
        return;
    }
    NSLog(@"扫描到外设服务：%@ -> %@",peripheral.name,peripheral.services);
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:nil forService:service];
         NSLog(@"服务id：%@",service.UUID.UUIDString);
    }
    NSLog(@"开始扫描外设服务的特征 %@...",peripheral.name);
    
    if(error && self.connectRejectBlock){
        RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
         rjBlock(@"",@"",error);
        self.connectRejectBlock = nil;
        self.connectResolveBlock = nil;
        connected = nil;
    }else
    if(_waitingConnect && _waitingConnect == peripheral.identifier.UUIDString){
        RCTPromiseResolveBlock rsBlock = self.connectResolveBlock;
        rsBlock(peripheral.identifier.UUIDString);
        self.connectRejectBlock = nil;
        self.connectResolveBlock = nil;
        connected = peripheral;
    }
}

/*!
 *  @method peripheral:didDiscoverCharacteristicsForService:error:
 *
 *  @param peripheral    The peripheral providing this information.
 *  @param service        The <code>CBService</code> object containing the characteristic(s).
 *    @param error        If an error occurred, the cause of the failure.
 *
 *  @discussion            This method returns the result of a @link discoverCharacteristics:forService: @/link call. If the characteristic(s) were read successfully,
 *                        they can be retrieved via <i>service</i>'s <code>characteristics</code> property.
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error{
    if(toWrite && connected
       && [connected.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]){
        if(error){
            NSLog(@"Discover characteristics error:%@",error);
           if(writeDataDelegate)
           {
               [writeDataDelegate didWriteDataToBle:false];
               return;
           }
        }
        
        // Try to write to any service that has writable characteristics
        // Don't filter by supportServices - many BLE printers use custom UUIDs
        NSLog(@"Checking service %@ for writable characteristics...", service.UUID.UUIDString);
        
        // Auto-detect writable characteristic by checking properties
        for(CBCharacteristic *cc in service.characteristics){
            NSLog(@"Characteristic found: %@ in service: %@" ,cc,service.UUID.UUIDString);
            
            // Check if characteristic supports write operations
            BOOL canWrite = (cc.properties & CBCharacteristicPropertyWrite) || 
                           (cc.properties & CBCharacteristicPropertyWriteWithoutResponse);
            
            if(canWrite){
                @try{
                    NSLog(@"Writing %lu bytes to characteristic %@ in service %@",[toWrite length], cc.UUID.UUIDString, service.UUID.UUIDString);
                    
                    // Use helper method to write with chunking
                    BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite 
                                                            toCharacteristic:cc 
                                                                onPeripheral:connected];
                    
                    // Cache this characteristic for future writes
                    cachedWriteCharacteristic = cc;
                    NSLog(@"Cached characteristic %@ for future writes", cc.UUID.UUIDString);
                    
                    if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
                    toWrite = nil; // Clear the write buffer
                    return; // Success, exit after first successful write
                }
                @catch(NSException *e){
                    NSLog(@"ERROR IN WRITE VALUE: %@",e);
                    [writeDataDelegate didWriteDataToBle:false];
                }
            }
        }
        
        // If we get here, no writable characteristic was found in this service
        NSLog(@"No writable characteristic found in service: %@", service.UUID.UUIDString);
        // Don't call didWriteDataToBle:false here - there might be other services to check
    }
    
    if(error){
        NSLog(@"Discover characteristics error:%@",error);
        return;
    }
    
//    ServiceUUID：49535343-fe7d-4ae5-8fa9-9fafd205e455；
//    写的是
//characteristicUUID:49535343-8841-43f4-a8d4-ecbe34729bb3；
//    读的是
//characteristicUUID:49535343-1e4d-4bd9-ba61-23c647249616;
//
//    如果要写，翻译成base64位；
//
//    调用监听改变，需要去使能设备的Notify，var param={
//    serviceUUID: '000018f0-0000-1000-8000-00805f9b34fb',//service的UUID  这个值需要获取查看设备，我暂认为他是通用的
//    characteristicUUID:'00002af0-0000-1000-8000-00805f9b34fb',//characteristic的UUID  这个值也需要获取查看，我认为他是通用的
//    enable:true //true 或false,开启或关闭监听
//    };
//    param = JSON.stringify(param);
//    uexBluetoothLE.setCharacteristicNotification(param);
    
    
    /** TESTING NSLOG OUTPUT:: ***/
//    2018-10-01 21:29:24.136033+0800 bluetoothEscposPrinterExamples[8239:4598148] Trying to connect....D7D39238-EF56-71A7-7DCC-D464EFD3BFF1
//    2018-10-01 21:29:24.302880+0800 bluetoothEscposPrinterExamples[8239:4598148] did connected: <CBPeripheral: 0x1c4302d90, identifier = D7D39238-EF56-71A7-7DCC-D464EFD3BFF1, name = BlueTooth Printer, state = connected>
//    2018-10-01 21:29:24.302982+0800 bluetoothEscposPrinterExamples[8239:4598148] going to discover services.
//    2018-10-01 21:29:24.303375+0800 bluetoothEscposPrinterExamples[8239:4598148] going to emit EVEnT_CONNECTED.
//    2018-10-01 21:29:24.431164+0800 bluetoothEscposPrinterExamples[8239:4598148] 扫描到外设服务：BlueTooth Printer -> (
//                                                                                                               "<CBService: 0x1c246b200, isPrimary = YES, UUID = 49535343-FE7D-4AE5-8FA9-9FAFD205E455>",
//                                                                                                               "<CBService: 0x1c246b280, isPrimary = YES, UUID = 18F0>",
//                                                                                                               "<CBService: 0x1c246a740, isPrimary = YES, UUID = E7810A71-73AE-499D-8C15-FAA9AEF0C3F2>"
//                                                                                                               )
//    2018-10-01 21:29:24.431354+0800 bluetoothEscposPrinterExamples[8239:4598148] 服务id：49535343-FE7D-4AE5-8FA9-9FAFD205E455
//    2018-10-01 21:29:24.431448+0800 bluetoothEscposPrinterExamples[8239:4598148] 服务id：18F0
//    2018-10-01 21:29:24.431535+0800 bluetoothEscposPrinterExamples[8239:4598148] 服务id：E7810A71-73AE-499D-8C15-FAA9AEF0C3F2
//    2018-10-01 21:29:24.431552+0800 bluetoothEscposPrinterExamples[8239:4598148] 开始扫描外设服务的特征 BlueTooth Printer...
//    2018-10-01 21:29:24.432374+0800 bluetoothEscposPrinterExamples[8239:4598148] Characterstic found: <CBCharacteristic: 0x1c04afa20, UUID = 49535343-1E4D-4BD9-BA61-23C647249616, properties = 0x10, value = <5f47505f 4c383031 3630>, notifying = NO> in service: 49535343-FE7D-4AE5-8FA9-9FAFD205E455
//    2018-10-01 21:29:24.432406+0800 bluetoothEscposPrinterExamples[8239:4598148] Notify
//    2018-10-01 21:29:24.432417+0800 bluetoothEscposPrinterExamples[8239:4598148] known properties: 16
//    2018-10-01 21:29:24.432455+0800 bluetoothEscposPrinterExamples[8239:4598148] Characterstic found: <CBCharacteristic: 0x1c04af480, UUID = 49535343-8841-43F4-A8D4-ECBE34729BB3, properties = 0xC, value = (null), notifying = NO> in service: 49535343-FE7D-4AE5-8FA9-9FAFD205E455
//    2018-10-01 21:29:24.432753+0800 bluetoothEscposPrinterExamples[8239:4598148] WriteWithoutResponse
//    2018-10-01 21:29:24.432772+0800 bluetoothEscposPrinterExamples[8239:4598148] Write
//    2018-10-01 21:29:24.432785+0800 bluetoothEscposPrinterExamples[8239:4598148] known properties: 12
//    2018-10-01 21:29:24.432988+0800 bluetoothEscposPrinterExamples[8239:4598148] Characterstic found: <CBCharacteristic: 0x1c44ac9c0, UUID = 2AF0, properties = 0x30, value = (null), notifying = NO> in service: 18F0
//    2018-10-01 21:29:24.433005+0800 bluetoothEscposPrinterExamples[8239:4598148] Notify
//    2018-10-01 21:29:24.433015+0800 bluetoothEscposPrinterExamples[8239:4598148] Indicate
//    2018-10-01 21:29:24.433024+0800 bluetoothEscposPrinterExamples[8239:4598148] known properties: 48
//    2018-10-01 21:29:24.433079+0800 bluetoothEscposPrinterExamples[8239:4598148] Characterstic found: <CBCharacteristic: 0x1c44aca80, UUID = 2AF1, properties = 0xC, value = (null), notifying = NO> in service: 18F0
//    2018-10-01 21:29:24.433647+0800 bluetoothEscposPrinterExamples[8239:4598148] WriteWithoutResponse
//    2018-10-01 21:29:24.433662+0800 bluetoothEscposPrinterExamples[8239:4598148] Write
//    2018-10-01 21:29:24.433672+0800 bluetoothEscposPrinterExamples[8239:4598148] known properties: 12
//    2018-10-01 21:29:24.433900+0800 bluetoothEscposPrinterExamples[8239:4598148] Characterstic found: <CBCharacteristic: 0x1c44ac780, UUID = BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F, properties = 0x3E, value = (null), notifying = NO> in service: E7810A71-73AE-499D-8C15-FAA9AEF0C3F2
//    2018-10-01 21:29:24.433928+0800 bluetoothEscposPrinterExamples[8239:4598148] Read
//    2018-10-01 21:29:24.433953+0800 bluetoothEscposPrinterExamples[8239:4598148] WriteWithoutResponse
//    2018-10-01 21:29:24.433964+0800 bluetoothEscposPrinterExamples[8239:4598148] Write
//    2018-10-01 21:29:24.433973+0800 bluetoothEscposPrinterExamples[8239:4598148] Notify
//    2018-10-01 21:29:24.434378+0800 bluetoothEscposPrinterExamples[8239:4598148] Indicate
//    2018-10-01 21:29:24.434389+0800 bluetoothEscposPrinterExamples[8239:4598148] known properties: 62
    
//    for(CBCharacteristic *cc in service.characteristics){
//       // NSLog(@"Characterstic found: %@ in service: %@" ,cc,service.UUID.UUIDString);
//        CBCharacteristicProperties pro = cc.properties;
//        Byte p = (Byte)pro;
////        CBCharacteristicPropertyBroadcast                                                = 0x01,
////        CBCharacteristicPropertyRead                                                    = 0x02,
////        CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
////        CBCharacteristicPropertyWrite                                                    = 0x08,
////        CBCharacteristicPropertyNotify                                                    = 0x10,
////        CBCharacteristicPropertyIndicate                                                = 0x20,
////        CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
////        CBCharacteristicPropertyExtendedProperties                                        = 0x80,
////        CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x100,
////        CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x200
//        if((p) & 0x01){
//            NSLog(@"Broadcast");
//        }
//        if((p>>1) & 0x01){
//            NSLog(@"Read");
//        }
//        if((p>>2) & 0x01){
//            NSLog(@"WriteWithoutResponse");
//        }
//        if((p>>3) & 0x01){
//            NSLog(@"Write");
//        }
//        if((p>>4) & 0x01){
//              NSLog(@"Notify");
//        }
//        if((p>>5) & 0x01){
//               NSLog(@"Indicate");
//        }
//        if((p>>6) & 0x01){
//            NSLog(@"AuthenticatedSignedWrites");
//        }
//        if((p>>7) & 0x01){
//            NSLog(@"ExtendedProperties");
//        }
//        {
//            NSLog(@"known properties: %lu", pro);
//        }
//    }
}

/*!
 *  @method peripheral:didWriteValueForCharacteristic:error:
 *
 *  @param peripheral        The peripheral providing this information.
 *  @param characteristic    A <code>CBCharacteristic</code> object.
 *    @param error            If an error occurred, the cause of the failure.
 *
 *  @discussion                This method returns the result of a {@link writeValue:forCharacteristic:type:} call, when the <code>CBCharacteristicWriteWithResponse</code> type is used.
 */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error{
    if(error){
        NSLog(@"Error in writing bluetooth: %@",error);
        if(writeDataDelegate){
            [writeDataDelegate didWriteDataToBle:false];
        }
    }
    
    NSLog(@"Write bluetooth success.");
    if(writeDataDelegate){
        [writeDataDelegate didWriteDataToBle:true];
    }
}
 
@end
