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

import Foundation
@preconcurrency import ImageCaptureCore

/// PTP Container 类型字段（ISO 15740）。
enum PTPContainerType: UInt16 {
    /// 主机发出的操作命令包
    case command = 0x0001
    /// 伴随命令的数据阶段（out/in Data）
    case data = 0x0002
    /// 设备对命令的响应包
    case response = 0x0003
    /// 设备主动上报的事件包（如 Sony SDIE）
    case event = 0x0004
}

/// 解析后的 PTP 容器：命令、数据、响应或事件包。
struct PTPContainer {
    let length: UInt32
    let type: UInt16
    let code: UInt16
    let transactionID: UInt32
    let params: [UInt32]
}

/// PTP Container 解析失败原因。
enum PTPParseError: Error {
    /// 数据长度不足 12 字节（Container 最小头）
    case tooShort
    /// length 字段与 buffer 实际长度不一致
    case invalidLength
}

/// 标准 PTP 操作码与响应码（ISO 15740）。
enum PTPStandardCommand {
    /// GetDeviceInfo (0x1001)，用于 PTP 管线 Probe
    static let getDeviceInfo: UInt16 = 0x1001
    /// GetObjectInfo (0x1008)，Probe 虚拟对象元数据
    static let getObjectInfo: UInt16 = 0x1008
    /// GetObject (0x1009)，下载对象二进制（Step 6）
    static let getObject: UInt16 = 0x1009
    /// ObjectAdded 事件 (0x4002)
    static let objectAdded: UInt16 = 0x4002
    /// RequestObjectTransfer 事件 (0x4009)
    static let requestObjectTransfer: UInt16 = 0x4009
    /// Response OK (0x2001)，命令成功响应码
    static let responseOK: UInt16 = 0x2001
}

/// PTP 包构建、解析与经 ImageCaptureCore 发送的通用工具。
enum PTPHelper {

    // MARK: - Build

    /// 构建 PTP Command Container（小端序，length = 12 + 4×参数个数）。
    static func buildCommand(
        code: UInt16,
        transactionID: UInt32,
        parameters: [UInt32] = []
    ) -> Data {
        let length = UInt32(12 + parameters.count * 4)
        var data = Data()
        appendUInt32LE(length, to: &data)
        appendUInt16LE(PTPContainerType.command.rawValue, to: &data)
        appendUInt16LE(code, to: &data)
        appendUInt32LE(transactionID, to: &data)
        for parameter in parameters {
            appendUInt32LE(parameter, to: &data)
        }
        return data
    }

    // MARK: - Parse

    /// 从二进制数据解析 PTP Container；参数区按 4 字节对齐读取。
    static func parseContainer(_ data: Data) throws -> PTPContainer {
        guard data.count >= 12 else { throw PTPParseError.tooShort }

        let length = readUInt32LE(data, offset: 0)
        guard length >= 12, data.count >= Int(length) else {
            throw PTPParseError.invalidLength
        }

        let type = readUInt16LE(data, offset: 4)
        let code = readUInt16LE(data, offset: 6)
        let transactionID = readUInt32LE(data, offset: 8)

        var params: [UInt32] = []
        var offset = 12
        while offset + 4 <= Int(length) {
            params.append(readUInt32LE(data, offset: offset))
            offset += 4
        }

        return PTPContainer(
            length: length,
            type: type,
            code: code,
            transactionID: transactionID,
            params: params
        )
    }

    /// 从 PTP Event Container 提取事件码（如 Sony 0xC203）。
    static func parseEventCode(from eventData: Data) -> UInt16? {
        try? parseContainer(eventData).code
    }

    static func isResponseOK(_ response: Data) -> Bool {
        guard !response.isEmpty else { return true }
        guard let parsed = try? parseContainer(response) else { return false }
        return parsed.code == PTPStandardCommand.responseOK
    }

    static func hexString(_ data: Data) -> String {
        data.map { String(format: "%02X", $0) }.joined(separator: " ")
    }

    /// 同一文件名+大小视为同一张待传照片，用于边拍边传去重。
    static func objectSignature(filename: String, size: UInt32) -> String {
        "\(filename)_\(size)"
    }

    /// ObjectInfo 固定布局：offset 8 为 CompressedSize（UInt32 LE）。
    static func parseObjectCompressedSize(_ data: Data) -> UInt32? {
        guard data.count >= 12 else { return nil }
        return readUInt32LE(data, offset: 8)
    }

    /// 从 ObjectInfo 解析文件名；优先 PTP 字符串格式，失败则扫描 UTF-16LE。
    static func parseObjectInfoFilename(_ data: Data) -> String? {
        guard data.count >= 53 else { return nil }

        if let filename = parsePTPString(data, offset: 52)?.string, !filename.isEmpty {
            return sanitizeFilename(filename)
        }

        var offset = 52
        var codeUnits: [UInt16] = []
        while offset + 1 < data.count {
            let unit = readUInt16LE(data, offset: offset)
            if unit == 0 { break }
            codeUnits.append(unit)
            offset += 2
        }

        guard !codeUnits.isEmpty else { return nil }
        let raw = String(utf16CodeUnits: codeUnits, count: codeUnits.count)
        return sanitizeFilename(raw)
    }

    /// PTP 字符串：1 字节字符数 + UTF-16LE 字符序列（无 NUL 终止）。
    static func parsePTPString(_ data: Data, offset start: Int) -> (string: String, nextOffset: Int)? {
        guard start < data.count else { return nil }
        let charCount = Int(data[start])
        var offset = start + 1
        guard charCount > 0, offset + charCount * 2 <= data.count else { return nil }

        var codeUnits: [UInt16] = []
        codeUnits.reserveCapacity(charCount)
        for index in 0..<charCount {
            let unit = readUInt16LE(data, offset: offset + index * 2)
            // 部分机身（如 ZV-E10）在 charCount 内含 NUL，需跳过
            if unit != 0 {
                codeUnits.append(unit)
            }
        }
        offset += charCount * 2

        guard !codeUnits.isEmpty else { return nil }
        let string = String(utf16CodeUnits: codeUnits, count: codeUnits.count)
        return (string, offset)
    }

    /// 去除 PTP 字符串残留的控制字符与非法路径字符。
    static func sanitizeFilename(_ name: String) -> String {
        let withoutNulls = name.replacingOccurrences(of: "\0", with: "")
        let trimmed = withoutNulls.trimmingCharacters(in: .whitespacesAndNewlines.union(.controlCharacters))
        let invalid = CharacterSet(charactersIn: "/\\:?%*|\"<>")
        let cleaned = trimmed.components(separatedBy: invalid).joined(separator: "_")
        return cleaned.isEmpty ? "photo.jpg" : cleaned
    }

    /// 粗略判断 inData 是否为 JPEG/PNG 或足够大的二进制块。
    static func isLikelyImageData(_ data: Data) -> Bool {
        guard data.count >= 3 else { return false }
        if data[0] == 0xFF, data[1] == 0xD8, data[2] == 0xFF { return true }
        if data.count >= 4, data[0] == 0x89, data[1] == 0x50, data[2] == 0x4E, data[3] == 0x47 { return true }
        return data.count > 1024
    }

    // MARK: - Send

    struct CommandResult {
        let success: Bool
        let response: Data
        let inData: Data
    }

    /// 经 `requestSendPTPCommand` 发送 PTP 命令；调用方传入并递增 `transactionID`。
    /// 必须在主线程发起（ImageCaptureCore 要求），completion 内 resume continuation。
    @MainActor
    static func sendCommand(
        on camera: ICCameraDevice,
        code: UInt16,
        transactionID: inout UInt32,
        parameters: [UInt32] = [],
        outData: Data? = nil,
        label: String
    ) async -> CommandResult {
        let currentID = transactionID
        transactionID += 1

        let commandData = buildCommand(
            code: code,
            transactionID: currentID,
            parameters: parameters
        )

        OTGLog.info(.ptp, "PTP command sending: \(label), bytes=\(commandData.count)")

        return await withCheckedContinuation { continuation in
            let finish: (CommandResult) -> Void = { result in
                continuation.resume(returning: result)
            }

            let sendPTP = {
                camera.requestSendPTPCommand(
                    commandData,
                    outData: outData,
                    completion: { inData, response, error in
                        OTGLog.info(
                            .ptp,
                            "PTP callback received: \(label), inData=\(inData.count), response=\(response.count)"
                        )

                        if let error {
                            OTGLog.error(.ptp, "PTP \(label) failed: \(error.localizedDescription)")
                            finish(CommandResult(success: false, response: response, inData: inData))
                            return
                        }

                        var ok = true
                        if !response.isEmpty {
                            if let parsed = try? parseContainer(response) {
                                // 非 0x2001 ResponseOK 视为失败
                                if parsed.code != PTPStandardCommand.responseOK {
                                    ok = false
                                    OTGLog.warning(
                                        .ptp,
                                        "PTP \(label) response code=0x\(String(format: "%04X", parsed.code))"
                                    )
                                }
                            } else {
                                ok = false
                                OTGLog.warning(.ptp, "PTP \(label) response parse failed")
                            }
                        }

                        if ok {
                            OTGLog.info(.ptp, "PTP \(label) OK, inData=\(inData.count) bytes")
                        }

                        finish(CommandResult(success: ok, response: response, inData: inData))
                    }
                )
            }

            // ImageCaptureCore PTP API 要求从主线程调用，方法本身由 MainActor 隔离。
            sendPTP()
        }
    }

    // MARK: - Little Endian

    static func appendUInt16LE(_ value: UInt16, to data: inout Data) {
        var littleEndian = value.littleEndian
        withUnsafeBytes(of: &littleEndian) { raw in
            data.append(contentsOf: raw)
        }
    }

    static func appendUInt32LE(_ value: UInt32, to data: inout Data) {
        var littleEndian = value.littleEndian
        withUnsafeBytes(of: &littleEndian) { raw in
            data.append(contentsOf: raw)
        }
    }

    static func readUInt16LE(_ data: Data, offset: Int) -> UInt16 {
        UInt16(data[offset])
            | (UInt16(data[offset + 1]) << 8)
    }

    static func readUInt32LE(_ data: Data, offset: Int) -> UInt32 {
        UInt32(data[offset])
            | (UInt32(data[offset + 1]) << 8)
            | (UInt32(data[offset + 2]) << 16)
            | (UInt32(data[offset + 3]) << 24)
    }
}
