#include "navigation/quest_navigator.hpp"
#include "navigation/movement_controller.hpp"
#include "brain/area_events.hpp"
#include "brain/character_fsm.hpp"

#include <cmath>
#include <iostream>
#include <thread>
#include <chrono>
#include <algorithm>
#include "common/logger.hpp"
#include "memory/player_finder.hpp"
#include "common/coordinate_transform.hpp"
#include "common/zone_classifier.hpp"
#include "common/window_utils.hpp"

QuestNavigator::QuestNavigator(const QuestNavConfig& config)
    : m_config(config), m_expectedMaxHP(config.expectedMaxHP) {
    if (!config.targetZone.empty()) {
        m_state.targetName = config.targetZone;
    }
    m_state.isMapRevealedExternally = config.mapRevealerMode;
    m_pathfinder.SetAlgorithm(navigation::PathAlgorithm::JPS_FAST);
}

void QuestNavigator::RecordPosition(float worldX, float worldY) {
    if (!m_heatmapInitialized) {
        m_heatmapOriginX = worldX - (static_cast<float>(kHeatmapDim) / 2.0f) * kHeatmapCellSize;
        m_heatmapOriginY = worldY - (static_cast<float>(kHeatmapDim) / 2.0f) * kHeatmapCellSize;
        m_visitedHeatmap.assign(static_cast<size_t>(kHeatmapDim) * kHeatmapDim, uint8_t{0});
        m_heatmapInitialized = true;
        m_totalTilesVisited = 0;
    }

    int32_t gx = static_cast<int32_t>(std::floor((worldX - m_heatmapOriginX) / kHeatmapCellSize));
    int32_t gy = static_cast<int32_t>(std::floor((worldY - m_heatmapOriginY) / kHeatmapCellSize));
    if (gx >= 0 && gx < kHeatmapDim && gy >= 0 && gy < kHeatmapDim) {
        size_t idx = static_cast<size_t>(gy) * kHeatmapDim + static_cast<size_t>(gx);
        if (m_visitedHeatmap[idx] == 0) {
            ++m_totalTilesVisited;
        }
        if (m_visitedHeatmap[idx] < 255) {
            ++m_visitedHeatmap[idx];
        }
    }
}

uint8_t QuestNavigator::GetVisitCount(float worldX, float worldY) const {
    if (!m_heatmapInitialized || m_visitedHeatmap.empty()) return 0;
    int32_t gx = static_cast<int32_t>(std::floor((worldX - m_heatmapOriginX) / kHeatmapCellSize));
    int32_t gy = static_cast<int32_t>(std::floor((worldY - m_heatmapOriginY) / kHeatmapCellSize));
    if (gx >= 0 && gx < kHeatmapDim && gy >= 0 && gy < kHeatmapDim) {
        return m_visitedHeatmap[static_cast<size_t>(gy) * kHeatmapDim + static_cast<size_t>(gx)];
    }
    return 0;
}

void QuestNavigator::ResetHeatmap() {
    if (!m_visitedHeatmap.empty()) {
        std::fill(m_visitedHeatmap.begin(), m_visitedHeatmap.end(), uint8_t{0});
    }
    m_heatmapInitialized = false;
    m_totalTilesVisited = 0;
}

float QuestNavigator::GetExplorationCoverage() const {
    if (m_totalTilesVisited == 0) return 0.0f;
    // Mỗi ô heatmap là 32u x 32u. Một bản đồ POE2 trung bình có ~180 ô hành lang đi được.
    constexpr float kExpectedMapWalkableTiles = 180.0f;
    float ratio = static_cast<float>(m_totalTilesVisited) / kExpectedMapWalkableTiles;
    return (std::min)(ratio, 1.0f);
}

bool QuestNavigator::IsMapFullyExplored(float threshold) const {
    return GetExplorationCoverage() >= threshold;
}

bool QuestNavigator::DetectRevealedMapState(const TelemetryPacket& packet) {
    if (m_config.mapRevealerMode) {
        m_state.isMapRevealedExternally = true;
        return true;
    }
    // Nếu cờ được bật thủ công hoặc qua SetMapRevealedExternally
    return m_state.isMapRevealedExternally;
}

bool QuestNavigator::FindTopologicalFrontier(float playerX, float playerY, float& outGoalX, float& outGoalY) {
    if (!m_terrainGrid) return false;

    float bestScore = -999999.0f;
    float bestX = 0.0f;
    float bestY = 0.0f;
    bool foundCandidate = false;

    const float radii[] = { 100.0f, 180.0f, 260.0f, 340.0f };
    const float currentDistFromEntry = std::sqrt(
        (playerX - m_state.mapEntryX) * (playerX - m_state.mapEntryX) +
        (playerY - m_state.mapEntryY) * (playerY - m_state.mapEntryY)
    );

    for (float r : radii) {
        for (int i = 0; i < 16; ++i) {
            float angle = static_cast<float>(i) * (6.283185f / 16.0f);
            float candX = playerX + std::cos(angle) * r;
            float candY = playerY + std::sin(angle) * r;

            if (!m_terrainGrid->IsWalkable(candX, candY)) continue;

            uint8_t visits = GetVisitCount(candX, candY);
            float score = 0.0f;
            if (visits == 0) {
                score += 1000.0f;
            } else {
                score -= static_cast<float>(visits) * 150.0f;
            }

            float candDistFromEntry = std::sqrt(
                (candX - m_state.mapEntryX) * (candX - m_state.mapEntryX) +
                (candY - m_state.mapEntryY) * (candY - m_state.mapEntryY)
            );
            score += (candDistFromEntry - currentDistFromEntry) * 2.5f;

            float angleDiff = std::abs(angle - m_state.exploreAngle);
            if (angleDiff > 3.14159f) angleDiff = 6.283185f - angleDiff;
            score -= angleDiff * 50.0f;

            if (score > bestScore) {
                bestScore = score;
                bestX = candX;
                bestY = candY;
                foundCandidate = true;
            }
        }
    }

    if (foundCandidate && bestScore > 0.0f) {
        outGoalX = bestX;
        outGoalY = bestY;
        return true;
    }
    return false;
}

void QuestNavigator::StopMovement(KMBoxNet& kmbox) {
    if (m_moveCtrl) {
        m_moveCtrl->Stop(kmbox);
    }
    if (m_currentlyHeldKeys.empty()) return;

    for (uint8_t vk : m_currentlyHeldKeys) {
        kmbox.KeyUpVk(vk);
    }
    m_currentlyHeldKeys.clear();
}

void QuestNavigator::SendDirection(float dx, float dy, KMBoxNet& kmbox, uint64_t nowMs) {
    if (!EnsurePoe2WindowFocus()) return;
    if (m_moveCtrl) {
        m_moveCtrl->SteerTowards(dx, dy, kmbox, nowMs);
        return;
    }
    if (m_config.moveMode == MovementMode::MOUSE) {
        StopMovement(kmbox);
        // Chế độ Click-to-Move: Chuyển vector thế giới (dx, dy) thành click chuột quanh tâm màn hình
        const float len = std::sqrt(dx * dx + dy * dy);
        if (len < 0.01f) return;

        int screenW = 0;
        int screenH = 0;
        common::GetGameResolution(screenW, screenH);

        const float dist = (std::min)(static_cast<float>(screenH) * 0.40f, 580.0f);
        const auto target = common::WorldToScreenIsometric(dx, dy, dist, static_cast<float>(screenW), static_cast<float>(screenH), 80.0f);

        kmbox.MoveMouseSmooth(static_cast<int>(target.x), static_cast<int>(target.y), m_curveGen, 12, 1);
        kmbox.ClickMouseHumanized(1, m_curveGen, 55.0f, 10.0f);
        return;
    }

    // Chế độ WASD: Chuyển vector 2D thành phím di chuyển WASD (Hỗ trợ 8 hướng chéo chuẩn Isometric)
    const auto wasd = common::WorldToWasd(dx, dy);
    if (wasd.empty()) {
        StopMovement(kmbox);
        return;
    }

    std::vector<uint8_t> moveKeys;
    if (wasd.up) {
        moveKeys.push_back(0x57);    // 'W'
        if (m_config.pulseEWithForward) {
            static uint64_t s_lastPulseEMs = 0;
            if (nowMs == 0 || nowMs - s_lastPulseEMs >= 800) {
                s_lastPulseEMs = nowMs;
                moveKeys.push_back(0x45);    // 'E' (VK 0x45)
            }
        }
    }
    if (wasd.down) moveKeys.push_back(0x53);  // 'S'
    if (wasd.left) moveKeys.push_back(0x41);  // 'A'
    if (wasd.right) moveKeys.push_back(0x44); // 'D'

    if (moveKeys.empty()) {
        StopMovement(kmbox);
        return;
    }

    // Cơ chế Hold-Until-Waypoint chuẩn hóa INV-WASD-MIN-DWELL:
    // Nếu hướng đang giữ đã khớp, duy trì KeyDown liên tục để nhân vật bước đi mượt mà!
    if (moveKeys == m_currentlyHeldKeys) {
        return;
    }

    // INV-WASD-MIN-DWELL: Khi đang giữ một tổ hợp phím di chuyển, duy trì tối thiểu 300ms trước khi đổi hướng mới
    // để loại bỏ triệt để micro-jitter khi vector nằm ở ranh giới 2 hướng liền kề và đảm bảo bước đi mượt mà
    if (nowMs > 0 && !m_currentlyHeldKeys.empty() && (nowMs - m_lastDirectionChangeMs < 300)) {
        return;
    }
    m_lastDirectionChangeMs = nowMs;

    // Hướng thay đổi: Nhả phím không còn thuộc hướng mới
    for (uint8_t oldVk : m_currentlyHeldKeys) {
        if (std::find(moveKeys.begin(), moveKeys.end(), oldVk) == moveKeys.end()) {
            kmbox.KeyUpVk(oldVk);
        }
    }
    for (uint8_t newVk : moveKeys) {
        if (std::find(m_currentlyHeldKeys.begin(), m_currentlyHeldKeys.end(), newVk) == m_currentlyHeldKeys.end()) {
            kmbox.KeyDownVk(newVk);
        }
    }

    m_currentlyHeldKeys = moveKeys;
}

void QuestNavigator::NavigateTo(float startX, float startY, float goalX, float goalY, KMBoxNet& kmbox, uint64_t nowMs) {
    m_currentGoal = navigation::Vec2(goalX, goalY);
    if (m_terrainGrid) {
        m_terrainGrid->RecenterIfNeeded(startX, startY);

        float goalDiffSq = (goalX - m_currentGoal.x) * (goalX - m_currentGoal.x) + (goalY - m_currentGoal.y) * (goalY - m_currentGoal.y);
        if (m_waypoints.empty() || m_waypointIdx >= m_waypoints.size() || goalDiffSq > 225.0f || (nowMs - m_lastPathPlanMs > 1800)) {
            m_lastPathPlanMs = nowMs;
            m_waypoints = m_pathfinder.FindPath(navigation::Vec2(startX, startY), m_currentGoal, *m_terrainGrid);
            m_waypointIdx = 0;
        }

        // Bỏ qua các waypoint đã đi tới gần (cự ly < 16 units)
        while (m_waypointIdx < m_waypoints.size()) {
            float dist = m_waypoints[m_waypointIdx].Distance(navigation::Vec2(startX, startY));
            if (dist < 16.0f && m_waypointIdx + 1 < m_waypoints.size()) {
                ++m_waypointIdx;
            } else {
                break;
            }
        }

        if (m_waypointIdx < m_waypoints.size()) {
            float steerDx = m_waypoints[m_waypointIdx].x - startX;
            float steerDy = m_waypoints[m_waypointIdx].y - startY;

            // Kỹ năng di chuyển đường trường (Mobility Skill Travel / Spacebar Dodge Roll):
            // Nếu đường thẳng tới waypoint dài > 240 units và không có vật cản
            if (m_config.enableMobilitySkillTravel && (nowMs - m_state.lastMobilitySkillMs >= m_config.mobilityCooldownMs)) {
                float directDist = std::sqrt(steerDx * steerDx + steerDy * steerDy);
                if (directDist > 240.0f && m_terrainGrid->HasLineOfSight(startX, startY, m_waypoints[m_waypointIdx].x, m_waypoints[m_waypointIdx].y)) {
                    kmbox.KeyPressVk(VK_SPACE, 25);
                    m_state.lastMobilitySkillMs = nowMs;
                }
            }

            SendDirection(steerDx, steerDy, kmbox, nowMs);
            return;
        }
    }

    // Fallback: Di chuyển thẳng nếu chưa có Grid
    SendDirection(goalX - startX, goalY - startY, kmbox, nowMs);
}

bool QuestNavigator::Update(const TelemetryPacket& packet, KMBoxNet& kmbox, uint64_t nowMs) {
    if (!m_config.enabled) return false;
    if (!EnsurePoe2WindowFocus()) return false;

    const auto& player = packet.player;
    if (player.maxHP > 0 && player.currentHP == 0) return false; // Chỉ chặn khi đã tử trận

    // AN TOÀN SINH MỆNH (Grace Period Protection):
    const bool isTransitioning = (packet.area.areaLoadTimestampMs > 0 && 
                                  nowMs >= packet.area.areaLoadTimestampMs && 
                                  nowMs - packet.area.areaLoadTimestampMs <= 4000);
    const bool allowUncalibratedBypass = (m_expectedMaxHP > 0 || isTransitioning);

    if (player.maxHP == 0 && !allowUncalibratedBypass) return false;

    const bool hasValidXYZ = PlayerFinder::IsValidXYZ(player.posX, player.posY, player.posZ);
    if (hasValidXYZ) {
        RecordPosition(player.posX, player.posY);
        if (m_state.lastMicroCheckMs == 0) {
            m_state.lastMicroCheckMs = nowMs;
            m_state.prevPosSampleX = player.posX;
            m_state.prevPosSampleY = player.posY;
        }
    }

    if (m_fsm != nullptr && m_fsm->WantsPortalExit()) {
        TriggerTownPortalExit(kmbox, nowMs);
        return true;
    }

    // Kiểm tra xem có mục tiêu nhiệm vụ cần tương tác ngay lập tức không
    float checkDist = 9999.0f;
    int immediateTarget = -1;
    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type == 3 || ent.type == 4) {
            const float dx = ent.posX - player.posX;
            const float dy = ent.posY - player.posY;
            const float d = std::sqrt(dx * dx + dy * dy);
            if (d < checkDist) {
                checkDist = d;
                immediateTarget = static_cast<int>(i);
            }
        }
    }
    const bool isReadyToInteract = (immediateTarget != -1 && checkDist <= m_config.interactRadius);

    // Dynamic Movement Cadence (Chống khựng giật & duy trì tốc độ chạy liên tục)
    uint32_t effectiveInterval = m_config.stepIntervalMs;
    if (!isReadyToInteract && effectiveInterval <= 120) {
        if (m_config.moveMode == MovementMode::MOUSE) {
            if (m_state.isPatrolling || m_state.targetDistance > 80.0f) {
                effectiveInterval = m_config.fastMoveIntervalMs; // 200ms - tốc độ liên tục khi tuần tra / chạy đường trường
            } else {
                effectiveInterval = m_config.normalMoveIntervalMs; // 450ms khi gần mục tiêu
            }
        } else {
            effectiveInterval = 180; // 180ms nhịp cập nhật vector chuẩn cho WASD
        }
    }
    if (nowMs - m_state.lastStepMs < effectiveInterval) return false;

    // [INV-NAV-TERRAIN-COMMERCIAL] Phản Xạ Trượt Tiếp Tuyến Thời Gian Thực (Tangent Vector Sliding / Wall Following):
    // Khi di chuyển gặp vật cản hoặc gờ đá (Delta XYZ xấp xỉ 0 trong khoảng 350ms):
    // Tự động tính toán vector trượt tiếp tuyến men theo bờ vật cản để đi vòng qua chướng ngại vật mượt mà.
    if (hasValidXYZ && (m_state.isPatrolling || m_state.targetDistance > 15.0f || m_state.targetEntityId != 0)) {
        if (m_state.lastMicroCheckMs == 0) {
            m_state.lastMicroCheckMs = nowMs;
            m_state.prevPosSampleX = player.posX;
            m_state.prevPosSampleY = player.posY;
        } else if (nowMs - m_state.lastMicroCheckMs >= 350) {
            const float microDx = player.posX - m_state.prevPosSampleX;
            const float microDy = player.posY - m_state.prevPosSampleY;
            const float microDistSq = microDx * microDx + microDy * microDy;

            // Nếu đã gửi lệnh di chuyển mà tọa độ dịch chuyển < 1.5u^2 (Delta XYZ ~ 0)
            if (microDistSq < 1.5f && (nowMs - m_state.lastStepMs < 800)) {
                float fwdDx = std::cos(m_state.exploreAngle);
                float fwdDy = std::sin(m_state.exploreAngle);
                if (!m_state.isPatrolling && (m_currentGoal.x != 0.0f || m_currentGoal.y != 0.0f)) {
                    float gdx = m_currentGoal.x - player.posX;
                    float gdy = m_currentGoal.y - player.posY;
                    float glen = std::sqrt(gdx * gdx + gdy * gdy);
                    if (glen > 0.001f) {
                        fwdDx = gdx / glen;
                        fwdDy = gdy / glen;
                    }
                }

                if (m_terrainGrid) {
                    m_terrainGrid->SetBlocked(player.posX + fwdDx * 12.0f, player.posY + fwdDy * 12.0f, 10.0f);
                    m_waypoints.clear();
                }

                navigation::Vec2 goalPos = m_currentGoal;
                if (goalPos.x == 0.0f && goalPos.y == 0.0f) {
                    goalPos = navigation::Vec2(player.posX + fwdDx * 50.0f, player.posY + fwdDy * 50.0f);
                }

                auto slideRes = m_pathfinder.ComputeTangentSlide(
                    navigation::Vec2(player.posX, player.posY),
                    navigation::Vec2(fwdDx, fwdDy),
                    goalPos,
                    m_terrainGrid,
                    m_state.tangentHand
                );

                if (slideRes.isSliding) {
                    m_state.isTangentSliding = true;
                    m_state.tangentHand = slideRes.handDirection;
                    m_state.exploreAngle = slideRes.slideAngle;
                    ++m_state.tangentSlideCount;

                    float escapeDx = slideRes.slideVector.x * 50.0f;
                    float escapeDy = slideRes.slideVector.y * 50.0f;
                    SendDirection(escapeDx, escapeDy, kmbox, nowMs);

                    const std::string sLog = "[INV-NAV-TERRAIN-COMMERCIAL #" + std::to_string(m_state.tangentSlideCount)
                        + "] Delta XYZ ~ 0 -> Truot tiep tuyen (hand=" + std::to_string(m_state.tangentHand)
                        + ", goc=" + std::to_string(static_cast<int>(slideRes.slideAngle * 57.2958f)) + " do)!";
                    std::cout << sLog << std::endl;
                    CoreLog(sLog);

                    m_state.lastStepMs = nowMs;
                    m_state.prevPosSampleX = player.posX;
                    m_state.prevPosSampleY = player.posY;
                    m_state.lastMicroCheckMs = nowMs;
                    return true;
                }
            } else {
                m_state.isTangentSliding = false;
                m_state.tangentHand = 0;
            }

            m_state.prevPosSampleX = player.posX;
            m_state.prevPosSampleY = player.posY;
            m_state.lastMicroCheckMs = nowMs;
        }
    }

    // Bộ Giám Sát Kẹt Tường & Địa Hình Thông Minh (A* Dynamic Learning & Tangent Glide)
    if (hasValidXYZ) {
        const float movedDx = player.posX - m_state.lastPlayerX;
        const float movedDy = player.posY - m_state.lastPlayerY;
        const float movedDistSq = movedDx * movedDx + movedDy * movedDy;
        if (movedDistSq > 3.0f) {
            m_state.lastPlayerX = player.posX;
            m_state.lastPlayerY = player.posY;
            m_state.lastPosCheckMs = nowMs;
            m_state.stuckCycles = 0;
        } else if (m_state.lastPosCheckMs == 0) {
            m_state.lastPosCheckMs = nowMs;
            m_state.lastPlayerX = player.posX;
            m_state.lastPlayerY = player.posY;
        } else if (nowMs - m_state.lastPosCheckMs >= 4000) {
            // Đã gửi lệnh di chuyển > 4000ms nhưng tọa độ đứng im -> PHẢN XẠ NÉ TƯỜNG TIẾP TUYẾN
            ++m_state.stuckCycles;
            m_state.lastPosCheckMs = nowMs;
            m_state.lastDirectionChangeMs = nowMs;

            if (m_terrainGrid) {
                float fwdX = player.posX + std::cos(m_state.exploreAngle) * 14.0f;
                float fwdY = player.posY + std::sin(m_state.exploreAngle) * 14.0f;
                m_terrainGrid->SetBlocked(fwdX, fwdY, 12.0f);
                m_waypoints.clear();
                CoreLog("[JPS Dynamic Obstacle] Va cham vat can -> danh dau Blocked va tinh huong truot!");
            }

            float deflection = (m_state.stuckCycles % 2 == 1) ? 1.309f : -1.309f;
            if (m_state.stuckCycles >= 3) {
                deflection = 3.14159f;
            }
            m_state.exploreAngle += deflection;
            if (m_state.exploreAngle > 6.283185f) m_state.exploreAngle -= 6.283185f;
            if (m_state.exploreAngle < 0.0f) m_state.exploreAngle += 6.283185f;

            float escapeDx = std::cos(m_state.exploreAngle) * 50.0f;
            float escapeDy = std::sin(m_state.exploreAngle) * 50.0f;
            SendDirection(escapeDx, escapeDy, kmbox, nowMs);

            const std::string sLog = "[AntiStuck #" + std::to_string(m_state.stuckCycles)
                + "] Phat hien cham tuong > 4000ms -> Be lai tiep tuyen (" + std::to_string(static_cast<int>(deflection * 57.2958f)) + " do)!";
            std::cout << sLog << std::endl;
            CoreLog(sLog);
            m_state.lastStepMs = nowMs;
            return true;
        }
    }

    // Nhận diện xem khu vực hiện tại có phải là Thành / Hideout hay không qua SSoT ZoneClassifier
    const bool isTown = common::IsTownOrHideout(common::ClassifyZone(packet.area));

    if (isTown) {
        ResetMapState();
    }

    // QUY TRÌNH HẬU CHIẾN (Post-Boss Exit Routine - Boss Rush / Endgame Mapping):
    if (!isTown && m_state.bossSlain) {
        if (m_areaEventMgr && m_areaEventMgr->HasCommittedEvent()) {
            m_state.targetName = "Committed Area Event Active (INV-EVENT-01)";
            return false;
        }

        if (!m_state.portalSpawned) {
            if (m_config.autoPortalAfterBoss) {
                if (nowMs - m_state.bossSlainTimestampMs >= m_config.postBossLootWaitMs) {
                    TriggerTownPortalExit(kmbox, nowMs);
                    m_state.portalSpawned = true;
                    m_state.targetName = "Portal to Hideout";
                    m_state.isPatrolling = false;
                    return true;
                } else {
                    const uint32_t remainSec = static_cast<uint32_t>((m_config.postBossLootWaitMs - (nowMs - m_state.bossSlainTimestampMs)) / 100);
                    m_state.targetName = "Looting Boss Drops (" + std::to_string(remainSec) + "ds)";
                    m_state.isPatrolling = false;
                    return false;
                }
            } else {
                m_state.targetName = "Boss Slain (Manual Mode)";
                return false;
            }
        } else {
            float closestPortalDist = 9999.0f;
            int portalIdx = -1;
            for (uint32_t i = 0; i < packet.entityCount; ++i) {
                const auto& ent = packet.entities[i];
                if (ent.type == 3) {
                    const float dx = ent.posX - player.posX;
                    const float dy = ent.posY - player.posY;
                    const float dist = std::sqrt(dx * dx + dy * dy);
                    if (dist < closestPortalDist) {
                        closestPortalDist = dist;
                        portalIdx = static_cast<int>(i);
                    }
                }
            }
            if (portalIdx != -1) {
                const auto& pEnt = packet.entities[portalIdx];
                if (closestPortalDist <= m_config.interactRadius) {
                    TriggerInteract(kmbox, nowMs, pEnt.name[0] ? pEnt.name : "Town Portal", pEnt.posX, pEnt.posY, player.posX, player.posY);
                    m_state.bossSlain = false;
                    return true;
                } else {
                    NavigateTo(player.posX, player.posY, pEnt.posX, pEnt.posY, kmbox, nowMs);
                    m_state.lastStepMs = nowMs;
                    return true;
                }
            }
            return false;
        }
    }

    // Kiểm tra trạng thái Boss còn sống hay đã chết
    bool currentFrameHasBoss = false;
    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type == 1 && !(ent.extraFlags & 4) && ((ent.extraFlags & 1) || ent.rarity == 3)) {
            currentFrameHasBoss = true;
            if (m_state.targetEntityId == ent.id && ent.currentHP == 0) {
                currentFrameHasBoss = false;
            }
            break;
        }
    }

    if (!isTown && m_state.bossSpotted && !m_state.bossSlain && !currentFrameHasBoss) {
        m_state.bossSlain = true;
        m_state.bossSlainTimestampMs = nowMs;
        ++m_state.mapCompletedCount;
        const std::string sKill = "[BossRush] >> BOSS MAP ĐÃ BỊ HẠ GỤC! (Map #" + std::to_string(m_state.mapCompletedCount)
            + "). Chờ " + std::to_string(m_config.postBossLootWaitMs) + "ms nhặt loot rồi tự mở Portal về Hideout! <<";
        std::cout << "\n" << sKill << std::endl;
        CoreLog(sKill);
        m_state.targetName = "Boss Defeated! Looting...";
        return false;
    }

    // KỊCH BẢN ATLAS_QUEST_RUSH: Tự động khóa và dẫn đường tới Sự Kiện / Nhiệm Vụ Atlas
    if (!isTown && m_config.scenario == MapScenario::ATLAS_QUEST_RUSH && m_areaEventMgr) {
        const auto& events = m_areaEventMgr->GetEvents();
        float bestEvtDist = 9999.0f;
        const AreaEventRecord* targetEvt = nullptr;

        for (const auto& evt : events) {
            if (evt.status == AreaEventStatus::Candidate || evt.status == AreaEventStatus::Committed) {
                const float dx = evt.x - player.posX;
                const float dy = evt.y - player.posY;
                const float dist = std::sqrt(dx * dx + dy * dy);
                if (dist < bestEvtDist) {
                    bestEvtDist = dist;
                    targetEvt = &evt;
                }
            }
        }

        if (targetEvt) {
            m_state.targetEntityId = targetEvt->entityId;
            m_state.targetName = std::string("Atlas Event: ") + (targetEvt->name[0] ? targetEvt->name : "Objective");
            m_state.targetDistance = bestEvtDist;
            m_state.isPatrolling = false;

            if (bestEvtDist <= m_config.interactRadius) {
                TriggerInteract(kmbox, nowMs, targetEvt->name, targetEvt->x, targetEvt->y, player.posX, player.posY);
                m_areaEventMgr->MarkCompleted(targetEvt->entityId);
                return true;
            } else {
                NavigateTo(player.posX, player.posY, targetEvt->x, targetEvt->y, kmbox, nowMs);
                m_state.lastStepMs = nowMs;
                return true;
            }
        }
    }

    // Tìm thực thể mục tiêu nhiệm vụ (Portal, Waypoint, Boss Map, hoặc NPC)
    float bestDist = 9999.0f;
    int targetIndex = -1;
    int bestPriority = -1; // 4: Boss Map, 3: Boss Arena Gate, 2: Portal/Waypoint, 1: NPC

    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        int priority = -1;

        if (!isTown && ent.type == 1 && !(ent.extraFlags & 4) && ((ent.extraFlags & 1) || ent.rarity == 3)) {
            priority = 4;
        } else if (!isTown && ent.type == 3) {
            std::string entName(ent.name);
            bool isArenaGate = false;
            for (const char* kw : {"Arena", "Lair", "Chamber", "Sanctum", "Throne", "Boss", "Gate"}) {
                if (entName.find(kw) != std::string::npos) {
                    isArenaGate = true;
                    break;
                }
            }
            priority = (isArenaGate && m_config.scenario == MapScenario::BOSS_RUSH) ? 3 : 2;
        } else if (isTown && ent.type == 3) {
            priority = 3;
        } else if (ent.type == 4) {
            priority = isTown ? 2 : 1;
        }

        if (priority > 0) {
            const float dx = ent.posX - player.posX;
            const float dy = ent.posY - player.posY;
            const float dist = std::sqrt(dx * dx + dy * dy);
            if (priority > bestPriority || (priority == bestPriority && dist < bestDist)) {
                bestDist = dist;
                targetIndex = static_cast<int>(i);
                bestPriority = priority;
            }
        }
    }

    if (targetIndex != -1) {
        const auto& target = packet.entities[targetIndex];
        const float dist = bestDist;
        m_state.targetEntityId = target.id;
        m_state.targetName = (bestPriority >= 3 && target.type == 1) ? (std::string("BOSS: ") + (target.name[0] ? target.name : "Elite Boss")) : target.name;
        m_state.targetDistance = dist;
        m_state.isPatrolling = false;

        if (bestPriority >= 4) {
            m_state.bossSpotted = true;
            if (dist <= m_config.combatStopDistance) {
                return false;
            } else {
                NavigateTo(player.posX, player.posY, target.posX, target.posY, kmbox, nowMs);
                m_state.lastStepMs = nowMs;
                return true;
            }
        } else {
            if (dist <= m_config.interactRadius) {
                TriggerInteract(kmbox, nowMs, target.name, target.posX, target.posY, player.posX, player.posY);
                return true;
            } else {
                NavigateTo(player.posX, player.posY, target.posX, target.posY, kmbox, nowMs);
                m_state.lastStepMs = nowMs;
                return true;
            }
        }
    }

    // ƯU TIÊN CHIẾN ĐẤU & LỌC QUÁI THEO KỊCH BẢN (Scenario Combat Filter)
    if (m_config.scenario == MapScenario::BOSS_RUSH && m_config.ignoreTrashMobsInRush) {
        // BOSS_RUSH: Bỏ qua quái rác trắng/magic, chỉ dừng khi Boss hoặc cản đường quá gần (< 85u)
        bool hasBlockingThreat = false;
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && !(ent.extraFlags & 4)) {
                const float dx = ent.posX - player.posX;
                const float dy = ent.posY - player.posY;
                const float len = std::sqrt(dx * dx + dy * dy);
                const float d = (len > 0.001f) ? len : ent.distanceToPlayer;
                const bool isBoss = (ent.rarity >= 3) || (ent.extraFlags & 1);
                if (isBoss && d <= m_config.combatStopDistance) {
                    hasBlockingThreat = true;
                    break;
                } else if (!isBoss && d <= 85.0f) {
                    hasBlockingThreat = true;
                    break;
                }
            }
        }
        if (hasBlockingThreat) {
            m_state.isPatrolling = false;
            return false;
        }
    } else if (m_config.scenario == MapScenario::FAST_CLEAR) {
        // FAST_CLEAR: Phân tích cụm quái (Pack Clustering). Bỏ qua 1-2 quái rác lẻ loi!
        uint32_t packCount = 0;
        bool hasRareOrBoss = false;
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && !(ent.extraFlags & 4)) {
                const float dx = ent.posX - player.posX;
                const float dy = ent.posY - player.posY;
                const float len = std::sqrt(dx * dx + dy * dy);
                const float d = (len > 0.001f) ? len : ent.distanceToPlayer;
                if (d <= m_config.combatStopDistance) {
                    if (ent.rarity >= 2 || (ent.extraFlags & 1)) {
                        hasRareOrBoss = true;
                        break;
                    }
                    ++packCount;
                }
            }
        }
        if (hasRareOrBoss || packCount >= 3) {
            m_state.isPatrolling = false;
            return false; // Chỉ dừng khi gặp cụm >= 3 quái hoặc quái Rare/Boss
        }
    } else {
        // FULL_CLEAR & Campaign: Dừng khi có bất kỳ quái nào trong cự ly combatStopDistance
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && !(ent.extraFlags & 4)) {
                const float dx = ent.posX - player.posX;
                const float dy = ent.posY - player.posY;
                const float len = std::sqrt(dx * dx + dy * dy);
                const float d = (len > 0.001f) ? len : ent.distanceToPlayer;
                if (d <= m_config.combatStopDistance) {
                    m_state.isPatrolling = false;
                    return false;
                }
            }
        }
    }

    // Chế độ Autonomous Map Explorer (Tuần tra Frontier Momentum 2.0 tích hợp Heatmap):
    if (m_config.autoPatrol) {
        if (!hasValidXYZ && kmbox.IsConnected()) {
            // Gỡ bỏ kẹt Cold-Start: Cho phép gửi một bước chân thăm dò khi mới vào map (WAITING_FIRST_STEP) để PlayerFinder bắt vi sai tọa độ
            float probeDx = std::cos(m_state.exploreAngle) * 50.0f;
            float probeDy = std::sin(m_state.exploreAngle) * 50.0f;
            SendDirection(probeDx, probeDy, kmbox, nowMs);
            m_state.lastStepMs = nowMs;
            m_state.targetName = "Cold-Start Probing Step (WAITING_FIRST_STEP)";
            return true;
        }

        m_state.isPatrolling = true;
        m_state.targetDistance = 0.0f;
        DetectRevealedMapState(packet);

        if (!m_state.mapEntryRecorded && hasValidXYZ) {
            m_state.mapEntryX = player.posX;
            m_state.mapEntryY = player.posY;
            m_state.mapEntryRecorded = true;
            m_state.maxDistanceFromEntry = 0.0f;
            CoreLog("[AutoPatrol] Da ghi nhan cong vao map tai (" + std::to_string(m_state.mapEntryX)
                + ", " + std::to_string(m_state.mapEntryY) + "). Khoi dong Frontier Momentum (Revealer Mode="
                + (m_state.isMapRevealedExternally ? "TRUE" : "FALSE") + ").");
        }

        const float distFromEntry = std::sqrt(
            (player.posX - m_state.mapEntryX) * (player.posX - m_state.mapEntryX) +
            (player.posY - m_state.mapEntryY) * (player.posY - m_state.mapEntryY)
        );
        if (distFromEntry > m_state.maxDistanceFromEntry) {
            m_state.maxDistanceFromEntry = distFromEntry;
        }

        const float stepDistance = (m_config.scenario == MapScenario::BOSS_RUSH) ? m_config.rushPatrolStep : 150.0f;
        m_state.targetName = (m_config.scenario == MapScenario::BOSS_RUSH ? "Boss Rush Push (~" : "Deep Map Push (~")
            + std::to_string(static_cast<int>(distFromEntry)) + "u)";

        if (nowMs - m_state.lastDirectionChangeMs >= m_config.patrolTurnIntervalMs) {
            m_state.lastDirectionChangeMs = nowMs;

            bool handledByFrontier = false;
            // CHẾ ĐỘ FRONTIER FOG EXPLORATION (Doc 69 §3.4):
            // Ưu tiên tuyệt đối Topological Frontier trên Walked Heatmap bất cứ khi nào có TerrainGrid
            if (m_terrainGrid != nullptr || m_state.isMapRevealedExternally) {
                float frontierX = 0.0f;
                float frontierY = 0.0f;
                if (FindTopologicalFrontier(player.posX, player.posY, frontierX, frontierY)) {
                    float fdx = frontierX - player.posX;
                    float fdy = frontierY - player.posY;
                    m_state.exploreAngle = std::atan2(fdy, fdx);
                    if (m_state.exploreAngle < 0.0f) m_state.exploreAngle += 6.283185f;
                    m_state.consecutiveVisitedSteps = 0;
                    m_state.isJunctionBacktracking = false;
                    handledByFrontier = true;
                    m_state.targetName = "Frontier Momentum (~" + std::to_string(static_cast<int>(distFromEntry)) + "u)";
                } else {
                    // Không tìm thấy frontier chưa đi trong bán kính 340u -> Đi vào ngõ cụt
                    ++m_state.consecutiveVisitedSteps;
                    if (m_state.consecutiveVisitedSteps >= 2) {
                        m_state.isJunctionBacktracking = true;
                        m_state.exploreAngle += 3.14159f; // Quay lui 180 độ
                        if (m_state.exploreAngle > 6.283185f) m_state.exploreAngle -= 6.283185f;
                        m_state.targetName = "Junction Backtrack (Dead-End Escape)";
                        CoreLog("[Dual-Mode Nav] Phat hien ngo cut (toan bo o deu da tham) -> Quay lui 180 do tim nga ba!");
                        handledByFrontier = true;
                    }
                }
            }

            if (!handledByFrontier) {
                float bestAngle = m_state.exploreAngle;
                float bestScore = -99999.0f;

                // Đánh giá 7 hướng thám hiểm ứng viên (-75 đến +75 độ)
                for (int step = -3; step <= 3; ++step) {
                    float testAngle = m_state.exploreAngle + step * 0.436f; // mỗi bước 25 độ
                    float testX = player.posX + std::cos(testAngle) * stepDistance;
                    float testY = player.posY + std::sin(testAngle) * stepDistance;

                    float score = 0.0f;
                    if (m_terrainGrid && m_terrainGrid->IsWalkable(testX, testY)) {
                        score += 500.0f;
                    }

                    float testDistFromEntry = std::sqrt(
                        (testX - m_state.mapEntryX) * (testX - m_state.mapEntryX) +
                        (testY - m_state.mapEntryY) * (testY - m_state.mapEntryY)
                    );
                    score += (testDistFromEntry - distFromEntry) * 3.0f;

                    if (testDistFromEntry < distFromEntry - 20.0f) {
                        score -= 400.0f;
                    }

                    // Heatmap Visited Penalty: Phạt nặng các hướng đã đi qua nhiều lần
                    uint8_t visits = GetVisitCount(testX, testY);
                    score -= static_cast<float>(visits) * 85.0f;

                    if (score > bestScore) {
                        bestScore = score;
                        bestAngle = testAngle;
                    }
                }

                m_state.exploreAngle = bestAngle;
                if (m_state.exploreAngle > 6.283185f) m_state.exploreAngle -= 6.283185f;
                if (m_state.exploreAngle < 0.0f) m_state.exploreAngle += 6.283185f;
            }

            m_waypoints.clear();
        }

        float patrolGoalX = player.posX + std::cos(m_state.exploreAngle) * stepDistance;
        float patrolGoalY = player.posY + std::sin(m_state.exploreAngle) * stepDistance;

        NavigateTo(player.posX, player.posY, patrolGoalX, patrolGoalY, kmbox, nowMs);
        m_state.lastStepMs = nowMs;
        return true;
    }

    return false;
}

void QuestNavigator::TriggerInteract(KMBoxNet& kmbox, uint64_t nowMs, const std::string& objectName,
                                     float targetX, float targetY, float playerX, float playerY) {
    if (!EnsurePoe2WindowFocus()) return;
    m_state.lastStepMs = nowMs + 1000;

    int screenW = 0;
    int screenH = 0;
    common::GetGameResolution(screenW, screenH);

    float screenX = static_cast<float>(screenW) / 2.0f;
    float screenY = static_cast<float>(screenH) / 2.0f;

    if (targetX != 0.0f || targetY != 0.0f) {
        float dx = targetX - playerX;
        float dy = targetY - playerY;
        float worldDist = std::sqrt(dx * dx + dy * dy);
        float pixelDist = (std::min)(worldDist * 16.0f, 400.0f);
        const auto target = common::WorldToScreenIsometric(dx, dy, pixelDist, static_cast<float>(screenW), static_cast<float>(screenH), 80.0f);
        screenX = target.x;
        screenY = target.y;
    }

    // Trong POE2 KBM, tương tác (Interact) với thực thể thế giới (NPC, Portal, Rương, Checkpoint)
    // hoàn toàn là Click chuột trái (LMB) khi trỏ vào thực thể ở cự ly gần (<= 18u).
    // Phím 'F' trong POE2 là gán kỹ năng (Skill 8 theo config thật), TUYỆT ĐỐI CẤM gửi phím F để tương tác!
    kmbox.MoveMouseSmooth(static_cast<int>(screenX), static_cast<int>(screenY), m_curveGen, 12, 1);
    int dwellMs = m_curveGen.GenerateDwellTimeMs(45.0f, 8.0f);
    kmbox.ClickMouse(1, dwellMs);

    ++m_state.questsProgressed;
    const std::string logMsg = "[AutoQuest #" + std::to_string(m_state.questsProgressed)
        + "] Tuong tac LMB thanh cong voi muc tieu: " + objectName;
    std::cout << logMsg << std::endl;
    CoreLog(logMsg);
}

void QuestNavigator::TriggerTownPortalExit(KMBoxNet& kmbox, uint64_t nowMs) {
    if (!EnsurePoe2WindowFocus()) return;
    m_state.lastStepMs = nowMs + 1200;
    const std::string msg = "[QuestNavigator] >> KÍCH HOẠT MỞ TOWN PORTAL (Phím '.') THOÁT VỀ HIDEOUT! <<";
    std::cout << "\n" << msg << std::endl;
    CoreLog(msg);

    kmbox.KeyPressVk(m_config.portalVk, 50);
}

ActionProposal QuestNavigator::Propose(const TelemetryPacket& packet, uint64_t nowMs) {
    if (!m_config.enabled && !m_config.autoPatrol) return {};
    const auto& player = packet.player;
    if (player.maxHP > 0 && player.currentHP == 0) return {};

    // INV-FSM-TOWN-NO-EXPLORE: Ở Town/Hideout Idle tuyệt đối không đề xuất bất kỳ hành động nào
    if (m_fsm != nullptr && m_fsm->IsTownOrHideoutIdle()) {
        return {};
    }

    if (m_fsm != nullptr && m_fsm->WantsPortalExit()) {
        ActionProposal p{};
        p.kind = BotActionKind::SwitchArea;
        p.priority = bot_priority::SWITCH_AREA;
        p.priorityName = "PORTAL_EXIT";
        p.distance = 0.0f;
        return p;
    }

    if (m_state.bossSlain && (nowMs - m_state.bossSlainTimestampMs < m_config.postBossLootWaitMs)) {
        ActionProposal p{};
        p.kind = BotActionKind::WaitLoot;
        p.priority = bot_priority::WAITING_FOR_LOOT;
        p.priorityName = "WAITING_FOR_LOOT";
        p.distance = 0.0f;
        return p;
    }

    if (m_state.bossSpotted && !m_state.bossSlain) {
        bool bossEntityAlive = false;
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && (ent.rarity == 3 || (ent.extraFlags & 1)) && !(ent.extraFlags & 4)) {
                bossEntityAlive = true;
                break;
            }
        }
        if (!bossEntityAlive) {
            ActionProposal p{};
            p.kind = BotActionKind::WaitBoss;
            p.priority = bot_priority::WAITING_FOR_BOSS;
            p.priorityName = "WAITING_FOR_BOSS";
            p.distance = 0.0f;
            return p;
        }
    }

    if (!m_areaEventMgr || !m_areaEventMgr->HasCommittedEvent()) {
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 3 && !(ent.extraFlags & 4)) {
                const float dx = ent.posX - player.posX;
                const float dy = ent.posY - player.posY;
                const float dist = std::sqrt(dx * dx + dy * dy);
                if (dist <= m_config.interactRadius) {
                    ActionProposal p{};
                    p.kind = BotActionKind::SwitchArea;
                    p.priority = bot_priority::SWITCH_AREA;
                    p.priorityName = "SWITCH_AREA";
                    p.entityId = ent.id;
                    p.targetX = ent.posX;
                    p.targetY = ent.posY;
                    p.targetZ = ent.posZ;
                    p.distance = dist;
                    return p;
                }
            }
        }
    }

    if (m_config.autoPatrol || m_config.enabled) {
        const bool isTown = common::IsTownOrHideout(common::ClassifyZone(packet.area));

        // INV-FSM-TOWN-NO-EXPLORE: Ở Town/Hideout Idle tuyệt đối không đề xuất Explore
        if (isTown || (m_fsm != nullptr && !m_fsm->CanExplore())) {
            return {};
        }

        ActionProposal p{};
        p.kind = BotActionKind::Explore;
        p.priority = bot_priority::EXPLORE;
        p.priorityName = "EXPLORE";
        p.distance = 100.0f;
        return p;
    }

    return {};
}

bool QuestNavigator::Execute(const ActionProposal& chosen, const TelemetryPacket& packet, KMBoxNet& kmbox, uint64_t nowMs) {
    if (chosen.kind == BotActionKind::WaitLoot || chosen.kind == BotActionKind::WaitBoss) {
        return true;
    }
    if (chosen.kind == BotActionKind::SwitchArea || chosen.kind == BotActionKind::Explore) {
        return Update(packet, kmbox, nowMs);
    }
    return false;
}
