//
//  SonyPTPHelper.swift
//  otg_swift
//
//  Created by hanqiu on 2026/7/3.
//

import Foundation
@preconcurrency import ImageCaptureCore

/// 索尼 PC Remote（SDIO）握手、属性解析与边拍边传 Probe 的 PTP 封装。
enum SonyPTPHelper {

    /// SDIO 握手结果；`nextTransactionID` 供后续 PTP 命令递增使用。
    struct RemoteSessionResult {
        let success: Bool
        let nextTransactionID: UInt32
    }
    
    private static func sendCommand(
        on camera: ICCameraDevice,
        code: UInt16,
        transactionID: inout UInt32,
        parameters: [UInt32],
        outData: Data?,
        label: String
    ) async -> PTPHelper.CommandResult {
        await PTPHelper.sendCommand(
            on: camera,
            code: code,
            transactionID: &transactionID,
            parameters: parameters,
            outData: outData,
            label: label
        )
    }
    
    private static func setDeviceProperty(
        on camera: ICCameraDevice,
        propertyCode: UInt16,
        value: UInt16,
        transactionID: inout UInt32,
        label: String
    ) async -> Bool {
        var payload = Data()
        PTPHelper.appendUInt16LE(value, to: &payload)

        return await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioSetExtDevicePropValue,
            transactionID: &transactionID,
            parameters: [UInt32(propertyCode), 1],
            outData: payload,
            label: label
        ).success
    }

    private static func setDevicePropertyUInt32(
        on camera: ICCameraDevice,
        propertyCode: UInt16,
        value: UInt32,
        transactionID: inout UInt32,
        label: String
    ) async -> Bool {
        var payload = Data()
        PTPHelper.appendUInt32LE(value, to: &payload)

        return await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioSetExtDevicePropValue,
            transactionID: &transactionID,
            parameters: [UInt32(propertyCode), 1],
            outData: payload,
            label: label
        ).success
    }
    
    /// 配置 PC Remote 保存目标与传输尺寸（D25A / D222 / D268）。
    private static func configureRemoteCapture(
        on camera: ICCameraDevice,
        transactionID: inout UInt32
    ) async -> Bool {
        guard await setDeviceProperty(
            on: camera,
            propertyCode: SonyPTPCommand.positionKeySetting,
            value: 1,
            transactionID: &transactionID,
            label: "Position_Key_Setting=PC Remote"
        ) else { return false }

        guard await setDeviceProperty(
            on: camera,
            propertyCode: SonyPTPCommand.stillImageSaveDestination,
            value: SonyPTPCommand.saveDestinationHostPC,
            transactionID: &transactionID,
            label: "Still_Image_Save_Destination=Host PC"
        ) else { return false }

        return await setDevicePropertyUInt32(
            on: camera,
            propertyCode: SonyPTPCommand.stillImageTransSize,
            value: 0xFFFF_FFFF,
            transactionID: &transactionID,
            label: "Still_Image_Trans_Size=Full"
        )
    }
    
    /// 完整 SDIO 握手：Connect×3 → GetExtDeviceInfo → GetAllExtDevicePropInfo → 远程拍摄属性。
    static func initializeRemoteSession(
        on camera: ICCameraDevice,
        startingTransactionID: UInt32
    ) async -> RemoteSessionResult {
        var transactionID = startingTransactionID

        OTGLog.info(.sony, "SDIO handshake starting (phase 1: steps 1-5)")

        // Phase 1：标准 SDIO 连接序列（与 libgphoto2 / my_otg 一致）
        let steps: [(UInt16, [UInt32], String)] = [
            (SonyPTPCommand.sdioConnect, [1, 0, 0], "SDIO Connect(1)"),
            (SonyPTPCommand.sdioConnect, [2, 0, 0], "SDIO Connect(2)"),
            (SonyPTPCommand.sdioGetExtDeviceInfo, [0x012C, 1], "SDIO GetExtDeviceInfo"),
            (SonyPTPCommand.sdioConnect, [3, 0, 0], "SDIO Connect(3)"),
            (SonyPTPCommand.sdioGetAllExtDevicePropInfo, [1], "SDIO GetAllExtDevicePropInfo"),
        ]

        for (code, parameters, label) in steps {
            let result = await sendCommand(
                on: camera,
                code: code,
                transactionID: &transactionID,
                parameters: parameters,
                outData: nil,
                label: label
            )
            guard result.success else {
                OTGLog.error(.sony, "SDIO failed at: \(label)")
                return RemoteSessionResult(success: false, nextTransactionID: transactionID)
            }
        }

        OTGLog.info(.sony, "SDIO phase 1 OK, nextTransactionID=\(transactionID)")

        let configured = await configureRemoteCapture(on: camera, transactionID: &transactionID)
        if configured {
            OTGLog.info(.sony, "SDIO handshake completed, nextTransactionID=\(transactionID)")
        } else {
            OTGLog.error(.sony, "SDIO handshake failed at remote capture configuration")
        }

        return RemoteSessionResult(success: configured, nextTransactionID: transactionID)
    }
    
    // MARK: - PTP Events

    static func describeEvent(_ eventData: Data) -> String {
        guard let code = PTPHelper.parseEventCode(from: eventData) else {
            return "unknown, hex=\(PTPHelper.hexString(eventData.prefix(16)))"
        }

        let name: String
        switch code {
        case SonyPTPCommand.sdieObjectAdded:
            name = "ObjectAdded"
        case SonyPTPCommand.sdieObjectRemoved:
            name = "ObjectRemoved"
        case SonyPTPCommand.sdieDevicePropChanged:
            name = "DevicePropChanged"
        case SonyPTPCommand.sdieCapturedEvent:
            name = "CapturedEvent"
        default:
            name = "Other"
        }

        if let container = try? PTPHelper.parseContainer(eventData), !container.params.isEmpty {
            let params = container.params
                .map { String(format: "0x%08X", $0) }
                .joined(separator: ", ")
            return "\(name) (0x\(String(format: "%04X", code))), params=[\(params)]"
        }

        return "\(name) (0x\(String(format: "%04X", code)))"
    }

    // MARK: - Shot detection

    /// Probe 成功时返回的待下载片信息（尚未 GetObject）。
    struct PendingShot {
        let filename: String
        let size: UInt32
        let handle: UInt32
    }

    /// 对虚拟句柄发 GetObjectInfo，解析文件名与 CompressedSize。
    static func probeShotObject(
        on camera: ICCameraDevice,
        handle: UInt32 = SonyPTPCommand.shotObjectHandle,
        transactionID: inout UInt32
    ) async -> PendingShot? {
        let infoResult = await sendCommand(
            on: camera,
            code: PTPStandardCommand.getObjectInfo,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "ProbeObjectInfo(0x\(String(format: "%08X", handle)))"
        )
        guard infoResult.success else { return nil }

        guard let size = PTPHelper.parseObjectCompressedSize(infoResult.inData), size > 0 else {
            return nil
        }

        let filename = PTPHelper.parseObjectInfoFilename(infoResult.inData)
            ?? "DSC_\(Int(Date().timeIntervalSince1970)).JPG"

        return PendingShot(filename: filename, size: size, handle: handle)
    }

    /// GetObject 下载结果。
    struct DownloadedShot {
        let filename: String
        let data: Data
        let handle: UInt32
    }

    /// 对虚拟句柄发 GetObject，返回 JPEG/RAW 二进制；Probe 后可 `skipObjectInfo` 省略重复 GetObjectInfo。
    static func downloadShotObject(
        on camera: ICCameraDevice,
        handle: UInt32 = SonyPTPCommand.shotObjectHandle,
        transactionID: inout UInt32,
        knownFilename: String? = nil,
        skipObjectInfo: Bool = false
    ) async -> DownloadedShot? {
        var filename = PTPHelper.sanitizeFilename(
            knownFilename ?? "DSC_\(Int(Date().timeIntervalSince1970)).JPG"
        )

        if !skipObjectInfo {
            let infoResult = await sendCommand(
                on: camera,
                code: PTPStandardCommand.getObjectInfo,
                transactionID: &transactionID,
                parameters: [handle],
                outData: nil,
                label: "GetObjectInfo(0x\(String(format: "%08X", handle)))"
            )
            if infoResult.success, let parsedName = PTPHelper.parseObjectInfoFilename(infoResult.inData) {
                filename = parsedName
            }
        }

        let objectResult = await sendCommand(
            on: camera,
            code: PTPStandardCommand.getObject,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "GetObject(0x\(String(format: "%08X", handle)))"
        )
        guard objectResult.success, PTPHelper.isLikelyImageData(objectResult.inData) else {
            return nil
        }

        return DownloadedShot(filename: filename, data: objectResult.inData, handle: handle)
    }

    /// 读取 SDIO 扩展属性；`onlyDiff=true` 仅返回自上次读取以来变化的属性。
    static func fetchExtDeviceProperties(
        on camera: ICCameraDevice,
        transactionID: inout UInt32,
        onlyDiff: Bool = true
    ) async -> [UInt16: Int64] {
        let result = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioGetAllExtDevicePropInfo,
            transactionID: &transactionID,
            parameters: [onlyDiff ? 1 : 0],
            outData: nil,
            label: "GetAllExtDevicePropInfo(\(onlyDiff ? "diff" : "all"))"
        )
        guard result.success else { return [:] }
        return parseExtDeviceProperties(result.inData)
    }

    /// 解析 GetAllExtDevicePropInfo 返回的二进制属性列表。
    static func parseExtDeviceProperties(_ data: Data) -> [UInt16: Int64] {
        guard data.count >= 8 else { return [:] }

        var properties: [UInt16: Int64] = [:]
        var offset = 8 // 跳过响应头

        while offset + 6 <= data.count {
            let propertyCode = PTPHelper.readUInt16LE(data, offset: offset)
            offset += 2
            let dataType = PTPHelper.readUInt16LE(data, offset: offset)
            offset += 2
            offset += 2 // GetSet 等标志位，当前不解析

            guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { break }
            guard let currentValue = readVariableValue(dataType: dataType, data: data, offset: &offset) else { break }
            properties[propertyCode] = currentValue

            guard offset < data.count else { break }
            let formFlag = data[offset]
            offset += 1
            // formFlag: 0=无表单, 1=Range, 2=Enumeration
            guard skipFormPayload(formFlag: formFlag, dataType: dataType, data: data, offset: &offset) else { break }
        }

        return properties
    }

    /// D215（Shooting_File_Info）大于阈值表示缓冲区可能有完整新片。
    static func isShotBufferReady(_ shootingFileInfo: Int64?) -> Bool {
        guard let shootingFileInfo else { return false }
        return shootingFileInfo > SonyPTPCommand.shotBufferReadyThreshold
    }

    static func shotObjectSignature(filename: String, size: UInt32) -> String {
        PTPHelper.objectSignature(filename: filename, size: size)
    }

    /// PTP 设备属性值的数据类型码（用于 parseExtDeviceProperties）。
    private enum PTPDataType: UInt16 {
        /// 有符号 8 位整数
        case int8 = 0x0001
        /// 无符号 8 位整数
        case uint8 = 0x0002
        /// 有符号 16 位整数
        case int16 = 0x0003
        /// 无符号 16 位整数
        case uint16 = 0x0004
        /// 有符号 32 位整数
        case int32 = 0x0005
        /// 无符号 32 位整数
        case uint32 = 0x0006
        /// 有符号 64 位整数（当前仅跳过 8 字节）
        case int64 = 0x0007
        /// 无符号 64 位整数（当前仅跳过 8 字节）
        case uint64 = 0x0008
        /// UTF-16LE 字符串；解析时跳过不参与数值 map
        case string = 0xFFFF
    }

    private static func readVariableValue(dataType: UInt16, data: Data, offset: inout Int) -> Int64? {
        guard let type = PTPDataType(rawValue: dataType) else { return nil }

        switch type {
        case .int8:
            guard offset < data.count else { return nil }
            let value = Int64(Int8(bitPattern: data[offset]))
            offset += 1
            return value
        case .uint8:
            guard offset < data.count else { return nil }
            let value = Int64(data[offset])
            offset += 1
            return value
        case .int16:
            guard offset + 1 < data.count else { return nil }
            let value = Int64(Int16(bitPattern: PTPHelper.readUInt16LE(data, offset: offset)))
            offset += 2
            return value
        case .uint16:
            guard offset + 1 < data.count else { return nil }
            let value = Int64(PTPHelper.readUInt16LE(data, offset: offset))
            offset += 2
            return value
        case .int32:
            guard offset + 3 < data.count else { return nil }
            let value = Int64(Int32(bitPattern: PTPHelper.readUInt32LE(data, offset: offset)))
            offset += 4
            return value
        case .uint32:
            guard offset + 3 < data.count else { return nil }
            let value = Int64(PTPHelper.readUInt32LE(data, offset: offset))
            offset += 4
            return value
        case .int64, .uint64:
            guard offset + 7 < data.count else { return nil }
            offset += 8
            return 0
        case .string:
            guard offset < data.count else { return nil }
            let length = Int(data[offset])
            offset += 1 + length * 2
            return 0
        }
    }

    private static func skipVariableValue(dataType: UInt16, data: Data, offset: inout Int) -> Bool {
        readVariableValue(dataType: dataType, data: data, offset: &offset) != nil
    }

    private static func skipFormPayload(formFlag: UInt8, dataType: UInt16, data: Data, offset: inout Int) -> Bool {
        switch formFlag {
        case 0:
            return true
        case 1:
            for _ in 0..<3 {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            return true
        case 2:
            guard offset + 1 < data.count else { return false }
            var enumCount = Int(PTPHelper.readUInt16LE(data, offset: offset))
            offset += 2
            for _ in 0..<enumCount {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            guard offset + 1 < data.count else { return false }
            enumCount = Int(PTPHelper.readUInt16LE(data, offset: offset))
            offset += 2
            for _ in 0..<enumCount {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            return true
        default:
            return true
        }
    }
}
