import Foundation
import SharedModels
import SwiftData

/// In-session "learning steps" queue for vocab practice — the short-term
/// scheduling layer that sits on top of the cross-session SM-2 schedule.
///
/// A card is requeued a relative number of positions ahead based on the
/// rating, mirroring Anki's learning steps but position-based instead of
/// time-based:
///   Again → reappears 5–8 cards later
///   Hard  → reappears 7–10 cards later
///   Good  → first time: advances to `review`, reappears ~20 cards later
///           already in review: graduates (leaves the session)
///   Easy  → graduates immediately
///
/// `answer` returns a `ReviewQuality` only when the card graduates — that's
/// the single rating fed to the long-term `VerbReviewStore`. Intermediate
/// Again/Hard presses don't touch the cross-session schedule.
struct VocabSessionQueue {

    enum CardState: String {
        case new        // never answered this session
        case learning   // answered Again/Hard at least once
        case review     // answered Good once, one confirmation pass to go
    }

    enum Rating {
        case again, hard, good, easy
    }

    struct Entry: Identifiable {
        let id = UUID()
        let verb: Verb
        var state: CardState
    }

    private(set) var queue: [Entry]
    private(set) var learnedCount: Int = 0
    private let originalVerbs: [Verb]

    init(verbs: [Verb]) {
        originalVerbs = verbs
        queue = verbs.map { Entry(verb: $0, state: .new) }
    }

    /// Resume a persisted group: rebuild the queue from saved (verb, state)
    /// pairs in order, restoring the learned count. The exact requeue
    /// positions aren't persisted — the queue order itself is what's saved.
    init(entries: [(verb: Verb, state: CardState)], learnedCount: Int) {
        originalVerbs = entries.map(\.verb)
        queue = entries.map { Entry(verb: $0.verb, state: $0.state) }
        self.learnedCount = learnedCount
    }

    /// Current queue (un-graduated cards) in order — for persistence.
    func snapshot() -> [(verbId: Int, state: CardState)] {
        queue.map { ($0.verb.id, $0.state) }
    }

    // MARK: - State

    var current: Entry? { queue.first }
    var isComplete: Bool { queue.isEmpty }
    var remainingCount: Int { queue.count }

    /// 0–1, climbs as cards graduate. Requeuing a card lowers it slightly but
    /// it always trends to 1 as the session drains.
    var progress: Double {
        let total = learnedCount + queue.count
        return total == 0 ? 1 : Double(learnedCount) / Double(total)
    }

    // MARK: - Answering

    /// Apply a rating to the current card. Returns the `ReviewQuality` to
    /// record in the long-term SRS *iff* the card graduated; nil if it was
    /// requeued for more in-session practice.
    @discardableResult
    mutating func answer(_ rating: Rating) -> ReviewQuality? {
        guard !queue.isEmpty else { return nil }
        var entry = queue.removeFirst()

        switch rating {
        // Again/Hard always (re)set the card to `.learning`. A card already in
        // `.review` therefore drops back a step — an intentional lapse, matching
        // Anki: missing a card you'd previously passed sends it back through the
        // learning steps rather than letting it graduate.
        case .again:
            entry.state = .learning
            insert(entry, offset: Int.random(in: 5...8))
            return nil

        case .hard:
            entry.state = .learning
            insert(entry, offset: Int.random(in: 7...10))
            return nil

        case .good:
            if entry.state == .review {
                learnedCount += 1
                return .good
            }
            entry.state = .review
            insert(entry, offset: Int.random(in: 16...24))
            return nil

        case .easy:
            learnedCount += 1
            return .easy
        }
    }

    /// Rebuild the session from the same verb set (re-shuffled) — "Study Again".
    mutating func restart() {
        queue = originalVerbs.shuffled().map { Entry(verb: $0, state: .new) }
        learnedCount = 0
    }

    // MARK: - Private

    /// Insert `entry` `offset` positions from the front, i.e. `offset` other
    /// cards will be shown before it reappears. Clamps to the queue's end.
    private mutating func insert(_ entry: Entry, offset: Int) {
        let idx = min(queue.count, offset)
        queue.insert(entry, at: idx)
    }
}

// MARK: - Session verb pool

/// Builds a vocab-practice session: level-filtered verbs ordered due-first
/// (per the cross-session `VerbReviewCard` schedule) and capped so a single
/// sitting is bounded — proper SRS behaviour rather than a 100+ card slog.
enum VocabVerbPool {

    /// Maximum verbs in one session, from the "Cards per session" setting
    /// (`vocabSessionCardLimit`). Defaults to 20 when unset; 999 means "All".
    /// Overdue cards are pulled first, then new (never-reviewed) verbs.
    static var sessionCardLimit: Int {
        let stored = UserDefaults.standard.integer(forKey: "vocabSessionCardLimit")
        return stored == 0 ? 20 : stored
    }

    static func sessionVerbs(
        localContext: ModelContext,
        cloudContext: ModelContext
    ) -> [Verb] {
        let progress = ReviewStore.fetchOrCreateUserProgress(context: cloudContext)
        let levels = Set(progress.selectedVerbLevels.map(\.rawValue))

        let store = ReferenceStore(context: localContext)
        let pool = levels.isEmpty
            ? store.fetchVerbs()
            : store.fetchVerbs(selectedLevels: levels)

        let reviewCards = (try? cloudContext.fetch(FetchDescriptor<VerbReviewCard>())) ?? []
        let cardByVerbId = Dictionary(
            reviewCards.map { ($0.verbId, $0) },
            uniquingKeysWith: { existing, _ in existing }
        )

        let now = Date()
        var due: [(verb: Verb, dueDate: Date)] = []
        var fresh: [Verb] = []
        for verb in pool {
            if let card = cardByVerbId[verb.id] {
                if card.dueDate <= now {
                    due.append((verb, card.dueDate))
                }
                // Not yet due → intentionally skipped; that's the SRS schedule.
            } else {
                fresh.append(verb)
            }
        }

        // Most-overdue first, then new verbs (lower rank = more common first).
        due.sort { $0.dueDate < $1.dueDate }
        fresh.sort { $0.rank < $1.rank }

        let ordered = due.map(\.verb) + fresh
        return Array(ordered.prefix(sessionCardLimit))
    }

    /// Verbs the user has already studied at least once (have a
    /// `VerbReviewCard`), most-recently-studied first. Used by the
    /// "Review Learned" consolidation pass — ignores due dates and the
    /// Level filter, and is uncapped: it's a deliberate cram over
    /// everything you've learned.
    static func reviewLearnedVerbs(
        localContext: ModelContext,
        cloudContext: ModelContext
    ) -> [Verb] {
        let reviewCards = (try? cloudContext.fetch(FetchDescriptor<VerbReviewCard>())) ?? []
        let sorted = reviewCards.sorted {
            ($0.lastReviewDate ?? .distantPast) > ($1.lastReviewDate ?? .distantPast)
        }

        let allVerbs = ReferenceStore(context: localContext).fetchVerbs()
        let byId = Dictionary(allVerbs.map { ($0.id, $0) }, uniquingKeysWith: { existing, _ in existing })
        return sorted.compactMap { byId[$0.verbId] }
    }
}

/// Canonical 6-tense set for `VerbExampleGenerator`. Its `@Generable` schema
/// requires exactly 6 examples, so callers must pass 6 distinct tense IDs.
enum VocabExampleTenseIds {
    static let canonical: [String] = [
        TenseID.ind_presente.rawValue,
        TenseID.ind_preterito.rawValue,
        TenseID.ind_imperfecto.rawValue,
        TenseID.ind_futuro.rawValue,
        TenseID.subj_presente.rawValue,
        TenseID.imp_afirmativo.rawValue,
    ]
}
