import Foundation

/// Pure geometry for the Intelligence slider — fraction ↔ level mapping, hit-testing, and
/// detent magnetism — kept out of the SwiftUI layer so it's unit-testable (NucleicApp is an
/// executable target with no test target). The view owns only drawing and gesture plumbing.
///
/// Coordinates: the nub's center travels `inset … trackWidth − inset`; `fraction` is its
/// normalized position along that span (0 = Quick, 1 = Max).
public enum IntelligenceSliderMath {
    /// The discrete stop nearest to a normalized position (clamped, so overdrag is safe).
    public static func nearestLevel(fraction: Double) -> IntelligenceLevel {
        let steps = IntelligenceLevel.allCases.count - 1
        let clamped = min(max(fraction, 0), 1)
        let raw = Int((clamped * Double(steps)).rounded()) + 1
        return IntelligenceLevel(rawValue: raw) ?? .fallback
    }

    /// Normalized position for a pointer x within the track (clamped to 0…1).
    public static func fraction(atX x: Double, trackWidth: Double, inset: Double) -> Double {
        let usable = trackWidth - inset * 2
        guard usable > 0 else { return 0 }
        return min(max((x - inset) / usable, 0), 1)
    }

    /// The stop a click/drag at pointer x selects.
    public static func level(atX x: Double, trackWidth: Double, inset: Double) -> IntelligenceLevel {
        nearestLevel(fraction: fraction(atX: x, trackWidth: trackWidth, inset: inset))
    }

    /// The nub's center x for a level.
    public static func nubCenterX(for level: IntelligenceLevel, trackWidth: Double, inset: Double) -> Double {
        inset + (trackWidth - inset * 2) * level.fraction
    }

    /// Soft detent magnetism while dragging. Attraction is strongest near a stop and
    /// smoothly fades to zero halfway between stops, avoiding a discontinuity when the
    /// nearest detent changes. `strength` 0 is free and 1 is a hard snap.
    public static func magnetized(fraction: Double, strength: Double = 0.35) -> Double {
        let steps = Double(IntelligenceLevel.allCases.count - 1)
        let clamped = min(max(fraction, 0), 1)
        let scaled = clamped * steps
        let nearest = scaled.rounded() / steps
        let clampedStrength = min(max(strength, 0), 1)
        guard clampedStrength < 1 else { return nearest }

        let proximity = 1 - abs(scaled - scaled.rounded()) * 2
        let easedProximity = proximity * proximity * (3 - 2 * proximity)
        return clamped + (nearest - clamped) * clampedStrength * easedProximity
    }
}
