#include "adapters/eft/items/wishlist/WishlistReader.h"

#include <utility>

#include "adapters/il2cpp/ManagedRuntime.h"

namespace ta::adapters::eft::items
{
std::string WishlistReader::to_string(const ta::adapters::eft::common::EftMongoId& id)
{
    return ta::adapters::eft::common::mongo_id_string(id);
}

const MethodInfo* WishlistReader::resolve_cached_method(
    Il2CppClass* klass,
    const char* name,
    int arg_count,
    MethodCache& cache)
{
    if (!klass)
        return nullptr;

    if (cache.klass != klass || !cache.resolved)
    {
        cache.klass = klass;
        cache.method = ta::adapters::il2cpp::ManagedRuntime::method_or_null(klass, name, arg_count);
        cache.resolved = true;
    }

    if (!cache.method || !cache.method->methodPointer)
        return nullptr;

    return cache.method;
}

std::ptrdiff_t WishlistReader::resolve_cached_field_offset(
    Il2CppClass* klass,
    const char* name,
    FieldCache& cache)
{
    if (!klass)
        return -1;

    if (cache.klass != klass || !cache.resolved)
    {
        cache.klass = klass;
        cache.resolved = true;

        const FieldInfo* field = ta::adapters::il2cpp::ManagedRuntime::field_or_null(klass, name);
        cache.offset = field ? static_cast<std::ptrdiff_t>(field->offset) : -1;
    }

    return cache.offset;
}

Il2CppObject* WishlistReader::read_object_field(Il2CppObject* object, std::ptrdiff_t offset)
{
    if (!object || offset < 0)
        return nullptr;

    return *reinterpret_cast<Il2CppObject**>(
        reinterpret_cast<std::uintptr_t>(object) +
        static_cast<std::uintptr_t>(offset));
}

ta::adapters::unity::dict<ta::adapters::eft::common::EftMongoId, int>* WishlistReader::as_valid_dictionary(Il2CppObject* dictionary)
{
    if (!dictionary)
        return nullptr;

    auto* values = reinterpret_cast<ta::adapters::unity::dict<ta::adapters::eft::common::EftMongoId, int>*>(dictionary);

    if (!values->m_p_entries)
        return nullptr;

    if (values->m_i_count <= 0 || values->m_i_count > max_dictionary_count)
        return nullptr;

    return values;
}

void WishlistReader::add_keys(
    Il2CppObject* dictionary,
    std::unordered_set<std::string>& target)
{
    auto* values = as_valid_dictionary(dictionary);
    if (!values)
        return;

    const int count = values->m_i_count;

    target.reserve(target.size() + static_cast<std::size_t>(count));

    const auto* entries = values->get_entry();
    if (!entries)
        return;

    for (int i = 0; i < count; ++i)
    {
        const auto* entry = entries + i;

        if (entry->m_i_hash_code < 0)
            continue;

        std::string id = to_string(entry->m_t_key);
        if (id.empty())
            continue;

        target.insert(std::move(id));
    }
}


Il2CppObject* WishlistReader::get_profile(ta::adapters::il2cpp::Il2CppObjectInstance& main_player)
{
    Il2CppObject* player = main_player.get_instance();
    if (!player || !player->klass)
        return nullptr;

    const MethodInfo* getter = resolve_cached_method(
        player->klass,
        "get_Profile",
        0,
        profile_getter_cache_);

    if (!getter || !getter->methodPointer)
        return nullptr;

    using get_profile_fn = Il2CppObject* (*)(Il2CppObject*);
    return reinterpret_cast<get_profile_fn>(getter->methodPointer)(player);
}

bool WishlistReader::refresh(ta::adapters::il2cpp::Il2CppObjectInstance& main_player)
{
    Il2CppObject* profile = get_profile(main_player);
    if (!profile || !profile->klass)
        return false;

    const std::ptrdiff_t manager_offset = resolve_cached_field_offset(
        profile->klass,
        "WishlistManager",
        wishlist_manager_field_cache_);

    Il2CppObject* manager = read_object_field(profile, manager_offset);
    if (!manager || !manager->klass)
        return false;

    const std::ptrdiff_t user_items_offset = resolve_cached_field_offset(
        manager->klass,
        "_userItems",
        user_items_field_cache_);

    const std::ptrdiff_t wishlist_items_offset = resolve_cached_field_offset(
        manager->klass,
        "_wishlistItems",
        wishlist_items_field_cache_);

    ta::features::items::contracts::WishlistSnapshot next;
    next.user_items.reserve(expected_item_count * 2);

    Il2CppObject* user_items_dictionary =
        read_object_field(manager, user_items_offset);

    Il2CppObject* wishlist_items_dictionary =
        read_object_field(manager, wishlist_items_offset);

    // Do not expose hideout/system requirements as a separate wishlist class.
    // Both user-pinned wishlist entries and WishlistManager::_wishlistItems are
    // folded into user_items so downstream consumers render them as ordinary
    // wishlist entries.
    add_keys(user_items_dictionary, next.user_items);
    add_keys(wishlist_items_dictionary, next.user_items);

    if (next.user_items == current_.user_items)
        return false;

    current_ = std::move(next);
    return true;
}

const ta::features::items::contracts::WishlistSnapshot& WishlistReader::current() const
{
    return current_;
}

void WishlistReader::clear()
{
    current_ = ta::features::items::contracts::WishlistSnapshot{};
}
}
