#pragma once
#include <cmath>
#include <Windows.h>

#include "../game_state.h"
#include "../Module.h"
#include "../ModuleCategory.h"
#include "../eft_api.h"
#include "../../config/CheckboxValue.h"
#include "../../config/FloatSliderValue.h"
#include "../../il2cpp/Il2CppObjectInstance.h"
#include "../../il2cpp/unity.h"
#include "../../util/utils.h"
class WorldModule : Module
{
public:
    WorldModule() : Module("World", Exploit, "world_mod")
    {
    }

    ActionRowValue* unlock_doors = conf(new ActionRowValue([&]
    {
        unlock_doors_impl();
    }, "unlock_doors_btn"));
    bool queue_unlock_doors = false;

    void unlock_doors_impl()
    {
        if (!game_state::is_in_raid) return;
        queue_unlock_doors = true;
    }

    // teleport every loose loot item on the map into rings around the player for easy looting
    FloatSliderValue* loot_circle_radius = conf(new FloatSliderValue(2.5f, 1.f, 10.f, "loot_circle_radius_itm"));
    FloatSliderValue* loot_height_offset = conf(new FloatSliderValue(0.5f, -2.f, 5.f, "loot_height_offset_itm"));
    ActionRowValue* loot_to_me = conf(new ActionRowValue([&]
    {
        loot_to_me_impl();
    }, "loot_to_me_btn"));
    bool queue_loot_to_me = false;

    void loot_to_me_impl()
    {
        if (!game_state::is_in_raid) return;
        queue_loot_to_me = true;
    }

    ActionRowValue* kill_all = conf(new ActionRowValue([&]
    {
        kill_all_impl();
    }, "kill_all_btn"));
    bool queue_kill_all = false;

    void kill_all_impl()
    {
        if (!game_state::is_in_raid) return;
        queue_kill_all = true;
    }

    ActionRowValue* tp_all = conf(new ActionRowValue([&]
    {
        tp_all_impl();
    }, "tp_all_btn"));
    bool queue_tp_all = false;

    void tp_all_impl()
    {
        if (!game_state::is_in_raid) return;
        queue_tp_all = true;
    }

    CheckboxValue* tp_boss = conf(new CheckboxValue(false, "tp_boss_only_cb"));
    CheckboxValue* tp_pmc = conf(new CheckboxValue(false, "tp_pmc_only_cb"));

    void draw_overlay(ImDrawList* draw_list) override
    {
    }

    void application_update() override
    {
    }

    static bool is_local_player(Il2CppObjectInstance& player_instance)
    {
        const auto is_your_player = player_instance.get_method<bool(*)(Il2CppObject*)>("get_IsYourPlayer", 0);
        if (is_your_player)
            return is_your_player(player_instance.get_instance());
        return false;
    }

    static bool try_teleport_pos_in_front(Il2CppObjectInstance& main_player, unity::vector3& out,
                                          const float distance = 5.f)
    {
        Il2CppObject* mp_go = unity::component_get_game_object(main_player.get_instance());
        Il2CppObject* mp_tr = mp_go ? unity::gameobject_get_transform(mp_go) : nullptr;
        if (!mp_tr) return false;

        const unity::vector3 pos = unity::transform_get_pos(mp_tr);
        Il2CppObjectInstance tr_inst(mp_tr);
        const auto get_forward = tr_inst.get_method<unity::vector3(*)(Il2CppObject*)>("get_forward", 0);
        if (!get_forward) return false;

        const unity::vector3 forward = get_forward(mp_tr);
        out = {
            pos.x + forward.x * distance,
            pos.y + forward.y * distance,
            pos.z + forward.z * distance
        };
        return true;
    }

    void kill_all_players(Il2CppObjectInstance& game_world_instance)
    {
        unity::list* alive_list = game_world_instance.get_field<unity::list*>("AllAlivePlayersList");
        if (!alive_list || !alive_list->m_p_list_array) return;

        Il2CppArray* alive_players = alive_list->m_p_list_array;
        constexpr int damage_type_landmine = 2048;
        int killed = 0;

        for (size_t i = 0; i < alive_players->max_length; ++i)
        {
            Il2CppObject* player_object = reinterpret_cast<Il2CppObject**>(&alive_players->data)[i];
            if (!player_object) continue;

            Il2CppObjectInstance player_instance(player_object);
            if (is_local_player(player_instance)) continue;

            Il2CppObjectInstance health_controller(
                player_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_ActiveHealthController", 0)(
                    player_object));
            if (!health_controller.get_instance()) continue;

            health_controller.get_method<void(*)(Il2CppObject*, int)>("Kill", 1)(
                health_controller.get_instance(), damage_type_landmine);
            killed++;
        }

        logger::info("Kill All: killed " + std::to_string(killed) + " players");
    }

    void tp_all_players(Il2CppObjectInstance& game_world_instance, Il2CppObjectInstance& main_player)
    {
        unity::list* alive_list = game_world_instance.get_field<unity::list*>("AllAlivePlayersList");
        if (!alive_list || !alive_list->m_p_list_array) return;

        unity::vector3 dest;
        if (!try_teleport_pos_in_front(main_player, dest)) return;

        const bool filter_boss = tp_boss->get_value();
        const bool filter_pmc = tp_pmc->get_value();
        const auto get_profile = [](Il2CppObject* player_obj) -> Il2CppObject*
        {
            Il2CppObjectInstance player_instance(player_obj);
            const auto fn = player_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Profile", 0);
            return fn ? fn(player_obj) : nullptr;
        };

        Il2CppArray* alive_players = alive_list->m_p_list_array;
        int moved = 0;

        for (size_t i = 0; i < alive_players->max_length; ++i)
        {
            Il2CppObject* player_object = reinterpret_cast<Il2CppObject**>(&alive_players->data)[i];
            if (!player_object) continue;

            Il2CppObjectInstance player_instance(player_object);
            if (is_local_player(player_instance)) continue;

            bool should_tp = false;
            if (!filter_boss && !filter_pmc)
            {
                should_tp = true;
            }
            else
            {
                Il2CppObject* profile_ptr = get_profile(player_object);
                if (!profile_ptr) continue;

                const bool is_pmc = eft_api::try_detect_is_pmc(player_object);
                const int wild_role = eft_api::get_wild_spawn_role(profile_ptr);
                const bool is_boss = eft_api::is_boss_role(wild_role);

                if (filter_boss && is_boss && !is_pmc)
                    should_tp = true;
                if (filter_pmc && is_pmc)
                    should_tp = true;
            }

            if (!should_tp) continue;

            player_instance.get_method<void(*)(Il2CppObject*, unity::vector3, bool)>("Teleport", 2)(
                player_object, dest, true);
            moved++;
        }

        logger::info("TP All: moved " + std::to_string(moved) + " players");
    }

    void gameworld_update(const Il2CppClass* game_world_class, Il2CppObjectInstance game_world_instance,
                          Il2CppObjectInstance main_player) override
    {
        if (queue_unlock_doors)
        {
            queue_unlock_doors = false;

            Il2CppObject* door_system_type = il2utils::get_system_type(game_world_class->image, "EFT.Interactive",
                                                                       "Door");
            size_t found_doors = 0;
            Il2CppObject** doors = unity::find_objects_of_type(door_system_type, &found_doors);
            for (size_t i = 0; i < found_doors; ++i)
            {
                Il2CppObjectInstance door_instance(doors[i]);
                // 2 = Shut
                door_instance.get_method<void(*)(Il2CppObject*, int)>("set_DoorState", 1)(
                    door_instance.get_instance(), 2);
                if (globals::verbose) logger::info("Unlocked Doors");
            }
        }

        if (queue_loot_to_me)
        {
            queue_loot_to_me = false;

            // player position (ring centre)
            Il2CppObject* mp_go = unity::component_get_game_object(main_player.get_instance());
            Il2CppObject* mp_tr = mp_go ? unity::gameobject_get_transform(mp_go) : nullptr;
            if (!mp_tr) return;
            const unity::vector3 center = unity::transform_get_pos(mp_tr);

            Il2CppObject* loot_hydra_obj = game_world_instance.get_field<Il2CppObject*>("LootItems");
            if (!loot_hydra_obj) return;
            Il2CppObjectInstance loot_hydra(loot_hydra_obj);
            unity::list* loot_list = loot_hydra.get_field<unity::list*>("_iteration");
            if (!loot_list || !loot_list->m_p_list_array) return;

            Il2CppArray* loot_array = loot_list->m_p_list_array;
            const int count = static_cast<int>(loot_array->max_length);

            constexpr float pi = 3.14159265f;
            const float spacing = 0.7f;                 // gap between items
            float ring_r = loot_circle_radius->get_value();
            int in_ring = 0;
            int per_ring = (std::max)(8, static_cast<int>(2.f * pi * ring_r / spacing));
            int moved = 0;

            for (int i = 0; i < count; ++i)
            {
                Il2CppObject* loot_obj = reinterpret_cast<Il2CppObject**>(&loot_array->data)[i];
                if (!loot_obj) continue;

                Il2CppObject* game_object = unity::component_get_game_object(loot_obj);
                if (!game_object) continue;
                Il2CppObject* transform = unity::gameobject_get_transform(game_object);
                if (!transform) continue;

                if (in_ring >= per_ring)
                {
                    ring_r += spacing;
                    in_ring = 0;
                    per_ring = (std::max)(8, static_cast<int>(2.f * pi * ring_r / spacing));
                }

                const float angle = 2.f * pi * static_cast<float>(in_ring) / static_cast<float>(per_ring);
                unity::vector3 pos = {
                    center.x + cosf(angle) * ring_r,
                    center.y + loot_height_offset->get_value(), // raise so items don't clip under the floor
                    center.z + sinf(angle) * ring_r
                };

                unity::transform_set_pos(transform, pos);
                in_ring++;
                moved++;
            }

            logger::info("Loot To Me: moved " + std::to_string(moved) + " items into rings");
        }

        if (queue_kill_all)
        {
            queue_kill_all = false;
            kill_all_players(game_world_instance);
        }

        if (queue_tp_all)
        {
            queue_tp_all = false;
            tp_all_players(game_world_instance, main_player);
        }
    }

    void init() override
    {
    }
};
