#pragma once

#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

#include "application/config/ConfigValue.h"
#include "application/config/persistence/ConfigParse.h"

namespace ta::application::config
{
    template <typename T>
    struct ConfigValueCodec;

    template <>
    struct ConfigValueCodec<bool> final
    {
        [[nodiscard]] static std::string encode(const bool value)
        {
            return value ? "true" : "false";
        }

        [[nodiscard]] static std::optional<bool> decode(const std::string_view text) noexcept
        {
            return parse::boolean(text);
        }
    };

    template <>
    struct ConfigValueCodec<int> final
    {
        [[nodiscard]] static std::string encode(const int value)
        {
            return std::to_string(value);
        }

        [[nodiscard]] static std::optional<int> decode(const std::string_view text) noexcept
        {
            return parse::integer(text);
        }
    };

    template <>
    struct ConfigValueCodec<float> final
    {
        [[nodiscard]] static std::string encode(const float value)
        {
            return std::to_string(value);
        }

        [[nodiscard]] static std::optional<float> decode(const std::string_view text) noexcept
        {
            return parse::floating(text);
        }
    };

    template <>
    struct ConfigValueCodec<std::string> final
    {
        [[nodiscard]] static std::string encode(const std::string& value)
        {
            return value;
        }

        [[nodiscard]] static std::optional<std::string> decode(const std::string_view text)
        {
            return std::string(text);
        }
    };

    template <typename T>
    class TypedConfigValue : public ConfigValue
    {
    public:
        [[nodiscard]] std::string get() const final
        {
            return ConfigValueCodec<T>::encode(value_);
        }

        [[nodiscard]] std::string get_default() const final
        {
            return ConfigValueCodec<T>::encode(default_);
        }

        [[nodiscard]] T get_value() const noexcept(std::is_nothrow_copy_constructible_v<T>)
        {
            return value_;
        }

    protected:
        TypedConfigValue(T initial, const std::string& name)
            : ConfigValue(name), value_(std::move(initial)), default_(value_)
        {
        }

        virtual ApplyError assign_typed(const T& value)
        {
            value_ = value;
            return ApplyError::None;
        }

        void store(const T& value)
        {
            value_ = value;
        }

        void store_initial(const T& value)
        {
            value_ = value;
            default_ = value;
        }

        T value_;
        T default_;

    private:
        [[nodiscard]] ApplyError apply_value(const ConfigValueData& value) final
        {
            const T* typed = std::get_if<T>(&value);
            return typed ? assign_typed(*typed) : ApplyError::InvalidType;
        }

        [[nodiscard]] ApplyError set_serialized(const std::string_view text) final
        {
            const auto parsed = ConfigValueCodec<T>::decode(text);
            return parsed ? assign_typed(*parsed) : ApplyError::DecodeFailed;
        }
    };
}
