#include "adapters/eft/world/ItemTeleportService.h"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <string>
#include <unordered_map>

#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/eft/world/LootQuery.h"
#include "adapters/eft/items/catalog/ItemTemplateCatalogService.h"
#include "foundation/math/SpatialMath.h"
#include "adapters/eft/world/WorldAccess.h"

namespace ta::adapters::eft::world
{
namespace
{
    enum class ItemTeleportKind
    {
        Normal,
        Wishlist,
        Quest,
        Corpse
    };

    constexpr float pi = 3.1415926535f;
    constexpr float spacing = 0.7f;
    constexpr int max_safe_loot = 4096;

    struct PlacementState
    {
        ta::adapters::unity::vector3 center{};
        float radius = 2.5f;
        float height_offset = 0.5f;
        int index_in_ring = 0;
        int per_ring = 8;
        std::unordered_map<std::string, WorldItemGroup> groups;
    };

    bool should_include_kind(const ItemTeleportRequest& request, const ItemTeleportKind kind)
    {
        switch (kind)
        {
        case ItemTeleportKind::Normal:
            return request.include_normal_items && request.normal_category_mask != 0;
        case ItemTeleportKind::Wishlist:
            return request.include_wishlist_items;
        case ItemTeleportKind::Quest:
            return request.include_quest_items;
        case ItemTeleportKind::Corpse:
            return request.include_corpses;
        default:
            return false;
        }
    }

    void count_kind(WorldActionResult& result, const ItemTeleportKind kind)
    {
        switch (kind)
        {
        case ItemTeleportKind::Normal:
            ++result.normal_items;
            break;
        case ItemTeleportKind::Wishlist:
            ++result.wishlist_items;
            break;
        case ItemTeleportKind::Quest:
            ++result.quest_items;
            break;
        case ItemTeleportKind::Corpse:
            ++result.corpses;
            break;
        }
    }

    ta::adapters::unity::vector3 next_ring_target(PlacementState& placement)
    {
        if (placement.index_in_ring >= placement.per_ring)
        {
            placement.radius += spacing;
            placement.index_in_ring = 0;
            placement.per_ring = (std::max)(8, static_cast<int>(2.0f * pi * placement.radius / spacing));
        }

        const float angle =
            2.0f * pi *
            static_cast<float>(placement.index_in_ring) /
            static_cast<float>(placement.per_ring);

        ta::adapters::unity::vector3 target{
            placement.center.x + cosf(angle) * placement.radius,
            placement.center.y + placement.height_offset,
            placement.center.z + sinf(angle) * placement.radius
        };

        ++placement.index_in_ring;
        return target;
    }

    ta::adapters::unity::vector3 target_for_object(PlacementState& placement,
                                     WorldActionResult& result,
                                     const std::string& template_id,
                                     const bool allow_grouping)
    {
        if (allow_grouping && !template_id.empty())
        {
            auto existing = placement.groups.find(template_id);
            if (existing != placement.groups.end())
            {
                WorldItemGroup& group = existing->second;
                ++group.item_count;
                ++result.grouped;

                return group.anchor;
            }
        }

        ta::adapters::unity::vector3 target = next_ring_target(placement);
        if (allow_grouping && !template_id.empty())
            placement.groups.emplace(template_id, WorldItemGroup{target, 0});

        return target;
    }

    bool move_component_to_target(Il2CppObject* object,
                                  PlacementState& placement,
                                  WorldActionResult& result,
                                  const ItemTeleportKind kind,
                                  const std::string& template_id,
                                  const bool allow_grouping)
    {
        if (!object)
        {
            ++result.skipped;
            return false;
        }

        Il2CppObject* transform = ta::adapters::eft::common::get_component_transform(object);
        if (!transform)
        {
            ++result.skipped;
            return false;
        }

        const ta::adapters::unity::vector3 target = target_for_object(placement, result, template_id, allow_grouping);
        ta::adapters::unity::transform_set_pos(transform, target);
        ++result.moved;
        count_kind(result, kind);

        const ta::adapters::unity::vector3 after = ta::adapters::unity::transform_get_pos(transform);
        if (ta::foundation::math::distance_sq(after, target) < 0.25f)
            ++result.changed;

        return true;
    }

    void move_regular_loot(ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                           const ItemTeleportRequest& request,
                           const ta::features::items::contracts::WishlistSnapshot& wishlist,
                           const ta::adapters::eft::items::ItemTemplateCatalogService& item_catalog,
                           const ta::adapters::eft::common::QuestLootIndex& quest_index,
                           PlacementState& placement,
                           WorldActionResult& result)
    {
        if ((!request.include_normal_items || request.normal_category_mask == 0) &&
            !request.include_wishlist_items)
        {
            return;
        }

        int visited = 0;
        ta::adapters::eft::common::for_each_loot_item(game_world_instance, [&](Il2CppObject* loot, int)
        {
            if (visited++ >= max_safe_loot) return;

            if (!loot)
            {
                ++result.skipped;
                return;
            }

            if (ta::adapters::eft::common::LootClassifier::looks_like_corpse(loot) ||
                ta::adapters::eft::common::LootClassifier::looks_like_default_inventory(loot))
            {
                ++result.skipped;
                return;
            }

            Il2CppObject* item = ta::adapters::eft::common::LootClassifier::get_item_object(loot);
            const std::string template_id =
                ta::adapters::eft::common::LootClassifier::get_template_id_from_item(item);

            if (quest_index.contains(loot, template_id))
            {
                // Quest items are handled by the dedicated quest pass so they do
                // not get moved once as normal/wishlist and then again as quest.
                return;
            }

            const auto classified = ta::adapters::eft::common::LootClassifier::classify_loot_item(
                loot,
                item,
                wishlist,
                template_id);

            if (classified == ta::adapters::eft::common::LootKind::Quest)
            {
                // Normal Item Teleport must not pull quest items. Quest items are
                // handled only by the dedicated quest pass when it is enabled.
                return;
            }

            const ItemTeleportKind kind = classified == ta::adapters::eft::common::LootKind::Wishlist
                ? ItemTeleportKind::Wishlist
                : ItemTeleportKind::Normal;

            if (!should_include_kind(request, kind))
                return;

            if (kind == ItemTeleportKind::Normal)
            {
                const ta::features::items::contracts::ItemCategory category = item_catalog.category_for(template_id);
                if (!request.includes_normal_category(category))
                    return;
            }

            move_component_to_target(loot, placement, result, kind, template_id, true);
        });
    }

    void move_quest_items(ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                          const ItemTeleportRequest& request,
                          PlacementState& placement,
                          WorldActionResult& result)
    {
        if (!request.include_quest_items)
            return;

        ta::adapters::eft::common::for_each_quest_loot(game_world_instance, [&](Il2CppObject* loot, int)
        {
            if (!loot) return;

            const std::string template_id = ta::adapters::eft::common::LootClassifier::get_loot_template_id(loot);
            move_component_to_target(loot, placement, result, ItemTeleportKind::Quest, template_id, true);
        });
    }

    void move_corpses(ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                      const ItemTeleportRequest& request,
                      PlacementState& placement,
                      WorldActionResult& result)
    {
        if (!request.include_corpses)
            return;

        int visited = 0;
        ta::adapters::eft::common::for_each_world_loot_object(game_world_instance, [&](Il2CppObject* object, int)
        {
            if (visited++ >= max_safe_loot) return;
            if (!ta::adapters::eft::common::LootClassifier::looks_like_corpse(object)) return;
            if (ta::adapters::eft::common::LootClassifier::looks_like_default_inventory(object)) return;

            // Corpses are moved individually. Do not group them by an item id.
            move_component_to_target(object, placement, result, ItemTeleportKind::Corpse, {}, false);
        });
    }
}

WorldActionResult teleport_items_to_player(
    ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
    ta::adapters::il2cpp::Il2CppObjectInstance main_player,
    const ItemTeleportRequest& request,
    const ta::features::items::contracts::WishlistSnapshot& wishlist,
    const ta::adapters::eft::items::ItemTemplateCatalogService& item_catalog)
{
    WorldActionResult result{};

    if ((!request.include_normal_items || request.normal_category_mask == 0) &&
        !request.include_wishlist_items &&
        !request.include_quest_items &&
        !request.include_corpses)
    {
        result.message = "Item Teleport: no categories selected";
        return result;
    }

    if (!ta::adapters::unity::is_initialized())
    {
        result.message = "Item Teleport: Unity API not initialized";
        return result;
    }

    ta::adapters::unity::vector3 center{};
    if (!ta::adapters::eft::common::get_player_position(main_player, center))
    {
        result.message = "Item Teleport: failed to get player position";
        return result;
    }

    PlacementState placement{};
    placement.center = center;
    placement.radius = std::clamp(request.circle_radius, 1.0f, 10.0f);
    placement.height_offset = std::clamp(request.height_offset, -2.0f, 5.0f);
    placement.per_ring = (std::max)(8, static_cast<int>(2.0f * pi * placement.radius / spacing));

    const ta::adapters::eft::common::QuestLootIndex quest_index = ta::adapters::eft::common::collect_quest_loot_index(game_world_instance);

    move_regular_loot(game_world_instance, request, wishlist, item_catalog, quest_index, placement, result);
    move_quest_items(game_world_instance, request, placement, result);
    move_corpses(game_world_instance, request, placement, result);

    result.executed = true;
    result.groups = static_cast<int>(placement.groups.size());
    result.message =
        "Item Teleport: moved " + std::to_string(result.moved) +
        ", verified " + std::to_string(result.changed) +
        ", normal " + std::to_string(result.normal_items) +
        ", wishlist " + std::to_string(result.wishlist_items) +
        ", quest " + std::to_string(result.quest_items) +
        ", corpses " + std::to_string(result.corpses) +
        ", grouped " + std::to_string(result.grouped) +
        ", groups " + std::to_string(result.groups) +
        ", skipped " + std::to_string(result.skipped);

    return result;
}
}
