import Foundation import CoreBluetooth /// Tries KBeacon → DXSmart → BlueCharm in sequence for unknown beacon types. /// Matches Android's fallback behavior when beacon type can't be determined. final class FallbackProvisioner: BeaconProvisioner { private let peripheral: CBPeripheral private let centralManager: CBCentralManager private var activeProvisioner: (any BeaconProvisioner)? private(set) var isConnected: Bool = false var diagnosticLog: ProvisionLog? var bleManager: BLEManager? init(peripheral: CBPeripheral, centralManager: CBCentralManager) { self.peripheral = peripheral self.centralManager = centralManager } func connect() async throws { let provisioners: [() -> any BeaconProvisioner] = [ { [self] in var p = KBeaconProvisioner(peripheral: peripheral, centralManager: centralManager) p.diagnosticLog = diagnosticLog p.bleManager = bleManager return p }, { [self] in var p = DXSmartProvisioner(peripheral: peripheral, centralManager: centralManager) p.diagnosticLog = diagnosticLog p.bleManager = bleManager return p }, { [self] in var p = BlueCharmProvisioner(peripheral: peripheral, centralManager: centralManager) p.diagnosticLog = diagnosticLog p.bleManager = bleManager return p }, ] let typeNames = ["KBeacon", "DXSmart", "BlueCharm"] var lastError: Error = ProvisionError.connectionTimeout for (index, makeProvisioner) in provisioners.enumerated() { await diagnosticLog?.log("fallback", "Trying \(typeNames[index]) provisioner…") let provisioner = makeProvisioner() do { try await provisioner.connect() activeProvisioner = provisioner isConnected = true await diagnosticLog?.log("fallback", "\(typeNames[index]) connected successfully") return } catch { provisioner.disconnect() lastError = error await diagnosticLog?.log("fallback", "\(typeNames[index]) failed: \(error.localizedDescription)", isError: true) } } throw lastError } func writeConfig(_ config: BeaconConfig) async throws { guard let provisioner = activeProvisioner else { throw ProvisionError.notConnected } try await provisioner.writeConfig(config) } func disconnect() { activeProvisioner?.disconnect() activeProvisioner = nil isConnected = false } }