import Foundation
import ImageCaptureCore

/// Canon EOS PTP 厂商扩展命令码。
enum CanonPTPCommand {
    static let eosGetObject: UInt16 = 0x9104
    static let eosGetStorageIDs: UInt16 = 0x9101
    static let eosGetObjectInfoEx: UInt16 = 0x9109
    static let eosGetPartialObject: UInt16 = 0x9107
    static let eosGetThumbEx: UInt16 = 0x910A
    static let eosSetRemoteMode: UInt16 = 0x9114
    static let eosSetEventMode: UInt16 = 0x9115
    static let eosGetEvent: UInt16 = 0x9116
    static let eosTransferComplete: UInt16 = 0x9117
    static let eosCancelTransfer: UInt16 = 0x9118
    static let eosKeepDeviceOn: UInt16 = 0x911D

    static let eosObjectAddedEx: UInt16 = 0xC181
    static let eosObjectRemoved: UInt16 = 0xC182
    static let eosRequestGetObjectInfoEx: UInt16 = 0xC183
    static let eosStorageStatusChanged: UInt16 = 0xC184
    static let eosStorageInfoChanged: UInt16 = 0xC185
    static let eosObjectInfoChangedEx: UInt16 = 0xC189
    static let eosCameraStatusChanged: UInt16 = 0xC18A
    static let eosRequestObjectTransfer: UInt16 = 0xC18B
    static let eosObjectAddedEx2: UInt16 = 0xC1A7

    static let getObjectInfo: UInt16 = 0x1008
    static let getObject: UInt16 = 0x1009
    static let getThumb: UInt16 = 0x100A
    static let closeSession: UInt16 = 0x1003

    static let getEventInterval: TimeInterval = 0.5
    static let keepOnIntervalTicks: Int = 60

    static let supportedExtensions: Set<String> = [
        "jpg", "jpeg", "png", "cr2", "cr3", "mp4", "mov"
    ]
}

/// Canon EOS PTP 命令封装与会话初始化。
enum CanonPTPHelper {
    /// ImageCaptureCore 已打开会话后，进入 Canon PC Remote 模式并启用事件推送。
    static func initializeRemoteSession(on camera: ICCameraDevice, startingTransactionID: UInt32) async -> (transactionID: UInt32, keepDeviceOnSupported: Bool) {
        var transactionID = startingTransactionID

        guard camera.capabilities.contains("ICCameraDeviceCanAcceptPTPCommands") else {
            CameraTetheringLogger.log("Canon: 相机不支持 PTP 指令", logger: CameraTetheringLogger.ptp)
            return (transactionID, false)
        }

        CameraTetheringLogger.log("开始 Canon EOS 远程会话初始化", logger: CameraTetheringLogger.ptp)

        let remoteMode = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosSetRemoteMode,
            transactionID: &transactionID,
            parameters: [1],
            outData: nil,
            label: "SetRemoteMode"
        )
        CameraTetheringLogger.log("SetRemoteMode: \(remoteMode.success ? "OK" : "failed")", logger: CameraTetheringLogger.ptp)

        let eventMode = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosSetEventMode,
            transactionID: &transactionID,
            parameters: [1],
            outData: nil,
            label: "SetEventMode"
        )
        CameraTetheringLogger.log("SetEventMode: \(eventMode.success ? "OK" : "failed")", logger: CameraTetheringLogger.ptp)

        let keepOn = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosKeepDeviceOn,
            transactionID: &transactionID,
            parameters: [],
            outData: nil,
            label: "KeepDeviceOn"
        )
        let keepOnSupported = keepOn.success
        CameraTetheringLogger.log("KeepDeviceOn: \(keepOnSupported ? "OK" : "skipped")", logger: CameraTetheringLogger.ptp)

        CameraTetheringLogger.log("Canon EOS 远程会话初始化完成", logger: CameraTetheringLogger.ptp)
        return (transactionID, keepOnSupported)
    }

    /// 关闭 PTP 远程会话，释放相机 PC Remote 占用。
    static func closeRemoteSession(on camera: ICCameraDevice, transactionID: inout UInt32) async {
        _ = await sendCommand(
            on: camera,
            code: CanonPTPCommand.closeSession,
            transactionID: &transactionID,
            parameters: [],
            outData: nil,
            label: "CloseSession"
        )
    }

    /// 轮询 Canon 事件队列。
    static func pollEvents(
        on camera: ICCameraDevice,
        transactionID: inout UInt32
    ) async -> Data {
        let result = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosGetEvent,
            transactionID: &transactionID,
            parameters: [],
            outData: nil,
            label: "GetEvent"
        )
        return result.success ? result.inData : Data()
    }

    /// 通过 Canon 扩展 GetObject 下载文件。
    static func downloadObject(
        handle: UInt32,
        on camera: ICCameraDevice,
        transactionID: inout UInt32
    ) async -> Data? {
        let result = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosGetObject,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "EOS_GetObject(0x\(String(format: "%08X", handle)))"
        )
        guard result.success, !result.inData.isEmpty else { return nil }
        return result.inData
    }

    /// 通知相机传输完成，释放缓冲。
    static func transferComplete(
        handle: UInt32,
        on camera: ICCameraDevice,
        transactionID: inout UInt32
    ) async {
        _ = await sendCommand(
            on: camera,
            code: CanonPTPCommand.eosTransferComplete,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "TransferComplete"
        )
    }

    static func isSupportedFilename(_ filename: String) -> Bool {
        let ext = (filename as NSString).pathExtension.lowercased()
        return CanonPTPCommand.supportedExtensions.contains(ext)
    }

    static func assetID(handle: UInt32, filename: String, size: Int64) -> String {
        "canon_\(handle)_\(filename)_\(size)"
    }

    private static func sendCommand(
        on camera: ICCameraDevice,
        code: UInt16,
        transactionID: inout UInt32,
        parameters: [UInt32],
        outData: Data?,
        label: String
    ) async -> PTPCommandResult {
        await PTPHelper.sendCommand(
            on: camera,
            code: code,
            transactionID: &transactionID,
            parameters: parameters,
            outData: outData,
            label: label
        )
    }
}
