import Foundation
import CoreBluetooth
import Combine
import Security

enum SparkConnectionPhase: Equatable {
    case disconnected
    case connecting
    case discovering
    case authenticating
    case ready
    case recording
    case reconnecting
    case failed(String)
}

enum SparkRecorderConnectionEvent {
    case recording(fileName: String)
    case reconnecting
    case ready
    case failed(String)
}

struct BLECommunicationLogEntry: Identifiable, Equatable {
    let id = UUID()
    let deviceID: String
    let timestamp: Date
    let kind: String
    let message: String
}

struct ObservedBLEAdvertisement: Identifiable, Equatable {
    let id: String
    var name: String
    var serviceUUIDs: [String]
    var rssi: Int
    var peakRSSI: Int
    var firstSeenAt: Date
    var lastSeenAt: Date
    var lastTransitionAt: Date
    var isPresent: Bool
    var reappearanceCount: Int

    var matchesSpark: Bool {
        BLEManager.isSparkAdvertisement(
            name: name,
            serviceUUIDs: serviceUUIDs.map { CBUUID(string: $0) }
        )
    }

    var isStrongSignal: Bool {
        rssi >= -60
    }
}

enum SparkBLEError: LocalizedError {
    case bluetoothUnavailable
    case deviceUnavailable
    case incompatibleDevice
    case connectionFailed(String)
    case commandUnavailable
    case commandTimedOut(String)
    case pairingFailed
    case deviceReported(String)

    var errorDescription: String? {
        switch self {
        case .bluetoothUnavailable: return "蓝牙当前不可用，请确认系统蓝牙已开启。"
        case .deviceUnavailable: return "找不到该微光设备，请让设备靠近 iPhone 后重试。"
        case .incompatibleDevice: return "该设备没有提供微光录音服务。"
        case .connectionFailed(let reason): return "连接微光失败：\(reason)"
        case .commandUnavailable: return "微光指令通道尚未就绪。"
        case .commandTimedOut(let command): return "设备未及时响应指令 \(command)。"
        case .pairingFailed: return "微光密钥配对失败，请重置设备后重试。"
        case .deviceReported(let message): return message
        }
    }
}

/// Owns the CoreBluetooth central role and implements the MR20/Spark command protocol.
final class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    static let shared = BLEManager()

    private static let debugMinimumRSSI = -75
    private static let advertisementDisappearanceInterval: TimeInterval = 4
    private static let disappearedAdvertisementRetentionInterval: TimeInterval = 30

    static let serviceUUID = CBUUID(string: "001120a0-2233-4455-6677-88995a5b5c5d")
    static let audioNotifyUUID = CBUUID(string: "001120a1-2233-4455-6677-88995a5b5c5d")
    static let writeUUID = CBUUID(string: "001120a2-2233-4455-6677-88995a5b5c5d")
    static let commandNotifyUUID = CBUUID(string: "001120a3-2233-4455-6677-88995a5b5c5d")

    static func isYLF20AdvertisementName(_ name: String?) -> Bool {
        let normalizedName = name?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() ?? ""
        return normalizedName == "ylf20" || normalizedName.hasPrefix("ylf20_")
    }

    static func isSparkAdvertisement(name: String?, serviceUUIDs: [CBUUID]) -> Bool {
        let normalizedName = name?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() ?? ""
        return serviceUUIDs.contains(serviceUUID)
            || isYLF20AdvertisementName(name)
            || normalizedName.contains("spark")
            || normalizedName.contains("mr20")
            || normalizedName.contains("微光")
    }

    @Published private(set) var state: CBManagerState = .unknown
    @Published private(set) var isScanning: Bool = false
    @Published private(set) var discoveredDevices: [DiscoveredBLEDevice] = []
    @Published private(set) var observedPeripheralCount: Int = 0
    @Published private(set) var observedAdvertisements: [ObservedBLEAdvertisement] = []
    @Published private(set) var communicationLogs: [BLECommunicationLogEntry] = []
    @Published private(set) var boundDevices: [BoundDevice] = []
    @Published private(set) var connectionPhases: [String: SparkConnectionPhase] = [:]
    @Published private(set) var recordingDeviceID: String?
    @Published private(set) var lastErrorMessage: String?

    private struct SparkCharacteristics {
        var audioNotify: CBCharacteristic?
        var commandNotify: CBCharacteristic?
        var write: CBCharacteristic?

        var isComplete: Bool {
            audioNotify != nil && commandNotify != nil && write != nil
        }
    }

    private struct PendingBinding {
        let discoveredDevice: DiscoveredBLEDevice
        let userID: String
        let password: String
        let completion: (Result<BoundDevice, Error>) -> Void
    }

    private struct PendingCommand {
        let token: UUID
        let completion: (Result<String, Error>) -> Void
    }

    private enum ProbeWireFormat: CaseIterable {
        case documented
        case documentedCRLF
        case documentedNull
        case legacyPrefix
        case legacyPrefixCRLF
        case legacyPrefixNull

        var label: String {
            switch self {
            case .documented: return "文档原格式"
            case .documentedCRLF: return "文档格式 + CRLF"
            case .documentedNull: return "文档格式 + NUL"
            case .legacyPrefix: return "无 PQ_ 前缀"
            case .legacyPrefixCRLF: return "无 PQ_ 前缀 + CRLF"
            case .legacyPrefixNull: return "无 PQ_ 前缀 + NUL"
            }
        }

        func data(for command: String) -> Data {
            let base: String
            switch self {
            case .legacyPrefix, .legacyPrefixCRLF, .legacyPrefixNull:
                base = command.replacingOccurrences(of: "PQ_BLE&", with: "BLE&")
            default:
                base = command
            }

            switch self {
            case .documentedCRLF, .legacyPrefixCRLF:
                return Data("\(base)\r\n".utf8)
            case .documentedNull, .legacyPrefixNull:
                var data = Data(base.utf8)
                data.append(0)
                return data
            default:
                return Data(base.utf8)
            }
        }
    }

    private var centralManager: CBCentralManager!
    private let boundDevicesKey = "com.celestia.trace.bound_devices"
    private let restorationIdentifier = "com.celestia.trace.spark.central"
    private let keychainService = "com.celestia.trace.spark.pairing"

    private var peripherals: [String: CBPeripheral] = [:]
    private var characteristics: [String: SparkCharacteristics] = [:]
    private var pendingBindings: [String: PendingBinding] = [:]
    private var authenticatingDeviceIDs: Set<String> = []
    private var pendingStarts: [String: PendingCommand] = [:]
    private var pendingStops: [String: PendingCommand] = [:]
    private var audioConsumers: [String: (Data) -> Void] = [:]
    private var recorderStateConsumers: [String: (SparkRecorderConnectionEvent) -> Void] = [:]
    private var scanRequested = false
    private var observedPeripheralIDs: Set<String> = []
    private var observedAdvertisementsByID: [String: ObservedBLEAdvertisement] = [:]
    private var advertisementRefreshTimer: Timer?
    private var pendingDiagnosticResponses: [String: [String: String]] = [:]
    private var diagnosticTokens: [String: UUID] = [:]
    private var protocolProbeTokens: [String: UUID] = [:]
    private var activeProbeFormats: [String: ProbeWireFormat] = [:]

    var scanUnavailableMessage: String? {
        switch state {
        case .unauthorized:
            return "蓝牙权限未开启。请前往“设置”允许星痕访问蓝牙。"
        case .poweredOff:
            return "系统蓝牙已关闭，请先在控制中心或“设置”中开启蓝牙。"
        case .unsupported:
            return "当前运行设备不支持 BLE 扫描。请使用支持蓝牙的 iPhone 真机测试。"
        default:
            return nil
        }
    }

    override private init() {
        super.init()
        loadBoundDevices()
        centralManager = CBCentralManager(
            delegate: self,
            queue: nil,
            options: [CBCentralManagerOptionRestoreIdentifierKey: restorationIdentifier]
        )
    }

    // MARK: - Public device access

    func devices(forUserId userId: String) -> [BoundDevice] {
        boundDevices.filter { $0.boundUserId == userId }
    }

    func connectedDevices(forUserId userId: String) -> [BoundDevice] {
        devices(forUserId: userId).filter { $0.isConnected }
    }

    func phase(for deviceID: String) -> SparkConnectionPhase {
        connectionPhases[deviceID] ?? .disconnected
    }

    func communicationLogs(for deviceID: String) -> [BLECommunicationLogEntry] {
        communicationLogs.filter { $0.deviceID == deviceID }
    }

    func renameDevice(id: String, to proposedName: String) {
        let trimmed = proposedName.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty, let index = boundDevices.firstIndex(where: { $0.id == id }) else { return }
        boundDevices[index].name = String(trimmed.prefix(30))
        saveBoundDevices()
    }

    func unbindDevice(id: String) {
        var peripheralToDisconnect: CBPeripheral?
        if boundDevices.first(where: { $0.id == id })?.isConnected == true {
            try? writeCommand("PQ_BLE&BLE&OFF", to: id)
            peripheralToDisconnect = peripherals[id]
        }
        deletePairingPassword(for: id)
        boundDevices.removeAll(where: { $0.id == id })
        connectionPhases[id] = .disconnected
        if let peripheralToDisconnect {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self, weak peripheralToDisconnect] in
                guard let self, let peripheralToDisconnect else { return }
                self.centralManager.cancelPeripheralConnection(peripheralToDisconnect)
            }
        } else if let peripheral = peripherals[id] {
            centralManager.cancelPeripheralConnection(peripheral)
        }
        saveBoundDevices()
    }

    func updateCloudRegistration(deviceID: String, cloudID: String?, error: String?) {
        updateBoundDevice(id: deviceID) {
            $0.cloudID = cloudID
            $0.cloudSyncError = error
        }
    }

    // MARK: - Scanning and binding

    func startScanning() {
        discoveredDevices.removeAll()
        observedPeripheralIDs.removeAll()
        observedPeripheralCount = 0
        observedAdvertisementsByID.removeAll()
        observedAdvertisements.removeAll()
        lastErrorMessage = nil
        scanRequested = true
        startAdvertisementRefreshTimer()

        guard centralManager.state == .poweredOn else {
            // Keep the request pending while CoreBluetooth initializes. The
            // delegate starts scanning if the state later becomes powered on.
            isScanning = false
            return
        }

        isScanning = true
        // Some Spark firmware versions do not advertise the service UUID. Scan
        // broadly, but only surface peripherals that explicitly advertise the
        // Spark service or a known product name.
        centralManager.scanForPeripherals(
            withServices: nil,
            options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
        )
    }

    func stopScanning() {
        scanRequested = false
        advertisementRefreshTimer?.invalidate()
        advertisementRefreshTimer = nil
        if centralManager.state == .poweredOn {
            centralManager.stopScan()
        }
        isScanning = false
    }

    func connectAndBind(
        _ device: DiscoveredBLEDevice,
        userId: String,
        completion: @escaping (Result<BoundDevice, Error>) -> Void
    ) {
        guard centralManager.state == .poweredOn else {
            completion(.failure(SparkBLEError.bluetoothUnavailable))
            return
        }
        guard let peripheral = peripherals[device.id] else {
            completion(.failure(SparkBLEError.deviceUnavailable))
            return
        }

        communicationLogs.removeAll { $0.deviceID == device.id }
        appendCommunicationLog(
            deviceID: device.id,
            kind: "INFO",
            message: "开始绑定 \(device.name)，RSSI \(device.rssi) dBm"
        )
        let password = generatePairingPassword()
        pendingBindings[device.id] = PendingBinding(
            discoveredDevice: device,
            userID: userId,
            password: password,
            completion: completion
        )
        connectionPhases[device.id] = .connecting
        peripheral.delegate = self
        stopScanning()
        appendCommunicationLog(deviceID: device.id, kind: "INFO", message: "正在建立 BLE 连接")
        centralManager.connect(peripheral, options: nil)
    }

    func runProtocolDiagnostics(deviceID: String) {
        guard peripherals[deviceID]?.state == .connected,
              characteristics[deviceID]?.isComplete == true else {
            appendCommunicationLog(
                deviceID: deviceID,
                kind: "ERROR",
                message: "通信通道尚未就绪，无法发送诊断指令"
            )
            return
        }
        appendCommunicationLog(
            deviceID: deviceID,
            kind: "INFO",
            message: "重新执行协议诊断：状态、电量、容量、固件、MAC、校时"
        )
        queryDeviceStatus(deviceID)
    }

    // MARK: - Spark recording control

    func startSparkRecording(
        deviceID: String,
        onAudioData: @escaping (Data) -> Void,
        onStateChange: @escaping (SparkRecorderConnectionEvent) -> Void,
        completion: @escaping (Result<String, Error>) -> Void
    ) {
        guard boundDevices.first(where: { $0.id == deviceID })?.isConnected == true else {
            completion(.failure(SparkBLEError.deviceUnavailable))
            return
        }

        audioConsumers[deviceID] = onAudioData
        recorderStateConsumers[deviceID] = onStateChange
        let token = UUID()
        pendingStarts[deviceID] = PendingCommand(token: token, completion: completion)

        do {
            try writeCommand("PQ_BLE&STA", to: deviceID)
            scheduleTimeout(for: deviceID, command: "STA", token: token, isStart: true)
        } catch {
            pendingStarts.removeValue(forKey: deviceID)
            completion(.failure(error))
        }
    }

    func stopSparkRecording(
        deviceID: String,
        completion: @escaping (Result<String, Error>) -> Void
    ) {
        guard boundDevices.first(where: { $0.id == deviceID })?.isConnected == true else {
            completion(.failure(SparkBLEError.deviceUnavailable))
            return
        }

        let token = UUID()
        pendingStops[deviceID] = PendingCommand(token: token, completion: completion)
        do {
            try writeCommand("PQ_BLE&STO", to: deviceID)
            scheduleTimeout(for: deviceID, command: "STO", token: token, isStart: false)
        } catch {
            pendingStops.removeValue(forKey: deviceID)
            completion(.failure(error))
        }
    }

    func detachRecorder(deviceID: String) {
        audioConsumers.removeValue(forKey: deviceID)
        recorderStateConsumers.removeValue(forKey: deviceID)
    }

    // MARK: - CBCentralManagerDelegate

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        state = central.state
        guard central.state == .poweredOn else {
            isScanning = false
            markAllDevicesDisconnected()
            return
        }
        reconnectBoundDevices()
        if scanRequested {
            isScanning = true
            central.scanForPeripherals(
                withServices: nil,
                options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
            )
        }
    }

    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) {
        let restored = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] ?? []
        for peripheral in restored {
            let id = peripheral.identifier.uuidString
            peripherals[id] = peripheral
            peripheral.delegate = self
            connectionPhases[id] = peripheral.state == .connected ? .discovering : .reconnecting
            if peripheral.state == .connected {
                peripheral.discoverServices([Self.serviceUUID])
            } else {
                central.connect(peripheral, options: nil)
            }
        }
    }

    func centralManager(
        _ central: CBCentralManager,
        didDiscover peripheral: CBPeripheral,
        advertisementData: [String: Any],
        rssi RSSI: NSNumber
    ) {
        let id = peripheral.identifier.uuidString
        observedPeripheralIDs.insert(id)
        observedPeripheralCount = observedPeripheralIDs.count

        let advertisedName = advertisementData[CBAdvertisementDataLocalNameKey] as? String
            ?? peripheral.name
        let advertisedServices = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []

        guard RSSI.intValue != 127 else { return }
        let now = Date()
        let trimmedAdvertisedName = advertisedName?
            .trimmingCharacters(in: .whitespacesAndNewlines)
        let displayName = trimmedAdvertisedName?.isEmpty == false
            ? trimmedAdvertisedName!
            : "未提供名称"
        let serviceUUIDStrings = advertisedServices.map(\.uuidString)

        if var existing = observedAdvertisementsByID[id] {
            let hadDisappeared = !existing.isPresent
                || now.timeIntervalSince(existing.lastSeenAt) >= Self.advertisementDisappearanceInterval
            existing.name = displayName
            existing.serviceUUIDs = serviceUUIDStrings
            existing.rssi = RSSI.intValue
            existing.peakRSSI = max(existing.peakRSSI, RSSI.intValue)
            existing.lastSeenAt = now
            existing.isPresent = true
            if hadDisappeared {
                existing.reappearanceCount += 1
                existing.lastTransitionAt = now
            }
            observedAdvertisementsByID[id] = existing
        } else if RSSI.intValue >= Self.debugMinimumRSSI {
            observedAdvertisementsByID[id] = ObservedBLEAdvertisement(
                id: id,
                name: displayName,
                serviceUUIDs: serviceUUIDStrings,
                rssi: RSSI.intValue,
                peakRSSI: RSSI.intValue,
                firstSeenAt: now,
                lastSeenAt: now,
                lastTransitionAt: now,
                isPresent: true,
                reappearanceCount: 0
            )
        }
        publishObservedAdvertisements(now: now)

        // The formal binding list is intentionally restricted to the confirmed
        // production broadcast name. Service/characteristic UUIDs are verified
        // after connecting.
        guard Self.isYLF20AdvertisementName(advertisedName) else { return }

        peripherals[id] = peripheral
        peripheral.delegate = self
        let device = DiscoveredBLEDevice(
            id: id,
            name: trimmedAdvertisedName?.isEmpty == false ? trimmedAdvertisedName! : "微光(Spark)",
            rssi: RSSI.intValue,
            peripheralUUID: id
        )
        if let index = discoveredDevices.firstIndex(where: { $0.id == id }) {
            discoveredDevices[index] = device
        } else {
            discoveredDevices.append(device)
        }
        discoveredDevices.sort { $0.rssi > $1.rssi }
    }

    private func startAdvertisementRefreshTimer() {
        advertisementRefreshTimer?.invalidate()
        advertisementRefreshTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            self?.publishObservedAdvertisements(now: Date())
        }
    }

    private func publishObservedAdvertisements(now: Date) {
        for (id, var advertisement) in observedAdvertisementsByID {
            let elapsed = now.timeIntervalSince(advertisement.lastSeenAt)
            if advertisement.isPresent,
               elapsed >= Self.advertisementDisappearanceInterval {
                advertisement.isPresent = false
                advertisement.lastTransitionAt = now
                observedAdvertisementsByID[id] = advertisement
            }
        }

        observedAdvertisementsByID = observedAdvertisementsByID.filter {
            now.timeIntervalSince($0.value.lastSeenAt)
                < Self.disappearedAdvertisementRetentionInterval
        }

        observedAdvertisements = Array(observedAdvertisementsByID.values
            .filter {
                ($0.isPresent
                    ? $0.rssi >= Self.debugMinimumRSSI
                    : $0.peakRSSI >= Self.debugMinimumRSSI)
                    && ($0.isPresent || now.timeIntervalSince($0.lastSeenAt) < 15)
            }
            .sorted {
                let leftTransitioning = now.timeIntervalSince($0.lastTransitionAt) < 8
                let rightTransitioning = now.timeIntervalSince($1.lastTransitionAt) < 8
                if leftTransitioning != rightTransitioning {
                    return leftTransitioning
                }
                if $0.isStrongSignal != $1.isStrongSignal {
                    return $0.isStrongSignal
                }
                if $0.matchesSpark != $1.matchesSpark {
                    return $0.matchesSpark
                }
                return $0.rssi > $1.rssi
            }
            .prefix(50))
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        let id = peripheral.identifier.uuidString
        appendCommunicationLog(deviceID: id, kind: "OK", message: "BLE 连接成功")
        appendCommunicationLog(
            deviceID: id,
            kind: "INFO",
            message: "查询主服务 \(Self.serviceUUID.uuidString)"
        )
        peripherals[id] = peripheral
        peripheral.delegate = self
        connectionPhases[id] = .discovering
        peripheral.discoverServices([Self.serviceUUID])
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        let id = peripheral.identifier.uuidString
        appendCommunicationLog(
            deviceID: id,
            kind: "ERROR",
            message: "BLE 连接失败：\(error?.localizedDescription ?? "未知错误")"
        )
        let failure = SparkBLEError.connectionFailed(error?.localizedDescription ?? "未知错误")
        connectionPhases[id] = .failed(failure.localizedDescription)
        finishPendingBinding(deviceID: id, result: .failure(failure))
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        let id = peripheral.identifier.uuidString
        appendCommunicationLog(
            deviceID: id,
            kind: error == nil ? "INFO" : "ERROR",
            message: error == nil
                ? "BLE 连接已断开"
                : "BLE 异常断开：\(error!.localizedDescription)"
        )
        updateBoundDevice(id: id) { $0.isConnected = false }
        characteristics.removeValue(forKey: id)
        authenticatingDeviceIDs.remove(id)

        if recordingDeviceID == id {
            connectionPhases[id] = .reconnecting
            recorderStateConsumers[id]?(.reconnecting)
        } else {
            connectionPhases[id] = .disconnected
        }

        if let pending = pendingStarts.removeValue(forKey: id) {
            pending.completion(.failure(SparkBLEError.connectionFailed("设备已断开")))
        }
        if let pending = pendingStops.removeValue(forKey: id) {
            pending.completion(.failure(SparkBLEError.connectionFailed("停止录音前设备已断开")))
        }
        finishPendingBinding(deviceID: id, result: .failure(SparkBLEError.connectionFailed("设备已断开")))

        guard boundDevices.contains(where: { $0.id == id }) else { return }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self, weak peripheral] in
            guard let self, let peripheral, self.centralManager.state == .poweredOn else { return }
            self.connectionPhases[id] = .reconnecting
            self.centralManager.connect(peripheral, options: nil)
        }
    }

    // MARK: - CBPeripheralDelegate

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        let id = peripheral.identifier.uuidString
        guard error == nil,
              let service = peripheral.services?.first(where: { $0.uuid == Self.serviceUUID }) else {
            let discovered = peripheral.services?.map(\.uuid.uuidString).joined(separator: ", ") ?? "无"
            appendCommunicationLog(
                deviceID: id,
                kind: "ERROR",
                message: "未发现协议主服务；设备提供：\(discovered)"
            )
            let failure = SparkBLEError.incompatibleDevice
            connectionPhases[id] = .failed(failure.localizedDescription)
            finishPendingBinding(deviceID: id, result: .failure(failure))
            centralManager.cancelPeripheralConnection(peripheral)
            return
        }
        appendCommunicationLog(
            deviceID: id,
            kind: "OK",
            message: "发现协议主服务 \(service.uuid.uuidString)"
        )
        appendCommunicationLog(deviceID: id, kind: "INFO", message: "查询音频 Notify、写、指令 Notify 特征")
        peripheral.discoverCharacteristics(
            [Self.audioNotifyUUID, Self.commandNotifyUUID, Self.writeUUID],
            for: service
        )
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        let id = peripheral.identifier.uuidString
        guard error == nil else {
            appendCommunicationLog(
                deviceID: id,
                kind: "ERROR",
                message: "发现特征失败：\(error?.localizedDescription ?? "未知错误")"
            )
            failTransport(deviceID: id, message: error?.localizedDescription ?? "发现特征失败")
            return
        }

        var set = SparkCharacteristics()
        for characteristic in service.characteristics ?? [] {
            switch characteristic.uuid {
            case Self.audioNotifyUUID: set.audioNotify = characteristic
            case Self.commandNotifyUUID: set.commandNotify = characteristic
            case Self.writeUUID: set.write = characteristic
            default: break
            }
        }
        guard set.isComplete, let audio = set.audioNotify, let command = set.commandNotify else {
            let discovered = service.characteristics?.map(\.uuid.uuidString).joined(separator: ", ") ?? "无"
            appendCommunicationLog(
                deviceID: id,
                kind: "ERROR",
                message: "协议特征不完整；设备提供：\(discovered)"
            )
            failTransport(deviceID: id, message: "设备缺少必要的录音特征")
            return
        }
        appendCommunicationLog(
            deviceID: id,
            kind: "OK",
            message: "三个协议特征齐全：A1 音频 Notify、A2 写、A3 指令 Notify"
        )
        characteristics[id] = set
        appendCommunicationLog(deviceID: id, kind: "INFO", message: "订阅 A1 音频与 A3 指令通知")
        peripheral.setNotifyValue(true, for: command)
        peripheral.setNotifyValue(true, for: audio)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        let id = peripheral.identifier.uuidString
        guard error == nil else {
            appendCommunicationLog(
                deviceID: id,
                kind: "ERROR",
                message: "订阅 \(characteristic.uuid.uuidString) 失败：\(error?.localizedDescription ?? "未知错误")"
            )
            failTransport(deviceID: id, message: error?.localizedDescription ?? "订阅设备通知失败")
            return
        }
        appendCommunicationLog(
            deviceID: id,
            kind: characteristic.isNotifying ? "OK" : "ERROR",
            message: "\(characteristic.uuid.uuidString) 通知\(characteristic.isNotifying ? "已开启" : "未开启")"
        )
        guard let set = characteristics[id],
              set.audioNotify?.isNotifying == true,
              set.commandNotify?.isNotifying == true else { return }
        transportDidBecomeReady(deviceID: id)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        let id = peripheral.identifier.uuidString
        guard error == nil, let data = characteristic.value else { return }

        if characteristic.uuid == Self.audioNotifyUUID {
            audioConsumers[id]?(data)
        } else if characteristic.uuid == Self.commandNotifyUUID {
            for message in decodeCommandMessages(data) {
                appendCommunicationLog(deviceID: id, kind: "RX", message: message)
                handleCommand(message, deviceID: id)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let error else { return }
        let id = peripheral.identifier.uuidString
        appendCommunicationLog(
            deviceID: id,
            kind: "ERROR",
            message: "写入 \(characteristic.uuid.uuidString) 失败：\(error.localizedDescription)"
        )
        lastErrorMessage = "发送设备指令失败：\(error.localizedDescription)"
        recorderStateConsumers[id]?(.failed(lastErrorMessage ?? "发送设备指令失败"))
    }

    // MARK: - Protocol handling

    private func transportDidBecomeReady(deviceID: String) {
        appendCommunicationLog(deviceID: deviceID, kind: "OK", message: "A1 与 A3 通知均已订阅，通信通道就绪")
        if let pending = pendingBindings[deviceID] {
            connectionPhases[deviceID] = .authenticating
            appendCommunicationLog(
                deviceID: deviceID,
                kind: "TX",
                message: "PQ_BLE&SK&••••••••••••••••（16 位绑定密码已隐藏）"
            )
            do {
                try writeCommand("PQ_BLE&SK&\(pending.password)", to: deviceID)
            } catch {
                finishPendingBinding(deviceID: deviceID, result: .failure(error))
            }
            return
        }

        if let password = pairingPassword(for: deviceID) {
            authenticatingDeviceIDs.insert(deviceID)
            connectionPhases[deviceID] = .authenticating
            do {
                try writeCommand("PQ_BLE&SK&\(password)", to: deviceID)
            } catch {
                authenticatingDeviceIDs.remove(deviceID)
                markDeviceReady(deviceID)
            }
        } else {
            // Existing pre-Spark installations have no key in Keychain. Keep them
            // usable and let the first explicit rebind establish one.
            markDeviceReady(deviceID)
        }
    }

    private func handleCommand(_ message: String, deviceID: String) {
        // Some YLF20 firmware omits the documented "PQ_" prefix in replies
        // (for example, DEV&UNKNOWN). Normalize it for the protocol parser
        // while preserving the raw RX value in the debug log.
        let message = message.hasPrefix("DEV&") ? "PQ_\(message)" : message
        let fields = message.components(separatedBy: "&")
        guard fields.count >= 2 else { return }
        recordDiagnosticResponse(message, deviceID: deviceID)
        recordProtocolProbeResponse(message, deviceID: deviceID)

        if message == "DEV&UNKNOWN" || message == "PQ_DEV&UNKNOWN" {
            appendCommunicationLog(
                deviceID: deviceID,
                kind: "ERROR",
                message: "设备收到写入，但不识别当前命令格式"
            )
            if pendingBindings[deviceID] != nil,
               protocolProbeTokens[deviceID] == nil {
                startCompatibilityProbe(deviceID: deviceID)
            }
            return
        }

        if message.hasPrefix("PQ_DEV&SK&OK") {
            appendCommunicationLog(deviceID: deviceID, kind: "OK", message: "设备确认密钥配对成功")
            if let pending = pendingBindings[deviceID] {
                savePairingPassword(pending.password, for: deviceID)
                var bound = BoundDevice(
                    id: deviceID,
                    name: "微光(Spark)",
                    peripheralUUID: pending.discoveredDevice.peripheralUUID,
                    boundAt: Date(),
                    isConnected: true,
                    batteryLevel: nil,
                    firmwareVersion: nil,
                    boundUserId: pending.userID,
                    advertisedName: pending.discoveredDevice.name
                )
                if let existing = boundDevices.first(where: { $0.id == deviceID }) {
                    bound.name = existing.name
                }
                upsertBoundDevice(bound)
                connectionPhases[deviceID] = .ready
                pending.completion(.success(bound))
                pendingBindings.removeValue(forKey: deviceID)
                queryDeviceStatus(deviceID)
            } else if authenticatingDeviceIDs.remove(deviceID) != nil {
                markDeviceReady(deviceID)
            }
            return
        }

        if message.hasPrefix("PQ_DEV&SK&ERR") {
            appendCommunicationLog(deviceID: deviceID, kind: "ERROR", message: "设备拒绝密钥配对")
            authenticatingDeviceIDs.remove(deviceID)
            finishPendingBinding(deviceID: deviceID, result: .failure(SparkBLEError.pairingFailed))
            connectionPhases[deviceID] = .failed(SparkBLEError.pairingFailed.localizedDescription)
            return
        }

        if message.hasPrefix("PQ_DEV&STA&") {
            let fileName = fields.dropFirst(2).joined(separator: "&")
            recordingDeviceID = deviceID
            connectionPhases[deviceID] = .recording
            recorderStateConsumers[deviceID]?(.recording(fileName: fileName))
            pendingStarts.removeValue(forKey: deviceID)?.completion(.success(fileName))
            return
        }

        if message == "PQ_DEV&STO" || message.hasPrefix("PQ_DEV&STO&") {
            recordingDeviceID = nil
            connectionPhases[deviceID] = .ready
            pendingStops.removeValue(forKey: deviceID)?.completion(.success(message))
            recorderStateConsumers[deviceID]?(.ready)
            return
        }

        if message.hasPrefix("PQ_DEV&RT&") {
            recordingDeviceID = deviceID
            connectionPhases[deviceID] = .recording
            if fields.count >= 4 {
                recorderStateConsumers[deviceID]?(.recording(fileName: fields[2]))
            }
            return
        }

        if message.hasPrefix("PQ_DEV&BAT&"), let value = fields.last.flatMap(Int.init) {
            updateBoundDevice(id: deviceID) { $0.batteryLevel = min(100, max(0, value)) }
        } else if message.hasPrefix("PQ_DEV&STE&"), let value = fields.last.flatMap(Int.init) {
            if value == 1 {
                recordingDeviceID = deviceID
                connectionPhases[deviceID] = .recording
            } else if recordingDeviceID == deviceID {
                recordingDeviceID = nil
                connectionPhases[deviceID] = .ready
            }
        } else if message.hasPrefix("PQ_DEV&FW&"), fields.count >= 3 {
            updateBoundDevice(id: deviceID) { $0.firmwareVersion = fields.dropFirst(2).joined(separator: "&") }
        } else if message.hasPrefix("PQ_DEV&SPA&"), fields.count >= 4 {
            updateBoundDevice(id: deviceID) {
                $0.freeStorageMB = Int(fields[2])
                $0.totalStorageMB = Int(fields[3])
            }
        } else if message.hasPrefix("PQ_DEV&MAC&"), fields.count >= 3 {
            updateBoundDevice(id: deviceID) { $0.hardwareMAC = fields[2] }
        } else if message == "PQ_EV&REC&ERR" || message == "PQ_DEV&REC&ERR" {
            reportRecordingError(deviceID: deviceID, message: "微光报告录音失败，请检查设备后重试。")
        } else if message == "PQ_DEV&DISK&ERR" {
            reportRecordingError(deviceID: deviceID, message: "微光存储空间已满，录音无法继续。")
        }
    }

    private func decodeCommandMessages(_ data: Data) -> [String] {
        guard var text = String(data: data, encoding: .utf8) else { return [] }
        text = text.replacingOccurrences(of: "\0", with: "")
            .trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else { return [] }

        let lineMessages = text.components(separatedBy: .newlines)
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { !$0.isEmpty }
        if lineMessages.count > 1 { return lineMessages }

        // Also tolerate multiple prefix-delimited messages in one notification.
        let pattern = "(?=PQ_(?:DEV|EV)&)"
        if let regex = try? NSRegularExpression(pattern: pattern) {
            let range = NSRange(text.startIndex..., in: text)
            let matches = regex.matches(in: text, range: range)
            if matches.count > 1 {
                return matches.enumerated().compactMap { index, match in
                    let start = match.range.location
                    let end = index + 1 < matches.count ? matches[index + 1].range.location : range.length
                    guard let swiftRange = Range(NSRange(location: start, length: end - start), in: text) else { return nil }
                    return String(text[swiftRange])
                }
            }
        }
        return [text]
    }

    private func writeCommand(_ command: String, to deviceID: String) throws {
        try writeCommand(command, to: deviceID, wireFormat: .documented)
    }

    private func writeCommand(
        _ command: String,
        to deviceID: String,
        wireFormat: ProbeWireFormat,
        logAsProbe: Bool = false
    ) throws {
        guard let peripheral = peripherals[deviceID],
              peripheral.state == .connected,
              let characteristic = characteristics[deviceID]?.write else {
            throw SparkBLEError.commandUnavailable
        }
        let data = wireFormat.data(for: command)
        let writeType: CBCharacteristicWriteType
        if characteristic.properties.contains(.write) {
            writeType = .withResponse
        } else if characteristic.properties.contains(.writeWithoutResponse) {
            writeType = .withoutResponse
        } else {
            throw SparkBLEError.commandUnavailable
        }
        guard data.count <= peripheral.maximumWriteValueLength(for: writeType) else {
            throw SparkBLEError.deviceReported("设备指令超过单帧写入长度。")
        }
        if logAsProbe {
            appendCommunicationLog(
                deviceID: deviceID,
                kind: "TX",
                message: "兼容探测[\(wireFormat.label)]：PQ_BLE&STE（\(data.count) bytes）"
            )
        } else if !command.hasPrefix("PQ_BLE&SK&") {
            appendCommunicationLog(deviceID: deviceID, kind: "TX", message: command)
        }
        peripheral.writeValue(data, for: characteristic, type: writeType)
    }

    private func startCompatibilityProbe(deviceID: String) {
        let token = UUID()
        protocolProbeTokens[deviceID] = token
        appendCommunicationLog(
            deviceID: deviceID,
            kind: "INFO",
            message: "开始只读兼容探测，不会改变录音或设备设置"
        )

        for (index, format) in ProbeWireFormat.allCases.enumerated() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 + Double(index) * 0.9) { [weak self] in
                guard let self, self.protocolProbeTokens[deviceID] == token else { return }
                self.activeProbeFormats[deviceID] = format
                do {
                    try self.writeCommand(
                        "PQ_BLE&STE",
                        to: deviceID,
                        wireFormat: format,
                        logAsProbe: true
                    )
                } catch {
                    self.appendCommunicationLog(
                        deviceID: deviceID,
                        kind: "ERROR",
                        message: "兼容探测写入失败：\(error.localizedDescription)"
                    )
                }
            }
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 7) { [weak self] in
            guard let self, self.protocolProbeTokens[deviceID] == token else { return }
            self.protocolProbeTokens.removeValue(forKey: deviceID)
            self.activeProbeFormats.removeValue(forKey: deviceID)
            self.appendCommunicationLog(
                deviceID: deviceID,
                kind: "ERROR",
                message: "六种安全格式均未得到录音状态回复；固件协议与当前文档不一致"
            )
        }
    }

    private func recordProtocolProbeResponse(_ message: String, deviceID: String) {
        guard protocolProbeTokens[deviceID] != nil,
              message.hasPrefix("PQ_DEV&STE&") || message.hasPrefix("DEV&STE&"),
              let format = activeProbeFormats[deviceID] else { return }
        protocolProbeTokens.removeValue(forKey: deviceID)
        activeProbeFormats.removeValue(forKey: deviceID)
        appendCommunicationLog(
            deviceID: deviceID,
            kind: "OK",
            message: "兼容探测成功：设备接受“\(format.label)”"
        )

        guard format != .documented,
              let pending = pendingBindings[deviceID] else {
            if pendingBindings[deviceID] != nil {
                appendCommunicationLog(
                    deviceID: deviceID,
                    kind: "ERROR",
                    message: "普通查询可用，但当前固件不支持文档中的 SK 配对命令"
                )
            }
            return
        }

        appendCommunicationLog(
            deviceID: deviceID,
            kind: "TX",
            message: "使用“\(format.label)”重试 16 位配对命令（密码已隐藏）"
        )
        do {
            try writeCommand(
                "PQ_BLE&SK&\(pending.password)",
                to: deviceID,
                wireFormat: format
            )
        } catch {
            appendCommunicationLog(
                deviceID: deviceID,
                kind: "ERROR",
                message: "兼容配对写入失败：\(error.localizedDescription)"
            )
        }
    }

    private func queryDeviceStatus(_ deviceID: String) {
        let token = UUID()
        diagnosticTokens[deviceID] = token
        pendingDiagnosticResponses[deviceID] = [
            "录音状态": "PQ_DEV&STE&",
            "电量": "PQ_DEV&BAT&",
            "容量": "PQ_DEV&SPA&",
            "固件": "PQ_DEV&FW&",
            "MAC": "PQ_DEV&MAC&",
            "校时": "PQ_DEV&T&OK"
        ]
        let commands = ["PQ_BLE&STE", "PQ_BLE&BAT", "PQ_BLE&SPACE", "PQ_BLE&FW", "PQ_BLE&MAC", currentTimeCommand()]
        for (index, command) in commands.enumerated() {
            DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.25) { [weak self] in
                try? self?.writeCommand(command, to: deviceID)
            }
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 6) { [weak self] in
            guard let self, self.diagnosticTokens[deviceID] == token else { return }
            let missing = self.pendingDiagnosticResponses[deviceID]?.keys.sorted() ?? []
            if missing.isEmpty {
                self.appendCommunicationLog(
                    deviceID: deviceID,
                    kind: "OK",
                    message: "协议诊断完成，全部查询均收到预期格式回复"
                )
            } else {
                self.appendCommunicationLog(
                    deviceID: deviceID,
                    kind: "ERROR",
                    message: "协议诊断超时，未收到：\(missing.joined(separator: "、"))"
                )
            }
            self.pendingDiagnosticResponses.removeValue(forKey: deviceID)
            self.diagnosticTokens.removeValue(forKey: deviceID)
        }
    }

    private func recordDiagnosticResponse(_ message: String, deviceID: String) {
        guard var pending = pendingDiagnosticResponses[deviceID] else { return }
        let matched = pending.first { message.hasPrefix($0.value) }
        guard let matched else { return }
        pending.removeValue(forKey: matched.key)
        pendingDiagnosticResponses[deviceID] = pending
        appendCommunicationLog(
            deviceID: deviceID,
            kind: "OK",
            message: "\(matched.key)回复格式正确"
        )
    }

    private func appendCommunicationLog(deviceID: String, kind: String, message: String) {
        communicationLogs.append(
            BLECommunicationLogEntry(
                deviceID: deviceID,
                timestamp: Date(),
                kind: kind,
                message: message
            )
        )
        if communicationLogs.count > 300 {
            communicationLogs.removeFirst(communicationLogs.count - 300)
        }
    }

    private func currentTimeCommand() -> String {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyyMMddHHmmss"
        return "PQ_BLE&T&\(formatter.string(from: Date()))"
    }

    // MARK: - State and persistence

    private func markDeviceReady(_ deviceID: String) {
        updateBoundDevice(id: deviceID) { $0.isConnected = true }
        connectionPhases[deviceID] = recordingDeviceID == deviceID ? .recording : .ready
        recorderStateConsumers[deviceID]?(.ready)
        queryDeviceStatus(deviceID)
    }

    private func reconnectBoundDevices() {
        let identifiers = boundDevices.compactMap { UUID(uuidString: $0.peripheralUUID) }
        guard !identifiers.isEmpty else { return }
        for peripheral in centralManager.retrievePeripherals(withIdentifiers: identifiers) {
            let id = peripheral.identifier.uuidString
            peripherals[id] = peripheral
            peripheral.delegate = self
            connectionPhases[id] = .reconnecting
            centralManager.connect(peripheral, options: nil)
        }
    }

    private func markAllDevicesDisconnected() {
        for index in boundDevices.indices {
            boundDevices[index].isConnected = false
            connectionPhases[boundDevices[index].id] = .disconnected
        }
        saveBoundDevices()
    }

    private func updateBoundDevice(id: String, mutation: (inout BoundDevice) -> Void) {
        guard let index = boundDevices.firstIndex(where: { $0.id == id }) else { return }
        mutation(&boundDevices[index])
        saveBoundDevices()
    }

    private func upsertBoundDevice(_ device: BoundDevice) {
        boundDevices.removeAll(where: { $0.id == device.id })
        boundDevices.append(device)
        saveBoundDevices()
    }

    private func finishPendingBinding(deviceID: String, result: Result<BoundDevice, Error>) {
        guard let pending = pendingBindings.removeValue(forKey: deviceID) else { return }
        pending.completion(result)
    }

    private func failTransport(deviceID: String, message: String) {
        let error = SparkBLEError.connectionFailed(message)
        connectionPhases[deviceID] = .failed(error.localizedDescription)
        finishPendingBinding(deviceID: deviceID, result: .failure(error))
        recorderStateConsumers[deviceID]?(.failed(error.localizedDescription))
    }

    private func reportRecordingError(deviceID: String, message: String) {
        lastErrorMessage = message
        recorderStateConsumers[deviceID]?(.failed(message))
        pendingStarts.removeValue(forKey: deviceID)?.completion(.failure(SparkBLEError.deviceReported(message)))
    }

    private func scheduleTimeout(for deviceID: String, command: String, token: UUID, isStart: Bool) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 8) { [weak self] in
            guard let self else { return }
            if isStart, self.pendingStarts[deviceID]?.token == token {
                self.pendingStarts.removeValue(forKey: deviceID)?.completion(.failure(SparkBLEError.commandTimedOut(command)))
            } else if !isStart, self.pendingStops[deviceID]?.token == token {
                self.pendingStops.removeValue(forKey: deviceID)?.completion(.failure(SparkBLEError.commandTimedOut(command)))
            }
        }
    }

    private func saveBoundDevices() {
        if let data = try? JSONEncoder().encode(boundDevices) {
            UserDefaults.standard.set(data, forKey: boundDevicesKey)
        }
    }

    private func loadBoundDevices() {
        guard let data = UserDefaults.standard.data(forKey: boundDevicesKey),
              var devices = try? JSONDecoder().decode([BoundDevice].self, from: data) else { return }
        let mockDeviceID = "MOCK-SPARK-01"
        if devices.contains(where: { $0.id == mockDeviceID || $0.peripheralUUID == mockDeviceID }) {
            devices.removeAll { $0.id == mockDeviceID || $0.peripheralUUID == mockDeviceID }
            deletePairingPassword(for: mockDeviceID)
            if let cleaned = try? JSONEncoder().encode(devices) {
                UserDefaults.standard.set(cleaned, forKey: boundDevicesKey)
            }
        }
        for index in devices.indices {
            devices[index].isConnected = false
        }
        boundDevices = devices
    }

    // MARK: - Pairing secret

    private func generatePairingPassword() -> String {
        let alphabet = Array("ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789")
        var bytes = [UInt8](repeating: 0, count: 16)
        let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
        if result == errSecSuccess {
            return String(bytes.map { alphabet[Int($0) % alphabet.count] })
        }
        return String(UUID().uuidString.replacingOccurrences(of: "-", with: "").prefix(16))
    }

    private func savePairingPassword(_ password: String, for deviceID: String) {
        deletePairingPassword(for: deviceID)
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecAttrAccount as String: deviceID,
            kSecValueData as String: Data(password.utf8),
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
        ]
        SecItemAdd(query as CFDictionary, nil)
    }

    private func pairingPassword(for deviceID: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecAttrAccount as String: deviceID,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var result: CFTypeRef?
        guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
              let data = result as? Data else { return nil }
        return String(data: data, encoding: .utf8)
    }

    private func deletePairingPassword(for deviceID: String) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: keychainService,
            kSecAttrAccount as String: deviceID
        ]
        SecItemDelete(query as CFDictionary)
    }
}

// MARK: - Spark audio recorder

/// Records the MP3 notification stream produced by a bound Spark device.
final class SparkAudioRecorder: AudioRecorderProtocol {
    @Published var isRecording: Bool = false
    @Published var elapsedTime: TimeInterval = 0
    @Published var currentAmplitude: Float = 0
    @Published var waveformSamples: [Float] = []
    @Published var outputFileURL: URL?
    @Published var isPaused: Bool = false
    @Published var statusMessage: String = "正在连接设备"
    @Published var errorMessage: String?

    let sourceDisplayName: String

    private let deviceID: String
    private let manager: BLEManager
    private let ioQueue = DispatchQueue(label: "com.celestia.trace.spark.audio-io")
    private var fileHandle: FileHandle?
    private var timer: Timer?
    private var recordingStartedAt: Date?
    private var stopCompletion: ((Result<URL?, Error>) -> Void)?

    init(deviceID: String, displayName: String, manager: BLEManager = .shared) {
        self.deviceID = deviceID
        self.sourceDisplayName = displayName
        self.manager = manager
    }

    func startRecording() {
        guard !isRecording, fileHandle == nil else { return }
        errorMessage = nil
        statusMessage = "正在启动 \(sourceDisplayName)"
        elapsedTime = 0
        waveformSamples = []
        currentAmplitude = 0

        do {
            let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let url = documents.appendingPathComponent("spark_\(UUID().uuidString).mp3")
            FileManager.default.createFile(atPath: url.path, contents: nil)
            fileHandle = try FileHandle(forWritingTo: url)
            outputFileURL = url
        } catch {
            errorMessage = "无法创建微光录音文件：\(error.localizedDescription)"
            statusMessage = "启动失败"
            return
        }

        manager.startSparkRecording(
            deviceID: deviceID,
            onAudioData: { [weak self] data in self?.appendAudioData(data) },
            onStateChange: { [weak self] event in self?.handleStateEvent(event) }
        ) { [weak self] result in
            guard let self else { return }
            DispatchQueue.main.async {
                switch result {
                case .success:
                    self.isRecording = true
                    self.statusMessage = "正在录音"
                    self.recordingStartedAt = Date()
                    self.startTimer()
                case .failure(let error):
                    self.errorMessage = error.localizedDescription
                    self.statusMessage = "启动失败"
                    self.finishFile { _ in }
                }
            }
        }
    }

    func stopRecording() {
        stopRecording { _ in }
    }

    func stopRecording(completion: @escaping (Result<URL?, Error>) -> Void) {
        guard fileHandle != nil else {
            completion(.success(outputFileURL))
            return
        }
        guard isRecording else {
            finishFile(completion: completion)
            return
        }

        statusMessage = "正在保存设备录音"
        stopCompletion = completion
        manager.stopSparkRecording(deviceID: deviceID) { [weak self] result in
            guard let self else { return }
            DispatchQueue.main.async {
                switch result {
                case .success:
                    self.isRecording = false
                    self.invalidateTimer()
                    self.finishFile { result in
                        self.manager.detachRecorder(deviceID: self.deviceID)
                        self.stopCompletion?(result)
                        self.stopCompletion = nil
                    }
                case .failure(let error):
                    self.errorMessage = error.localizedDescription
                    self.statusMessage = "设备尚未确认停止"
                    self.stopCompletion?(.failure(error))
                    self.stopCompletion = nil
                }
            }
        }
    }

    func pauseRecording() {
        errorMessage = "微光暂不支持暂停，请结束当前录音。"
    }

    func resumeRecording() {}

    private func appendAudioData(_ data: Data) {
        guard !data.isEmpty else { return }
        ioQueue.async { [weak self] in
            guard let self else { return }
            do {
                try self.fileHandle?.write(contentsOf: data)
            } catch {
                DispatchQueue.main.async {
                    self.errorMessage = "保存微光音频失败：\(error.localizedDescription)"
                }
            }
        }

        // This represents stream activity until incremental MP3 PCM metering is available.
        let activity = min(1, max(0.08, Float(data.count) / 244.0))
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            self.currentAmplitude = activity
            self.waveformSamples.append(activity)
            if self.waveformSamples.count > 200 {
                self.waveformSamples.removeFirst(self.waveformSamples.count - 200)
            }
        }
    }

    private func handleStateEvent(_ event: SparkRecorderConnectionEvent) {
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            switch event {
            case .recording:
                if self.isRecording { self.statusMessage = "正在录音" }
            case .reconnecting:
                self.statusMessage = "连接中断，正在重连"
            case .ready:
                if self.isRecording { self.statusMessage = "正在录音" }
            case .failed(let message):
                self.errorMessage = message
                self.statusMessage = "设备异常"
            }
        }
    }

    private func startTimer() {
        invalidateTimer()
        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
            guard let self, let startedAt = self.recordingStartedAt else { return }
            self.elapsedTime = Date().timeIntervalSince(startedAt)
        }
        if let timer { RunLoop.main.add(timer, forMode: .common) }
    }

    private func invalidateTimer() {
        timer?.invalidate()
        timer = nil
    }

    private func finishFile(completion: @escaping (Result<URL?, Error>) -> Void) {
        let url = outputFileURL
        ioQueue.async { [weak self] in
            guard let self else { return }
            do {
                try self.fileHandle?.synchronize()
                try self.fileHandle?.close()
                self.fileHandle = nil
                DispatchQueue.main.async { completion(.success(url)) }
            } catch {
                self.fileHandle = nil
                DispatchQueue.main.async { completion(.failure(error)) }
            }
        }
    }

    deinit {
        invalidateTimer()
        try? fileHandle?.close()
        manager.detachRecorder(deviceID: deviceID)
    }
}
