#pragma once

#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>

#include "application/config/ConfigIdentity.h"
#include "application/config/ConfigValueData.h"

namespace ta::application::config
{
enum class ConfigValueKind : std::uint8_t
{
    Checkbox,
    Colorpicker,
    FloatSlider,
    IntSlider,
    IntTextbox,
    Keybind,
    Select,
    Textbox
};

class ConfigValue
{
public:
    explicit ConfigValue(std::string display_name) : display_name_(std::move(display_name)) {}
    virtual ~ConfigValue() = default;

    void reg(std::string owner_id, std::string value_id)
    {
        if (!identity::is_valid_identifier(owner_id))
            throw std::invalid_argument("invalid config owner id: " + owner_id);
        if (!identity::is_valid_identifier(value_id))
            throw std::invalid_argument("invalid config value id: " + value_id);
        owner_id_ = std::move(owner_id);
        value_id_ = std::move(value_id);
        config_key_ = owner_id_ + "." + value_id_;
    }

    [[nodiscard]] virtual ConfigValueKind kind() const = 0;

    [[nodiscard]] ApplyResult apply(const ConfigValueData& value)
    {
        const std::string before = get();
        const ApplyError error = apply_value(value);
        if (error != ApplyError::None)
            return {false, error};
        return finish_apply(before);
    }

    [[nodiscard]] ApplyResult apply_serialized(const std::string_view value)
    {
        const std::string before = get();
        const ApplyError error = set_serialized(value);
        if (error != ApplyError::None)
            return {false, error};
        return finish_apply(before);
    }

    [[nodiscard]] virtual std::string get() const = 0;
    [[nodiscard]] virtual std::string get_default() const = 0;

    [[nodiscard]] const std::string& get_config_key() const noexcept { return config_key_; }
    [[nodiscard]] const std::string& value_id() const noexcept { return value_id_; }
    [[nodiscard]] const std::string& display_name() const noexcept { return display_name_; }

protected:
    [[nodiscard]] virtual ApplyError apply_value(const ConfigValueData& value) = 0;
    [[nodiscard]] virtual ApplyError set_serialized(std::string_view value) = 0;

private:
    friend class ConfigRegistry;
    void bind_revision(std::uint64_t& revision) noexcept { revision_ = &revision; }

    [[nodiscard]] ApplyResult finish_apply(const std::string& before)
    {
        const bool changed = get() != before;
        if (changed && revision_)
            ++*revision_;
        return {changed, ApplyError::None};
    }

    std::uint64_t* revision_ = nullptr;
    std::string display_name_;
    std::string owner_id_;
    std::string value_id_;
    std::string config_key_;
};
}
