#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#include "visual/poi_radar.hpp"
#include <algorithm>
#include <cstring>
#include <atomic>
#include <immintrin.h>

namespace visual {

PoiRadar::PoiRadar(IMemoryReader& memory)
    : memory_(memory) {
    activeEntities_.reserve(kMaxPoiEntries);
}

void PoiRadar::Clear() {
    activeEntities_.clear();
}

bool PoiRadar::AddEntity(
    uint64_t id,
    PoiType type,
    const Vector3& worldPos,
    float healthPct,
    const std::string& name
) {
    // Filter by type options
    switch (type) {
        case PoiType::Boss:
            if (!options_.showBosses) return false;
            break;
        case PoiType::RareMonster:
            if (!options_.showRareMonsters) return false;
            break;
        case PoiType::Strongbox:
            if (!options_.showStrongboxes) return false;
            break;
        case PoiType::Shrine:
            if (!options_.showShrines) return false;
            break;
        case PoiType::BreachHand:
        case PoiType::RitualAltar:
        case PoiType::DeliriumMirror:
        case PoiType::ExpeditionChest:
            if (!options_.showBreachRitualDelirium) return false;
            break;
        default:
            return false;
    }

    // Distance check to player
    const float dist = playerPos_.Distance2D(worldPos);
    if (dist > options_.maxDetectionDistance) {
        return false;
    }

    if (activeEntities_.size() >= kMaxPoiEntries) {
        return false;
    }

    // Project world pos to screen
    Vector2 screenPos;
    bool projected = WorldToScreen::Project(viewProj_, worldPos, screenWidth_, screenHeight_, screenPos);
    bool onScreen = projected && WorldToScreen::IsOnScreen(screenPos, screenWidth_, screenHeight_, 20.0f);

    PoiSharedEntity entity{};
    entity.id = id;
    entity.type = static_cast<uint32_t>(type);
    entity.world_x = worldPos.x;
    entity.world_y = worldPos.y;
    entity.world_z = worldPos.z;
    entity.screen_x = projected ? screenPos.x : -9999.0f;
    entity.screen_y = projected ? screenPos.y : -9999.0f;
    entity.on_screen = onScreen ? 1 : 0;
    entity.distance = dist;
    entity.health_pct = std::clamp(healthPct, 0.0f, 1.0f);

    if (!name.empty()) {
        std::strncpy(entity.name, name.c_str(), sizeof(entity.name) - 1);
        entity.name[sizeof(entity.name) - 1] = '\0';
    } else {
        std::strncpy(entity.name, GetPoiTypeName(type), sizeof(entity.name) - 1);
        entity.name[sizeof(entity.name) - 1] = '\0';
    }

    activeEntities_.push_back(entity);
    return true;
}

size_t PoiRadar::ScanRawEntities(uintptr_t entityListAddr, size_t maxCount) {
    if (!memory_.IsAttached() || entityListAddr == 0 || maxCount == 0) {
        return 0;
    }

    Clear();
    size_t count = (std::min)(maxCount, kMaxPoiEntries);

    // In POE2 InGameState EntityMap, elements contain [EntityPtr (8B)].
    // Each Entity contains components: Position, Monster, Shrine, Chest, etc.
    for (size_t i = 0; i < count; ++i) {
        uintptr_t entityPtr = 0;
        if (!memory_.ReadValue<uintptr_t>(entityListAddr + i * sizeof(uintptr_t), entityPtr) || entityPtr == 0) {
            continue;
        }

        // Read mock or real entity header
        // Entity struct layout in POE2:
        // +0x08: Id (uint64_t)
        // +0x10: ComponentListPtr
        // For simulated / generic layout:
        uint64_t entityId = 0;
        memory_.ReadValue<uint64_t>(entityPtr + 0x08, entityId);

        uint32_t rawType = 0;
        memory_.ReadValue<uint32_t>(entityPtr + 0x20, rawType);

        float posX = 0.0f, posY = 0.0f, posZ = 0.0f;
        memory_.ReadValue<float>(entityPtr + 0x28, posX);
        memory_.ReadValue<float>(entityPtr + 0x2C, posY);
        memory_.ReadValue<float>(entityPtr + 0x30, posZ);

        float health = 1.0f;
        memory_.ReadValue<float>(entityPtr + 0x34, health);

        char nameBuffer[32] = {0};
        memory_.Read(entityPtr + 0x38, nameBuffer, sizeof(nameBuffer) - 1);

        if (rawType >= static_cast<uint32_t>(PoiType::Boss) &&
            rawType <= static_cast<uint32_t>(PoiType::ExpeditionChest)) {
            AddEntity(
                entityId,
                static_cast<PoiType>(rawType),
                Vector3(posX, posY, posZ),
                health,
                nameBuffer
            );
        }
    }

    return activeEntities_.size();
}

void PoiRadar::ExportToSharedState(PoiRadarSharedState& outState) {
    // Seqlock write sequence:
    // 1. Begin write by making seqlock odd
    seqlock_++;
    if ((seqlock_ & 1) == 0) seqlock_++;
    outState.seqlock = seqlock_;
    std::atomic_thread_fence(std::memory_order_release);

    outState.magic = kVisualToolPoiMagic;
    outState.player_x = playerPos_.x;
    outState.player_y = playerPos_.y;
    outState.player_z = playerPos_.z;
    outState.camera_yaw = cameraYaw_;

    const uint32_t count = static_cast<uint32_t>((std::min)(activeEntities_.size(), kMaxPoiEntries));
    outState.count = count;
    outState.reserved = 0;

    for (uint32_t i = 0; i < count; ++i) {
        outState.entities[i] = activeEntities_[i];
    }

    // 2. End write by making seqlock even with release fence
    std::atomic_thread_fence(std::memory_order_release);
    seqlock_++;
    if ((seqlock_ & 1) != 0) seqlock_++;
    outState.seqlock = seqlock_;
}

bool PoiRadar::ReadSharedStateSafely(const PoiRadarSharedState& inState, PoiRadarSharedState& outCopy) {
    constexpr int kMaxRetries = 32;
    for (int retry = 0; retry < kMaxRetries; ++retry) {
        const uint32_t seq1 = inState.seqlock;
        if (seq1 & 1) {
            _mm_pause();
            continue;
        }

        std::atomic_thread_fence(std::memory_order_acquire);
        outCopy = inState;
        std::atomic_thread_fence(std::memory_order_acquire);

        const uint32_t seq2 = inState.seqlock;
        if (seq1 == seq2 && outCopy.magic == kVisualToolPoiMagic) {
            return true;
        }
        _mm_pause();
    }
    return false;
}

const char* PoiRadar::GetPoiTypeName(PoiType type) {
    switch (type) {
        case PoiType::Boss:            return "Boss";
        case PoiType::RareMonster:     return "Rare Monster";
        case PoiType::Strongbox:       return "Strongbox";
        case PoiType::Shrine:          return "Shrine";
        case PoiType::BreachHand:      return "Breach Hand";
        case PoiType::RitualAltar:     return "Ritual Altar";
        case PoiType::DeliriumMirror:  return "Delirium Mirror";
        case PoiType::ExpeditionChest: return "Expedition Chest";
        default:                       return "Unknown";
    }
}

} // namespace visual
