//
//  TravelAlbumOTGTransferModels.swift
//  suixinkan
//

import Foundation

/// OTG 传输页展示照片项。
struct TravelAlbumOTGPhotoItem: Hashable, Sendable {
    let id: String
    let sourceId: String
    let fileName: String
    let thumbnailURL: URL?
    let capturedAt: String
    let fileSizeText: String
    let fileSizeBytes: Int64
    var status: TravelAlbumOTGUploadStatus
    var progress: Int
    var errorMessage: String?
    let localPath: String
    var remoteUrl: String

    /// 是否未上传完成。
    var isNotUploaded: Bool {
        status != .uploaded
    }

    /// 是否可被选择上传。
    var canSelectForUpload: Bool {
        status == .pending || status == .failed
    }
}

/// OTG 传输页半小时维度时间槽。
struct TravelAlbumOTGTimeSlot: Hashable, Sendable {
    let id: String
    let dateLabel: String
    let slotStartLabel: String
    let photoCount: Int
}

/// OTG 传输页左侧时间导航日期分组。
struct TravelAlbumOTGDateGroup: Hashable, Sendable {
    let dateLabel: String
    let slots: [TravelAlbumOTGTimeSlot]
}

/// OTG 传输页照片列表半小时分段。
struct TravelAlbumOTGPhotoSection: Hashable, Sendable {
    let slotId: String
    let headerTitle: String
    let photos: [TravelAlbumOTGPhotoItem]
}

/// OTG 传输页 Tab。
enum TravelAlbumOTGTransferTab: Int, CaseIterable, Sendable {
    case all
    case uploaded
    case failed

    var title: String {
        switch self {
        case .all: return "全部"
        case .uploaded: return "已上传"
        case .failed: return "失败"
        }
    }
}

/// OTG 传输模式，决定新照片传入 App 后是否立即上传 OSS。
enum TravelAlbumOTGTransferMode: String, CaseIterable, Sendable {
    case liveUpload
    case postTransfer

    /// 展示标题。
    var title: String {
        switch self {
        case .liveUpload: return "边拍边传"
        case .postTransfer: return "拍后传输"
        }
    }

    /// 是否在新照片导入 App 后自动上传。
    var shouldAutoUploadNewImports: Bool {
        self == .liveUpload
    }

    /// 根据展示标题解析传输模式。
    static func option(title: String) -> TravelAlbumOTGTransferMode? {
        allCases.first { $0.title == title }
    }
}

/// OTG 指定上传弹窗选项。
enum TravelAlbumOTGSpecifyUploadOption: String, CaseIterable, Sendable {
    case allPending
    case todayCaptured

    /// 展示标题。
    var title: String {
        switch self {
        case .allPending: return "上传所有未上传的照片"
        case .todayCaptured: return "上传所有今日拍摄的照片"
        }
    }
}

extension TravelAlbumOTGPhotoItem {
    /// 将 Android 同款 `yyyy-MM-dd HH:mm:ss` 拍摄时间转为本地日期。
    func capturedDate() -> Date? {
        TravelAlbumOTGTimeGrouping.capturedDate(from: capturedAt)
    }
}

extension Array where Element == TravelAlbumOTGPhotoItem {
    /// 按 Android OTG 页规则构造半小时照片分段。
    func buildPhotoSections() -> [TravelAlbumOTGPhotoSection] {
        let pairs = compactMap { photo -> (slotStart: Date, photo: TravelAlbumOTGPhotoItem)? in
            guard let date = photo.capturedDate() else { return nil }
            return (TravelAlbumOTGTimeGrouping.halfHourSlotStart(for: date), photo)
        }
        let grouped = Dictionary(grouping: pairs, by: \.slotStart)
        return grouped.keys
            .sorted(by: >)
            .map { slotStart in
                let photos = (grouped[slotStart] ?? [])
                    .map(\.photo)
                    .sorted { lhs, rhs in
                        (lhs.capturedDate() ?? .distantPast) > (rhs.capturedDate() ?? .distantPast)
                    }
                return TravelAlbumOTGPhotoSection(
                    slotId: TravelAlbumOTGTimeGrouping.slotId(for: slotStart),
                    headerTitle: TravelAlbumOTGTimeGrouping.sectionHeaderTitle(for: slotStart),
                    photos: photos
                )
            }
    }

    /// 按 Android OTG 页规则构造左侧时间快速导航分组。
    func buildSidebarGroups() -> [TravelAlbumOTGDateGroup] {
        var slotCounts: [Date: Int] = [:]
        for photo in self {
            guard let date = photo.capturedDate() else { continue }
            let slotStart = TravelAlbumOTGTimeGrouping.halfHourSlotStart(for: date)
            slotCounts[slotStart, default: 0] += 1
        }
        let groupedByDay = Dictionary(grouping: slotCounts.keys) { slotStart in
            TravelAlbumOTGTimeGrouping.dayStart(for: slotStart)
        }
        return groupedByDay.keys
            .sorted(by: >)
            .map { dayStart in
                let slots = (groupedByDay[dayStart] ?? [])
                    .sorted(by: >)
                    .map { slotStart in
                        TravelAlbumOTGTimeSlot(
                            id: TravelAlbumOTGTimeGrouping.slotId(for: slotStart),
                            dateLabel: TravelAlbumOTGTimeGrouping.dateLabel(for: slotStart),
                            slotStartLabel: TravelAlbumOTGTimeGrouping.timeLabel(for: slotStart),
                            photoCount: slotCounts[slotStart] ?? 0
                        )
                    }
                return TravelAlbumOTGDateGroup(
                    dateLabel: TravelAlbumOTGTimeGrouping.dateLabel(for: dayStart),
                    slots: slots
                )
            }
    }
}

/// OTG 传输页时间分组格式化工具，对齐 Android 的半小时槽规则。
enum TravelAlbumOTGTimeGrouping {
    private static let calendar = Calendar(identifier: .gregorian)

    /// 解析 Android OTG 页面使用的拍摄时间字符串。
    static func capturedDate(from text: String) -> Date? {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return formatter.date(from: text)
    }

    /// 计算半小时槽起点。
    static func halfHourSlotStart(for date: Date) -> Date {
        let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
        let minute = ((components.minute ?? 0) / 30) * 30
        return calendar.date(
            from: DateComponents(
                year: components.year,
                month: components.month,
                day: components.day,
                hour: components.hour,
                minute: minute
            )
        ) ?? date
    }

    /// 计算日期起点。
    static func dayStart(for date: Date) -> Date {
        calendar.startOfDay(for: date)
    }

    /// 时间槽唯一标识。
    static func slotId(for date: Date) -> String {
        format(date, pattern: "yyyy-MM-dd'T'HH:mm", locale: Locale(identifier: "en_US_POSIX"))
    }

    /// 主列表分段标题。
    static func sectionHeaderTitle(for date: Date) -> String {
        let end = calendar.date(byAdding: .minute, value: 30, to: date) ?? date
        return "\(dateLabel(for: date)) \(timeLabel(for: date)) - \(timeLabel(for: end))"
    }

    /// 日期标签。
    static func dateLabel(for date: Date) -> String {
        format(date, pattern: "MM月dd日", locale: Locale(identifier: "zh_CN"))
    }

    /// 时间标签。
    static func timeLabel(for date: Date) -> String {
        format(date, pattern: "HH:mm", locale: Locale(identifier: "en_US_POSIX"))
    }

    private static func format(_ date: Date, pattern: String, locale: Locale) -> String {
        let formatter = DateFormatter()
        formatter.locale = locale
        formatter.dateFormat = pattern
        return formatter.string(from: date)
    }
}

/// 相机照片格式匹配工具，对齐 Android `OtgPhotoFormatMatcher`。
enum TravelAlbumOTGPhotoFormatMatcher {
    /// 相机照片类型。
    enum PhotoKind: Equatable {
        case jpeg
        case raw
        case other
    }

    private static let jpegExtensions: Set<String> = ["jpg", "jpeg", "heif", "hif"]
    private static let rawExtensions: Set<String> = [
        "dng", "cr2", "cr3", "nef", "nrw", "arw", "srf", "sr2",
        "orf", "rw2", "raw", "raf", "srw", "pef", "ptx", "rwl",
    ]
    private static let jpegFormatCodes: Set<Int> = [0x3801, 0x3808]
    private static let rawFormatCodes: Set<Int> = [0x380D, 0xB101, 0xB103, 0xB108]

    /// 判断文件类型。
    static func classify(name: String, format: Int = 0, isVideo: Bool = false) -> PhotoKind {
        if isVideo { return .other }
        let ext = URL(fileURLWithPath: name).pathExtension.lowercased()
        if rawExtensions.contains(ext) || rawFormatCodes.contains(format) {
            return .raw
        }
        if jpegExtensions.contains(ext) || jpegFormatCodes.contains(format) {
            return .jpeg
        }
        return .other
    }

    /// 判断相机对象是否匹配当前上传格式。
    static func matchesUploadKind(_ object: CameraObject, option: TravelAlbumOTGPhotoFormatOption) -> Bool {
        switch option {
        case .jpg:
            return classify(name: object.filename) == .jpeg
        case .raw:
            return classify(name: object.filename) == .raw
        case .jpegRaw:
            let kind = classify(name: object.filename)
            return kind == .jpeg || kind == .raw
        }
    }

    /// 根据格式偏好展开实际上传目标。
    static func resolveUploadTargets(
        requested: [CameraObject],
        allObjects: [CameraObject],
        option: TravelAlbumOTGPhotoFormatOption
    ) -> [CameraObject] {
        guard !requested.isEmpty else { return [] }
        var ordered: [CameraObject] = []
        var seen = Set<String>()
        for object in requested {
            let peers = peers(of: object, allObjects: allObjects)
            let jpegs = peers.filter { classify(name: $0.filename) == .jpeg }
            let raws = peers.filter { classify(name: $0.filename) == .raw }
            let candidates: [CameraObject]
            switch option {
            case .jpg:
                candidates = Array(jpegs.prefix(1))
            case .raw:
                candidates = Array(raws.prefix(1))
            case .jpegRaw:
                candidates = jpegs + raws
            }
            candidates.forEach { candidate in
                if seen.insert(candidate.id).inserted {
                    ordered.append(candidate)
                }
            }
        }
        return ordered
    }

    private static func peers(of object: CameraObject, allObjects: [CameraObject]) -> [CameraObject] {
        let stem = captureStem(object.filename)
        guard !stem.isEmpty else { return [object] }
        let matches = allObjects.filter { captureStem($0.filename) == stem }
        return matches.isEmpty ? [object] : matches
    }

    private static func captureStem(_ name: String) -> String {
        (name as NSString).deletingPathExtension.uppercased()
    }
}

extension TravelAlbumOTGPhotoRecord {
    /// 转为页面展示项。
    func toPhotoItem(storage: TravelAlbumOTGPhotoStore, albumId: Int) -> TravelAlbumOTGPhotoItem {
        let previewPath = [thumbnailPath, localPath, remoteUrl].first { path in
            guard !path.isEmpty else { return false }
            if path.hasPrefix("http") { return true }
            return storage.fileExists(relativePath: path, albumId: albumId)
        } ?? ""
        let previewURL: URL?
        if previewPath.hasPrefix("http") {
            previewURL = URL(string: previewPath)
        } else if !previewPath.isEmpty {
            previewURL = storage.absoluteURL(for: previewPath, albumId: albumId)
        } else {
            previewURL = nil
        }
        return TravelAlbumOTGPhotoItem(
            id: id,
            sourceId: sourceId,
            fileName: fileName,
            thumbnailURL: previewURL,
            capturedAt: capturedAt,
            fileSizeText: TravelAlbumOTGPhotoStore.fileSizeText(fileSizeBytes),
            fileSizeBytes: fileSizeBytes,
            status: status,
            progress: progress,
            errorMessage: errorMessage,
            localPath: localPath,
            remoteUrl: remoteUrl
        )
    }
}
