import Foundation enum RecognitionSource: String, Codable, CaseIterable, Identifiable { case cloud case onDeviceOffline case onDeviceFallback case manual var id: String { rawValue } var title: String { switch self { case .cloud: return "Cloud OCR" case .onDeviceOffline: return "On-Device (Offline)" case .onDeviceFallback: return "On-Device Fallback" case .manual: return "Manual Entry" } } var detail: String { switch self { case .cloud: return "Prepared client boundary for future Convex/Mistral OCR." case .onDeviceOffline: return "Offline erkannt — Ergebnis kann weniger genau sein." case .onDeviceFallback: return "Cloud path unavailable — used the on-device Vision fallback." case .manual: return "No image is persisted after confirmation." } } } enum ConfidenceLevel: String, Codable, CaseIterable, Comparable { case low case medium case high private var score: Int { switch self { case .low: return 0 case .medium: return 1 case .high: return 2 } } static func < (lhs: ConfidenceLevel, rhs: ConfidenceLevel) -> Bool { lhs.score < rhs.score } var title: String { rawValue.capitalized } var helperText: String { switch self { case .high: return "Looks solid — a quick confirmation should be enough." case .medium: return "Please review the extracted fields before confirming." case .low: return "Low confidence — manual corrections are recommended." } } } enum CardRarity: String, CaseIterable, Identifiable { case unknown = "Unknown" case common = "Common" case uncommon = "Uncommon" case rare = "Rare" case holoRare = "Holo Rare" case ultraRare = "Ultra Rare" case illustrationRare = "Illustration Rare" case specialArtRare = "Special Art Rare" case hyperRare = "Hyper Rare" case secretRare = "Secret Rare" var id: String { rawValue } } struct CardRecognitionDraft: Equatable { var cardName: String var cardNumber: String var setIdentifier: String var rarity: String var source: RecognitionSource var confidence: ConfidenceLevel var notes: [String] var rawText: String var hasDetectedContent: Bool { !cardName.isBlank || !cardNumber.isBlank || !setIdentifier.isBlank || !rarity.isBlank } var combinedNumberAndSet: String { let components = [cardNumber.trimmedNilIfEmpty, setIdentifier.trimmedNilIfEmpty].compactMap { $0 } return components.joined(separator: " · ") } static func manualPrefill(rawText: String = "") -> CardRecognitionDraft { CardRecognitionDraft( cardName: "", cardNumber: "", setIdentifier: "", rarity: CardRarity.unknown.rawValue, source: .manual, confidence: .low, notes: ["Manual entry is always available when OCR misses fields."], rawText: rawText ) } } struct RecognitionSession: Identifiable, Equatable { let id: UUID var draft: CardRecognitionDraft var thumbnailJPEGData: Data? init(id: UUID = UUID(), draft: CardRecognitionDraft, thumbnailJPEGData: Data? = nil) { self.id = id self.draft = draft self.thumbnailJPEGData = thumbnailJPEGData } } private extension String { var isBlank: Bool { trimmedNilIfEmpty == nil } var trimmedNilIfEmpty: String? { let trimmed = trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : trimmed } }