#include "adapters/hooks/game/LocalRaidHooks.h"
#include "adapters/hooks/Il2CppMethodHook.h"
#include "application/lifecycle/CallbackBarrier.h"

#include <Windows.h>
#include <string>
#include <utility>

#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "application/lifecycle/RuntimeState.h"
#include "foundation/text/StringUtils.h"

namespace ta::adapters::hooks::local_raid
{
    namespace
    {
        Il2CppImage* assembly_csharp = nullptr;

        std::string get_string_field(Il2CppObject* object, const char* name)
        {
            if (!object || !object->klass || !name)
                return {};

            FieldInfo* field = ta::adapters::il2cpp::il2cpp_class_get_field_from_name(object->klass, name);
            if (!field)
                return {};

            Il2CppString* value = *reinterpret_cast<Il2CppString**>(
                reinterpret_cast<UINT64>(object) + field->offset);
            return ta::adapters::il2cpp::conv_string(value);
        }

        bool matches_any(std::string value, const std::initializer_list<const char*> names)
        {
            value = ta::foundation::text::lower_copy(std::move(value));
            for (const char* name : names)
            {
                if (value == ta::foundation::text::lower_copy(name))
                    return true;
            }
            return false;
        }

        std::string location_id(Il2CppObject* location)
        {
            return get_string_field(location, "Id");
        }

        using display_location_sig = bool(*)(Il2CppObject*, Il2CppObject*);
        display_location_sig o_display_location = nullptr;

        bool hk_display_location(Il2CppObject* instance, Il2CppObject* location)
        {
            ta::application::runtime::CallbackGuard callback_guard;
            if (!o_display_location)
                return false;

            const bool res = o_display_location(instance, location);
            if (!ta::application::runtime::should_accept_callbacks() || !location)
                return res;

            ta::adapters::il2cpp::Il2CppObjectInstance location_instance(location);
            location_instance.set_field("ForceOnlineRaidInPVE", false);
            location_instance.set_field("ForceOfflineRaidInPVE", true);

            const std::string location_name = location_id(location);

            // No Terminal.
            if (matches_any(location_name, {"terminal"}))
                return false;

            // Force these locations to be displayed/selectable locally.
            if (matches_any(location_name, {"labyrinth", "icebreaker"}))
                return true;

            return res;
        }

        using transit_sig = Il2CppObject* (*)(Il2CppObject*, Il2CppObject*, int, Il2CppString*, Il2CppObject*,
                                             Il2CppObject*);
        transit_sig o_transit = nullptr;

        Il2CppObject* hk_transit(Il2CppObject* instance,
                                 Il2CppObject* point,
                                 int player_count,
                                 Il2CppString* hash,
                                 Il2CppObject* keys,
                                 Il2CppObject* player)
        {
            ta::application::runtime::CallbackGuard callback_guard;
            if (!o_transit)
                return nullptr;

            if (!ta::application::runtime::should_accept_callbacks())
                return o_transit(instance, point, player_count, hash, keys, player);

            // Force local/single-player transit and always blank the route hash.
            return o_transit(instance, point, 1, ta::adapters::il2cpp::il2cpp_string_new(""), keys, player);
        }
    }

    bool init()
    {
        if (o_display_location && o_transit) return true;

        // set assembly csharp image
        while (!ta::adapters::il2cpp::resolve_image("Assembly-CSharp.dll"))
        {
            if (ta::application::runtime::state().is_terminal())
                return false;

            Sleep(50);
        }
        assembly_csharp = ta::adapters::il2cpp::resolve_image("Assembly-CSharp.dll");
        if (!assembly_csharp) return false;

        bool ok = true;

        // always offline raid and location unlock
        const Il2CppClass* match_maker_selection_location_screen = ta::adapters::il2cpp::resolve_class(
            assembly_csharp, "EFT.UI.Matchmaker", "MatchMakerSelectionLocationScreen");
        ok = ta::adapters::il2cpp::hook_method(match_maker_selection_location_screen, "DisplayLocation", 1,
            hk_display_location,
            &o_display_location) && ok;

        // offline transits
        const Il2CppClass* transit_controller = ta::adapters::il2cpp::resolve_class(
            assembly_csharp, "EFT", "LocalTransitController");
        ok = ta::adapters::il2cpp::hook_method(transit_controller, "Transit", 5,
            hk_transit,
            &o_transit) && ok;

        return ok;
    }

    void reset()
    {
        assembly_csharp = nullptr;
        o_display_location = nullptr;
        o_transit = nullptr;
    }
}
