#pragma once

#include <concepts>
#include <type_traits>
#include <utility>

#include "application/config/ConfigOwner.h"
#include "application/contracts/IApplicationTick.h"
#include "application/contracts/IGameTick.h"

namespace ta::bootstrap::modules
{
    enum class ModuleResetScope
    {
        Raid,
        Session
    };

    struct ModulePorts final
    {
        const char* application_tick_stage = nullptr;
        const char* game_tick_stage = nullptr;
        const char* reset_stage = nullptr;
        ModuleResetScope reset_scope = ModuleResetScope::Raid;
        ta::application::config::ConfigOwner* config_owner = nullptr;
        ta::application::IApplicationTick* application_tick = nullptr;
        ta::application::IGameTick* game_tick = nullptr;

        void initialize() const
        {
            if (initialize_callback_)
                initialize_callback_(lifecycle_context_);
        }

        void reset_session() const noexcept
        {
            if (reset_callback_)
                reset_callback_(lifecycle_context_);
        }

        [[nodiscard]] bool has_initializer() const noexcept { return initialize_callback_ != nullptr; }
        [[nodiscard]] bool has_reset() const noexcept { return reset_callback_ != nullptr; }

        template <typename Feature>
        [[nodiscard]] static ModulePorts bind(
            Feature& feature,
            ta::application::config::ConfigOwner* owner,
            const char* application_stage,
            const char* game_stage,
            const char* session_reset_stage,
            const ModuleResetScope scope = ModuleResetScope::Raid) noexcept
        {
            ModulePorts ports{};
            ports.application_tick_stage = application_stage;
            ports.game_tick_stage = game_stage;
            ports.reset_stage = session_reset_stage;
            ports.reset_scope = scope;
            ports.config_owner = owner;
            ports.lifecycle_context_ = &feature;

            if constexpr (std::derived_from<Feature, ta::application::IApplicationTick>)
                ports.application_tick = &feature;
            if constexpr (std::derived_from<Feature, ta::application::IGameTick>)
                ports.game_tick = &feature;
            if constexpr (requires(Feature& value) { value.initialize(); })
            {
                ports.initialize_callback_ = [](void* context)
                {
                    static_cast<Feature*>(context)->initialize();
                };
            }
            if constexpr (requires(Feature& value) { value.reset_session(); })
            {
                static_assert(noexcept(std::declval<Feature&>().reset_session()),
                              "Module reset_session must be noexcept");
                ports.reset_callback_ = [](void* context) noexcept
                {
                    static_cast<Feature*>(context)->reset_session();
                };
            }
            return ports;
        }

    private:
        void* lifecycle_context_ = nullptr;
        void (*initialize_callback_)(void*) = nullptr;
        void (*reset_callback_)(void*) noexcept = nullptr;
    };
}
