#pragma once

#include <span>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <vector>

#include "application/actions/UiAction.h"
#include "application/config/ConfigCategory.h"
#include "application/config/ConfigIdentity.h"
#include "application/config/ConfigValue.h"

namespace ta::application::config
{
template <typename T>
class RegisteredConfigValue;
class RegisteredUiAction;


class ConfigOwner
{
public:
    ConfigOwner(std::string owner_id, std::string display_name, ConfigCategory category)
        : name(std::move(display_name)), category(category), id_(std::move(owner_id))
    {
        if (!identity::is_valid_identifier(id_))
            throw std::invalid_argument("invalid config owner id: " + id_);
    }

    ConfigOwner(const ConfigOwner&) = delete;
    ConfigOwner& operator=(const ConfigOwner&) = delete;
    ConfigOwner(ConfigOwner&&) = delete;
    ConfigOwner& operator=(ConfigOwner&&) = delete;
    virtual ~ConfigOwner() = default;

    std::string name;
    ConfigCategory category;

    [[nodiscard]] const std::string& config_owner_id() const noexcept { return id_; }
    [[nodiscard]] std::span<ConfigValue* const> config_values() const noexcept { return config_values_; }
    [[nodiscard]] std::span<ta::application::UiAction* const> ui_actions() const noexcept { return ui_actions_; }

private:
    template <typename T>
    friend class RegisteredConfigValue;
    friend class RegisteredUiAction;

    template <typename T>
    T* register_reference(std::string value_id, T& value)
    {
        static_assert(std::is_base_of_v<ConfigValue, T>, "Config values must derive from ConfigValue");
        if (!identity::is_valid_identifier(value_id))
            throw std::invalid_argument("invalid config value id: " + value_id);
        if (!registered_ids_.insert(value_id).second)
            throw std::logic_error("duplicate config value id: " + id_ + "." + value_id);

        value.reg(id_, std::move(value_id));
        config_values_.push_back(&value);
        return &value;
    }

    ta::application::UiAction* register_action(std::string action_id, ta::application::UiAction& action)
    {
        if (!identity::is_valid_identifier(action_id))
            throw std::invalid_argument("invalid UI action id: " + action_id);
        if (!registered_ids_.insert(action_id).second)
            throw std::logic_error("duplicate config/action id: " + id_ + "." + action_id);

        action.reg(id_, action_id);
        ui_actions_.push_back(&action);
        return &action;
    }

    std::string id_;
    std::vector<ConfigValue*> config_values_;
    std::vector<ta::application::UiAction*> ui_actions_;
    std::unordered_set<std::string> registered_ids_;
};

template <typename T>
class RegisteredConfigValue final
{
    static_assert(std::is_base_of_v<ConfigValue, T>, "Registered values must derive from ConfigValue");

public:
    template <typename... Args>
    RegisteredConfigValue(ConfigOwner& owner, std::string value_id, Args&&... args)
        : value_(std::forward<Args>(args)...)
    {
        owner.register_reference(std::move(value_id), value_);
    }

    RegisteredConfigValue(const RegisteredConfigValue&) = delete;
    RegisteredConfigValue& operator=(const RegisteredConfigValue&) = delete;
    RegisteredConfigValue(RegisteredConfigValue&&) = delete;
    RegisteredConfigValue& operator=(RegisteredConfigValue&&) = delete;

    [[nodiscard]] T* operator->() noexcept { return &value_; }
    [[nodiscard]] const T* operator->() const noexcept { return &value_; }
    [[nodiscard]] T& operator*() noexcept { return value_; }
    [[nodiscard]] const T& operator*() const noexcept { return value_; }
    [[nodiscard]] operator T*() noexcept { return &value_; }
    [[nodiscard]] operator const T*() const noexcept { return &value_; }
    [[nodiscard]] T& get() noexcept { return value_; }
    [[nodiscard]] const T& get() const noexcept { return value_; }

private:
    T value_;
};

class RegisteredUiAction final
{
public:
    RegisteredUiAction(ConfigOwner& owner, std::string action_id, std::string display_name)
        : action_(std::move(display_name))
    {
        owner.register_action(std::move(action_id), action_);
    }

    RegisteredUiAction(const RegisteredUiAction&) = delete;
    RegisteredUiAction& operator=(const RegisteredUiAction&) = delete;
    RegisteredUiAction(RegisteredUiAction&&) = delete;
    RegisteredUiAction& operator=(RegisteredUiAction&&) = delete;

    [[nodiscard]] ta::application::UiAction* operator->() noexcept { return &action_; }
    [[nodiscard]] const ta::application::UiAction* operator->() const noexcept { return &action_; }
    [[nodiscard]] operator ta::application::UiAction*() noexcept { return &action_; }
    [[nodiscard]] operator const ta::application::UiAction*() const noexcept { return &action_; }

private:
    ta::application::UiAction action_;
};
}
