import CoreBluetooth
import Foundation
import React

@objc(SdkBle)
class SdkBleModule: RCTEventEmitter, CBCentralManagerDelegate, CBPeripheralDelegate {

  // MARK: - Properties
  private var centralManager: CBCentralManager!
  private var discoveredPeripherals: [String: CBPeripheral] = [:]
  private var connectedPeripherals: [String: CBPeripheral] = [:]
  private var peripheralDelegates: [String: PeripheralContext] = [:]
  private var isScanning = false
  private var scanTimeout: Timer?
  private var allowDuplicates = false
  private var reportedDevices: Set<String> = []

  // Dedicated BLE queue for iPad compatibility
  private let bleQueue = DispatchQueue(label: "com.sdkble.central", qos: .userInitiated)

  // Service to characteristics mapping - matches Android exactly
  private let serviceCharacteristicMap: [CBUUID: [CBUUID]] = [
    CBUUID(string: "0000ff00-0000-1000-8000-00805f9b34fb"): [
      CBUUID(string: "0000ff01-0000-1000-8000-00805f9b34fb"),
      CBUUID(string: "0000ff02-0000-1000-8000-00805f9b34fb"),
    ],
    CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb"): [
      CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")
    ],  // Authentication
    CBUUID(string: "0000ff20-0000-1000-8000-00805f9b34fb"): [
      CBUUID(string: "0000ff21-0000-1000-8000-00805f9b34fb")
    ],
    CBUUID(string: "0000ff30-0000-1000-8000-00805f9b34fb"): [
      CBUUID(string: "0000ff31-0000-1000-8000-00805f9b34fb")
    ],
    CBUUID(string: "0000ff40-0000-1000-8000-00805f9b34fb"): [
      CBUUID(string: "0000ff41-0000-1000-8000-00805f9b34fb")
    ],
  ]

  // Polling configuration
  private var pollTimer: Timer?
  private var pollIntervalMs: TimeInterval = 10.0  // 10 seconds - increased to reduce interference
  private var pollingActive = false
  private var pollingQueue: [(serviceUUID: CBUUID, characteristicUUID: CBUUID)] = []
  private var pollingInProgress = false
  private var pollingPeripheral: CBPeripheral?

  // Chunk buffer for ff31 data
  private var ff31ChunkBuffers: [Int: ChunkBuffer] = [:]
  private let chunkTimeoutMs: TimeInterval = 5.0  // 5 seconds

  // Characteristic action queue
  private var charActionQueue: [CharAction] = []

  // Thread safety for notifications
  private let notificationQueue = DispatchQueue(
    label: "com.sdkble.notifications", qos: .userInitiated, attributes: .concurrent)
  private let chunkBufferQueue = DispatchQueue(
    label: "com.sdkble.chunkbuffer", qos: .userInitiated)
  private let characteristicUpdateQueue = DispatchQueue(
    label: "com.sdkble.characteristicupdates", qos: .userInitiated)
  private var pendingNotifications: [String: (data: Data, timestamp: TimeInterval)] = [:]

  // Track characteristics with notifications enabled
  private var notifyEnabledCharacteristics: Set<CBUUID> = []

  // Track expected data lengths for validation
  private let expectedDataLengths: [String: Int] = [
    "0000ff02-0000-1000-8000-00805f9b34fb": 3,  // FF02
    "0000ff21-0000-1000-8000-00805f9b34fb": 11,  // FF21 (variable length, 9 bytes common)
    "0000ff41-0000-1000-8000-00805f9b34fb": 18,  // FF41
  ]

  // Authentication state
  private var authenticationPending = false
  private var serialNumberPending = false
  private var deviceTypePending = false
  private var authenticationCharacteristic: CBCharacteristic?
  private var authenticationPeripheral: CBPeripheral?

  // User role for sending role command on connection
  private var userRole: String = "patient"  // Default role

  // MARK: - Structures

  private struct CharAction {
    let characteristic: CBCharacteristic
    var notifyPending: Bool
    var readPending: Bool
    var readAttempted: Bool = false
  }

  private struct ChunkBuffer {
    let structId: Int
    var chunks: [Data] = []
    var lastUpdateTime: Date = Date()
  }

  private class PeripheralContext {
    weak var peripheral: CBPeripheral?
    var pendingPromises:
      [String: (resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock)] = [:]

    init(peripheral: CBPeripheral) {
      self.peripheral = peripheral
    }
  }

  // MARK: - Initialization

  override init() {
    super.init()
    // Initialize with dedicated queue for better iPad/iPhone compatibility
    // Using a serial queue ensures proper callback ordering and prevents race conditions
    if Thread.isMainThread {
      centralManager = CBCentralManager(delegate: self, queue: bleQueue)
    } else {
      DispatchQueue.main.sync {
        centralManager = CBCentralManager(delegate: self, queue: bleQueue)
      }
    }
  }

  deinit {
    stopScanInternal()
    stopPolling()
    scanTimeout?.invalidate()
    pollTimer?.invalidate()
    print("SdkBleModule: Deallocated")
  }

  override static func requiresMainQueueSetup() -> Bool {
    return false
  }

  override func supportedEvents() -> [String]! {
    return [
      "scanResult",
      "BleManagerScanFailed",
      "connected",
      "disconnected",
      "servicesDiscovered",
      "characteristicChanged",
      "bluetoothStateChanged",
    ]
  }

  // Required methods for RCTEventEmitter
  @objc
  override func addListener(_ eventName: String) {
    super.addListener(eventName)
  }

  @objc
  override func removeListeners(_ count: Double) {
    super.removeListeners(count)
  }

  // MARK: - React Native Methods

  @objc
  func startScan(
    _ options: NSDictionary?, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    print("SdkBleModule: startScan called with options: \(String(describing: options))")

    guard centralManager != nil else {
      reject("BLE_NOT_INITIALIZED", "Bluetooth manager not initialized", nil)
      return
    }

    guard centralManager.state == .poweredOn else {
      let stateString = bleStateString(centralManager.state)
      print("SdkBleModule: Bluetooth not powered on, current state: \(stateString)")
      reject("BLE_NOT_ENABLED", "Bluetooth is not enabled. Current state: \(stateString)", nil)
      return
    }

    if isScanning {
      print("SdkBleModule: Already scanning, returning")
      resolve(nil)
      return
    }

    var serviceUUIDs: [CBUUID]? = nil
    if let uuids = options?["serviceUUIDs"] as? [String], !uuids.isEmpty {
      serviceUUIDs = uuids.compactMap { uuidString in
        let uuid = CBUUID(string: uuidString)
        print("SdkBleModule: Will scan for service UUID: \(uuid)")
        return uuid
      }
    }

    allowDuplicates = options?["allowDuplicates"] as? Bool ?? false
    let scanOptions: [String: Any] = [
      CBCentralManagerScanOptionAllowDuplicatesKey: allowDuplicates
    ]

    // Clear reported devices when starting a new scan
    reportedDevices.removeAll()

    print("SdkBleModule: Starting scan with allowDuplicates: \(allowDuplicates)")
    centralManager.scanForPeripherals(withServices: serviceUUIDs, options: scanOptions)
    isScanning = true

    // Handle timeout
    let timeout = options?["timeout"] as? Double ?? 10.0
    if timeout > 0 {
      scanTimeout?.invalidate()
      scanTimeout = Timer.scheduledTimer(withTimeInterval: timeout / 1000.0, repeats: false) {
        [weak self] _ in
        print("SdkBleModule: Scan timeout reached")
        self?.stopScanInternal()
      }
      print("SdkBleModule: Scan timeout set to \(timeout)ms")
    }

    print("SdkBleModule: Scan started successfully")
    resolve(nil)
  }

  private func bleStateString(_ state: CBManagerState) -> String {
    switch state {
    case .unknown:
      return "unknown"
    case .resetting:
      return "resetting"
    case .unsupported:
      return "unsupported"
    case .unauthorized:
      return "unauthorized"
    case .poweredOff:
      return "poweredOff"
    case .poweredOn:
      return "poweredOn"
    @unknown default:
      return "unknown(\(state.rawValue))"
    }
  }

  @objc
  func stopScan(
    _ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    stopScanInternal()
    resolve(nil)
  }

  private func stopScanInternal() {
    if isScanning {
      centralManager.stopScan()
      isScanning = false
      scanTimeout?.invalidate()
      scanTimeout = nil
      // Clear reported devices when scan stops
      reportedDevices.removeAll()
    }
  }

  @objc
  func connect(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = discoveredPeripherals[deviceId] ?? connectedPeripherals[deviceId] else {
      // Try to find by UUID
      let uuid = UUID(uuidString: deviceId)
      if let uuid = uuid {
        let peripherals = centralManager.retrievePeripherals(withIdentifiers: [uuid])
        if let peripheral = peripherals.first {
          discoveredPeripherals[deviceId] = peripheral
          peripheral.delegate = self
          centralManager.connect(peripheral, options: nil)
          resolve(nil)
          return
        }
      }
      reject("DEVICE_NOT_FOUND", "Device not found: \(deviceId)", nil)
      return
    }

    if connectedPeripherals[deviceId] != nil {
      resolve(nil)
      return
    }

    // Ensure delegate is set before connecting (important for iPad)
    peripheral.delegate = self

    // Store peripheral context for strong reference
    let context = PeripheralContext(peripheral: peripheral)
    peripheralDelegates[deviceId] = context

    print(
      "SdkBleModule: Connecting to peripheral: \(deviceId), name: \(peripheral.name ?? "Unknown")")
    centralManager.connect(peripheral, options: nil)
    resolve(nil)
  }

  @objc
  func disconnect(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      resolve(nil)
      return
    }

    print("SdkBleModule: Disconnecting device: \(deviceId)")

    // Reset peripheral delegate before canceling connection
    peripheral.delegate = nil

    centralManager.cancelPeripheralConnection(peripheral)
    resolve(nil)
  }

  @objc
  func isConnected(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    resolve(connectedPeripherals[deviceId] != nil)
  }

  @objc
  func getConnectedDevices(
    _ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    var devices: [[String: Any]] = []

    for (deviceId, peripheral) in connectedPeripherals {
      devices.append([
        "id": deviceId,
        "name": peripheral.name ?? "Unknown",
      ])
    }

    resolve(devices)
  }

  @objc
  func discoverServices(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    peripheral.discoverServices(nil)
    resolve([])
  }

  @objc
  func readCharacteristic(
    _ deviceId: String, serviceUuid: String, characteristicUuid: String,
    resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    guard
      let service = peripheral.services?.first(where: {
        $0.uuid.uuidString.lowercased() == serviceUuid.lowercased()
      })
    else {
      reject("SERVICE_NOT_FOUND", "Service not found: \(serviceUuid)", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: {
        $0.uuid.uuidString.lowercased() == characteristicUuid.lowercased()
      })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic not found: \(characteristicUuid)", nil)
      return
    }

    peripheral.readValue(for: characteristic)
    resolve(nil)
  }

  @objc
  func writeCharacteristic(
    _ deviceId: String, serviceUuid: String, characteristicUuid: String, data: String,
    options: NSDictionary?, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    guard
      let service = peripheral.services?.first(where: {
        $0.uuid.uuidString.lowercased() == serviceUuid.lowercased()
      })
    else {
      reject("SERVICE_NOT_FOUND", "Service not found: \(serviceUuid)", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: {
        $0.uuid.uuidString.lowercased() == characteristicUuid.lowercased()
      })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic not found: \(characteristicUuid)", nil)
      return
    }

    var dataToWrite: Data
    if options?["encoding"] as? String == "base64" {
      guard let decodedData = Data(base64Encoded: data) else {
        reject("INVALID_DATA", "Failed to decode base64 data", nil)
        return
      }
      dataToWrite = decodedData
    } else {
      dataToWrite = data.data(using: .utf8) ?? Data()
    }

    let writeType: CBCharacteristicWriteType =
      (options?["type"] as? String == "withoutResponse") ? .withoutResponse : .withResponse

    peripheral.writeValue(dataToWrite, for: characteristic, type: writeType)
    resolve(nil)
  }

  @objc
  func setNotify(
    _ deviceId: String, serviceUuid: String, characteristicUuid: String, enable: Bool,
    resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    guard
      let service = peripheral.services?.first(where: {
        $0.uuid.uuidString.lowercased() == serviceUuid.lowercased()
      })
    else {
      reject("SERVICE_NOT_FOUND", "Service not found: \(serviceUuid)", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: {
        $0.uuid.uuidString.lowercased() == characteristicUuid.lowercased()
      })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic not found: \(characteristicUuid)", nil)
      return
    }

    // Check if characteristic supports notifications or indications
    let hasNotify = characteristic.properties.contains(.notify)
    let hasIndicate = characteristic.properties.contains(.indicate)

    if !hasNotify && !hasIndicate {
      reject("NOT_SUPPORTED", "Characteristic does not support notifications or indications", nil)
      return
    }

    print(
      "SdkBleModule: setNotify for \(characteristicUuid), enable: \(enable), hasNotify: \(hasNotify), hasIndicate: \(hasIndicate)"
    )

    // Enable/disable notifications
    peripheral.setNotifyValue(enable, for: characteristic)

    // On iOS, Core Bluetooth handles the CCCD descriptor automatically when setNotifyValue is called
    // However, we can verify the descriptor exists and optionally configure it manually
    if let cccdDescriptor = characteristic.descriptors?.first(where: {
      $0.uuid == CBUUID(string: "2902")  // Client Characteristic Configuration Descriptor UUID
    }) {
      print("SdkBleModule: Found CCCD descriptor for \(characteristicUuid)")
      // Core Bluetooth will handle this automatically, but we could write manually if needed
      // For now, let the automatic handling work
    } else {
      print(
        "SdkBleModule: No CCCD descriptor found for \(characteristicUuid), this might be normal")
    }

    resolve(nil)
  }

  @objc
  func requestPermissions(
    _ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    // iOS handles BLE permissions automatically when accessing the central manager
    if centralManager.state == .poweredOn {
      resolve("granted")
    } else {
      resolve("denied")
    }
  }

  @objc
  func isBluetoothEnabled(
    _ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    resolve(centralManager.state == .poweredOn)
  }

  @objc
  func enableBluetooth(
    _ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock
  ) {
    // iOS doesn't allow programmatic enabling of Bluetooth
    // The system will show an alert automatically
    resolve(nil)
  }

  @objc
  func base64ToHexDash(
    _ base64: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let data = Data(base64Encoded: base64) else {
      reject("DECODE_ERROR", "Failed to decode base64", nil)
      return
    }

    let hex = data.map { String(format: "%02X", $0) }.joined(separator: "-")
    resolve("(0x) \(hex)")
  }

  // MARK: - Infusion Control

  @objc
  func startInfusion(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    let serviceUUID = CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb")
    let characteristicUUID = CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")

    guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
      reject("SERVICE_NOT_FOUND", "Service FF10 not found", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUUID })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic FF11 not found", nil)
      return
    }

    // Command: A4 01 01
    let command = Data([0xA4, 0x01, 0x01])
    peripheral.writeValue(command, for: characteristic, type: .withResponse)

    print("SdkBleModule: Start infusion command sent: A4 01 01")
    resolve(nil)
  }

  @objc
  func stopInfusion(
    _ deviceId: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    let serviceUUID = CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb")
    let characteristicUUID = CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")

    guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
      reject("SERVICE_NOT_FOUND", "Service FF10 not found", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUUID })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic FF11 not found", nil)
      return
    }

    // Command: A4 01 00
    let command = Data([0xA4, 0x01, 0x00])
    peripheral.writeValue(command, for: characteristic, type: .withResponse)

    print("SdkBleModule: Stop infusion command sent: A4 01 00")
    resolve(nil)
  }

  @objc
  func setInfusionLevel(
    _ deviceId: String, level: NSNumber, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let peripheral = connectedPeripherals[deviceId] else {
      reject("DEVICE_NOT_CONNECTED", "Device not connected: \(deviceId)", nil)
      return
    }

    // Validate level (1-5)
    let levelInt = level.intValue
    if levelInt < 1 || levelInt > 5 {
      reject("INVALID_LEVEL", "Invalid level: \(levelInt). Level must be between 1 and 5", nil)
      return
    }

    let serviceUUID = CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb")
    let characteristicUUID = CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")

    guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
      reject("SERVICE_NOT_FOUND", "Service FF10 not found", nil)
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUUID })
    else {
      reject("CHARACTERISTIC_NOT_FOUND", "Characteristic FF11 not found", nil)
      return
    }

    // Command: A5 01 [level]
    // Level 1: A5 01 01
    // Level 2: A5 01 02
    // Level 3: A5 01 03
    // Level 4: A5 01 04
    // Level 5: A5 01 05
    let command = Data([0xA5, 0x01, UInt8(levelInt)])
    peripheral.writeValue(command, for: characteristic, type: .withResponse)

    print(
      "SdkBleModule: Set infusion level \(levelInt) command sent: A5 01 \(String(format: "%02X", levelInt))"
    )
    resolve(nil)
  }

  @objc
  func setUserRole(
    _ role: String, resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    // Validate role
    let validRoles = ["patient", "nurse", "engineer"]
    let roleLower = role.lowercased()

    if !validRoles.contains(roleLower) {
      reject(
        "INVALID_ROLE", "Invalid role: \(role). Must be 'patient', 'nurse', or 'engineer'", nil)
      return
    }

    userRole = roleLower
    print("SdkBleModule: User role set to: \(userRole)")
    resolve(nil)
  }

  private func sendRoleCommand(_ peripheral: CBPeripheral) {
    let serviceUUID = CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb")
    let characteristicUUID = CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")

    guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
      print("SdkBleModule: Service FF10 not found for role command")
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUUID })
    else {
      print("SdkBleModule: Characteristic FF11 not found for role command")
      return
    }

    // Map role to command byte
    // patient: A3 01 01
    // nurse: A3 01 02
    // engineer: A3 01 03
    let roleByte: UInt8
    switch userRole {
    case "patient":
      roleByte = 0x01
    case "nurse":
      roleByte = 0x02
    case "engineer":
      roleByte = 0x03
    default:
      roleByte = 0x01  // Default to patient
    }

    let command = Data([0xA3, 0x01, roleByte])
    peripheral.writeValue(command, for: characteristic, type: .withResponse)

    print(
      "SdkBleModule: Role command sent for \(userRole): A3 01 \(String(format: "%02X", roleByte))"
    )
  }

  // MARK: - CBCentralManagerDelegate

  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    let stateString = bleStateString(central.state)
    print("SdkBleModule: Bluetooth state changed to: \(stateString)")

    // Stop scanning if bluetooth is turned off
    if central.state != .poweredOn && isScanning {
      print("SdkBleModule: Stopping scan due to bluetooth state change")
      stopScanInternal()
    }

    // Send state change event
    sendEvent(
      withName: "bluetoothStateChanged",
      body: ["state": stateString]
    )
  }

  func centralManager(
    _ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
    advertisementData: [String: Any], rssi RSSI: NSNumber
  ) {
    // Filter for EMED devices - check manufacturer data for 'E','M','E','D'
    var hasEMED = false
    if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data {
      let bytes = [UInt8](manufacturerData)
      // Search for 'E','M','E','D' (0x45, 0x4D, 0x45, 0x44) anywhere in manufacturer data
      // Need at least 4 bytes to match the pattern
      if bytes.count >= 4 {
        for i in 0...(bytes.count - 4) {
          if bytes[i] == 0x45 && bytes[i + 1] == 0x4D && bytes[i + 2] == 0x45
            && bytes[i + 3] == 0x44
          {
            hasEMED = true
            break
          }
        }
      }
    }

    if !hasEMED {
      print(
        "SdkBleModule: Skipping device '\(peripheral.name ?? "Unknown")' - no EMED marker found")
      return  // Skip devices not containing 'E','M','E','D'
    }

    let deviceId = peripheral.identifier.uuidString

    // Check if we should report this device (handle allowDuplicates)
    if !allowDuplicates && reportedDevices.contains(deviceId) {
      // Device already reported, skip sending event
      print(
        "SdkBleModule: Skipping duplicate device: \(peripheral.name ?? "Unknown") (\(deviceId))")
      return
    }

    // Mark device as reported
    reportedDevices.insert(deviceId)
    discoveredPeripherals[deviceId] = peripheral

    // Prefer advertised local name over peripheral.name for consistency with Android
    let deviceName =
      (advertisementData[CBAdvertisementDataLocalNameKey] as? String)
      ?? peripheral.name
      ?? "Unknown"

    let deviceInfo: [String: Any] = [
      "id": deviceId,
      "name": deviceName,
      "rssi": RSSI.intValue,
    ]

    sendEvent(withName: "scanResult", body: ["device": deviceInfo])
  }

  func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    let deviceId = peripheral.identifier.uuidString
    connectedPeripherals[deviceId] = peripheral

    // Create and store peripheral context to maintain strong reference
    let context = PeripheralContext(peripheral: peripheral)
    peripheralDelegates[deviceId] = context

    peripheral.delegate = self

    print(
      "SdkBleModule: Connected to peripheral: \(deviceId), name: \(peripheral.name ?? "Unknown")")
    print("SdkBleModule: Maximum write value length: \(peripheral.maximumWriteValueLength(for: .withResponse)) bytes")

    sendEvent(withName: "connected", body: ["deviceId": deviceId])

    // Always start service discovery after connection
    // Add small delay for iPad stability
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
      peripheral.discoverServices(nil)
    }
  }

  func centralManager(
    _ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?
  ) {
    let deviceId = peripheral.identifier.uuidString
    print("SdkBleModule: didDisconnectPeripheral called for device: \(deviceId)")

    // Reset peripheral delegate
    peripheral.delegate = nil

    // Remove from connected peripherals
    connectedPeripherals.removeValue(forKey: deviceId)
    peripheralDelegates.removeValue(forKey: deviceId)

    // Stop polling
    stopPolling()

    // Clear polling queue
    pollingQueue.removeAll()
    pollingInProgress = false
    pollingPeripheral = nil
    print("SdkBleModule: Cleared polling state on disconnect")

    // Clear action queue
    charActionQueue.removeAll()
    print("SdkBleModule: Cleared action queue on disconnect")

    // Clear chunk buffers on disconnect
    ff31ChunkBuffers.removeAll()
    print("SdkBleModule: Cleared chunk buffers on disconnect")

    // Clear notification tracking
    notifyEnabledCharacteristics.removeAll()
    print("SdkBleModule: Cleared notification tracking on disconnect")

    // Clear authentication state
    authenticationPending = false
    serialNumberPending = false
    deviceTypePending = false
    authenticationCharacteristic = nil
    authenticationPeripheral = nil
    print("SdkBleModule: Cleared authentication state on disconnect")

    sendEvent(
      withName: "disconnected",
      body: [
        "deviceId": deviceId,
        "reason": error?.localizedDescription ?? "Disconnected",
      ])
  }

  func centralManager(
    _ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?
  ) {
    let deviceId = peripheral.identifier.uuidString
    sendEvent(
      withName: "disconnected",
      body: [
        "deviceId": deviceId,
        "reason": error?.localizedDescription ?? "Failed to connect",
      ])
  }

  // MARK: - CBPeripheralDelegate

  func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    let deviceId = peripheral.identifier.uuidString
    print("SdkBleModule: didDiscoverServices called for device: \(deviceId)")

    if let error = error {
      print("SdkBleModule: Service discovery failed: \(error.localizedDescription)")
      return
    }

    guard let services = peripheral.services else {
      print("SdkBleModule: No services found for device: \(deviceId)")
      return
    }

    print("SdkBleModule: Found \(services.count) services, discovering characteristics...")

    // Discover characteristics for all services
    for service in services {
      print("SdkBleModule: Discovering characteristics for service: \(service.uuid)")
      peripheral.discoverCharacteristics(nil, for: service)
    }

    pollingPeripheral = peripheral
  }

  func peripheral(
    _ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?
  ) {
    if let error = error {
      print("SdkBleModule: Characteristic discovery failed: \(error.localizedDescription)")
      return
    }

    print("SdkBleModule: didDiscoverCharacteristicsFor service: \(service.uuid)")

    if let characteristics = service.characteristics {
      for characteristic in characteristics {
        print(
          "SdkBleModule:   Found characteristic: \(characteristic.uuid), properties: \(characteristic.properties.rawValue)"
        )

        // Discover descriptors for characteristics that support notifications
        if characteristic.properties.contains(.notify)
          || characteristic.properties.contains(.indicate)
        {
          print(
            "SdkBleModule:   Discovering descriptors for notification characteristic: \(characteristic.uuid)"
          )
          peripheral.discoverDescriptors(for: characteristic)
        }
      }
    }

    // Check if all services have discovered their characteristics and descriptors
    guard let services = peripheral.services else { return }

    var allCharacteristicsDiscovered = true

    for service in services {
      if service.characteristics == nil {
        allCharacteristicsDiscovered = false
        break
      }
    }

    if !allCharacteristicsDiscovered {
      print("SdkBleModule: Still discovering characteristics for other services...")
      return
    }

    // Use the shared function to check and start action queue
    checkAndStartActionQueue(peripheral)
  }

  func peripheral(
    _ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic,
    error: Error?
  ) {
    print(
      "SdkBleModule: didUpdateNotificationStateFor: \(characteristic.uuid), isNotifying: \(characteristic.isNotifying), error: \(error?.localizedDescription ?? "none")"
    )

    // Find the action in the queue
    guard
      let actionIndex = charActionQueue.firstIndex(where: {
        $0.characteristic.uuid == characteristic.uuid
      })
    else {
      print("SdkBleModule: No action found for characteristic \(characteristic.uuid)")
      return
    }

    var action = charActionQueue[actionIndex]

    // Handle errors with retry logic for iPad
    if let error = error {
      print(
        "SdkBleModule: ERROR: Failed to set notification state for \(characteristic.uuid): \(error.localizedDescription)"
      )

      // Retry once for iPad compatibility
      if action.notifyPending {
        print("SdkBleModule: Retrying notification setup for \(characteristic.uuid)...")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
          peripheral.setNotifyValue(true, for: characteristic)
        }
      } else {
        // Mark as done and continue even if failed
        action.notifyPending = false
        charActionQueue[actionIndex] = action

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
          self?.processNextCharAction(peripheral)
        }
      }
      return
    }

    // Success - track notification state and mark as done
    if characteristic.isNotifying {
      notifyEnabledCharacteristics.insert(characteristic.uuid)
      print("SdkBleModule: ✅ 🔔 Notification ENABLED for \(characteristic.uuid) - tracking added")
    } else {
      notifyEnabledCharacteristics.remove(characteristic.uuid)
      print(
        "SdkBleModule: ❌ 🔔 Notification DISABLED for \(characteristic.uuid) - tracking removed")
    }
    action.notifyPending = false
    charActionQueue[actionIndex] = action

    // If we haven't attempted a read yet, do it now
    if !action.readAttempted {
      action.readAttempted = true
      charActionQueue[actionIndex] = action
      print("SdkBleModule: Reading characteristic after notification setup: \(characteristic.uuid)")
      // Increased delay for iPad stability
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
        peripheral.readValue(for: characteristic)
      }
      return
    }

    // Both notification and read are done, continue processing
    print("SdkBleModule: Notification setup complete for \(characteristic.uuid), continuing...")
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
      self?.processNextCharAction(peripheral)
    }
  }

  func peripheral(
    _ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?
  ) {
    print(
      "🔶 SdkBleModule: didWriteValueFor: \(characteristic.uuid), authPending=\(authenticationPending), serialPending=\(serialNumberPending), deviceTypePending=\(deviceTypePending)"
    )

    if let error = error {
      print("❌ SdkBleModule: didWriteValueFor error: \(error.localizedDescription)")
      return
    }

    // Check if this is the authentication characteristic
    if authenticationPending
      && characteristic.uuid == CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")
    {
      print("✅ SdkBleModule: Authentication command written successfully to FF11")
      // Authentication response will be handled in didUpdateValueFor when the characteristic notifies back
    }

    // Check if this is the serial number request characteristic
    if serialNumberPending
      && characteristic.uuid == CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")
    {
      print("✅ SdkBleModule: Serial number request written successfully to FF11")
      // Serial number response will be handled in didUpdateValueFor when the characteristic notifies back
    }

    // Check if this is the device type request characteristic
    if deviceTypePending
      && characteristic.uuid == CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")
    {
      print("✅ SdkBleModule: Device type request written successfully to FF11")
      // Device type response will be handled in didUpdateValueFor when the characteristic notifies back
    }
  }

  func peripheral(
    _ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic,
    error: Error?
  ) {
    if let error = error {
      print(
        "SdkBleModule: Descriptor discovery failed for \(characteristic.uuid): \(error.localizedDescription)"
      )
      return
    }

    print("SdkBleModule: didDiscoverDescriptorsFor characteristic: \(characteristic.uuid)")

    if let descriptors = characteristic.descriptors {
      for descriptor in descriptors {
        print("SdkBleModule:   Found descriptor: \(descriptor.uuid)")
        if descriptor.uuid == CBUUID(string: "2902") {
          print("SdkBleModule:   Found CCCD descriptor for notifications")
        }
      }
    } else {
      print("SdkBleModule:   No descriptors found for \(characteristic.uuid)")
    }

    // Check if all descriptors are now discovered and trigger action queue setup if ready
    checkAndStartActionQueue(peripheral)
  }

  private func checkAndStartActionQueue(_ peripheral: CBPeripheral) {
    guard let services = peripheral.services else { return }

    var allCharacteristicsDiscovered = true
    var allDescriptorsDiscovered = true

    for service in services {
      if service.characteristics == nil {
        allCharacteristicsDiscovered = false
        break
      }

      // Check if descriptors are discovered for notification characteristics
      if let characteristics = service.characteristics {
        for characteristic in characteristics {
          if characteristic.properties.contains(.notify)
            || characteristic.properties.contains(.indicate)
          {
            if characteristic.descriptors == nil {
              allDescriptorsDiscovered = false
              break
            }
          }
        }
      }

      if !allDescriptorsDiscovered {
        break
      }
    }

    if !allCharacteristicsDiscovered || !allDescriptorsDiscovered {
      return  // Not ready yet
    }

    print("SdkBleModule: All characteristics and descriptors discovered! Building action queue...")

    // Build the services array for the event
    var servicesArray: [[String: Any]] = []

    for service in services {
      var characteristicsArray: [[String: Any]] = []

      if let characteristics = service.characteristics {
        for characteristic in characteristics {
          var properties: [String] = []
          if characteristic.properties.contains(.read) {
            properties.append("read")
          }
          if characteristic.properties.contains(.write) {
            properties.append("write")
          }
          if characteristic.properties.contains(.notify) {
            properties.append("notify")
          }
          if characteristic.properties.contains(.indicate) {
            properties.append("indicate")
          }

          characteristicsArray.append([
            "uuid": characteristic.uuid.uuidString,
            "properties": properties,
          ])
        }
      }

      servicesArray.append([
        "uuid": service.uuid.uuidString,
        "characteristics": characteristicsArray,
      ])
    }

    // Send the servicesDiscovered event
    sendEvent(
      withName: "servicesDiscovered",
      body: [
        "deviceId": peripheral.identifier.uuidString,
        "services": servicesArray,
      ])

    // Queue all mapped characteristics for notification and/or read
    charActionQueue.removeAll()

    print("SdkBleModule: Looking for mapped characteristics...")
    for (serviceUUID, characteristicUUIDs) in serviceCharacteristicMap {
      print("SdkBleModule: Looking for service \(serviceUUID)")

      if let service = services.first(where: { $0.uuid == serviceUUID }) {
        print("SdkBleModule: Found service \(serviceUUID)")

        for charUUID in characteristicUUIDs {
          print("SdkBleModule: Looking for characteristic \(charUUID) in service \(serviceUUID)")

          if let characteristic = service.characteristics?.first(where: { $0.uuid == charUUID }) {
            let doNotify =
              characteristic.properties.contains(.notify)
              || characteristic.properties.contains(.indicate)

            // Only attempt read if characteristic supports it
            let doRead = characteristic.properties.contains(.read)

            charActionQueue.append(
              CharAction(
                characteristic: characteristic,
                notifyPending: doNotify,
                readPending: doRead,
                readAttempted: false
              ))
            print(
              "SdkBleModule: ✅ Queued \(characteristic.uuid), notify: \(doNotify), read: \(doRead), properties: \(characteristic.properties.rawValue), hasNotify: \(characteristic.properties.contains(.notify)), hasIndicate: \(characteristic.properties.contains(.indicate)), hasRead: \(characteristic.properties.contains(.read))"
            )
          } else {
            print("SdkBleModule: ❌ Characteristic \(charUUID) NOT FOUND in service \(serviceUUID)")
          }
        }
      } else {
        print("SdkBleModule: ❌ Service \(serviceUUID) NOT FOUND")

        // Debug: List all available services
        print("SdkBleModule: Available services:")
        for service in services {
          print("SdkBleModule:   - \(service.uuid)")
        }
      }
    }

    print("SdkBleModule: Action queue built with \(charActionQueue.count) characteristics")

    // Debug: Print all queued characteristics
    for (index, action) in charActionQueue.enumerated() {
      let charUuid = action.characteristic.uuid.uuidString
      let charType =
        charUuid.contains("ff02")
        ? "FF02"
        : charUuid.contains("ff11")
          ? "FF11"
          : charUuid.contains("ff21")
            ? "FF21"
            : charUuid.contains("ff31") ? "FF31" : charUuid.contains("ff41") ? "FF41" : "OTHER"
      print(
        "SdkBleModule: [\(index)] [\(charType)] \(charUuid) - notify: \(action.notifyPending), read: \(action.readPending), properties: \(action.characteristic.properties.rawValue)"
      )
    }

    // Now start processing the action queue and polling
    processNextCharAction(peripheral)
    startSequentialPolling()
  }

  func peripheral(
    _ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?
  ) {
    if let error = error {
      print(
        "SdkBleModule: didUpdateValueFor error for \(characteristic.uuid): \(error.localizedDescription)"
      )

      // Handle read errors in action queue - continue processing even if read fails
      if let actionIndex = charActionQueue.firstIndex(where: {
        $0.characteristic.uuid == characteristic.uuid
      }) {
        var action = charActionQueue[actionIndex]
        action.readPending = false
        charActionQueue[actionIndex] = action
        print(
          "SdkBleModule: ⚠️ Read failed for \(characteristic.uuid), but continuing action queue processing"
        )

        // Continue processing the action queue even after read failure
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
          self?.processNextCharAction(peripheral)
        }
      }
      return
    }

    guard let value = characteristic.value else { return }

    // CRITICAL: Make an immediate deep copy of the data to prevent race conditions
    let dataCopy = Data(value)
    let charUuid = characteristic.uuid.uuidString.lowercased()
    let timestamp = Date().timeIntervalSince1970 * 1000

    // Determine characteristic type
    let charType: String
    if charUuid.contains("ff02") {
      charType = "FF02"
    } else if charUuid.contains("ff11") {
      charType = "FF11"
    } else if charUuid.contains("ff21") {
      charType = "FF21"
    } else if charUuid.contains("ff31") {
      charType = "FF31"
    } else if charUuid.contains("ff41") {
      charType = "FF41"
    } else {
      charType = "OTHER"
    }

    print(
      "SdkBleModule: >>> didUpdateValueFor [\(timestamp)] [\(charType)]: \(characteristic.uuid) - \(dataCopy.count) bytes, isNotifying: \(characteristic.isNotifying), pollingInProgress: \(pollingInProgress), hasNotificationsEnabled: \(notifyEnabledCharacteristics.contains(characteristic.uuid))"
    )

    // Log the raw hex data for debugging
    let debugHex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
    print("SdkBleModule: Raw data: \(debugHex)")

    // Handle authentication response first
    if authenticationPending && charUuid.contains("ff11") {
      print("SdkBleModule: Received authentication response from FF11: \(debugHex)")

      // Check for success response B000 (or just B0 00)
      if dataCopy.count >= 2 && dataCopy[0] == 0xB0 && dataCopy[1] == 0x00 {
        print("SdkBleModule: ✅ Authentication successful (B000)")
        authenticationPending = false

        // Send authentication success event
        let base64Value = dataCopy.base64EncodedString()
        let hex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
        let authParams: [String: Any] = [
          "deviceId": peripheral.identifier.uuidString,
          "serviceUuid": characteristic.service!.uuid.uuidString,
          "characteristicUuid": characteristic.uuid.uuidString,
          "value": base64Value,
          "hex": hex,
          "timestamp": timestamp,
          "type": "FF11",
          "authenticated": true,
        ]

        sendEvent(withName: "characteristicChanged", body: authParams)

        // Now request serial number (add small delay to ensure device is ready)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
          self?.requestSerialNumber(peripheral: peripheral, characteristic: characteristic)
        }
      } else {
        print("SdkBleModule: ❌ Authentication failed - unexpected response: \(debugHex)")
        authenticationPending = false
        authenticationCharacteristic = nil
        authenticationPeripheral = nil
      }

      return  // Don't process further for authentication responses
    }

    // Handle serial number response
    if serialNumberPending && charUuid.contains("ff11") {
      print("SdkBleModule: Received serial number response from FF11: \(debugHex)")

      // Check for serial number response B1XX where XX is the length
      if dataCopy.count >= 2 && dataCopy[0] == 0xB1 {
        let dataLength = Int(dataCopy[1])
        print("SdkBleModule: ✅ Serial number response received, length: \(dataLength)")

        // Extract serial number from response (skip B1 and length byte)
        let serialNumberBytes: Data
        if dataCopy.count >= 2 + dataLength {
          serialNumberBytes = dataCopy.subdata(in: 2..<(2 + dataLength))
        } else {
          serialNumberBytes = dataCopy.subdata(in: 2..<dataCopy.count)
        }

        let serialNumber = String(data: serialNumberBytes, encoding: .utf8) ?? ""
        print("SdkBleModule: Serial Number: \(serialNumber)")

        serialNumberPending = false

        // Send serial number event
        let base64Value = dataCopy.base64EncodedString()
        let hex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
        let serialParams: [String: Any] = [
          "deviceId": peripheral.identifier.uuidString,
          "serviceUuid": characteristic.service!.uuid.uuidString,
          "characteristicUuid": characteristic.uuid.uuidString,
          "value": base64Value,
          "hex": hex,
          "timestamp": timestamp,
          "type": "FF11",
          "serialNumber": serialNumber,
          "dataLength": dataLength,
        ]

        sendEvent(withName: "characteristicChanged", body: serialParams)

        // Now request device type (add small delay to ensure device is ready)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
          guard let self = self,
            let char = self.authenticationCharacteristic,
            let periph = self.authenticationPeripheral
          else { return }
          self.requestDeviceType(peripheral: periph, characteristic: char)
        }
      } else {
        print("SdkBleModule: ❌ Serial number request failed - unexpected response: \(debugHex)")
        serialNumberPending = false
        authenticationCharacteristic = nil
        authenticationPeripheral = nil
      }

      return  // Don't process further for serial number responses
    }

    // Handle device type response
    if deviceTypePending && charUuid.contains("ff11") {
      print("SdkBleModule: Received device type response from FF11: \(debugHex)")

      // Check for device type response B2XX where XX is the length
      if dataCopy.count >= 2 && dataCopy[0] == 0xB2 {
        let dataLength = Int(dataCopy[1])
        print("SdkBleModule: ✅ Device type response received, length: \(dataLength)")

        // Extract device type from response (skip B2 and length byte)
        let deviceTypeBytes: Data
        if dataCopy.count >= 2 + dataLength {
          deviceTypeBytes = dataCopy.subdata(in: 2..<(2 + dataLength))
        } else {
          deviceTypeBytes = dataCopy.subdata(in: 2..<dataCopy.count)
        }

        let deviceType = String(data: deviceTypeBytes, encoding: .utf8) ?? ""
        print("SdkBleModule: Device Type: \(deviceType)")

        deviceTypePending = false

        // Send device type event
        let base64Value = dataCopy.base64EncodedString()
        let hex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
        let deviceTypeParams: [String: Any] = [
          "deviceId": peripheral.identifier.uuidString,
          "serviceUuid": characteristic.service!.uuid.uuidString,
          "characteristicUuid": characteristic.uuid.uuidString,
          "value": base64Value,
          "hex": hex,
          "timestamp": timestamp,
          "type": "FF11",
          "deviceType": deviceType,
          "dataLength": dataLength,
        ]

        sendEvent(withName: "characteristicChanged", body: deviceTypeParams)

        // Now send role command (add small delay to ensure device is ready)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
          guard let self = self else { return }
          self.sendRoleCommand(peripheral)
          // Clean up authentication references after role command is sent
          self.authenticationCharacteristic = nil
          self.authenticationPeripheral = nil
        }
      } else {
        print("SdkBleModule: ❌ Device type request failed - unexpected response: \(debugHex)")
        deviceTypePending = false
        authenticationCharacteristic = nil
        authenticationPeripheral = nil
      }

      return  // Don't process further for device type responses
    }

    // Handle device capabilities response (B3)
    if charUuid.contains("ff11") && dataCopy.count >= 3 && dataCopy[0] == 0xB3 {
      print("SdkBleModule: Received device capabilities response from FF11: \(debugHex)")

      let dataLength = Int(dataCopy[1])
      let capabilitiesByte = Int(dataCopy[2])

      // Parse capabilities bitmap (bits from right to left)
      let capDevInfo = (capabilitiesByte & 0x01) != 0  // Bit 0
      let capAlert = (capabilitiesByte & 0x02) != 0  // Bit 1
      let capTelem = (capabilitiesByte & 0x04) != 0  // Bit 2
      let capInfusion = (capabilitiesByte & 0x08) != 0  // Bit 3
      let capCtrl = (capabilitiesByte & 0x10) != 0  // Bit 4
      let capDfu = (capabilitiesByte & 0x20) != 0  // Bit 5

      print(
        "SdkBleModule: Device Capabilities: DevInfo=\(capDevInfo), Alert=\(capAlert), Telem=\(capTelem), Infusion=\(capInfusion), Ctrl=\(capCtrl), DFU=\(capDfu)"
      )

      // Send capabilities event
      let base64Value = dataCopy.base64EncodedString()
      let hex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
      let capabilities: [String: Any] = [
        "CAP_DEVINFO": capDevInfo,
        "CAP_ALERT": capAlert,
        "CAP_TELEM": capTelem,
        "CAP_INFUSION": capInfusion,
        "CAP_CTRL": capCtrl,
        "CAP_DFU": capDfu,
      ]
      let capabilitiesParams: [String: Any] = [
        "deviceId": peripheral.identifier.uuidString,
        "serviceUuid": characteristic.service!.uuid.uuidString,
        "characteristicUuid": characteristic.uuid.uuidString,
        "value": base64Value,
        "hex": hex,
        "timestamp": timestamp,
        "type": "FF11",
        "dataLength": dataLength,
        "capabilitiesByte": capabilitiesByte,
        "capabilitiesBinary": String(capabilitiesByte, radix: 2).leftPadding(
          toLength: 8, withPad: "0"),
        "capabilities": capabilities,
      ]

      sendEvent(withName: "characteristicChanged", body: capabilitiesParams)
      return  // Don't process further for capabilities responses
    }

    // Validate data length for known characteristics
    if let expectedLength = expectedDataLengths[charUuid] {
      if dataCopy.count < expectedLength {
        print(
          "SdkBleModule: ⚠️ WARNING: Partial data received for \(charType)! Got \(dataCopy.count) bytes, expected \(expectedLength) bytes"
        )
        print(
          "SdkBleModule: This indicates the device sent incomplete data or BLE read was interrupted"
        )
      } else if dataCopy.count == expectedLength {
        print("SdkBleModule: ✅ Complete data received for \(charType): \(dataCopy.count) bytes")
      }
    }

    // Determine if this is a notification or read
    // iOS has ONE callback for both, Android has TWO separate callbacks
    // We track which characteristics have notifications enabled
    let hasNotificationsEnabled = notifyEnabledCharacteristics.contains(characteristic.uuid)
    let isFromPolling = pollingInProgress

    print(
      "SdkBleModule: hasNotificationsEnabled=\(hasNotificationsEnabled), isFromPolling=\(isFromPolling)"
    )

    // Handle action queue for initial reads
    if let actionIndex = charActionQueue.firstIndex(where: {
      $0.characteristic.uuid == characteristic.uuid
    }) {
      print("SdkBleModule: [IN ACTION QUEUE] Processing \(charType) - \(dataCopy.count) bytes")
      var action = charActionQueue[actionIndex]
      action.readPending = false
      charActionQueue[actionIndex] = action

      // Only send event for initial setup reads when not polling
      // During polling, notifications from didUpdateValueFor will be used instead
      // This matches Android's onCharacteristicRead behavior
      if !isFromPolling {
        // Send immediately without delay to ensure stability
        self.sendCharacteristicEvent(
          peripheral: peripheral, characteristic: characteristic, value: dataCopy,
          timestamp: timestamp, charType: charType)
        print("SdkBleModule: Initial read data sent for \(characteristic.uuid)")
      }

      processNextCharAction(peripheral)

      // Sequential polling: trigger next read
      if pollingActive && peripheral == pollingPeripheral && pollingInProgress {
        // Add delay to prevent overwhelming the BLE stack
        // Increased to 0.2s to ensure complete data reads and reduce interference
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
          self?.pollNextCharacteristic()
        }
      }

      return
    }

    // This is a notification update or polling read
    print(
      "SdkBleModule: [NOT IN ACTION QUEUE] This is a notification or polling read for \(charType) - \(dataCopy.count) bytes"
    )

    // MATCH ANDROID BEHAVIOR EXACTLY:
    // Android has TWO separate callbacks:
    // 1. onCharacteristicChanged (notifications) - ALWAYS sends events immediately, NO CHECKS
    // 2. onCharacteristicRead (reads) - Only sends if (!pollingInProgress)
    //
    // iOS has ONE callback (didUpdateValueFor) for BOTH, so we must separate them:
    // - If notifications enabled → treat like onCharacteristicChanged → ALWAYS send
    // - If notifications NOT enabled → treat like onCharacteristicRead → check polling

    if hasNotificationsEnabled {
      // This behaves like Android's onCharacteristicChanged
      // ALWAYS send notification events immediately, regardless of polling state
      // Process on notification queue to ensure thread safety and prevent data loss
      print("SdkBleModule: 📤 Sending NOTIFICATION event for [\(charType)] \(characteristic.uuid)")

      // Send immediately without delay to ensure stability
      self.sendCharacteristicEvent(
        peripheral: peripheral, characteristic: characteristic, value: dataCopy,
        timestamp: timestamp, charType: charType)
      print(
        "SdkBleModule: <<< Notification EVENT SENT [\(timestamp)] [\(charType)] for \(characteristic.uuid) (like Android onCharacteristicChanged)"
      )
    } else if !isFromPolling {
      // This behaves like Android's onCharacteristicRead when !pollingInProgress
      // Only send read events when NOT polling
      print("SdkBleModule: 📤 Sending READ event for [\(charType)] \(characteristic.uuid)")

      // Send immediately without delay
      self.sendCharacteristicEvent(
        peripheral: peripheral, characteristic: characteristic, value: dataCopy,
        timestamp: timestamp, charType: charType)
      print(
        "SdkBleModule: <<< Read EVENT SENT [\(timestamp)] [\(charType)] for \(characteristic.uuid) (like Android onCharacteristicRead)"
      )
    } else {
      // This is a polling read - skip event (matches Android behavior)
      print(
        "SdkBleModule: 🚫 Skipping polling read event for [\(charType)] \(characteristic.uuid) (matches Android)"
      )
    }

    // Sequential polling: trigger next read
    if pollingActive && peripheral == pollingPeripheral && pollingInProgress {
      // Add delay to prevent overwhelming the BLE stack
      // Increased to 0.2s to ensure complete data reads
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
        self?.pollNextCharacteristic()
      }
    }
  }

  // MARK: - Helper Methods

  private func sendCharacteristicEvent(
    peripheral: CBPeripheral, characteristic: CBCharacteristic, value: Data,
    timestamp: TimeInterval, charType: String
  ) {
    print("SdkBleModule: 🚀 sendCharacteristicEvent called for [\(charType)] \(characteristic.uuid)")

    // Ensure we're working with a copy of the data
    let dataCopy = Data(value)

    let base64Value = dataCopy.base64EncodedString()
    let hex = dataCopy.map { String(format: "%02X", $0) }.joined(separator: "-")
    let charUuid = characteristic.uuid.uuidString.lowercased()

    var params: [String: Any] = [
      "deviceId": peripheral.identifier.uuidString,
      "serviceUuid": characteristic.service?.uuid.uuidString ?? "",
      "characteristicUuid": characteristic.uuid.uuidString,
      "value": base64Value,
      "hex": hex,
      "timestamp": timestamp,
      "type": charType,
    ]

    // Parse specific characteristics
    if charUuid == "0000ff31-0000-1000-8000-00805f9b34fb" || charUuid.contains("ff31") {
      if dataCopy.count >= 2 {
        let structId = Int(dataCopy[0])
        let totalLen = Int(dataCopy[1])
        params["struct_id"] = structId
        params["total_len"] = totalLen

        // Extract the data portion (skip first 2 bytes)
        let chunkData = dataCopy.count > 2 ? dataCopy.subdata(in: 2..<dataCopy.count) : Data()

        // Add to buffer (thread-safe) - use separate queue to avoid deadlock
        chunkBufferQueue.async { [weak self] in
          guard let self = self else { return }
          if self.ff31ChunkBuffers[structId] == nil {
            self.ff31ChunkBuffers[structId] = ChunkBuffer(structId: structId)
          }
          self.ff31ChunkBuffers[structId]?.chunks.append(chunkData)
          self.ff31ChunkBuffers[structId]?.lastUpdateTime = Date()
        }

        // Clean up old buffers
        cleanupOldChunkBuffers()

        // Combine all chunks for this struct_id
        chunkBufferQueue.sync { [weak self] in
          guard let self = self else { return }
          if let buffer = self.ff31ChunkBuffers[structId] {
            var combinedData = Data()
            for chunk in buffer.chunks {
              combinedData.append(chunk)
            }

            let combinedString = String(data: combinedData, encoding: .utf8) ?? ""
            params["stringData"] = combinedString
            params["stringDataHex"] = combinedString
            params["chunk_count"] = buffer.chunks.count
            params["combined_length"] = combinedData.count
            params["chunk_marker"] = String(format: "%c", structId)

            print(
              "SdkBleModule: FF31 chunk received: struct_id=\(structId) (\(String(format: "%c", structId))), totalLen=\(totalLen), chunkSize=\(chunkData.count), bufferSize=\(buffer.chunks.count), combined='\(combinedString)'"
            )
          }
        }
      } else {
        // Fallback: convert entire payload to string
        let strData = String(data: dataCopy, encoding: .utf8) ?? ""
        params["stringData"] = strData
        params["stringDataHex"] = strData
      }
    } else if charUuid == "0000ff02-0000-1000-8000-00805f9b34fb" || charUuid.contains("ff02") {
      let parsed = parseFF02Data(data: dataCopy)
      params["parsed"] = parsed
      print("SdkBleModule: FF02 parsed data: \(parsed)")
    } else if charUuid == "0000ff21-0000-1000-8000-00805f9b34fb" || charUuid.contains("ff21") {
      print("SdkBleModule: 🔍 FF21 detected, calling parseBleNotification with \(dataCopy.count) bytes")
      let parsed = parseBleNotification(data: dataCopy)
      params["parsed"] = parsed
      print("SdkBleModule: ✅ FF21 parsed data: \(parsed)")
    } else if charUuid == "0000ff41-0000-1000-8000-00805f9b34fb" || charUuid.contains("ff41") {
      params["parsed"] = parseFF41Data(data: dataCopy)
    }

    // Send event immediately - no delay to ensure stability
    print(
      "SdkBleModule: 📡 Emitting 'characteristicChanged' event for [\(charType)] \(characteristic.uuid.uuidString)"
    )
    self.sendEvent(withName: "characteristicChanged", body: params)
    print(
      "SdkBleModule: ✅ Event emitted successfully for [\(charType)] \(characteristic.uuid.uuidString)"
    )
  }

  private func processNextCharAction(_ peripheral: CBPeripheral) {
    print("SdkBleModule: processNextCharAction: queue size=\(charActionQueue.count)")

    while !charActionQueue.isEmpty {
      let action = charActionQueue[0]
      let charUuid = action.characteristic.uuid.uuidString.lowercased()
      let charType: String
      if charUuid.contains("ff02") {
        charType = "FF02"
      } else if charUuid.contains("ff11") {
        charType = "FF11"
      } else if charUuid.contains("ff21") {
        charType = "FF21"
      } else if charUuid.contains("ff31") {
        charType = "FF31"
      } else if charUuid.contains("ff41") {
        charType = "FF41"
      } else {
        charType = "OTHER"
      }

      if action.notifyPending {
        // Check if characteristic actually supports notifications
        let hasNotify = action.characteristic.properties.contains(.notify)
        let hasIndicate = action.characteristic.properties.contains(.indicate)

        if hasNotify || hasIndicate {
          print(
            "SdkBleModule: 🔔 Enabling notification for [\(charType)] \(action.characteristic.uuid) (hasNotify: \(hasNotify), hasIndicate: \(hasIndicate))"
          )
          peripheral.setNotifyValue(true, for: action.characteristic)
          print("SdkBleModule: 🔔 setNotifyValue(true) called for \(action.characteristic.uuid)")
          // didUpdateNotificationStateFor will handle the rest and continue processing
          return
        } else {
          print(
            "SdkBleModule: WARNING: Characteristic [\(charType)] \(action.characteristic.uuid) doesn't support notifications! Skipping..."
          )
          var mutableAction = action
          mutableAction.notifyPending = false
          charActionQueue[0] = mutableAction
          // Continue to read attempt
        }
      }

      if !action.readAttempted {
        var mutableAction = action
        mutableAction.readAttempted = true
        charActionQueue[0] = mutableAction

        // Check if characteristic supports reading before attempting
        let hasRead = action.characteristic.properties.contains(.read)

        if hasRead {
          print("SdkBleModule: Reading characteristic [\(charType)] \(action.characteristic.uuid)")
          peripheral.readValue(for: action.characteristic)
          // didUpdateValueFor will handle the rest and continue processing
          return
        } else {
          print(
            "SdkBleModule: ⚠️ Characteristic [\(charType)] \(action.characteristic.uuid) doesn't support reading, skipping read"
          )
          // Mark read as done even though we didn't attempt it
          mutableAction.readPending = false
          charActionQueue[0] = mutableAction
          // Continue to completion check below
        }
      }

      // If both actions are done, remove and continue
      print("SdkBleModule: Completed actions for [\(charType)] \(action.characteristic.uuid)")
      charActionQueue.removeFirst()
    }

    print("SdkBleModule: All notifications enabled and all mapped characteristics processed")

    // Start authentication if FF11 characteristic is available and not already authenticated
    if !authenticationPending {
      performAuthentication(peripheral)
    }
  }

  private func startSequentialPolling() {
    pollingActive = true
    pollingInProgress = false
    print(
      "SdkBleModule: startSequentialPolling: pollingActive=\(pollingActive), pollingPeripheral=\(pollingPeripheral != nil)"
    )

    // Delay the start of polling to allow initial notifications to flow freely
    // Increased delay to 10 seconds to ensure notifications are stable
    DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
      self?.pollSequentialRunnable()
    }
  }

  private func pollSequentialRunnable() {
    print(
      "SdkBleModule: pollSequentialRunnable: pollingActive=\(pollingActive), pollingPeripheral=\(pollingPeripheral != nil), pollingInProgress=\(pollingInProgress)"
    )

    guard pollingActive, let peripheral = pollingPeripheral else { return }

    if !pollingInProgress {
      // Fill the queue with all service/char pairs
      pollingQueue.removeAll()
      for (serviceUUID, characteristicUUIDs) in serviceCharacteristicMap {
        for charUUID in characteristicUUIDs {
          pollingQueue.append((serviceUUID: serviceUUID, characteristicUUID: charUUID))
        }
      }
      pollingInProgress = true
      pollNextCharacteristic()
    }
  }

  private func pollNextCharacteristic() {
    print(
      "SdkBleModule: pollNextCharacteristic: pollingActive=\(pollingActive), pollingPeripheral=\(pollingPeripheral != nil), queueSize=\(pollingQueue.count)"
    )

    guard pollingActive, let peripheral = pollingPeripheral else { return }

    if pollingQueue.isEmpty {
      pollingInProgress = false
      // Increase interval between polling cycles to reduce interference with notifications
      DispatchQueue.main.asyncAfter(deadline: .now() + pollIntervalMs) { [weak self] in
        self?.pollSequentialRunnable()
      }
      return
    }

    let (serviceUUID, charUUID) = pollingQueue.removeFirst()

    guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }) else {
      print("SdkBleModule: Polling: Service \(serviceUUID) not found")
      // Continue to next characteristic with small delay
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        self?.pollNextCharacteristic()
      }
      return
    }

    guard let characteristic = service.characteristics?.first(where: { $0.uuid == charUUID }) else {
      print("SdkBleModule: Polling: Characteristic \(charUUID) not found in service \(serviceUUID)")
      // Continue to next characteristic with small delay
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        self?.pollNextCharacteristic()
      }
      return
    }

    // Add delay before read to prevent overwhelming the BLE stack and ensure complete data
    // Increased to 0.3s to allow device to prepare complete data packet and reduce interference
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self, weak peripheral] in
      guard let self = self, let peripheral = peripheral else { return }
      peripheral.readValue(for: characteristic)
      print("SdkBleModule: Polling: Read attempted for \(charUUID) in \(serviceUUID)")
      // The next pollNextCharacteristic will be triggered from didUpdateValueFor
    }
  }

  private func stopPolling() {
    pollingActive = false
    pollTimer?.invalidate()
    pollTimer = nil
    pollingPeripheral = nil
    pollingQueue.removeAll()
    pollingInProgress = false
    print("SdkBleModule: Polling stopped and state cleared")
  }

  private func performAuthentication(_ peripheral: CBPeripheral) {
    guard
      let service = peripheral.services?.first(where: {
        $0.uuid == CBUUID(string: "0000ff10-0000-1000-8000-00805f9b34fb")
      })
    else {
      print("SdkBleModule: Authentication service FF10 not found")
      return
    }

    guard
      let characteristic = service.characteristics?.first(where: {
        $0.uuid == CBUUID(string: "0000ff11-0000-1000-8000-00805f9b34fb")
      })
    else {
      print("SdkBleModule: Authentication characteristic FF11 not found")
      return
    }

    authenticationPending = true
    authenticationCharacteristic = characteristic
    authenticationPeripheral = peripheral

    // Construct command: A006414243313233
    // A0 = authentication prefix, 06 = data length, 414243313233 = "ABC123" in hex
    let authCommand: [UInt8] = [0xA0, 0x06, 0x41, 0x42, 0x43, 0x31, 0x32, 0x33]
    let authData = Data(authCommand)

    print(
      "SdkBleModule: Writing authentication command to FF11: \(authCommand.map { String(format: "%02X", $0) }.joined())"
    )
    peripheral.writeValue(authData, for: characteristic, type: .withResponse)
  }

  private func requestSerialNumber(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
    print("🔵 SdkBleModule: Starting serial number request...")
    serialNumberPending = true

    // Construct command: A100
    // A1 = serial number request prefix, 00 = data length (no additional data)
    let serialCommand: [UInt8] = [0xA1, 0x00]
    let serialData = Data(serialCommand)

    print(
      "🔵 SdkBleModule: Writing serial number request to FF11: \(serialCommand.map { String(format: "%02X", $0) }.joined())"
    )
    peripheral.writeValue(serialData, for: characteristic, type: .withResponse)
    print("🔵 SdkBleModule: Serial number write command sent")
  }

  private func requestDeviceType(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
    print("🟣 SdkBleModule: Starting device type request...")
    deviceTypePending = true

    // Construct command: A200
    // A2 = device type request prefix, 00 = data length (no additional data)
    let deviceTypeCommand: [UInt8] = [0xA2, 0x00]
    let deviceTypeData = Data(deviceTypeCommand)

    print(
      "🟣 SdkBleModule: Writing device type request to FF11: \(deviceTypeCommand.map { String(format: "%02X", $0) }.joined())"
    )
    peripheral.writeValue(deviceTypeData, for: characteristic, type: .withResponse)
    print("🟣 SdkBleModule: Device type write command sent")
  }

  private func cleanupOldChunkBuffers() {
    chunkBufferQueue.async { [weak self] in
      guard let self = self else { return }

      let currentTime = Date()
      var keysToRemove: [Int] = []

      for (structId, buffer) in self.ff31ChunkBuffers {
        if currentTime.timeIntervalSince(buffer.lastUpdateTime) > self.chunkTimeoutMs {
          keysToRemove.append(structId)
        }
      }

      for structId in keysToRemove {
        self.ff31ChunkBuffers.removeValue(forKey: structId)
        print("SdkBleModule: Cleaned up expired chunk buffer for struct_id=\(structId)")
      }
    }
  }

  private func parseBleNotification(data: Data) -> [String: Any] {
    var map: [String: Any] = [:]

    let debugHex = data.map { String(format: "%02X", $0) }.joined(separator: "-")
    print("SdkBleModule: FF21 hex: \(debugHex)")

    guard data.count >= 9 else {
      return map  // Not enough data
    }

    let bytes = [UInt8](data)
    
    // Handle 9-byte format (iOS) vs 11-byte format (Android)
    if data.count == 9 {
      // iOS receives 9 bytes without struct_id and total_len header
      // Parse directly as data fields
      map["struct_id"] = 0  // Default
      map["total_len"] = 9  // Default
      map["rgb_en"] = Int(bytes[0])
      map["rgb_color"] = Int(bytes[1])
      map["rgb_action"] = Int(bytes[2])
      map["dled_en"] = Int(bytes[3])
      map["dled_mask"] = Int(bytes[4])
      let binaryString = String(bytes[4], radix: 2)
      map["dled_mask_bin"] = String(repeating: "0", count: max(0, 8 - binaryString.count)) + binaryString
      map["dled_action"] = Int(bytes[5])
      map["buzzer_en"] = Int(bytes[6])
      map["buzzer_action"] = Int(bytes[7])
      map["device_state"] = Int(bytes[8])
    } else {
      // 11-byte format (Android) - includes struct_id and total_len
      map["struct_id"] = Int(bytes[0])
      map["total_len"] = Int(bytes[1])
      map["rgb_en"] = Int(bytes[2])
      map["rgb_color"] = Int(bytes[3])
      map["rgb_action"] = Int(bytes[4])
      map["dled_en"] = Int(bytes[5])
      map["dled_mask"] = Int(bytes[6])
      let binaryString = String(bytes[6], radix: 2)
      map["dled_mask_bin"] = String(repeating: "0", count: max(0, 8 - binaryString.count)) + binaryString
      map["dled_action"] = Int(bytes[7])
      map["buzzer_en"] = Int(bytes[8])
      
      if data.count >= 10 {
        map["buzzer_action"] = Int(bytes[9])
      }
      if data.count >= 11 {
        map["device_state"] = Int(bytes[10])
      }
    }

    return map
  }

  private func parseFF02Data(data: Data) -> [String: Any] {
    var map: [String: Any] = [:]

    guard data.count >= 3 else {
      print("SdkBleModule: FF02 data too short: \(data.count) bytes")
      return map
    }

    let bytes = [UInt8](data)

    // Extract bytes
    let structId = Int(bytes[0])
    let totalLen = Int(bytes[1])
    let value = Int(bytes[2])  // Convert hex to decimal

    map["struct_id"] = structId
    map["total_len"] = totalLen
    map["value"] = value

    // Add hex representation for debugging
    map["value_hex"] = String(format: "%02X", value)

    return map
  }

  private func parseFF41Data(data: Data) -> [String: Any] {
    var map: [String: Any] = [:]

    guard data.count >= 18 else { return map }

    let bytes = [UInt8](data)

    // Always expect Android format: struct_id and total_len at start
    let structId = Int(bytes[0])
    let totalLen = Int(bytes[1])

    map["struct_id"] = structId
    map["total_len"] = totalLen

    // Parse 4-byte fields with little-endian to big-endian conversion
    // infusion_state: bytes 2-5 (reversed)
    let infusionState =
      (Int(bytes[5]) << 24) | (Int(bytes[4]) << 16) | (Int(bytes[3]) << 8) | Int(bytes[2])

    // infusion_duration: bytes 6-9 (reversed)
    let infusionDuration =
      (Int(bytes[9]) << 24) | (Int(bytes[8]) << 16) | (Int(bytes[7]) << 8) | Int(bytes[6])

    // infusion_pressure: bytes 10-13 (reversed)
    let infusionPressure =
      (Int(bytes[13]) << 24) | (Int(bytes[12]) << 16) | (Int(bytes[11]) << 8) | Int(bytes[10])

    // infusion_level: bytes 14-17 (reversed)
    let infusionLevel =
      (Int(bytes[17]) << 24) | (Int(bytes[16]) << 16) | (Int(bytes[15]) << 8) | Int(bytes[14])

    map["infusion_state"] = infusionState
    map["infusion_duration"] = infusionDuration  // Already in milliseconds
    map["infusion_pressure"] = Double(infusionPressure) / 1000.0  // Divide by 1000
    map["infusion_level"] = infusionLevel

    // Add hex representations for debugging
    map["infusion_state_hex"] = String(format: "%08X", infusionState)
    map["infusion_duration_hex"] = String(format: "%08X", infusionDuration)
    map["infusion_pressure_hex"] = String(format: "%08X", infusionPressure)
    map["infusion_level_hex"] = String(format: "%08X", infusionLevel)

    return map
  }
}

// String extension for left padding
extension String {
  func leftPadding(toLength: Int, withPad: String) -> String {
    guard toLength > self.count else { return self }
    let padding = String(repeating: withPad, count: toLength - self.count)
    return padding + self
  }
}
