import Foundation /// Deck-DTO. Wire-Format aus `cards/apps/api/src/lib/dto.ts:toDeckDto`. /// snake_case-Felder via `CodingKeys`, Optionals explizit nullable. struct Deck: Codable, Identifiable, Hashable, Sendable { let id: String let userId: String let name: String let description: String? let color: String? let category: DeckCategory? let visibility: DeckVisibility let fsrsSettings: FsrsSettings let contentHash: String? let forkedFromMarketplaceDeckId: String? let forkedFromMarketplaceVersionId: String? let archivedAt: Date? let createdAt: Date let updatedAt: Date enum CodingKeys: String, CodingKey { case id case userId = "user_id" case name case description case color case category case visibility case fsrsSettings = "fsrs_settings" case contentHash = "content_hash" case forkedFromMarketplaceDeckId = "forked_from_marketplace_deck_id" case forkedFromMarketplaceVersionId = "forked_from_marketplace_version_id" case archivedAt = "archived_at" case createdAt = "created_at" case updatedAt = "updated_at" } /// Geforkt aus dem Cardecky-Marketplace? var isFromMarketplace: Bool { forkedFromMarketplaceDeckId != nil } } enum DeckVisibility: String, Codable, Sendable { case `private` case space case `public` } /// Aus `cards/packages/cards-domain/src/schemas/deck.ts:DECK_CATEGORY_IDS`. enum DeckCategory: String, Codable, Sendable, CaseIterable { case language case medicine case science case math case history case law case technology case arts case music case sport case other /// Deutsche Labels aus `DECK_CATEGORY_LABELS`. var label: String { switch self { case .language: "Sprache" case .medicine: "Medizin" case .science: "Wissenschaft" case .math: "Mathematik" case .history: "Geschichte" case .law: "Recht" case .technology: "Technik" case .arts: "Kunst" case .music: "Musik" case .sport: "Sport" case .other: "Sonstiges" } } } /// FSRS-Settings — Native bleibt schematisch agnostisch, FSRS rechnet /// nur der Server. Wir behalten die Felder als roh-JSON, damit eine /// neue Setting auf dem Server uns nicht bricht. struct FsrsSettings: Codable, Sendable, Hashable { let requestRetention: Double? let maximumInterval: Int? let enableFuzz: Bool? enum CodingKeys: String, CodingKey { case requestRetention = "request_retention" case maximumInterval = "maximum_interval" case enableFuzz = "enable_fuzz" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) requestRetention = try container.decodeIfPresent(Double.self, forKey: .requestRetention) maximumInterval = try container.decodeIfPresent(Int.self, forKey: .maximumInterval) enableFuzz = try container.decodeIfPresent(Bool.self, forKey: .enableFuzz) } static let empty = FsrsSettings() private init( requestRetention: Double? = nil, maximumInterval: Int? = nil, enableFuzz: Bool? = nil ) { self.requestRetention = requestRetention self.maximumInterval = maximumInterval self.enableFuzz = enableFuzz } } /// Server-Response von `GET /api/v1/decks`. struct DeckListResponse: Decodable, Sendable { let decks: [Deck] let total: Int } /// Server-Response von `GET /api/v1/cards?deck_id=...`. struct CardListResponse: Decodable, Sendable { let cards: [Card] let total: Int } /// Server-Response von `GET /api/v1/reviews/due?deck_id=...`. struct DueReviewsResponse: Decodable, Sendable { let total: Int }