#pragma once

#include <array>
#include <string>

#include "foundation/text/StringUtils.h"

#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace ta::adapters::eft::quests::bindings
{
    class QuestAccess
    {
    public:
        static Il2CppString* quest_id(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest_template)
        {
            Il2CppString* value = nullptr;
            if (!invoke_exact(quest_template, "get_Id", value) || !value)
            {
                report_error("quest-template-id-null", "Quest template get_Id returned null");
                return nullptr;
            }
            return value;
        }

        static std::string quest_id_string(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest_template)
        {
            Il2CppString* value = quest_id(quest_template);
            if (!value)
                return {};
            const std::string result = ta::adapters::il2cpp::conv_string(value);
            if (result.empty())
                report_error("quest-template-id-empty", "Quest template get_Id returned an empty string");
            return result;
        }

        static Il2CppObject* quest_template(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest)
        {
            Il2CppObject* value = nullptr;
            if (!invoke_exact(quest, "get_Template", value))
                return nullptr;
            if (!value)
                report_error("quest-template-null", "Quest get_Template returned null");
            return value;
        }

        static bool status(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest,
            int& value)
        {
            return invoke_exact(quest, "get_QuestStatus", value);
        }

        static std::string trader_id(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest_template)
        {
            Il2CppString* value = nullptr;
            if (!invoke_exact(quest_template, "get_TraderId", value) || !value)
            {
                report_error("quest-trader-id-null", "Quest template get_TraderId returned null");
                return {};
            }
            return ta::adapters::il2cpp::conv_string(value);
        }

        static bool is_main_quest(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest_template,
            bool& value)
        {
            return invoke_exact(quest_template, "get_IsMainQuest", value);
        }

        static bool is_operational_quest(
            ta::adapters::il2cpp::Il2CppObjectInstance& quest,
            ta::adapters::il2cpp::Il2CppObjectInstance& quest_template)
        {
            constexpr std::array<const char*, 3> method_names = {
                "get_IsDaily", "get_IsRepeatable", "get_IsOperational"
            };
            constexpr std::array<const char*, 6> field_names = {
                "IsDaily", "_isDaily", "<IsDaily>k__BackingField",
                "IsRepeatable", "_isRepeatable", "<IsRepeatable>k__BackingField"
            };

            const std::array<Il2CppObject*, 2> objects = {
                quest.get_instance(), quest_template.get_instance()
            };
            for (Il2CppObject* object : objects)
            {
                if (!object || !object->klass)
                    continue;

                const std::string class_chain = ta::foundation::text::lower_copy(
                    ta::adapters::il2cpp::ManagedRuntime::class_chain_name(object->klass));
                if (class_chain.find("daily") != std::string::npos ||
                    class_chain.find("repeatable") != std::string::npos ||
                    class_chain.find("operational") != std::string::npos)
                {
                    return true;
                }

                for (const char* method_name : method_names)
                {
                    const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<bool>(
                        object, method_name, ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
                    if (result && result.value())
                        return true;
                }

                for (const char* field_name : field_names)
                {
                    const auto result = ta::adapters::il2cpp::ManagedRuntime::read_field<bool>(
                        object, field_name, ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
                    if (result && result.value())
                        return true;
                }
            }
            return false;
        }

    private:
        static void report_error(const char* key, const char* message)
        {
            ta::foundation::diagnostics::OnceLogger::error("QUEST", key, message);
        }

        template <typename TResult>
        static bool invoke_exact(
            ta::adapters::il2cpp::Il2CppObjectInstance& instance,
            const char* method_name,
            TResult& value)
        {
            const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<TResult>(
                instance.get_instance(),
                method_name,
                ta::adapters::il2cpp::LookupPolicy::ExactClass);
            if (!result)
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "QUEST",
                    std::string("quest-method-") + method_name,
                    std::string("Quest method unavailable: ") + method_name);
                return false;
            }
            value = result.value();
            return true;
        }
    };
}
