#include "looting/loot_density_clusterer.hpp"
#include "common/string_utils.hpp"
#include <algorithm>

namespace looting {

LootDensityClusterer::LootDensityClusterer(const LootClusterConfig& config)
    : m_config(config) {
}

float LootDensityClusterer::EstimateItemValueInChaos(const EntityTelemetryData& item) {
    const std::string nameLower = ToLower(item.name);

    if (nameLower.find("mirror") != std::string::npos) return 5000.0f;
    if (nameLower.find("divine") != std::string::npos) return 150.0f;
    if (nameLower.find("perfect jeweller") != std::string::npos) return 15.0f;
    if (nameLower.find("exalt") != std::string::npos) return 10.0f;
    if (nameLower.find("greater jeweller") != std::string::npos) return 5.0f;
    if (nameLower.find("annul") != std::string::npos) return 2.0f;
    if (nameLower.find("chaos") != std::string::npos) return 1.0f;
    if (nameLower.find("vaal") != std::string::npos) return 0.5f;

    if (nameLower.find("waystone") != std::string::npos ||
        nameLower.find("tablet") != std::string::npos ||
        nameLower.find("logbook") != std::string::npos) {
        return 2.5f;
    }

    if (nameLower.find("uncut") != std::string::npos) return 2.0f;
    if (nameLower.find("gem") != std::string::npos) return 1.2f;
    if (nameLower.find("soul core") != std::string::npos ||
        nameLower.find("rune") != std::string::npos) {
        return 1.0f;
    }

    if (nameLower.find("omen") != std::string::npos ||
        nameLower.find("distilled") != std::string::npos ||
        nameLower.find("essence") != std::string::npos) {
        return 1.5f;
    }

    if (nameLower.find("gold") != std::string::npos) {
        return 0.05f; // Vàng hút tự động, không tốn slot
    }

    if (nameLower.find("regal") != std::string::npos ||
        nameLower.find("alchemy") != std::string::npos) {
        return 0.25f;
    }

    if (nameLower.find("orb") != std::string::npos ||
        nameLower.find("bauble") != std::string::npos ||
        nameLower.find("prism") != std::string::npos) {
        return 0.10f;
    }

    if (nameLower.find("transmutation") != std::string::npos ||
        nameLower.find("augmentation") != std::string::npos ||
        nameLower.find("chance") != std::string::npos) {
        return 0.02f;
    }

    if (nameLower.find("scroll") != std::string::npos ||
        nameLower.find("scrap") != std::string::npos ||
        nameLower.find("whetstone") != std::string::npos) {
        return 0.005f;
    }

    // Theo Rarity trang bị
    if (item.rarity >= 3) return 2.0f;   // Unique
    if (item.rarity == 2) return 0.10f;  // Rare
    if (item.rarity == 1) return 0.01f;  // Magic
    return 0.001f;                       // Normal / White
}

std::vector<LootCluster> LootDensityClusterer::ClusterAndEvaluate(
    const TelemetryPacket& packet,
    const navigation::Vec2& playerPos,
    const navigation::Vec2& mapProgressionDir,
    const std::unordered_set<uint32_t>& ignoredIds
) const {
    std::vector<LootCluster> clusters;

    // 1. Gom cụm không gian (Spatial Clustering)
    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type != 2) continue; // Chỉ xử lý ItemDrop
        if (ignoredIds.contains(ent.id)) continue;

        const navigation::Vec2 itemPos{ ent.posX, ent.posY };
        const float val = EstimateItemValueInChaos(ent);

        // Tính priority cơ sở
        int prio = 10;
        const std::string nl = ToLower(ent.name);
        if (nl.find("gold") != std::string::npos) prio = 100;
        else if (nl.find("mirror") != std::string::npos || nl.find("divine") != std::string::npos) prio = 99;
        else if (nl.find("chaos") != std::string::npos || nl.find("exalt") != std::string::npos) prio = 95;
        else if (nl.find("waystone") != std::string::npos) prio = 92;
        else if (nl.find("uncut") != std::string::npos || nl.find("gem") != std::string::npos) prio = 88;
        else if (nl.find("rune") != std::string::npos) prio = 85;
        else if (ent.rarity >= 3) prio = 75;
        else if (ent.rarity == 2) prio = 65;

        // Dò tìm cụm lân cận trong bán kính clusterRadius
        bool merged = false;
        for (auto& cl : clusters) {
            if (cl.centerPos.Distance(itemPos) <= m_config.clusterRadius) {
                cl.entityIds.push_back(ent.id);
                cl.packetIndices.push_back(i);
                cl.totalChaosValue += val;
                cl.highestPriority = (std::max)(cl.highestPriority, prio);
                if (prio >= 90) cl.hasCriticalItem = true;

                // Cập nhật lại trọng tâm cụm
                const float n = static_cast<float>(cl.entityIds.size());
                cl.centerPos.x = (cl.centerPos.x * (n - 1.0f) + itemPos.x) / n;
                cl.centerPos.y = (cl.centerPos.y * (n - 1.0f) + itemPos.y) / n;
                merged = true;
                break;
            }
        }

        if (!merged) {
            LootCluster newCl;
            newCl.clusterId = static_cast<int>(clusters.size()) + 1;
            newCl.centerPos = itemPos;
            newCl.entityIds.push_back(ent.id);
            newCl.packetIndices.push_back(i);
            newCl.totalChaosValue = val;
            newCl.highestPriority = prio;
            newCl.hasCriticalItem = (prio >= 90);
            clusters.push_back(newCl);
        }
    }

    // 2. Tính toán Kinetic Cost và Value Density cho từng cụm
    const float progLenSq = mapProgressionDir.x * mapProgressionDir.x + mapProgressionDir.y * mapProgressionDir.y;
    const bool hasProgDir = (progLenSq > 0.001f);
    navigation::Vec2 normProgDir{ 0.0f, 0.0f };
    if (hasProgDir) {
        const float invLen = 1.0f / std::sqrt(progLenSq);
        normProgDir.x = mapProgressionDir.x * invLen;
        normProgDir.y = mapProgressionDir.y * invLen;
    }

    for (auto& cl : clusters) {
        cl.distanceToPlayer = playerPos.Distance(cl.centerPos);

        float penalty = 1.0f;
        cl.isBacktracking = false;
        cl.detourAngleDeg = 0.0f;

        if (hasProgDir && cl.distanceToPlayer > 5.0f) {
            const float toClusterX = (cl.centerPos.x - playerPos.x) / cl.distanceToPlayer;
            const float toClusterY = (cl.centerPos.y - playerPos.y) / cl.distanceToPlayer;
            const float dot = toClusterX * normProgDir.x + toClusterY * normProgDir.y;
            const float clampedDot = std::clamp(dot, -1.0f, 1.0f);

            cl.detourAngleDeg = std::acos(clampedDot) * (180.0f / 3.14159265f);

            if (dot < -0.1f) {
                cl.isBacktracking = true;
                // Phạt lũy tiến theo góc quay đầu ngược hướng
                penalty = 1.0f + m_config.backtrackPenaltyFactor * (1.0f - dot);
            }
        }

        const float travelTime = cl.distanceToPlayer / (std::max)(1.0f, m_config.playerMovementSpeed);
        const float dwellTime = static_cast<float>(cl.entityIds.size()) * m_config.itemPickupDwellSec;
        cl.kineticCostSeconds = travelTime * penalty + dwellTime;
        cl.kineticCostSeconds = (std::max)(0.05f, cl.kineticCostSeconds);

        cl.valueDensity = cl.totalChaosValue / cl.kineticCostSeconds;

        // Quyết định nhặt hay bỏ qua
        if (cl.hasCriticalItem) {
            cl.shouldPickup = true; // Bất biến: Divine/Mirror/T15+ luôn nhặt
        } else if (cl.isBacktracking) {
            // Đi ngược đường: Chỉ nhặt nếu giá trị tổng >= ngưỡng và mật độ đạt chuẩn
            cl.shouldPickup = (cl.totalChaosValue >= m_config.minBacktrackChaosValue &&
                               cl.valueDensity >= m_config.minDensityThreshold);
        } else {
            cl.shouldPickup = (cl.valueDensity >= m_config.minDensityThreshold || cl.distanceToPlayer <= 22.0f);
        }
    }

    return clusters;
}

const LootCluster* LootDensityClusterer::SelectBestCluster(const std::vector<LootCluster>& clusters) const {
    const LootCluster* best = nullptr;
    float bestScore = -1.0f;

    for (const auto& cl : clusters) {
        if (!cl.shouldPickup) continue;

        // Cụm chứa item critical luôn có điểm vượt trội
        float score = cl.valueDensity;
        if (cl.hasCriticalItem) {
            score += 10000.0f;
        }

        if (score > bestScore) {
            bestScore = score;
            best = &cl;
        }
    }

    return best;
}

std::vector<uint32_t> LootDensityClusterer::OrderItemsWithinCluster(
    const LootCluster& cluster,
    const TelemetryPacket& packet,
    const navigation::Vec2& playerPos
) const {
    std::vector<uint32_t> ordered;
    if (cluster.packetIndices.empty()) return ordered;

    std::vector<bool> visited(cluster.packetIndices.size(), false);
    navigation::Vec2 currentPos = playerPos;

    for (size_t step = 0; step < cluster.packetIndices.size(); ++step) {
        int bestIdx = -1;
        float bestDist = 1e9f;

        for (size_t i = 0; i < cluster.packetIndices.size(); ++i) {
            if (visited[i]) continue;
            const auto& ent = packet.entities[cluster.packetIndices[i]];
            const float d = currentPos.Distance({ ent.posX, ent.posY });
            if (d < bestDist) {
                bestDist = d;
                bestIdx = static_cast<int>(i);
            }
        }

        if (bestIdx >= 0) {
            visited[bestIdx] = true;
            const auto& ent = packet.entities[cluster.packetIndices[bestIdx]];
            ordered.push_back(ent.id);
            currentPos = { ent.posX, ent.posY };
        }
    }

    return ordered;
}

} // namespace looting
