#include "pch.hpp"
#include "common.hpp"
#include "core/memory.hpp"
#include "core/interfaces.hpp"
#include "utils/fnv1a.hpp"
#include "sdk/entity.hpp"
#include "sdk/interfaces/game_entity_system.hpp"
#include "features/features.hpp"

static int32_t get_thrower_idx(c_base_grenade* grenade)
{
    const auto handle = grenade->m_hThrower();
    if (!handle.is_valid())
        return -1;

    return handle.get_entry_idx() & ENT_ENTRY_MASK;
}

static nlohmann::json make_grenade_entry(
    const std::string& type,
    e_team             team,
    int32_t            thrower_idx,
    const f_vector&    pos,
    const f_vector&    vel = {})
{
    auto entry = nlohmann::json{
        { "type",        type },
        { "team",        static_cast<uint8_t>(team) },
        { "thrower_idx", thrower_idx },
        { "x",           pos.m_x },
        { "y",           pos.m_y }
    };
    if (!vel.is_zero())
    {
        entry["vx"] = vel.m_x;
        entry["vy"] = vel.m_y;
    }
    return entry;
}

void f::grenades::get_grenade_info()
{
    f::m_data["m_grenades"] = nlohmann::json::array();

    // Grenade projectiles are world entities always at higher indices
    constexpr int32_t min_idx = 65;
    constexpr int32_t max_idx = 2048;
    for (int32_t idx = min_idx; idx < max_idx; idx++)
    {
        const auto entity = i::m_game_entity_system->get(idx);
        if (!entity)
            continue;

        const auto entity_handle = entity->get_ref_e_handle();
        if (!entity_handle.is_valid())
            continue;

        const auto class_name = entity->get_schema_class_name();
        if (class_name.empty())
            continue;

        const auto hashed = fnv1a::hash(class_name);

        // Smoke
        if (hashed == fnv1a::hash("C_SmokeGrenadeProjectile"))
        {
            const auto smoke = reinterpret_cast<c_smoke_grenade_projectile*>(entity);

            const auto pos = smoke->get_scene_origin();
            if (pos.is_zero())
                continue;

            const auto smoke_vel = smoke->m_vInitialVelocity();

            auto entry = make_grenade_entry(
                "smoke",
                smoke->m_iTeamNum(),
                get_thrower_idx(smoke),
                pos,
                smoke_vel);

            entry["m_did_smoke_effect"]     = smoke->m_bDidSmokeEffect();
            entry["m_smoke_effect_spawned"] = smoke->m_bSmokeEffectSpawned();

            if (smoke->m_bDidSmokeEffect())
            {
                const auto det_pos = smoke->m_vSmokeDetonationPos();
                if (!det_pos.is_zero())
                {
                    entry["x"] = det_pos.m_x;
                    entry["y"] = det_pos.m_y;
                }
            }

            f::m_data["m_grenades"].push_back(std::move(entry));
        }

        // Flashbang
        else if (hashed == fnv1a::hash("C_FlashbangProjectile"))
        {
            const auto flash = reinterpret_cast<c_base_cs_grenade_projectile*>(entity);

            const auto pos = flash->get_scene_origin();
            if (pos.is_zero())
                continue;

            if (flash->m_bExplodeEffectBegan())
                continue;

            const auto flash_vel = flash->m_vInitialVelocity();

            f::m_data["m_grenades"].push_back(
                make_grenade_entry(
                    "flashbang",
                    flash->m_iTeamNum(),
                    get_thrower_idx(flash),
                    pos,
                    flash_vel));
        }

        // HE Grenade
        else if (hashed == fnv1a::hash("C_HEGrenadeProjectile"))
        {
            const auto he = reinterpret_cast<c_base_cs_grenade_projectile*>(entity);

            const auto pos = he->get_scene_origin();
            if (pos.is_zero())
                continue;

            if (he->m_bExplodeEffectBegan())
                continue;

            const auto he_vel = he->m_vInitialVelocity();

            f::m_data["m_grenades"].push_back(
                make_grenade_entry(
                    "he",
                    he->m_iTeamNum(),
                    get_thrower_idx(he),
                    pos,
                    he_vel));
        }

        // Molotov / Incendiary
        else if (hashed == fnv1a::hash("C_MolotovProjectile"))
        {
            const auto molotov = reinterpret_cast<c_molotov_projectile*>(entity);

            const auto pos = molotov->get_scene_origin();
            if (pos.is_zero())
                continue;

            if (molotov->m_bExplodeEffectBegan())
                continue;

            const std::string type = molotov->m_bIsIncGrenade() ? "incendiary" : "molotov";
            const auto molotov_vel = molotov->m_vInitialVelocity();

            f::m_data["m_grenades"].push_back(
                make_grenade_entry(
                    type,
                    molotov->m_iTeamNum(),
                    get_thrower_idx(molotov),
                    pos,
                    molotov_vel));
        }

        // Decoy
        else if (hashed == fnv1a::hash("C_DecoyProjectile"))
        {
            const auto decoy = reinterpret_cast<c_decoy_projectile*>(entity);

            const auto pos = decoy->get_scene_origin();
            if (pos.is_zero())
                continue;

            const auto decoy_vel = decoy->m_vInitialVelocity();

            auto entry = make_grenade_entry(
                "decoy",
                decoy->m_iTeamNum(),
                get_thrower_idx(decoy),
                pos,
                decoy_vel);

            entry["m_is_active"] = (decoy->m_nDecoyShotTick() != 0);

            f::m_data["m_grenades"].push_back(std::move(entry));
        }

        // Inferno (fire entity spawned when molotov/incendiary lands)
        else if (hashed == fnv1a::hash("C_Inferno"))
        {
            const auto inferno = reinterpret_cast<c_inferno*>(entity);

            if (inferno->m_bInPostEffectTime())
                continue;

            const auto fire_count = inferno->m_fireCount();
            if (fire_count <= 0)
                continue;

            const auto pos = inferno->get_scene_origin();
            if (pos.is_zero())
                continue;

            auto entry = make_grenade_entry(
                "inferno",
                inferno->m_iTeamNum(),
                -1,
                pos);

            entry["m_fire_count"]   = fire_count;
            entry["m_inferno_type"] = inferno->m_nInfernoType();

            f::m_data["m_grenades"].push_back(std::move(entry));
        }
    }
}
