#include "Readers/NearEntityListReader.h"

#include <algorithm>
#include <cstring>

#include "Offsets.h"
#include "RuntimeOffsets.h"
#include "Memory/MemoryValidation.h"

bool NearEntityListReader::TryRead(VmmAccessor&                      mem,
                                   const RuntimeSession&             session,
                                   std::vector<DayZNearEntityEntry>& entries)
{
    const uint32_t pid = session.processId;

    // -----------------------------------------------------------------
    // 1. Near-entity list base pointer
    // -----------------------------------------------------------------
    uint64_t nearListPtr = 0;
    if (!mem.TryReadPointer(pid,
                            session.worldAddress + RuntimeOffsets::World::NearEntityList,
                            nearListPtr))
    {
        return false;
    }

    // -----------------------------------------------------------------
    // 2. Table size
    // -----------------------------------------------------------------
    int32_t rawCount = 0;
    if (!mem.TryReadValue<int32_t>(pid,
                                   session.worldAddress + RuntimeOffsets::World::NearTableSize,
                                   rawCount))
    {
        return false;
    }

    const int count = std::clamp(rawCount, 0, kMaxEntities);
    if (count == 0) {
        entries.clear();
        return true;
    }

    // -----------------------------------------------------------------
    // 3. Bulk-read pointer table  (count * 8 bytes)
    // -----------------------------------------------------------------
    std::vector<uint8_t> tableBytes;
    tableBytes.reserve(static_cast<size_t>(count) * 8);
    if (!mem.ReadBytes(pid, nearListPtr, static_cast<size_t>(count) * 8, tableBytes)) {
        return false;
    }

    // Decode entity addresses and filter invalid ones.
    struct EntitySlot {
        uint64_t entityAddr;
        int      tableIndex;
    };
    std::vector<EntitySlot> slots;
    slots.reserve(static_cast<size_t>(count));
    for (int i = 0; i < count; ++i) {
        uint64_t addr = 0;
        std::memcpy(&addr, tableBytes.data() + static_cast<size_t>(i) * 8, 8);
        if (MemoryValidation::IsValidUserAddress(addr))
            slots.push_back({ addr, i });
    }

    if (slots.empty()) {
        entries.clear();
        return true;
    }

    // -----------------------------------------------------------------
    // 4. Scatter pass A — read Type pointer + VisualState pointer + NetworkId
    //    for all valid entities in one DMA round-trip.
    // -----------------------------------------------------------------
    struct PerEntity {
        uint64_t typeAddr       = 0;
        uint64_t visualStatePtr = 0;
        uint32_t networkId      = 0;
        uint32_t _pad           = 0;
    };
    std::vector<PerEntity> perEntity(slots.size());

    {
        std::vector<VmmAccessor::ScatterEntry> scat;
        scat.reserve(slots.size() * 3);
        for (size_t i = 0; i < slots.size(); ++i) {
            uint64_t base = slots[i].entityAddr;
            scat.push_back({ base + Offsets::Common::Type,
                             &perEntity[i].typeAddr,       sizeof(uint64_t) });
            scat.push_back({ base + RuntimeOffsets::Common::VisualState,
                             &perEntity[i].visualStatePtr, sizeof(uint64_t) });
            scat.push_back({ base + Offsets::Network::EntityNetworkId,
                             &perEntity[i].networkId,      sizeof(uint32_t) });
        }
        mem.ScatterRead(pid, scat);
    }

    // -----------------------------------------------------------------
    // 5. Scatter pass B — read position (Vector3) from each VisualState
    // -----------------------------------------------------------------
    std::vector<Vector3> positions(slots.size(), Vector3{});

    {
        std::vector<VmmAccessor::ScatterEntry> scat;
        scat.reserve(slots.size());
        for (size_t i = 0; i < slots.size(); ++i) {
            if (!MemoryValidation::IsValidUserAddress(perEntity[i].visualStatePtr))
                continue;
            scat.push_back({ perEntity[i].visualStatePtr + Offsets::Common::Position,
                             &positions[i], sizeof(Vector3) });
        }
        mem.ScatterRead(pid, scat);
    }

    // -----------------------------------------------------------------
    // 6. Assemble entries
    // -----------------------------------------------------------------
    entries.clear();
    entries.reserve(slots.size());

    for (size_t i = 0; i < slots.size(); ++i) {
        if (!MemoryValidation::IsValidUserAddress(perEntity[i].typeAddr))
            continue;

        EntityTypeMetadata typeMeta;
        if (!m_typeCache.GetOrRead(perEntity[i].typeAddr, typeMeta)) {
            typeMeta = ReadEntityTypeMetadata(mem, pid, perEntity[i].typeAddr);
            m_typeCache.Set(perEntity[i].typeAddr, typeMeta);
        }

        std::optional<Vector3> position;
        if (MemoryValidation::IsValidVector(positions[i]))
            position = positions[i];

        DayZNearEntityEntry entry;
        entry.address        = slots[i].entityAddr;
        entry.networkId      = perEntity[i].networkId;
        entry.position       = position;
        entry.headingDegrees = std::nullopt;
        entry.typeName       = typeMeta.typeName;
        entry.configName     = typeMeta.configName;
        entry.modelName      = typeMeta.modelName;
        entry.entityName     = GetBestEntityName(typeMeta.typeName,
                                                  typeMeta.configName,
                                                  typeMeta.cleanName,
                                                  typeMeta.modelName,
                                                  slots[i].entityAddr);
        entries.push_back(std::move(entry));
    }

    return true;
}

void NearEntityListReader::Reset()
{
    m_typeCache.Clear();
}
