import Foundation import ManaCore /// Konstanten für `DeckEditorView` — Farbpalette, File-Limits. /// Werte gespiegelt aus `forest`-Theme und Server-Limits in /// `cards/apps/api/src/routes/decks-from-image.ts`. enum DeckEditorPresets { /// 8 Farb-Presets aus dem forest-Theme. Freie Hex-Werte später /// via Custom-Picker (β-3-extension). static let colors: [String] = [ "#10803D", // forest primary light "#1E3A2F", // forest dark "#D97706", // amber "#DC2626", // red "#2563EB", // blue "#7C3AED", // violet "#0D9488", // teal "#737373" // neutral ] static let maxMediaFiles = 5 static let maxImageBytes = 10 * 1024 * 1024 static let maxPDFBytes = 30 * 1024 * 1024 } /// Reine Hilfsfunktionen für `DeckEditorView` — kein State, keine Bindings. enum DeckEditorHelpers { /// Nil zurück wenn String nach Trim leer ist. static func nonEmpty(_ value: String) -> String? { let trimmed = value.trimmingCharacters(in: .whitespaces) return trimmed.isEmpty ? nil : trimmed } /// http:// oder https:// und nicht-leer. static func isValidURL(_ value: String) -> Bool { let trimmed = value.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { return false } guard let url = URL(string: trimmed), let scheme = url.scheme else { return false } return scheme == "http" || scheme == "https" } /// Magic-Byte-Check für die häufigsten Image-Formate. Fallback JPEG. static func inferImageMimeType(from data: Data) -> String { guard data.count > 4 else { return "image/jpeg" } let bytes = Array(data.prefix(8)) if bytes.starts(with: [0xFF, 0xD8, 0xFF]) { return "image/jpeg" } if bytes.starts(with: [0x89, 0x50, 0x4E, 0x47]) { return "image/png" } if bytes.starts(with: [0x47, 0x49, 0x46, 0x38]) { return "image/gif" } if bytes.count >= 4, bytes[0 ... 3] == [0x52, 0x49, 0x46, 0x46] { return "image/webp" } return "image/jpeg" } /// Dateiendung für ein erkanntes Image-MIME. static func imageExtension(forMime mime: String) -> String { switch mime { case "image/png": "png" case "image/gif": "gif" case "image/webp": "webp" default: "jpg" } } /// AuthError-Server-Codes auf nutzerfreundliche deutsche Texte mappen. /// Greift für beide AI-Endpoints, fällt sonst auf `errorDescription`. static func mapAIError(_ error: AuthError) -> String { if case let .serverError(status, _, message) = error { switch status { case 429: return "Zu viele KI-Anfragen. Bitte eine Minute warten." case 413: return message ?? "Datei zu groß." case 422, 400: return message ?? "Eingabe ungültig." case 502: return message ?? "KI-Server gerade nicht erreichbar." default: break } } return error.errorDescription ?? "Unbekannter Fehler." } }