import Testing
@testable import SharedModels

// Practice pool = selected levels ∩ selected tenses ∩ selected irregular types
// (Issue #26). This suite covers the level + irregular intersection; tense
// filtering is a separate concern handled at the review-card layer.

@Suite("PracticeFilter — level selection")
struct PracticeFilterLevelTests {

    @Test("single level expands to that group's data-levels")
    func singleLevelExpansion() {
        let expected = VerbLevelGroup.dataLevels(for: "elementary")
        #expect(PracticeFilter.dataLevels(forSelectedLevels: ["elementary"]) == expected)
    }

    @Test("multi-level union merges every group's data-levels")
    func multiLevelUnion() {
        let union = PracticeFilter.dataLevels(forSelectedLevels: ["elementary", "intermediate"])
        #expect(union.isSuperset(of: VerbLevelGroup.dataLevels(for: "elementary")))
        #expect(union.isSuperset(of: VerbLevelGroup.dataLevels(for: "intermediate")))
    }

    @Test("empty selected-levels produces empty data-levels")
    func emptyLevelsProducesEmpty() {
        #expect(PracticeFilter.dataLevels(forSelectedLevels: []).isEmpty)
    }

    @Test("unknown level passes through to its raw value")
    func unknownLevelPassthrough() {
        // Preserves VerbLevelGroup's fallback contract.
        #expect(PracticeFilter.dataLevels(forSelectedLevels: ["custom"]) == ["custom"])
    }
}

@Suite("PracticeFilter — verb IDs by level")
struct PracticeFilterVerbLevelTests {

    // Fixtures: four verbs spanning basic + elementary subgroups + intermediate.
    private let verbs: [PracticeFilter.VerbSlot] = [
        .init(id: 1, level: "basic"),
        .init(id: 2, level: "elementary"),
        .init(id: 3, level: "elementary_2"),
        .init(id: 4, level: "intermediate_1"),
    ]

    @Test("elementary selection matches elementary base and subgroup levels")
    func elementarySubgroupMatch() {
        let ids = PracticeFilter.verbIDs(matchingLevels: ["elementary"], in: verbs)
        #expect(ids == [2, 3])
    }

    @Test("multi-level selection unions matching verb IDs")
    func multiLevelUnionIds() {
        let ids = PracticeFilter.verbIDs(matchingLevels: ["basic", "intermediate"], in: verbs)
        #expect(ids == [1, 4])
    }

    @Test("empty level selection returns no verbs (literal semantics)")
    func emptySelectionReturnsEmpty() {
        let ids = PracticeFilter.verbIDs(matchingLevels: [], in: verbs)
        #expect(ids.isEmpty)
    }
}

@Suite("PracticeFilter — verb IDs by irregular category")
struct PracticeFilterIrregularTests {

    // Verb 10: regular (no spans).
    // Verb 20: one spelling-change span.
    // Verb 30: stem-change + unique-irregular spans.
    private let spans: [PracticeFilter.IrregularSlot] = [
        .init(verbId: 20, category: .spelling),
        .init(verbId: 30, category: .stemChange),
        .init(verbId: 30, category: .uniqueIrregular),
    ]

    @Test("empty category set returns empty — caller decides the semantics")
    func emptyCategoriesReturnsEmpty() {
        let ids = PracticeFilter.verbIDs(matchingIrregularCategories: [], in: spans)
        #expect(ids.isEmpty)
    }

    @Test("single category picks matching verbs only")
    func singleCategoryMatch() {
        let ids = PracticeFilter.verbIDs(matchingIrregularCategories: [.spelling], in: spans)
        #expect(ids == [20])
    }

    @Test("multiple categories union their matches")
    func multipleCategoriesUnion() {
        let ids = PracticeFilter.verbIDs(
            matchingIrregularCategories: [.spelling, .stemChange],
            in: spans
        )
        #expect(ids == [20, 30])
    }

    @Test("a verb with multiple matching spans is returned once")
    func verbWithMultipleSpansDeduped() {
        let ids = PracticeFilter.verbIDs(
            matchingIrregularCategories: [.stemChange, .uniqueIrregular],
            in: spans
        )
        #expect(ids == [30])
    }
}

@Suite("PracticeFilter — allowedVerbIDs (levels ∩ irregulars)")
struct PracticeFilterIntersectionTests {

    // Realistic fixture:
    // #1 — basic, regular
    // #2 — basic, spelling-change
    // #3 — elementary, spelling-change
    // #4 — elementary, stem-change
    // #5 — intermediate, unique-irregular
    private let verbs: [PracticeFilter.VerbSlot] = [
        .init(id: 1, level: "basic"),
        .init(id: 2, level: "basic"),
        .init(id: 3, level: "elementary"),
        .init(id: 4, level: "elementary_1"),
        .init(id: 5, level: "intermediate"),
    ]
    private let spans: [PracticeFilter.IrregularSlot] = [
        .init(verbId: 2, category: .spelling),
        .init(verbId: 3, category: .spelling),
        .init(verbId: 4, category: .stemChange),
        .init(verbId: 5, category: .uniqueIrregular),
    ]

    @Test("no irregular filter keeps every verb at the selected level")
    func noIrregularConstraint() {
        let ids = PracticeFilter.allowedVerbIDs(
            verbs: verbs,
            spans: spans,
            selectedLevels: ["basic"],
            irregularCategories: []
        )
        #expect(ids == [1, 2])
    }

    @Test("Issue #26 worked example: beginner + spelling-change → only #2")
    func issueWorkedExample() {
        let ids = PracticeFilter.allowedVerbIDs(
            verbs: verbs,
            spans: spans,
            selectedLevels: ["basic"],
            irregularCategories: [.spelling]
        )
        #expect(ids == [2])
    }

    @Test("filter is an intersection, not a union: level-mismatched spans are excluded")
    func intersectionExcludesOtherLevels() {
        // Elementary has a spelling-change verb (#3). Selecting basic + spelling
        // must NOT leak #3 through the irregular filter alone.
        let ids = PracticeFilter.allowedVerbIDs(
            verbs: verbs,
            spans: spans,
            selectedLevels: ["basic"],
            irregularCategories: [.spelling]
        )
        #expect(!ids.contains(3))
    }

    @Test("empty level selection produces empty pool regardless of irregular filter")
    func emptyLevelsLocksOutPractice() {
        let ids = PracticeFilter.allowedVerbIDs(
            verbs: verbs,
            spans: spans,
            selectedLevels: [],
            irregularCategories: [.spelling]
        )
        #expect(ids.isEmpty)
    }

    @Test("multi-level + multi-category pulls every matching pair")
    func multiLevelMultiCategory() {
        let ids = PracticeFilter.allowedVerbIDs(
            verbs: verbs,
            spans: spans,
            selectedLevels: ["elementary", "intermediate"],
            irregularCategories: [.stemChange, .uniqueIrregular]
        )
        #expect(ids == [4, 5])
    }
}

@Suite("VerbLevel.highest")
struct VerbLevelHighestTests {

    @Test("returns the highest-ranked level in the set")
    func highestOfMany() {
        #expect(VerbLevel.highest(in: [.basic, .intermediate, .elementary]) == .intermediate)
    }

    @Test("returns the sole element when set is a singleton")
    func singleton() {
        #expect(VerbLevel.highest(in: [.advanced]) == .advanced)
    }

    @Test("returns nil for the empty set")
    func empty() {
        #expect(VerbLevel.highest(in: []) == nil)
    }

    @Test("ranks expert above advanced above intermediate above elementary above basic")
    func fullRanking() {
        #expect(VerbLevel.highest(in: [.basic, .elementary, .intermediate, .advanced, .expert]) == .expert)
    }
}
