// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#pragma once

#include "imgui.h"
#include <cmath>
#include <functional>

namespace dragonx {
namespace ui {
namespace material {

// ============================================================================
// Material Design Motion System
// ============================================================================
// Based on https://m2.material.io/design/motion/speed.html
// and https://m2.material.io/design/motion/customization.html
//
// Material motion uses specific easing curves and durations to create
// natural, responsive animations that feel connected to user input.

// ============================================================================
// Standard Durations (in seconds)
// ============================================================================

namespace duration {
    // Simple transitions (toggle, fade)
    constexpr float Instant    = 0.0f;
    constexpr float VeryFast   = 0.05f;   // 50ms
    constexpr float Fast       = 0.1f;    // 100ms - simple toggles
    constexpr float Short      = 0.15f;   // 150ms
    
    // Standard transitions
    constexpr float Medium     = 0.2f;    // 200ms - collapse, simple move
    constexpr float Standard   = 0.25f;   // 250ms - expand, standard
    constexpr float Long       = 0.3f;    // 300ms - large transforms
    
    // Complex transitions
    constexpr float Complex    = 0.375f;  // 375ms
    constexpr float VeryLong   = 0.5f;    // 500ms - elaborate sequences
    
    // Screen transitions
    constexpr float EnterScreen  = 0.225f;  // Entering screen
    constexpr float ExitScreen   = 0.195f;  // Leaving screen
    constexpr float ScreenChange = 0.3f;    // Full screen transition
}

// ============================================================================
// Easing Curves
// ============================================================================

/**
 * @brief Cubic bezier curve evaluation
 * 
 * Evaluates a cubic bezier curve defined by control points (0,0), (x1,y1), (x2,y2), (1,1)
 * 
 * @param t Progress 0.0-1.0
 * @param x1 First control point X
 * @param y1 First control point Y
 * @param x2 Second control point X
 * @param y2 Second control point Y
 * @return Eased value
 */
float CubicBezier(float t, float x1, float y1, float x2, float y2);

/**
 * @brief Standard easing - for objects moving between on-screen positions
 * 
 * CSS: cubic-bezier(0.4, 0.0, 0.2, 1.0)
 * Starts quickly, slows down to rest
 */
float EaseStandard(float t);

/**
 * @brief Deceleration easing - for objects entering the screen
 * 
 * CSS: cubic-bezier(0.0, 0.0, 0.2, 1.0)
 * Starts at full velocity, decelerates to rest
 */
float EaseDecelerate(float t);

/**
 * @brief Acceleration easing - for objects leaving the screen
 * 
 * CSS: cubic-bezier(0.4, 0.0, 1.0, 1.0)  
 * Accelerates from rest, exits at full speed
 */
float EaseAccelerate(float t);

/**
 * @brief Sharp easing - for objects that may return to screen
 * 
 * CSS: cubic-bezier(0.4, 0.0, 0.6, 1.0)
 * Quicker than standard, maintains connection
 */
float EaseSharp(float t);

/**
 * @brief Linear interpolation (no easing)
 */
float EaseLinear(float t);

/**
 * @brief Overshoot easing - goes past target then settles
 * 
 * Good for bouncy, playful animations
 */
float EaseOvershoot(float t, float overshoot = 1.70158f);

/**
 * @brief Elastic easing - springy oscillation
 */
float EaseElastic(float t);

// ============================================================================
// Easing Function Type
// ============================================================================

using EasingFunction = float(*)(float);

// ============================================================================
// Animated Value
// ============================================================================

/**
 * @brief Animated value with automatic interpolation
 * 
 * Template class for smooth value transitions.
 */
template<typename T>
class AnimatedValue {
public:
    AnimatedValue(const T& initialValue = T())
        : m_current(initialValue)
        , m_target(initialValue)
        , m_start(initialValue)
        , m_duration(duration::Standard)
        , m_elapsed(0)
        , m_easingFunc(EaseStandard)
        , m_animating(false)
    {}
    
    /**
     * @brief Set target value with animation
     */
    void animateTo(const T& target, float dur = duration::Standard, 
                   EasingFunction easing = EaseStandard) {
        if (target == m_target && m_animating)
            return;  // Already animating to this target
            
        m_start = m_current;
        m_target = target;
        m_duration = dur;
        m_elapsed = 0;
        m_easingFunc = easing;
        m_animating = true;
    }
    
    /**
     * @brief Set value immediately (no animation)
     */
    void set(const T& value) {
        m_current = value;
        m_target = value;
        m_start = value;
        m_animating = false;
    }
    
    /**
     * @brief Update animation (call each frame)
     * @param deltaTime Frame delta time in seconds
     */
    void update(float deltaTime) {
        if (!m_animating)
            return;
        
        m_elapsed += deltaTime;
        
        if (m_elapsed >= m_duration) {
            m_current = m_target;
            m_animating = false;
        } else {
            float t = m_elapsed / m_duration;
            float eased = m_easingFunc(t);
            m_current = lerp(m_start, m_target, eased);
        }
    }
    
    /**
     * @brief Get current value
     */
    const T& get() const { return m_current; }
    
    /**
     * @brief Get target value
     */
    const T& getTarget() const { return m_target; }
    
    /**
     * @brief Check if currently animating
     */
    bool isAnimating() const { return m_animating; }
    
    /**
     * @brief Get animation progress (0-1)
     */
    float getProgress() const {
        if (!m_animating) return 1.0f;
        return m_elapsed / m_duration;
    }
    
    /**
     * @brief Implicit conversion to value type
     */
    operator const T&() const { return m_current; }
    
private:
    T m_current;
    T m_target;
    T m_start;
    float m_duration;
    float m_elapsed;
    EasingFunction m_easingFunc;
    bool m_animating;
    
    // Lerp specializations
    static T lerp(const T& a, const T& b, float t) {
        return a + (b - a) * t;
    }
};

// Specialization for ImVec2
template<>
inline ImVec2 AnimatedValue<ImVec2>::lerp(const ImVec2& a, const ImVec2& b, float t) {
    return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
}

// Specialization for ImVec4/color
template<>
inline ImVec4 AnimatedValue<ImVec4>::lerp(const ImVec4& a, const ImVec4& b, float t) {
    return ImVec4(
        a.x + (b.x - a.x) * t,
        a.y + (b.y - a.y) * t,
        a.z + (b.z - a.z) * t,
        a.w + (b.w - a.w) * t
    );
}

// ============================================================================
// Animation Sequencer
// ============================================================================

/**
 * @brief Staggered animation for lists
 * 
 * Creates staggered entrance animations for list items.
 */
class StaggerAnimation {
public:
    StaggerAnimation(int itemCount, float staggerDelay = 0.05f, 
                     float itemDuration = duration::EnterScreen)
        : m_itemCount(itemCount)
        , m_staggerDelay(staggerDelay)
        , m_itemDuration(itemDuration)
        , m_elapsed(0)
        , m_running(false)
    {}
    
    /**
     * @brief Start the stagger animation
     */
    void start() {
        m_elapsed = 0;
        m_running = true;
    }
    
    /**
     * @brief Update animation
     */
    void update(float deltaTime) {
        if (!m_running) return;
        m_elapsed += deltaTime;
        
        // Check if all items have finished
        float totalDuration = m_staggerDelay * (m_itemCount - 1) + m_itemDuration;
        if (m_elapsed >= totalDuration) {
            m_running = false;
        }
    }
    
    /**
     * @brief Get animation progress for a specific item
     * 
     * @param itemIndex Item index (0-based)
     * @return Progress 0.0-1.0 (clamped)
     */
    float getItemProgress(int itemIndex) const {
        if (!m_running && m_elapsed > 0)
            return 1.0f;  // Animation complete
        if (itemIndex < 0 || itemIndex >= m_itemCount)
            return 0.0f;
        
        float itemStart = m_staggerDelay * itemIndex;
        float itemElapsed = m_elapsed - itemStart;
        
        if (itemElapsed <= 0) return 0.0f;
        if (itemElapsed >= m_itemDuration) return 1.0f;
        
        return EaseDecelerate(itemElapsed / m_itemDuration);
    }
    
    /**
     * @brief Get eased alpha for item (for fade-in)
     */
    float getItemAlpha(int itemIndex) const {
        return getItemProgress(itemIndex);
    }
    
    /**
     * @brief Get Y offset for item (for slide-in from bottom)
     */
    float getItemYOffset(int itemIndex, float maxOffset = 20.0f) const {
        float progress = getItemProgress(itemIndex);
        return maxOffset * (1.0f - progress);
    }
    
    bool isRunning() const { return m_running; }
    
private:
    int m_itemCount;
    float m_staggerDelay;
    float m_itemDuration;
    float m_elapsed;
    bool m_running;
};

// ============================================================================
// Container Transform
// ============================================================================

/**
 * @brief Container transform animation state
 * 
 * For hero-style transitions where a card expands into a full dialog/page.
 */
struct ContainerTransform {
    ImRect startRect;     // Starting bounds (e.g., card)
    ImRect endRect;       // Ending bounds (e.g., dialog)
    float progress;       // 0 = start, 1 = end
    bool expanding;       // Direction
    
    ContainerTransform()
        : progress(0)
        , expanding(true)
    {}
    
    /**
     * @brief Get interpolated bounds at current progress
     */
    ImRect getCurrentRect() const {
        float t = expanding ? progress : (1.0f - progress);
        float eased = EaseStandard(t);
        
        return ImRect(
            ImLerp(startRect.Min, endRect.Min, eased),
            ImLerp(startRect.Max, endRect.Max, eased)
        );
    }
    
    /**
     * @brief Get corner radius (shrinks as container expands)
     */
    float getCornerRadius(float startRadius, float endRadius) const {
        float t = expanding ? progress : (1.0f - progress);
        float eased = EaseStandard(t);
        return startRadius + (endRadius - startRadius) * eased;
    }
};

// ============================================================================
// Implementation
// ============================================================================

inline float CubicBezier(float t, float x1, float y1, float x2, float y2) {
    // Attempt to find t value for given x (Newton-Raphson approximation)
    // This is needed because CSS bezier curves are defined in terms of x
    
    // For simplicity, we'll use a direct parametric approach
    // which is accurate enough for UI animations
    
    float cx = 3.0f * x1;
    float bx = 3.0f * (x2 - x1) - cx;
    float ax = 1.0f - cx - bx;
    
    float cy = 3.0f * y1;
    float by = 3.0f * (y2 - y1) - cy;
    float ay = 1.0f - cy - by;
    
    // Sample y at parameter t
    // Note: This assumes t directly maps to time, which is an approximation
    // For more accuracy, we'd need to solve for the bezier parameter given x=t
    
    float t2 = t * t;
    float t3 = t2 * t;
    
    return ay * t3 + by * t2 + cy * t;
}

inline float EaseStandard(float t) {
    // cubic-bezier(0.4, 0.0, 0.2, 1.0)
    return CubicBezier(t, 0.4f, 0.0f, 0.2f, 1.0f);
}

inline float EaseDecelerate(float t) {
    // cubic-bezier(0.0, 0.0, 0.2, 1.0)
    return CubicBezier(t, 0.0f, 0.0f, 0.2f, 1.0f);
}

inline float EaseAccelerate(float t) {
    // cubic-bezier(0.4, 0.0, 1.0, 1.0)
    return CubicBezier(t, 0.4f, 0.0f, 1.0f, 1.0f);
}

inline float EaseSharp(float t) {
    // cubic-bezier(0.4, 0.0, 0.6, 1.0)
    return CubicBezier(t, 0.4f, 0.0f, 0.6f, 1.0f);
}

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

inline float EaseOvershoot(float t, float overshoot) {
    // Back ease out
    t = t - 1.0f;
    return t * t * ((overshoot + 1.0f) * t + overshoot) + 1.0f;
}

inline float EaseElastic(float t) {
    if (t == 0.0f || t == 1.0f) return t;
    
    float p = 0.3f;
    float s = p / 4.0f;
    
    return std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * IM_PI) / p) + 1.0f;
}

} // namespace material
} // namespace ui
} // namespace dragonx
