#pragma once

#include <algorithm>
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>

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

namespace ta::application::config
{
class SelectValue : public TypedConfigValue<int>
{
public:
    SelectValue(const int initial, std::vector<std::string> options, const std::string& name)
        : TypedConfigValue(initial, name), options_(std::move(options))
    {
        normalize_value();
    }

    SelectValue(const int initial, std::initializer_list<const char*> options, const std::string& name)
        : TypedConfigValue(initial, name)
    {
        options_.reserve(options.size());
        for (const char* option : options)
            options_.emplace_back(option ? option : "");
        normalize_value();
    }

    SelectValue(const int initial, const std::vector<const char*>& options, const std::string& name)
        : TypedConfigValue(initial, name)
    {
        options_.reserve(options.size());
        for (const char* option : options)
            options_.emplace_back(option ? option : "");
        normalize_value();
    }

    [[nodiscard]] ConfigValueKind kind() const override { return ConfigValueKind::Select; }
    [[nodiscard]] std::string get_selected() const
    {
        return options_.empty() ? std::string{} : options_[static_cast<std::size_t>(value_)];
    }
    [[nodiscard]] const std::vector<std::string>& options_ref() const noexcept { return options_; }

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

private:
    std::vector<std::string> options_;

    [[nodiscard]] bool in_range(const int value) const noexcept
    {
        return options_.empty() ? value == 0 : value >= 0 && value < static_cast<int>(options_.size());
    }

    void normalize_value()
    {
        const int normalized = options_.empty()
            ? 0
            : (std::clamp)(value_, 0, static_cast<int>(options_.size()) - 1);
        store_initial(normalized);
    }
};

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

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