#include "adapters/unity/chams/NpcChamsService.h"

#include <cstddef>
#include <set>
#include <string>

#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "foundation/hash/Fnv1a.h"
#include "adapters/eft/common/ManagedObjectAccess.h"
#include "adapters/eft/common/PlayerWorldReader.h"
#include "adapters/unity/chams/ChamsService.h"
#include "adapters/unity/chams/ChamsSupport.h"

namespace ta::adapters::unity::chams
{
NpcChamsService::Signature NpcChamsService::make_signature(const ChamsConfigSnapshot& config)
{
    return {config.npc_chams, config.gear_chams, config.chams_type,
            config.visible_color, config.invisible_color};
}

void NpcChamsService::restore_all()
{
    for (auto it = original_materials_.rbegin(); it != original_materials_.rend(); ++it)
        (void)ChamsService::restore_material_state(*it);
    original_materials_.clear();
}

void NpcChamsService::update(const Il2CppClass* game_world_class,
                             const Il2CppImage* unity_image,
                             ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                             const std::string& main_nickname,
                             const ChamsConfigSnapshot& config)
{
    if (!game_world_class || !unity_image)
        return;

    const Signature signature = make_signature(config);
    if (cached_signature_ != signature)
    {
        restore_all();
        applied_npcs.clear();
        cached_signature_ = signature;
    }

    Il2CppObject* renderer_type = ta::adapters::il2cpp::get_system_type(unity_image, "UnityEngine", "Renderer");
    if (!renderer_type)
        return;

    const std::string shader_name = ChamsShaderCatalog::shader_name(config.chams_type);
    if (shader_name.empty())
        return;

    ta::adapters::eft::common::for_each_alive_player(game_world_instance, main_nickname,
        [&](const ta::adapters::eft::common::WorldPlayerEntry& player, int)
        {
            if (player.is_local || player.nickname.empty())
                return;

            const std::size_t npc_hash = static_cast<std::size_t>(ta::foundation::hash::fnv1a(player.nickname));
            if (applied_npcs.contains(npc_hash))
                return;

            Il2CppObject* body_object = ta::adapters::eft::common::get_object_method(
                player.object,
                "get_PlayerBody");
            if (!body_object)
                return;

            Il2CppObject* body_game_object = ta::adapters::unity::component_get_game_object(body_object);
            Il2CppObject* body_transform = ta::adapters::unity::gameobject_get_transform(body_game_object);
            if (!body_transform)
                return;

            bool applied_any = false;
            std::set<Il2CppObject*> visited_materials;
            const auto apply_transform = [&](Il2CppObject* transform)
            {
                Il2CppObject* game_object = ta::adapters::unity::transform_get_game_object(transform);
                Il2CppObject* renderer = ta::adapters::unity::gameobject_get_component(game_object, renderer_type);
                if (!renderer)
                    return;

                ta::adapters::il2cpp::Il2CppObjectInstance renderer_instance(renderer);
                const auto get_material = renderer_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                    "get_material", 0);
                if (!get_material)
                    return;

                Il2CppObject* material = get_material(renderer);
                if (!material || !visited_materials.insert(material).second)
                    return;

                const std::string classification_name =
                    ta::adapters::unity::get_name(transform) + " " +
                    ta::adapters::unity::get_name(game_object) + " " +
                    ta::adapters::unity::get_name(material);
                const bool is_gear = ChamsMaterialClassifier::is_npc_gear(classification_name);
                if ((is_gear && !config.gear_chams) || (!is_gear && !config.npc_chams))
                    return;

                ChamsMaterialState original{};
                if (!ChamsService::capture_material_state(material, original))
                    return;

                if (ChamsService::apply_material_chams(
                        unity_image,
                        ta::adapters::il2cpp::Il2CppObjectInstance(material),
                        shader_name.c_str(),
                        config.visible_color,
                        config.invisible_color))
                {
                    original_materials_.push_back(original);
                    applied_any = true;
                }
            };

            apply_transform(body_transform);
            ta::adapters::unity::transform_apply_to_all_children(body_transform, apply_transform);

            if (applied_any)
                applied_npcs.insert(npc_hash);
        });
}

void NpcChamsService::reset()
{
    restore_all();
    applied_npcs.clear();
    cached_signature_ = Signature{};
}

void NpcChamsService::discard_session() noexcept
{
    original_materials_.clear();
    applied_npcs.clear();
    cached_signature_ = Signature{};
}
}
