#pragma once

// tween_curves — easing functions (Penner) for animations and interpolations.
// Header-mostly so the compiler inlines on hot paths. Pure: no I/O, no state.
//
// Reference: easings.net (Penner formulas).
// Conventions:
//   - All functions take t in [0,1] and return f(t).
//   - linear, *_quad, *_cubic, *_expo: f(0)=0, f(1)=1 exactly.
//   - elastic / bounce: f(0)=0, f(1)=1 but f overshoots/undershoots in between.
//
// Usage:
//   #include "core/tween_curves.h"
//   float y = fn::tween::out_cubic(t);
//   float y2 = fn::tween::apply(fn::tween::Ease::OutElastic, t);

#include <cmath>

namespace fn::tween {

enum class Ease {
    Linear,
    InQuad,    OutQuad,    InOutQuad,
    InCubic,   OutCubic,   InOutCubic,
    InExpo,    OutExpo,    InOutExpo,
    InElastic, OutElastic, InOutElastic,
    InBounce,  OutBounce,  InOutBounce,
};

// --- Linear -----------------------------------------------------------------

inline float linear(float t) { return t; }

// --- Quadratic (t^2) --------------------------------------------------------

inline float in_quad(float t)     { return t * t; }
inline float out_quad(float t)    { return 1.0f - (1.0f - t) * (1.0f - t); }
inline float in_out_quad(float t) {
    return (t < 0.5f) ? (2.0f * t * t)
                      : (1.0f - 0.5f * (2.0f - 2.0f * t) * (2.0f - 2.0f * t));
}

// --- Cubic (t^3) ------------------------------------------------------------

inline float in_cubic(float t)     { return t * t * t; }
inline float out_cubic(float t)    { float u = 1.0f - t; return 1.0f - u * u * u; }
inline float in_out_cubic(float t) {
    if (t < 0.5f) return 4.0f * t * t * t;
    float u = -2.0f * t + 2.0f;
    return 1.0f - 0.5f * u * u * u;
}

// --- Exponential ------------------------------------------------------------

inline float in_expo(float t) {
    return (t <= 0.0f) ? 0.0f : std::pow(2.0f, 10.0f * t - 10.0f);
}
inline float out_expo(float t) {
    return (t >= 1.0f) ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
}
inline float in_out_expo(float t) {
    if (t <= 0.0f) return 0.0f;
    if (t >= 1.0f) return 1.0f;
    return (t < 0.5f) ? 0.5f * std::pow(2.0f, 20.0f * t - 10.0f)
                      : 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
}

// --- Elastic (oscillation) --------------------------------------------------

inline float in_elastic(float t) {
    if (t <= 0.0f) return 0.0f;
    if (t >= 1.0f) return 1.0f;
    constexpr float c4 = 6.283185307179586f / 3.0f; // 2*pi/3
    return -std::pow(2.0f, 10.0f * t - 10.0f) * std::sin((t * 10.0f - 10.75f) * c4);
}
inline float out_elastic(float t) {
    if (t <= 0.0f) return 0.0f;
    if (t >= 1.0f) return 1.0f;
    constexpr float c4 = 6.283185307179586f / 3.0f;
    return std::pow(2.0f, -10.0f * t) * std::sin((t * 10.0f - 0.75f) * c4) + 1.0f;
}
inline float in_out_elastic(float t) {
    if (t <= 0.0f) return 0.0f;
    if (t >= 1.0f) return 1.0f;
    constexpr float c5 = 6.283185307179586f / 4.5f; // 2*pi/4.5
    if (t < 0.5f) {
        return -0.5f * std::pow(2.0f, 20.0f * t - 10.0f) * std::sin((20.0f * t - 11.125f) * c5);
    }
    return 0.5f * std::pow(2.0f, -20.0f * t + 10.0f) * std::sin((20.0f * t - 11.125f) * c5) + 1.0f;
}

// --- Bounce -----------------------------------------------------------------

inline float out_bounce(float t) {
    constexpr float n1 = 7.5625f;
    constexpr float d1 = 2.75f;
    if (t < 1.0f / d1) {
        return n1 * t * t;
    } else if (t < 2.0f / d1) {
        float u = t - 1.5f / d1;
        return n1 * u * u + 0.75f;
    } else if (t < 2.5f / d1) {
        float u = t - 2.25f / d1;
        return n1 * u * u + 0.9375f;
    } else {
        float u = t - 2.625f / d1;
        return n1 * u * u + 0.984375f;
    }
}
inline float in_bounce(float t) { return 1.0f - out_bounce(1.0f - t); }
inline float in_out_bounce(float t) {
    return (t < 0.5f) ? 0.5f * (1.0f - out_bounce(1.0f - 2.0f * t))
                      : 0.5f * (1.0f + out_bounce(2.0f * t - 1.0f));
}

// --- Dispatch ---------------------------------------------------------------

// Apply easing by enum. Useful when the curve is data-driven (Keyframe.ease,
// dropdown selection in UI).
float apply(Ease e, float t);

// Human-readable name for UI dropdowns.
const char* name(Ease e);

// Total number of easing modes (for iterating dropdowns).
constexpr int ease_count = 16;

} // namespace fn::tween
