import Foundation

struct PackResource: Codable, Hashable, Identifiable {
    var id: String
    var name: String
    var kind: String
    var path: String
}

struct CourseManifest: Codable, Hashable {
    var schemaVersion: Int
    var id: String
    var title: String
    var mode: String
    var updatedAt: String
    var items: [AlbumItem]
    var resources: [PackResource]

    var albumMode: AlbumMode {
        AlbumMode(rawValue: mode) ?? .combo
    }

    func resource(id: String) -> PackResource? {
        resources.first { $0.id == id }
    }

    func validate() throws {
        guard schemaVersion >= 1 else {
            throw CoursePackError.invalidManifest("unsupported schemaVersion")
        }
        guard !id.isEmpty else {
            throw CoursePackError.invalidManifest("missing id")
        }
        guard !title.isEmpty else {
            throw CoursePackError.invalidManifest("missing title")
        }
        guard !items.isEmpty else {
            throw CoursePackError.invalidManifest("empty items")
        }
        guard !resources.isEmpty else {
            throw CoursePackError.invalidManifest("empty resources")
        }

        let resourceIds = Set(resources.map(\.id))
        for item in items {
            switch item {
            case .single(let resourceId):
                guard resourceIds.contains(resourceId) else {
                    throw CoursePackError.invalidManifest("item references missing resource \(resourceId)")
                }
            case .combo(_, let ids):
                for rid in [ids.video, ids.video2, ids.audio, ids.pdf, ids.pdf2].compactMap({ $0 }) {
                    guard resourceIds.contains(rid) else {
                        throw CoursePackError.invalidManifest("item references missing resource \(rid)")
                    }
                }
            }
        }
    }
}

enum CoursePackError: LocalizedError {
    case invalidManifest(String)
    case missingMedia(String)
    case pathEscape
    case unzipFailed(String)
    case network(String)

    var errorDescription: String? {
        switch self {
        case .invalidManifest(let m): return "无效课程包: \(m)"
        case .missingMedia(let m): return "缺少媒体: \(m)"
        case .pathEscape: return "非法路径"
        case .unzipFailed(let m): return "解压失败: \(m)"
        case .network(let m): return m
        }
    }
}
