namespace DeepDrftPublic.Client.Controls;

/// <summary>
/// Pure mapping between the waveform visualizer's zoom slider position [0, 1] and the visible time-span in
/// seconds. The span range is wide (0.333 s … 30 s, ~90×), so the mapping is logarithmic — equal
/// slider travel changes the span by an equal *ratio*, which feels even to the hand. Slider
/// orientation: fraction 0 = most zoomed-out (longest span), fraction 1 = most zoomed-in (the
/// 0.333 s quarter-note-@-180-BPM anchor). Extracted from the component so the math is unit-testable.
/// </summary>
public static class WaveformZoomMapping
{
    /// <summary>Shortest span (max zoom): one quarter note at 180 BPM = 60/180 s. Hard anchor.</summary>
    public const double MinVisibleSeconds = 60.0 / 180.0;

    /// <summary>Longest span (min zoom). Tunable.</summary>
    public const double MaxVisibleSeconds = 30.0;

    /// <summary>
    /// Lower edge of the useful zoom band on the underlying fraction axis. Phase 10 retune: the bottom
    /// 60% of the old knob travel (fraction 0…0.6) was a useless slow/wide window, so the scroll knob's
    /// full [0,1] travel now maps onto the upper 0.6…1.0 band where every position reads as a useful
    /// zoom (Daniel: "range below 60% is useless; optimize for the current 60%–110% zoom values" — 110%
    /// caps at the hard 0.333 s max-zoom anchor, fraction 1.0).
    /// </summary>
    public const double ScrollKnobZoomFloor = 0.60;

    /// <summary>
    /// Maps the scroll-speed knob [0,1] onto the useful zoom band [<see cref="ScrollKnobZoomFloor"/>, 1.0]
    /// of the underlying fraction axis, then to visible seconds. So knob 0 sits at the slow edge of the
    /// *useful* range (not the dead slow end), and knob 1 reaches max zoom. Phase 10 scroll retune.
    /// </summary>
    public static double ScrollKnobToSeconds(double knob)
    {
        knob = Math.Clamp(knob, 0, 1);
        var fraction = ScrollKnobZoomFloor + (1.0 - ScrollKnobZoomFloor) * knob;
        return FractionToSeconds(fraction);
    }

    /// <summary>Slider position [0, 1] -> visible seconds. 0 = zoomed out, 1 = zoomed in.</summary>
    public static double FractionToSeconds(double fraction)
    {
        fraction = Math.Clamp(fraction, 0, 1);
        var logMax = Math.Log(MaxVisibleSeconds);
        var logMin = Math.Log(MinVisibleSeconds);
        // Interpolate in log space from out (frac 0 -> logMax) to in (frac 1 -> logMin).
        return Math.Exp(logMax + (logMin - logMax) * fraction);
    }

    /// <summary>Visible seconds -> slider position [0, 1]. Inverse of <see cref="FractionToSeconds"/>.</summary>
    public static double SecondsToFraction(double seconds)
    {
        seconds = Math.Clamp(seconds, MinVisibleSeconds, MaxVisibleSeconds);
        var logMax = Math.Log(MaxVisibleSeconds);
        var logMin = Math.Log(MinVisibleSeconds);
        return (logMax - Math.Log(seconds)) / (logMax - logMin);
    }
}
