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

#include <cstdint>
#include <cstring>
#include <string>

#include "adapters/eft/common/ManagedObjectAccess.h"
#include "adapters/eft/world/WorldAccess.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace
{
    void report_path(const char*, const char*)
    {
        ta::foundation::diagnostics::OnceLogger::trace(
            "ITEM",
            "compat-loot-query",
            "compat.item | loot=loot._item template_id=get_StringTemplateId quest_item=item.get_QuestItem->item.get_Template->template.QuestItem");
    }

    bool item_quest_flag(Il2CppObject* item)
    {
        if (!item || !item->klass)
            return false;

        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<bool>(
            item, "get_QuestItem", ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        if (!result)
            return false;

        report_path(
            "quest-flag-path-item-get-quest-item",
            "Quest item flag path active: item.get_QuestItem");
        return result.value();
    }

    bool template_quest_flag(Il2CppObject* item_template)
    {
        if (!item_template || !item_template->klass)
            return false;

        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::read_field<bool>(item_template, "QuestItem");
        if (!result)
            return false;

        report_path(
            "quest-flag-path-template-field-quest-item",
            "Quest item flag path active: template.QuestItem");
        return result.value();
    }

    Il2CppObject* item_template(Il2CppObject* item)
    {
        if (!item || !item->klass)
            return nullptr;

        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<Il2CppObject*>(
                item, "get_Template", ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        Il2CppObject* value = result ? result.value() : nullptr;
        if (value)
        {
            report_path(
                "quest-template-path-get-template",
                "Quest item template path active: item.get_Template");
        }
        return value;
    }
}

namespace ta::adapters::eft::common
{
    bool LootClassifier::looks_like_corpse(Il2CppObject* object)
    {
        if (!object)
            return false;

        const char* class_name = object->klass && object->klass->name ? object->klass->name : "";
        if (std::strstr(class_name, "Corpse") != nullptr)
            return true;

        return ta::adapters::unity::get_name(object).find("Corpse") != std::string::npos;
    }

    bool LootClassifier::looks_like_default_inventory(Il2CppObject* object)
    {
        return object && ta::adapters::unity::get_name(object) == "Default Inventory";
    }

    bool LootClassifier::looks_like_container(Il2CppObject* object)
    {
        if (!object || !object->klass || !object->klass->name)
            return false;

        const char* class_name = object->klass->name;
        return std::strstr(class_name, "LootableContainer") != nullptr ||
               std::strstr(class_name, "Container") != nullptr;
    }

    Il2CppObject* LootClassifier::get_item_object(Il2CppObject* loot_object)
    {
        if (!loot_object || !loot_object->klass)
            return nullptr;

        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::read_field<Il2CppObject*>(loot_object, "_item");
        Il2CppObject* item = result ? result.value() : nullptr;
        if (item)
        {
            report_path(
                "loot-item-path-private-item",
                "Loot item object path active: loot._item");
        }
        return item;
    }

    std::string LootClassifier::get_template_id_from_item(Il2CppObject* item)
    {
        if (!item || !item->klass)
            return {};

        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<Il2CppString*>(
                item, "get_StringTemplateId", ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        Il2CppString* template_id = result ? result.value() : nullptr;
        if (!template_id)
            return {};

        const std::string value = ta::adapters::il2cpp::conv_string(template_id);
        if (!value.empty())
        {
            report_path(
                "template-id-path-method",
                "Item template id path active: get_StringTemplateId");
        }
        return value;
    }

    std::string LootClassifier::get_loot_template_id(Il2CppObject* loot_object)
    {
        Il2CppObject* item = get_item_object(loot_object);
        return get_template_id_from_item(item ? item : loot_object);
    }

    bool LootClassifier::is_quest_item(Il2CppObject* item_object)
    {
        return is_quest_item(nullptr, item_object);
    }

    bool LootClassifier::is_quest_item(Il2CppObject* loot_object, Il2CppObject* item_object)
    {
        Il2CppObject* item = item_object ? item_object : get_item_object(loot_object);
        if (!item)
            return false;

        if (item_quest_flag(item))
            return true;
        return template_quest_flag(item_template(item));
    }

    LootKind LootClassifier::classify_loot_item(
        Il2CppObject* item_object,
        const ta::features::items::contracts::WishlistSnapshot& wishlist,
        const std::string& template_id)
    {
        return classify_loot_item(nullptr, item_object, wishlist, template_id);
    }

    LootKind LootClassifier::classify_loot_item(
        Il2CppObject* loot_object,
        Il2CppObject* item_object,
        const ta::features::items::contracts::WishlistSnapshot& wishlist,
        const std::string& template_id)
    {
        if (is_quest_item(loot_object, item_object))
            return LootKind::Quest;

        if (!template_id.empty() && wishlist.user_items.contains(template_id))
            return LootKind::Wishlist;

        return LootKind::Normal;
    }

    QuestLootIndex collect_quest_loot_index(
        ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance)
    {
        QuestLootIndex index{};

        for_each_quest_loot(game_world_instance, [&](Il2CppObject* object, int)
        {
            if (!object)
                return;

            index.objects.insert(object);

            const std::string template_id = LootClassifier::get_loot_template_id(object);
            if (!template_id.empty())
                index.template_ids.insert(template_id);
        });

        return index;
    }
}
