// loop.h
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <mutex>
#include <thread>
#include <chrono>
#include <memory>
#include <iostream>
#include "vectors.hpp"
#include "sdk.hpp"
#include "../driver/driver.hpp"
#include "../extern/Imgui/imgui.h"
#include "../utils/settings.hpp"
#include <windows.h>
#include <algorithm>
#include <shared_mutex>
#include <atomic>

namespace WorldESPGlobals {
    inline int rarity_background_opacity = 0.20f;
    inline bool rarity_colored_background = false;
    inline bool text_background = false;
}

class FName
{
public:
    int32_t ComparisonIndex;
    uint8_t pad[0x8]{};

    std::string ToString() const
    {
        return ToString(ComparisonIndex);
    }

    static std::string ToString(int32_t comparisonIndex)
    {
        if (comparisonIndex == 0)
            return "None";

        int32_t decryptedIndex = DecryptIndex(comparisonIndex);
        if (decryptedIndex <= 0)
            return "None";

    ResolveAddress:
        uint32_t chunkIndex = decryptedIndex >> 16;
        uint16_t nameEntryIndex = static_cast<uint16_t>(decryptedIndex);

        uintptr_t gnames_addr = kernel->BaseAddress + 0x172BCD80;
        uint64_t chunkPtr = kernel->read_t<uint64_t>(gnames_addr + 8 * (chunkIndex + 0xDF2));
        if (!chunkPtr)
            return "None";

        uint64_t entryPtr = chunkPtr + 2 * nameEntryIndex;
        uint16_t header = kernel->read_t<uint16_t>(entryPtr);

        if ((header & 0x3FF) == 650)
        {
            int32_t redirectIndex = kernel->read_t<int32_t>(entryPtr + 6);
            decryptedIndex = DecryptIndex(redirectIndex);
            goto ResolveAddress;
        }

        int32_t length = (header & 0x3FF) ^ 0x28A;
        if (length <= 0 || length > 1024)
            return "None";

        std::vector<char> nameBuffer(length);
        kernel->read_array(entryPtr + 2, nameBuffer.data(), length);
        DecryptName(nameBuffer.data(), length);

        return std::string(nameBuffer.data(), length);
    }

private:
    static int32_t DecryptIndex(int32_t index)
    {
        if (index == 0)
            return 0;

        uint32_t temp = static_cast<uint32_t>(index - 1) ^ 0x7ADEE523;
        uint32_t v3 = _rotl(temp, 20) + 1;

        uint32_t v4 = 0xADD8C492; // -1379380718
        if (v3 != 0)
            v4 = v3;

        return static_cast<int32_t>(v4);
    }

    static void DecryptName(char* buffer, int32_t length)
    {
        if (length <= 0 || length > 1024)
            return;

        std::vector<uint8_t> temp(buffer, buffer + length);

        int32_t key = (8517 * length + 596447118) ^ (8 * length);

        for (int32_t i = 0; i < length; ++i)
        {
            uint8_t encrypted = static_cast<uint8_t>(temp[i]);
            uint8_t xored = static_cast<uint8_t>(key ^ encrypted);
            buffer[i] = static_cast<char>(_rotr8(xored, 1));

            key = (8517 * key + 596447118) ^ (8 * key);
        }
    }
};





#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif

using ::Vector2;
using ::Vector3;
using ::ImColor;
using ::ImVec2;
using ::ImDrawList;
using ::ImU32;
#ifndef IM_COL32
#define IM_COL32(R,G,B,A) (((ImU32)(A)<<24) | ((ImU32)(B)<<16) | ((ImU32)(G)<<8) | ((ImU32)(R)))
#endif

inline ImFont* get_esp_font() {
    return ImGui::GetFont();
}

inline ImFont* get_world_esp_font() {
    return ImGui::GetFont();
}

enum class ItemType : uint8_t {
    None = 0,
    Pickup = 1,
    Chest = 2,
    AmmoBox = 3,
    SupplyDrop = 4
};

enum class ItemRarity : uint8_t {
    Common = 0,
    Uncommon = 1,
    Rare = 2,
    Epic = 3,
    Legendary = 4,
    Mythic = 5,
    Exotic = 6
};

struct WorldItem {
    uintptr_t actor;
    Vector3 position;
    float distance;
    ItemType type;
    ItemRarity rarity;
    std::string name;

    bool is_valid() const {
        return actor != 0;
    }
};

namespace WorldItemHelpers {
    ItemType GetItemType(uintptr_t actor);
    ItemRarity GetItemRarity(uintptr_t actor);
    std::string GetItemName(uintptr_t actor);
    bool IsContainerSearched(uintptr_t actor);
    ImColor GetItemColor(ItemRarity rarity);

    ItemType GetItemType(uintptr_t actor) {
        auto search_text_ptr = kernel->read_t<uintptr_t>(actor + 0xda8);
        if (search_text_ptr) {
            auto text_data = kernel->read_t<uintptr_t>(search_text_ptr + 0x20);
            auto text_len = kernel->read_t<int>(search_text_ptr + 0x28);
            if (text_data && text_len > 0 && text_len < 100) {
                std::vector<wchar_t> buffer(text_len + 1);
                kernel->read_array(text_data, buffer.data(), text_len * sizeof(wchar_t));
                buffer[text_len] = L'\0';
                std::wstring text(buffer.data());

                if (text.find(L"Chest") != std::wstring::npos)
                    return ItemType::Chest;

                if (text.find(L"Ammo") != std::wstring::npos)
                    return ItemType::AmmoBox;
            }
        }

        auto simulating_time = kernel->read_t<float>(actor + 0x724);
        if (simulating_time == 30.0f) {
            return ItemType::Pickup;
        }

        return ItemType::None;
    }

    ItemRarity GetItemRarity(uintptr_t actor) {
        auto pickup_entry = kernel->read_t<uintptr_t>(actor + offsets::PrimaryPickupItemEntry + 0x10);
        if (!pickup_entry)
            return ItemRarity::Common;

        auto rarity = kernel->read_t<uint8_t>(pickup_entry + offsets::Rarity);
        return static_cast<ItemRarity>(rarity);
    }

    std::string GetItemName(uintptr_t actor) {
        auto pickup_entry = kernel->read_t<uintptr_t>(actor + offsets::PrimaryPickupItemEntry + 0x10);
        if (!pickup_entry)
            return "Item";

        auto item_name = kernel->read_t<uintptr_t>(pickup_entry + offsets::ItemName);
        if (!item_name)
            return "Item";

        auto f_data = kernel->read_t<uintptr_t>(item_name + 0x20);
        auto f_length = kernel->read_t<int32_t>(item_name + 0x28);

        if (!f_data || f_length <= 0 || f_length > 100)
            return "Item";

        std::vector<wchar_t> buffer(f_length + 1);
        kernel->read_array(f_data, buffer.data(), f_length * sizeof(wchar_t));
        buffer[f_length] = L'\0';

        std::wstring wstr(buffer.data());
        return std::string(wstr.begin(), wstr.end());
    }

    bool IsContainerSearched(uintptr_t actor) {
        return (kernel->read_t<uint8_t>(actor + offsets::bAlreadySearched) >> 2) & 1;
    }

    ImColor GetItemColor(ItemRarity rarity) {
        switch (rarity) {
        case ItemRarity::Common:
            return ImColor(180, 180, 180, 255);
        case ItemRarity::Uncommon:
            return ImColor(120, 220, 0, 255);
        case ItemRarity::Rare:
            return ImColor(100, 180, 255, 255);
        case ItemRarity::Epic:
            return ImColor(220, 90, 255, 255);
        case ItemRarity::Legendary:
            return ImColor(255, 215, 0, 255);
        case ItemRarity::Mythic:
            return ImColor(255, 230, 120, 255);
        default:
            return ImColor(255, 255, 255, 255);
        }
    }
}

ItemRarity yo_gurt(const char* name) {
    if (strstr(name, "Tiered_Chest") || strstr(name, "AthenaSupplyDrop_Llama")) {
        return ItemRarity::Legendary;
    }
    return ItemRarity::Uncommon;
}

ItemType yo_gurt2(const char* name) {
    if (strstr(name, "Tiered_Chest")) {
        return ItemType::Chest;
    }
    if (strstr(name, "AthenaSupplyDrop_Llama")) {
        return ItemType::SupplyDrop;
    }
    return ItemType::Pickup;
}

std::string yo_gurt3(const char* name) {
    if (strstr(name, "AthenaSupplyDrop_Llama")) {
        return "Llama";
    }
    if (strstr(name, "FoodBox_Produce")) {
        return "FoodBox";
    }
    if (strstr(name, "Tiered_Ammo")) {
        return "Ammo";
    }
    if (strstr(name, "Tiered_Chest")) {
        return "Chest";
    }
    if (strstr(name, "NPC_Pawn_Irwin")) {
        return "NPC";
    }
    return "Unknown";
}

class WorldItemManager {
private:
    std::vector<WorldItem> cache_;
    std::atomic<bool> scanning_{ false };
    std::thread scan_thread_;
    std::atomic<bool> should_stop_{ false };

public:
    WorldItemManager() = default;

    ~WorldItemManager() {
        Stop();
    }

    void Start() {
        should_stop_ = false;
        scan_thread_ = std::thread(&WorldItemManager::ScanLoop, this);
    }

    void Stop() {
        should_stop_ = true;
        if (scan_thread_.joinable()) {
            scan_thread_.join();
        }
    }

    std::vector<WorldItem> GetItems() {
        return cache_;
    }

    bool HasData() const {
        return !cache_.empty();
    }

private:
    void ScanLoop() {
        while (!should_stop_) {
            if (scanning_.exchange(true)) {
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                continue;
            }

            std::vector<WorldItem> newItems;

            auto uworld = kernel->read_t<uintptr_t>(kernel->BaseAddress + offsets::UWorld);
            if (!uworld) {
                scanning_ = false;
                std::this_thread::sleep_for(std::chrono::milliseconds(1000));
                continue;
            }

            auto levels_array = kernel->read_t<uintptr_t>(uworld + offsets::Levels);
            auto levels_count = kernel->read_t<int>(uworld + (offsets::Levels + sizeof(PVOID)));

            if (!levels_array || levels_count <= 0 || levels_count > 100) {
                scanning_ = false;
                std::this_thread::sleep_for(std::chrono::milliseconds(1000));
                continue;
            }

            float max_scan_distance = settings::world.items_distance;
            if (settings::world.lootables_distance > max_scan_distance) {
                max_scan_distance = settings::world.lootables_distance;
            }
            if (settings::world.render_distance > max_scan_distance) {
                max_scan_distance = settings::world.render_distance;
            }
            max_scan_distance += 50.0f;

            newItems.reserve(500);

            for (int i = 0; i < levels_count; i++) {
                auto level = kernel->read_t<uintptr_t>(levels_array + (i * sizeof(uintptr_t)));
                if (!level) continue;

                uintptr_t actors_array = kernel->read_t<uintptr_t>(level + offsets::AActor);
                DWORD actor_count = kernel->read_t<DWORD>(level + (offsets::AActor + 0x8));

                if (actor_count == 0 || actor_count > 5000) continue;

                DWORD maxActors = (actor_count > 1000) ? 1000 : actor_count;

                for (DWORD j = 0; j < maxActors; ++j)
                {
                    uintptr_t CurrentItemPawn = kernel->read_t<uintptr_t>(actors_array + (j * sizeof(uintptr_t)));
                    if (!is_valid(CurrentItemPawn)) continue;

                    int32_t nameIndex = kernel->read_t<int32_t>(CurrentItemPawn + 0x18);
                    if (nameIndex <= 0) continue;

                    std::string CurrentItemPawnName = FName::ToString(nameIndex);
                    if (CurrentItemPawnName.empty()) continue;

                    if (!(strstr(CurrentItemPawnName.c_str(), "FortPickup") ||
                        strstr(CurrentItemPawnName.c_str(), "Tiered_Chest") ||
                        strstr(CurrentItemPawnName.c_str(), "AthenaSupplyDrop_Llama") ||
                        strstr(CurrentItemPawnName.c_str(), "FoodBox_Produce") ||
                        strstr(CurrentItemPawnName.c_str(), "Tiered_Ammo") ||
                        strstr(CurrentItemPawnName.c_str(), "NPC_Pawn_Irwin"))) {
                        continue;
                    }

                    uintptr_t rootComp = kernel->read_t<uintptr_t>(CurrentItemPawn + offsets::RootComponent);
                    if (!is_valid(rootComp)) continue;

                    Vector3 ItemPosition = kernel->read_t<Vector3>(rootComp + offsets::RelativeLocation);
                    float ItemDist = vCamera.Location.Distance(ItemPosition) / 100.0f;

                    if (ItemDist > max_scan_distance) continue;

                    WorldItem Item{};
                    Item.actor = CurrentItemPawn;
                    Item.position = ItemPosition;
                    Item.distance = ItemDist;

                    if (strstr(CurrentItemPawnName.c_str(), "FortPickup")) {
                        Item.name = WorldItemHelpers::GetItemName(CurrentItemPawn);
                        Item.rarity = WorldItemHelpers::GetItemRarity(CurrentItemPawn);
                        Item.type = ItemType::Pickup;
                    }
                    else {
                        Item.rarity = yo_gurt(CurrentItemPawnName.c_str());
                        Item.type = yo_gurt2(CurrentItemPawnName.c_str());
                        Item.name = yo_gurt3(CurrentItemPawnName.c_str());
                    }

                    newItems.push_back(Item);

                    if (newItems.size() >= 500) {
                        i = levels_count;
                        break;
                    }
                }
            }

            cache_ = std::move(newItems);

            scanning_ = false;

            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
};

inline WorldItemManager g_WorldItems;

void DrawWorldItemText(
    ImDrawList* draw_list,
    const char* text,
    ImVec2 position,
    ImColor color,
    bool draw_outline = true,
    bool draw_background = false,
    ImColor background_color = ImColor(0, 0, 0, 150)
) {
    ImFont* esp_font = get_world_esp_font();
    float font_size = 14.0f;

    if (draw_background) {
        ImVec2 text_size = esp_font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text);
        ImVec2 bg_min(position.x - text_size.x / 2 - 5, position.y - 2);
        ImVec2 bg_max(position.x + text_size.x / 2 + 5, position.y + text_size.y + 2);
        draw_list->AddRectFilled(bg_min, bg_max, background_color, 3.0f);
    }

    if (draw_outline) {
        ImU32 outline_color = IM_COL32(0, 0, 0, 255);
        float offset = 1.0f;

        float text_width = esp_font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text).x;
        float center_offset = text_width / 2;

        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset - offset, position.y - offset), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset + offset, position.y - offset), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset - offset, position.y + offset), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset + offset, position.y + offset), outline_color, text);

        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset - offset, position.y), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset + offset, position.y), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset, position.y - offset), outline_color, text);
        draw_list->AddText(esp_font, font_size, ImVec2(position.x - center_offset, position.y + offset), outline_color, text);
    }

    float text_center_offset = esp_font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text).x / 2;
    draw_list->AddText(esp_font, font_size, ImVec2(position.x - text_center_offset, position.y), color, text);
}

void RenderWorldESP(ImDrawList* draw_list) {
    if (!settings::world.WorldEsp)
        return;

    auto items = g_WorldItems.GetItems();
    if (items.empty())
        return;

    int rendered_count = 0;
    const int max_render = 100;

    for (const auto& item : items) {
        if (rendered_count >= max_render)
            break;

        if (!item.is_valid())
            continue;

        float max_distance = settings::world.render_distance;
        if (item.type == ItemType::Pickup) {
            max_distance = settings::world.items_distance;
        }
        else if (item.type == ItemType::Chest || item.type == ItemType::AmmoBox || item.type == ItemType::SupplyDrop) {
            max_distance = settings::world.lootables_distance;
        }

        if (item.distance > max_distance)
            continue;

        Vector2 screen_pos = ProjectWorldToScreen(item.position);

        if (!is_in_screen(screen_pos))
            continue;

        bool should_render = false;
        ImColor text_color;

        if (item.type == ItemType::Pickup) {
            switch (item.rarity) {
            case ItemRarity::Common:
                should_render = settings::world.common_loot;
                break;
            case ItemRarity::Uncommon:
                should_render = settings::world.uncommon_loot;
                break;
            case ItemRarity::Rare:
                should_render = settings::world.rare_loot;
                break;
            case ItemRarity::Epic:
                should_render = settings::world.epic_loot;
                break;
            case ItemRarity::Legendary:
                should_render = settings::world.legendary_loot;
                break;
            case ItemRarity::Mythic:
                should_render = settings::world.mythic_loot;
                break;
            case ItemRarity::Exotic:
                should_render = settings::world.exotic_loot;
                break;
            }

            text_color = WorldItemHelpers::GetItemColor(item.rarity);
        }
        else if (item.type == ItemType::Chest) {
            should_render = settings::world.chests;
            text_color = ImColor(255, 200, 0);
        }
        else if (item.type == ItemType::AmmoBox) {
            should_render = settings::world.ammo;
            text_color = ImColor(180, 180, 180);
        }
        else if (item.type == ItemType::SupplyDrop) {
            should_render = settings::world.SupplyDrop;
            text_color = ImColor(255, 0, 255);
        }

        if (!should_render)
            continue;

        std::string display_name = item.name;
        if (settings::world.show_distance) {
            char dist_str[32];
            sprintf_s(dist_str, " [%.0fm]", item.distance);
            display_name += dist_str;
        }

        if (settings::world.snaplines) {
            ImVec2 snapline_start;
            float screen_center_x = Width / 2.0f;

            switch (settings::world.snapline_position) {
            case 0:
                snapline_start = ImVec2(screen_center_x, 0);
                break;
            case 1:
                snapline_start = ImVec2(screen_center_x, Height / 2.0f);
                break;
            case 2:
                snapline_start = ImVec2(screen_center_x, static_cast<float>(Height));
                break;
            default:
                snapline_start = ImVec2(screen_center_x, Height / 2.0f);
                break;
            }

            draw_list->AddLine(snapline_start, ImVec2(screen_pos.x, screen_pos.y), text_color, 1.0f);
        }

        ImColor bg_color = ImColor(0, 0, 0, 150);
        if (WorldESPGlobals::rarity_colored_background && settings::world.text_background) {
            int opacity = static_cast<int>(WorldESPGlobals::rarity_background_opacity * 255.0f);
            bg_color = ImColor(
                static_cast<int>(text_color.Value.x * 255),
                static_cast<int>(text_color.Value.y * 255),
                static_cast<int>(text_color.Value.z * 255),
                opacity
            );
        }

        DrawWorldItemText(
            draw_list,
            display_name.c_str(),
            ImVec2(screen_pos.x, screen_pos.y - 15),
            text_color,
            settings::world.outline,
            settings::world.text_background,
            bg_color
        );

        rendered_count++;
    }
}

void WorldLoop(ImDrawList* draw_list) {
    static bool initialized = false;
    if (!initialized) {
        g_WorldItems.Start();
        initialized = true;
    }

    RenderWorldESP(draw_list);
}

void CacheLevels() {

    while (true) {
        std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    }
}