import Foundation
import Testing

@testable import NucleicCore

/// The Intelligence slider's pure geometry: fraction ↔ level round trips, hit-testing with
/// insets, clamping at the rails, and detent magnetism that never fights the cursor.
@Suite struct IntelligenceSliderMathTests {
    @Test func levelFractionsSpanZeroToOne() {
        #expect(IntelligenceLevel.quick.fraction == 0)
        #expect(IntelligenceLevel.max.fraction == 1)
        #expect(IntelligenceLevel.balanced.fraction == 0.5)
        // Strictly increasing — the visuals lerp on it.
        let fractions = IntelligenceLevel.allCases.map(\.fraction)
        #expect(fractions == fractions.sorted())
        #expect(Set(fractions).count == fractions.count)
    }

    @Test func nearestLevelRoundTripsAndClamps() {
        for level in IntelligenceLevel.allCases {
            #expect(IntelligenceSliderMath.nearestLevel(fraction: level.fraction) == level)
        }
        #expect(IntelligenceSliderMath.nearestLevel(fraction: -0.4) == .quick)
        #expect(IntelligenceSliderMath.nearestLevel(fraction: 1.7) == .max)
        // Midpoints round to the nearer stop.
        #expect(IntelligenceSliderMath.nearestLevel(fraction: 0.10) == .quick)
        #expect(IntelligenceSliderMath.nearestLevel(fraction: 0.15) == .light)
    }

    @Test func hitTestingHonorsInsets() {
        let width = 180.0
        let inset = 8.0
        #expect(IntelligenceSliderMath.level(atX: 0, trackWidth: width, inset: inset) == .quick)
        #expect(IntelligenceSliderMath.level(atX: width, trackWidth: width, inset: inset) == .max)
        #expect(
            IntelligenceSliderMath.level(atX: width / 2, trackWidth: width, inset: inset)
                == .balanced)
        // Nub centers land back on the stop that produced them.
        for level in IntelligenceLevel.allCases {
            let x = IntelligenceSliderMath.nubCenterX(for: level, trackWidth: width, inset: inset)
            #expect(IntelligenceSliderMath.level(atX: x, trackWidth: width, inset: inset) == level)
            #expect(x >= inset && x <= width - inset)
        }
        // Degenerate width never divides by zero.
        #expect(IntelligenceSliderMath.level(atX: 5, trackWidth: 10, inset: 8) == .quick)
    }

    @Test func magnetismPullsTowardDetentsWithoutOvershoot() {
        // On a detent, magnetism is a no-op.
        for level in IntelligenceLevel.allCases {
            #expect(IntelligenceSliderMath.magnetized(fraction: level.fraction) == level.fraction)
        }
        // Off-detent, it moves toward (never past) the nearest stop, and never flips which
        // stop is nearest — the release snap must agree with what the drag showed.
        for raw in stride(from: 0.0, through: 1.0, by: 0.01) {
            let pulled = IntelligenceSliderMath.magnetized(fraction: raw)
            let nearest = IntelligenceSliderMath.nearestLevel(fraction: raw).fraction
            #expect(abs(pulled - nearest) <= abs(raw - nearest) + 1e-12)
            #expect(
                IntelligenceSliderMath.nearestLevel(fraction: pulled)
                    == IntelligenceSliderMath.nearestLevel(fraction: raw))
        }
        // Crossing a midpoint is continuous: attraction fades out before nearest flips.
        let midpoint = 0.125
        #expect(IntelligenceSliderMath.magnetized(fraction: midpoint) == midpoint)
        let justBelow = IntelligenceSliderMath.magnetized(fraction: midpoint - 0.000_001)
        let justAbove = IntelligenceSliderMath.magnetized(fraction: midpoint + 0.000_001)
        #expect(abs(justAbove - justBelow) < 0.000_01)

        // Full strength is a hard snap; zero strength is identity.
        #expect(IntelligenceSliderMath.magnetized(fraction: 0.6, strength: 1) == 0.5)
        #expect(IntelligenceSliderMath.magnetized(fraction: 0.6, strength: 0) == 0.6)
    }
}
