#include "adapters/eft/items/actions/ItemFactoryService.h"
#include "adapters/eft/items/interop/ItemObjectAccess.h"

#include <algorithm>
#include <cstring>
#include <optional>
#include <unordered_set>
#include <vector>

#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "foundation/diagnostics/BuildOptions.h"
#include "foundation/diagnostics/Logger.h"
#include "adapters/hooks/items/ItemHookState.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace
{
    constexpr std::size_t kMaxItemChildArrayLength = 256;
    constexpr int kMaxItemChildListLength = 1024;

    using GetObjectFn = Il2CppObject*(*)(Il2CppObject*);
    using SetSpawnedFn = void(*)(Il2CppObject*, bool);

    void info(std::string message)
    {
        if (ta::foundation::diagnostics::build_options::verbose) ta::foundation::logger::info(message);
    }

    void error(std::string message)
    {
        ta::foundation::logger::error("ITEM", std::move(message));
    }

    Il2CppObject* get_item_factory()
    {
        if (!ta::adapters::hooks::items::ensure_item_factory_available())
            return nullptr;
        return ta::adapters::hooks::items::item_factory();
    }

    const char* get_class_name(Il2CppObject* object)
    {
        return object && object->klass && object->klass->name ? object->klass->name : "<unknown>";
    }

    template <typename T>
    bool try_get_instance_field(Il2CppObject* object, const char* field_name, T& value)
    {
        const auto result =
            ta::adapters::il2cpp::ManagedRuntime::read_field<T>(object, field_name);
        if (!result)
            return false;
        value = result.value();
        return true;
    }

    bool try_set_spawned_in_session(Il2CppObject* item)
    {
        if (!item) return false;

        ta::adapters::il2cpp::Il2CppObjectInstance instance(item);
        const auto set_spawned = instance.get_method<SetSpawnedFn>("set_SpawnedInSession", 1);
        if (!set_spawned)
        {
            const std::string class_name = get_class_name(item);
            ta::foundation::diagnostics::OnceLogger::warn(
                "ITEM",
                "fir_setter:" + class_name,
                "FiR: set_SpawnedInSession not found on class " + class_name);
            return false;
        }

        set_spawned(item, true);
        return true;
    }


    void collect_slot_children(Il2CppObject* item, std::vector<Il2CppObject*>& children)
    {
        Il2CppArray* slots = nullptr;
        if (!try_get_instance_field(item, "Slots", slots) || !slots ||
            slots->max_length > kMaxItemChildArrayLength)
        {
            return;
        }

        for (std::size_t i = 0; i < slots->max_length; ++i)
        {
            Il2CppObject* slot = reinterpret_cast<Il2CppObject**>(&slots->data)[i];
            if (Il2CppObject* child = ta::adapters::eft::items::object_access::contained_item(slot))
                children.push_back(child);
        }
    }

    void collect_list_items(Il2CppObject* list_object, std::vector<Il2CppObject*>& children)
    {
        if (!list_object) return;

        const char* class_name = get_class_name(list_object);
        if (!class_name || !std::strstr(class_name, "List")) return;

        auto* list = reinterpret_cast<ta::adapters::unity::list*>(list_object);
        if (!list || !list->m_p_list_array) return;

        const int array_length = static_cast<int>(list->m_p_list_array->max_length);
        if (array_length < 0 || array_length > kMaxItemChildListLength) return;

        const int size = list->m_i_size;
        if (size < 0 || size > array_length)
        {
            ta::foundation::diagnostics::OnceLogger::error(
                "ITEM", "item-child-list-size-invalid",
                "Item child list size is outside its backing-array capacity");
            return;
        }
        for (int i = 0; i < size; ++i)
        {
            Il2CppObject* child = reinterpret_cast<Il2CppObject**>(&list->m_p_list_array->data)[i];
            if (child) children.push_back(child);
        }
    }

    void collect_grid_children(Il2CppObject* item, std::vector<Il2CppObject*>& children)
    {
        Il2CppArray* grids = nullptr;
        if (!try_get_instance_field(item, "Grids", grids) || !grids ||
            grids->max_length > kMaxItemChildArrayLength)
        {
            return;
        }

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

            Il2CppObject* contained_items = nullptr;
            if (!try_get_instance_field(grid, "ContainedItems", contained_items))
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "ITEM", "item-grid-contained-items-field",
                    "Item traversal failed: Grid.ContainedItems unavailable");
                continue;
            }
            if (contained_items)
                collect_list_items(contained_items, children);
        }
    }

    int mark_found_in_raid(Il2CppObject* item,
                           std::unordered_set<Il2CppObject*>& visited,
                           const int depth)
    {
        constexpr int kMaxTreeDepth = 12;
        if (!item || depth > kMaxTreeDepth || !visited.insert(item).second)
            return 0;

        int tagged = try_set_spawned_in_session(item) ? 1 : 0;
        std::vector<Il2CppObject*> children;
        children.reserve(16);
        collect_slot_children(item, children);
        collect_grid_children(item, children);

        for (Il2CppObject* child : children)
            tagged += mark_found_in_raid(child, visited, depth + 1);
        return tagged;
    }

    int mark_found_in_raid(Il2CppObject* item)
    {
        std::unordered_set<Il2CppObject*> visited;
        const int tagged = mark_found_in_raid(item, visited, 0);
        if (ta::foundation::diagnostics::build_options::verbose)
            ta::foundation::logger::info("FiR: tagged item tree count=" + std::to_string(tagged));
        return tagged;
    }

    Il2CppObject* quest_flag_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)
        {
            ta::foundation::diagnostics::OnceLogger::trace(
                "ITEM",
                "compat-spawned-quest-item",
                "compat.item.spawn | quest_item=item.get_QuestItem->item.get_Template->template.QuestItem");
        }
        return value;
    }

    std::optional<bool> classify_spawned_quest_item(Il2CppObject* item)
    {
        bool resolved = false;

        if (item && item->klass)
        {
            const auto result =
                ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<bool>(
                    item, "get_QuestItem", ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
            if (result)
            {
                resolved = true;
                ta::foundation::diagnostics::OnceLogger::trace(
                    "ITEM",
                    "compat-spawned-quest-item",
                    "compat.item.spawn | quest_item=item.get_QuestItem->item.get_Template->template.QuestItem");
                if (result.value())
                    return true;
            }
        }

        Il2CppObject* item_template = quest_flag_template(item);
        if (item_template && item_template->klass)
        {
            const auto result =
                ta::adapters::il2cpp::ManagedRuntime::read_field<bool>(item_template, "QuestItem");
            if (result)
            {
                resolved = true;
                ta::foundation::diagnostics::OnceLogger::trace(
                    "ITEM",
                    "compat-spawned-quest-item",
                    "compat.item.spawn | quest_item=item.get_QuestItem->item.get_Template->template.QuestItem");
                if (result.value())
                    return true;
            }
        }

        return resolved ? std::optional<bool>{false} : std::nullopt;
    }

}

namespace ta::adapters::eft::items
{
    Il2CppObject* ItemFactoryService::create_preset_item(const std::string& template_id)
    {
        if (template_id.empty())
        {
            error("Item ID is empty");
            return nullptr;
        }

        Il2CppObject* factory = get_item_factory();
        if (!factory)
        {
            error("No Item Factory; enter a new raid after injection if InitLevel has already run");
            return nullptr;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance instance(factory);
        const auto get_preset = instance.get_method<Il2CppObject*(*)(Il2CppObject*, Il2CppString*)>(
            "GetPresetItem", 1);
        if (!get_preset)
        {
            error("GetPresetItem not found");
            return nullptr;
        }

        Il2CppObject* item = get_preset(factory, ta::adapters::il2cpp::il2cpp_string_new(template_id.c_str()));
        if (!item)
        {
            error("Failed to create item: " + template_id);
            return nullptr;
        }

        info("Created Item: " + template_id);
        return item;
    }

    bool ItemFactoryService::configure_created_item(Il2CppObject* item, const CreatedItemOptions& options)
    {
        if (!item) return false;

        ta::adapters::il2cpp::Il2CppObjectInstance instance(item);
        const int requested_amount = (std::max)(1, options.amount);
        if (options.override_max_amount)
        {
            instance.set_field("StackObjectsCount", requested_amount);
        }
        else
        {
            if (options.max_stack_size <= 0)
            {
                error("Created item configuration failed: catalog max stack size is invalid");
                return false;
            }
            instance.set_field(
                "StackObjectsCount",
                (std::min)(requested_amount, options.max_stack_size));
        }

        if (options.found_in_raid)
            mark_found_in_raid(item);
        return true;
    }

    std::optional<bool> ItemFactoryService::is_quest_item(Il2CppObject* item)
    {
        if (!item || !item->klass)
        {
            error("Quest item classification failed: invalid item object");
            return std::nullopt;
        }

        const std::optional<bool> result = classify_spawned_quest_item(item);
        if (!result)
        {
            ta::foundation::diagnostics::OnceLogger::error(
                "ITEM",
                "spawn-quest-flag-path-none",
                "Spawned quest item classification failed: no quest flag member resolved");
        }
        return result;
    }
}
