#pragma once

#include <algorithm>
#include <cmath>

#include "application/config/values/TypedConfigValue.h"

namespace ta::application::config
{
class FloatSliderValue : public TypedConfigValue<float>
{
public:
    FloatSliderValue(const float initial, const float min, const float max,
                     const std::string& name, const float step = 0.1f)
        : TypedConfigValue(initial, name), min_(min), max_(max),
          step_(step > 0.0f ? step : 0.1f)
    {
        store_initial(clamp_and_snap(initial));
    }

    [[nodiscard]] ConfigValueKind kind() const override { return ConfigValueKind::FloatSlider; }
    void normalize() { store(clamp_and_snap(value_)); }
    [[nodiscard]] float min_value() const noexcept { return min_; }
    [[nodiscard]] float max_value() const noexcept { return max_; }
    [[nodiscard]] float step_value() const noexcept { return step_; }

protected:
    [[nodiscard]] ApplyError assign_typed(const float& value) override
    {
        if (!std::isfinite(value))
            return ApplyError::InvalidValue;
        store(clamp_and_snap(value));
        return ApplyError::None;
    }

private:
    float min_;
    float max_;
    float step_;

    [[nodiscard]] float clamp_and_snap(float value) const
    {
        value = (std::clamp)(value, min_, max_);
        value = std::round(value / step_) * step_;
        return (std::clamp)(value, min_, max_);
    }
};

class IntSliderValue : public TypedConfigValue<int>
{
public:
    IntSliderValue(const int initial, const int min, const int max, const std::string& name)
        : TypedConfigValue((std::clamp)(initial, min, max), name), min_(min), max_(max) {}

    [[nodiscard]] ConfigValueKind kind() const override { return ConfigValueKind::IntSlider; }
    void normalize() { store(clamp(value_)); }
    [[nodiscard]] int min_value() const noexcept { return min_; }
    [[nodiscard]] int max_value() const noexcept { return max_; }

protected:
    [[nodiscard]] ApplyError assign_typed(const int& value) override
    {
        store(clamp(value));
        return ApplyError::None;
    }

private:
    int min_;
    int max_;

    [[nodiscard]] int clamp(const int value) const { return (std::clamp)(value, min_, max_); }
};

class IntTextboxValue : public TypedConfigValue<int>
{
public:
    IntTextboxValue(const int initial, const std::string& name)
        : TypedConfigValue(initial, name) {}

    [[nodiscard]] ConfigValueKind kind() const override { return ConfigValueKind::IntTextbox; }
};
}
