import Foundation import ManaCore import Observation import SwiftData /// State-Machine für eine Lern-Session. Lädt Due-Reviews beim Start, /// rendert eine Karte nach der anderen, schickt Grades via GradeQueue ab. @MainActor @Observable final class StudySession { enum Phase: Sendable { case loading case studying case finished case failed(String) } private(set) var phase: Phase = .loading private(set) var queue: [DueReview] = [] private(set) var currentIndex: Int = 0 private(set) var isFlipped: Bool = false private(set) var totalGraded: Int = 0 let deckId: String let deckName: String private let api: CardsAPI private let gradeQueue: GradeQueue init(deckId: String, deckName: String, auth: AuthClient, context: ModelContext) { self.deckId = deckId self.deckName = deckName api = CardsAPI(auth: auth) gradeQueue = GradeQueue(api: api, context: context) } var current: DueReview? { guard queue.indices.contains(currentIndex) else { return nil } return queue[currentIndex] } var remaining: Int { max(0, queue.count - currentIndex) } func start() async { phase = .loading do { queue = try await api.dueReviews(deckId: deckId, limit: 500) currentIndex = 0 isFlipped = false totalGraded = 0 if queue.isEmpty { phase = .finished } else { phase = .studying } Log.study.info("Session start — \(self.queue.count, privacy: .public) due in deck \(self.deckId, privacy: .public)") } catch { let msg = (error as? LocalizedError)?.errorDescription ?? String(describing: error) phase = .failed(msg) Log.study.error("Session start failed: \(msg, privacy: .public)") } } func flip() { guard case .studying = phase else { return } isFlipped.toggle() } func grade(_ rating: Rating) async { guard case .studying = phase, let card = current else { return } let reviewedAt = Date.now await gradeQueue.submit( cardId: card.review.cardId, subIndex: card.review.subIndex, rating: rating, reviewedAt: reviewedAt ) totalGraded += 1 advance() } private func advance() { currentIndex += 1 isFlipped = false if currentIndex >= queue.count { phase = .finished Log.study.info("Session finished — graded \(self.totalGraded, privacy: .public)") } } }