#include "combat/combo_manager.hpp"

#include <cmath>
#include <iostream>
#include <thread>
#include <chrono>
#include "common/logger.hpp"
#include "combat/reflex_manager.hpp"
#include "common/coordinate_transform.hpp"
#include "common/window_utils.hpp"
#include "navigation/movement_controller.hpp"
#include "input/non_blocking_actuator.hpp"

#include <algorithm>

ComboManager::ComboManager(const ComboConfig& config)
    : m_config(config) {
}

uint32_t ComboManager::GenerateGaussianDwellTime(float meanMs, float stddevMs, float minMs, float maxMs) {
    std::normal_distribution<float> dist(meanMs, stddevMs);
    float val = dist(m_rng);
    return static_cast<uint32_t>(std::clamp(val, minMs, maxMs));
}

void ComboManager::SendKey(uint8_t vkCode, KMBoxNet& kmbox, uint32_t holdMs, uint64_t nowMs) {
    if (!EnsurePoe2WindowFocus()) return;
    uint32_t actualHoldMs = (holdMs > 65) ? holdMs : GenerateGaussianDwellTime(48.0f, 7.0f, 35.0f, 65.0f);

    // Chế độ phi khóa NonBlockingActuator (Phase P7 SSoT: 0ms sleep, dispatch <0.005ms)
    if (m_actuator != nullptr) {
        if (vkCode == VK_LBUTTON) {
            m_actuator->ClickMouse(1, actualHoldMs, nowMs);
        } else if (vkCode == VK_RBUTTON) {
            m_actuator->ClickMouse(2, actualHoldMs, nowMs);
        } else {
            m_actuator->KeyPressVk(vkCode, actualHoldMs, nowMs);
        }
        return;
    }

    if (kmbox.IsEmulationMode()) {
        if (vkCode == VK_LBUTTON) {
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            if (actualHoldMs > 0) std::this_thread::sleep_for(std::chrono::milliseconds(actualHoldMs));
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
        } else if (vkCode == VK_RBUTTON) {
            mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
            if (actualHoldMs > 0) std::this_thread::sleep_for(std::chrono::milliseconds(actualHoldMs));
            mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
        } else {
            keybd_event(vkCode, 0, 0, 0);
            if (actualHoldMs > 0) std::this_thread::sleep_for(std::chrono::milliseconds(actualHoldMs));
            keybd_event(vkCode, 0, KEYEVENTF_KEYUP, 0);
        }
    } else {
        if (vkCode == VK_LBUTTON) {
            kmbox.ClickMouse(1, actualHoldMs);
        } else if (vkCode == VK_RBUTTON) {
            kmbox.ClickMouse(2, actualHoldMs);
        } else {
            // Mapping cơ bản VK sang HID
            uint8_t hid = 0x14; // Mặc định 'Q' (0x14)
            if (vkCode == 0x45) hid = 0x08;      // 'E' (USB HID 0x08)
            else if (vkCode == 0x52) hid = 0x15; // 'R' (USB HID 0x15)
            else if (vkCode == 0x51) hid = 0x14; // 'Q' (USB HID 0x14)
            else if (vkCode == 0x57) hid = 0x1A; // 'W' (USB HID 0x1A)
            else if (vkCode == 0x54) hid = 0x17; // 'T' (USB HID 0x17)
            else if (vkCode == 0x46) hid = 0x09; // 'F' (USB HID 0x09)
            else if (vkCode == VK_SPACE) hid = 0x2C; // Spacebar (USB HID 0x2C)
            else if (vkCode == 0x31) hid = 0x1E; // '1' (USB HID 0x1E)
            else if (vkCode == 0x32) hid = 0x1F; // '2' (USB HID 0x1F)
            kmbox.KeyPress(hid, actualHoldMs);
        }
    }
}

void ComboManager::AimAndCast(uint8_t vkCode, float dx, float dy, KMBoxNet& kmbox, uint32_t holdMs, uint64_t nowMs) {
    if (!EnsurePoe2WindowFocus()) return;
    const float len = std::sqrt(dx * dx + dy * dy);
    if (len > 0.001f) {
        int screenW = 0;
        int screenH = 0;
        common::GetGameResolution(screenW, screenH);

        // Tự động điều chỉnh khoảng cách ngắm theo cự ly thực tế của quái
        const float aimDist = (std::max)(120.0f, (std::min)(len * 12.0f + 80.0f, static_cast<float>(screenH * 0.42f)));

        // Chiếu isometric chuẩn từ vector (dx, dy) sang pixel màn hình POE2
        const auto target = common::WorldToScreenIsometric(dx, dy, aimDist, static_cast<float>(screenW), static_cast<float>(screenH), 120.0f);

        kmbox.MoveMouseSmooth(static_cast<int>(target.x), static_cast<int>(target.y), m_curveGen, 10, 0);
    }

    SendKey(vkCode, kmbox, holdMs, nowMs);
}

#include "navigation/terrain_grid.hpp"
#include "combat/kiting_engine.hpp"

void ComboManager::AimAndMove(float dx, float dy, KMBoxNet& kmbox) {
    if (!EnsurePoe2WindowFocus()) return;
    const float len = std::sqrt(dx * dx + dy * dy);
    if (len < 0.001f) return;

    if (m_moveCtrl) {
        m_moveCtrl->SteerTowards(dx, dy, kmbox, m_state.lastSkillCastMs);
        return;
    }

    // Fallback phi khóa (Non-Blocking): giữ phím chuyển hướng, không gọi sleep_for(300)
    common::WasdVector wasd = common::WorldToWasd(dx, dy);
    if (!wasd.empty()) {
        std::vector<uint8_t> keys;
        if (wasd.up) keys.push_back(0x1A);
        if (wasd.left) keys.push_back(0x04);
        if (wasd.down) keys.push_back(0x16);
        if (wasd.right) keys.push_back(0x07);

        if (keys != m_heldEngageKeys) {
            for (uint8_t oldK : m_heldEngageKeys) {
                if (std::find(keys.begin(), keys.end(), oldK) == keys.end()) {
                    kmbox.KeyUp(oldK);
                }
            }
            for (uint8_t newK : keys) {
                if (std::find(m_heldEngageKeys.begin(), m_heldEngageKeys.end(), newK) == m_heldEngageKeys.end()) {
                    kmbox.KeyDown(newK);
                }
            }
            m_heldEngageKeys = keys;
        }
    }
}

bool ComboManager::TriggerDoubleDodge(KMBoxNet& kmbox, uint64_t nowMs, float threatDx, float threatDy, const std::string& reason) {
    if (!m_config.doubleDodgeEnabled) return false;
    if (!EnsurePoe2WindowFocus()) return false;
    if (nowMs - m_state.lastDoubleDodgeMs < m_config.doubleDodgeCooldownMs) return false;

    m_state.lastDoubleDodgeMs = nowMs;
    m_state.lastDoubleDodgeStepMs = nowMs;
    m_state.doubleDodgeStep = 1;
    m_state.threatDx = (threatDx != 0.0f || threatDy != 0.0f) ? threatDx : 1.0f;
    m_state.threatDy = threatDy;
    ++m_state.totalDoubleDodges;

    CoreLog("[DoubleDodge #" + std::to_string(m_state.totalDoubleDodges) + "] " + reason
        + " -> Buoc 1: Tro chuot vao quai (" + std::to_string(m_state.threatDx).substr(0, 5) + ", "
        + std::to_string(m_state.threatDy).substr(0, 5) + ") de nhay lui voi Frozen Locus [E]!");

    // Frozen Locus [E]: Monk vaults backward AWAY from cursor.
    // Trỏ chuột trực tiếp vào hướng quái để nhân vật nhảy lùi ngược ra xa quái!
    AimAndCast(m_config.vaultSkillVk, m_state.threatDx, m_state.threatDy, kmbox, 40);
    m_state.lastSkillCastMs = nowMs;
    return true;
}

bool ComboManager::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ỉ dừng khi người chơi đã tử trận

    // Interlock CI: Nếu ReflexManager đang ở trạng thái CI Recovery Mode (ES < 90%)
    // Tạm dừng toàn bộ tấn công áp sát (Whirling Assault LMB), giữ khoảng cách để ES/Ward hồi phục!
    if (m_reflexManager && m_reflexManager->IsCiRecoveryMode()) {
        m_state.previousHP = player.currentHP;
        return false;
    }

    // -0.5. Boss Invulnerability & Phase Detection (CHU KỲ 8)
    const auto bossPhase = m_bossPhaseCtrl.UpdatePhase(packet, m_state.activeBossId, nowMs);
    if (m_bossPhaseCtrl.IsBossInvulnerable()) {
        // Boss đang bất tử (Immunity / Inactive Phase / Disappeared):
        // 1. Ngắt ngay lập tức xả Heavy Finisher / Boss Burst
        m_state.isBossBurstWindow = false;
        m_config.autoStaggerFinisher = false;

        // 2. Chuyển sang thế trận né chiêu diện rộng Orbital Kiting Pattern quanh tâm sàn đấu
        const auto orb = m_bossPhaseCtrl.ComputeOrbitalKiteVector(player.posX, player.posY);
        if (m_moveCtrl) {
            m_moveCtrl->SteerTowards(orb.moveDx, orb.moveDy, kmbox, nowMs);
        } else {
            AimAndMove(orb.moveDx, orb.moveDy, kmbox);
        }
        m_state.previousHP = player.currentHP;
        return true;
    } else if (bossPhase == combat::BossPhaseState::ActiveCombat) {
        // Boss kết thúc pha bất tử: Tự động phục hồi trạng thái Auto-Finisher & Burst nếu ở profile BOSS_BURST
        m_config.autoStaggerFinisher = true;
        if (m_state.activeCombatProfile == CombatProfile::BOSS_BURST) {
            m_state.isBossBurstWindow = true;
        }
    }

    // -1. Chuỗi thoát hiểm khẩn cấp Double Dodge (E -> Space -> Flask -> Đánh tiếp/Chạy)
    if (m_state.doubleDodgeStep == 1) {
        // Bước 1 (E) đã kích hoạt, chờ 140ms để animation bắt đầu, sau đó thi triển Bước 2: Dodge Roll (Space)
        if (nowMs - m_state.lastDoubleDodgeStepMs >= 140) {
            m_state.doubleDodgeStep = 2;
            m_state.lastDoubleDodgeStepMs = nowMs;
            m_state.lastSkillCastMs = nowMs;
            CoreLog("[DoubleDodge] Buoc 2: Tro chuot huong thao lui va lan ne Dodge Roll [Space]!");
            // Trỏ chuột ngược hướng quái (-threatDx, -threatDy) để lăn né về sau
            AimAndCast(m_config.dodgeRollVk, -m_state.threatDx, -m_state.threatDy, kmbox, 30);
            return true;
        }
        return false;
    } else if (m_state.doubleDodgeStep == 2) {
        // Bước 2 (Space) đang lăn iframe, sau 180ms thi triển Bước 3: Bơm Life Flask [1]
        if (nowMs - m_state.lastDoubleDodgeStepMs >= 180) {
            m_state.doubleDodgeStep = 3;
            m_state.lastDoubleDodgeStepMs = nowMs;
            CoreLog("[DoubleDodge] Buoc 3: Bom Life Flask [1] hoi phuc sinh luc!");
            if (player.maxHP == 1 || (m_reflexManager && !m_reflexManager->GetConfig().flaskEnabled)) {
                // CI build (1 HP) hoặc tắt Life Flask -> không bấm bình máu
            } else if (m_reflexManager) {
                m_reflexManager->TriggerLifeFlask(kmbox, nowMs, "Emergency Double-Dodge Recovery");
            } else {
                SendKey(m_config.lifeFlaskVk, kmbox, 30);
            }
            return true;
        }
        return false;
    } else if (m_state.doubleDodgeStep == 3) {
        // Bước 3 (Bình máu) xong, sau 150ms hoàn tất chuỗi thoát hiểm
        if (nowMs - m_state.lastDoubleDodgeStepMs >= 150) {
            m_state.doubleDodgeStep = 0;
            CoreLog("[DoubleDodge] Buoc 4: Hoan tat chuoi Double-Dodge. Tiep tuc chien dau / di chuyen.");
        }
    }

    // 1c. Quản lý cửa sổ dồn sát thương Boss Stagger Burst Window (CHU KỲ 5)
    // Đánh giá trạng thái Choáng của Boss TRƯỚC Kiting để lập tức bỏ qua kiting và mở burst DPS
    if (m_config.bossStaggerBurstEnabled) {
        bool bossStaggeredNow = false;
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && !(ent.extraFlags & 4) && ent.currentHP > 0) {
                const bool isBossOrRare = (ent.rarity >= 2) || (ent.extraFlags & 1);
                if (isBossOrRare && (ent.staggerProgress >= m_config.bossStaggerThreshold || (ent.extraFlags & (1 << 7)))) {
                    bossStaggeredNow = true;
                    break;
                }
            }
        }

        if (bossStaggeredNow) {
            if (!m_state.isBossBurstWindow) {
                m_state.isBossBurstWindow = true;
                m_state.burstWindowStartMs = nowMs;
                ++m_state.totalBossBurstWindows;
                CoreLog("[Boss Stagger] >> BOSS CHOÁNG (VULNERABLE)! KÍCH HOẠT BURST DPS ROTATION CỰC ĐẠI! <<");
            }
        } else if (m_state.activeCombatProfile != CombatProfile::BOSS_BURST && m_state.isBossBurstWindow && (nowMs - m_state.burstWindowStartMs >= m_config.burstWindowDurationMs)) {
            m_state.isBossBurstWindow = false;
        }
    }

    // 0. Kiting Engine: Nếu quái vật áp sát nguy hiểm -> Stutter-step lùi ra vùng an toàn
    // (Bỏ qua lùi kiting khi Boss đang bị Staggered/Vulnerable để xả tối đa dồn sát thương)
    if (m_kitingEngine && m_terrainGrid && !m_state.isBossBurstWindow) {
        navigation::Vec2 kiteTarget;
        if (m_kitingEngine->ComputeKiteStep(packet, *m_terrainGrid, nowMs, kiteTarget)) {
            float kdx = kiteTarget.x - player.posX;
            float kdy = kiteTarget.y - player.posY;
            AimAndMove(kdx, kdy, kmbox);
            m_state.lastSkillCastMs = nowMs;
            CoreLog("[Kiting] Thuc hien buoc lui Stutter-Step ne quai nguy hiem!");
            return true;
        }
    }

    // 1. Phát hiện nhận sát thương -> Kích hoạt và duy trì trạng thái giao chiến (3 giây)
    const bool takingDamage = (m_state.previousHP > 0 && player.currentHP < m_state.previousHP);
    if (takingDamage) {
        m_state.lastDamageTakenMs = nowMs;
        m_state.inCombatUntilMs = nowMs + 3000;
    }
    const bool isUnderAttack = (nowMs < m_state.inCombatUntilMs);

    // 1b. Cơ chế Phòng Thủ Né Thoát Hiểm [E] Khi Đánh Xa (Ranged Defense):
    // Khi quái áp sát quá gần (dist <= kitingDangerDist ví dụ <= 120.0f) hoặc khi bị sát thương:
    // (Bỏ qua khi Boss đang bị Staggered/Vulnerable để xả tối đa dồn sát thương)
    if (!m_state.isBossBurstWindow && !m_config.isCiChargedStaffMode && (m_config.isRangedMode || m_config.primarySkillVk == VK_RBUTTON)) {
        float threatDx = 1.0f, threatDy = 0.0f;
        float closestDist = 9999.0f;
        bool hasThreat = 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 dist = (len > 0.001f) ? len : ent.distanceToPlayer;
                if (dist < closestDist) {
                    closestDist = dist;
                    threatDx = (dx != 0.0f || dy != 0.0f) ? dx : 1.0f;
                    threatDy = (dx != 0.0f || dy != 0.0f) ? dy : 0.0f;
                    hasThreat = true;
                }
            }
        }

        if ((hasThreat && closestDist <= m_config.kitingDangerDist) || takingDamage) {
            if (nowMs - m_state.lastVaultMs >= m_config.vaultCooldownMs) {
                float dist = hasThreat ? closestDist : 0.0f;
                AimAndCast(m_config.vaultSkillVk, threatDx, threatDy, kmbox, 40);
                m_state.lastVaultMs = nowMs;
                m_state.lastSkillCastMs = nowMs;
                m_state.previousHP = player.currentHP;
                CoreLog("[Ranged Defense] Quai ap sat (" + std::to_string(static_cast<int>(dist)) + ") -> Tung chieu ne [E]!");
                return true;
            }
        }
    } else if (m_config.useMonkRotation && m_config.doubleDodgeEnabled && m_state.doubleDodgeStep == 0) {
        const float hpRatio = (player.maxHP > 0) ? (static_cast<float>(player.currentHP) / player.maxHP) : 1.0f;
        const bool isDangerHp = (hpRatio <= m_config.emergencyHpThreshold);
        bool isBurstDamage = false;
        if (m_state.previousHP > 0 && player.currentHP < m_state.previousHP && player.maxHP > 0) {
            const float dropRatio = static_cast<float>(m_state.previousHP - player.currentHP) / player.maxHP;
            if (dropRatio >= m_config.emergencyBurstRatio) {
                isBurstDamage = true;
            }
        }

        if (isDangerHp || isBurstDamage) {
            float threatX = 1.0f, threatY = 0.0f;
            float minDist = 9999.0f;
            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 dist = (len > 0.001f) ? len : ent.distanceToPlayer;
                    if (dist < minDist) {
                        minDist = dist;
                        threatX = (dx != 0.0f || dy != 0.0f) ? dx : 1.0f;
                        threatY = (dx != 0.0f || dy != 0.0f) ? dy : 0.0f;
                    }
                }
            }
            std::string reason = isBurstDamage ? "Phat hien soc sat thuong (tut >= 20%)" : "HP nguy hiem (<= 45%)";
            if (TriggerDoubleDodge(kmbox, nowMs, threatX, threatY, reason)) {
                m_state.previousHP = player.currentHP;
                return true;
            }
        }
    }

    // Kiểm tra Mana: Khi KHÔNG bị tấn công thì mới tiết kiệm mana
    if (!isUnderAttack && player.maxMana >= 20 && player.maxMana <= 50000 && player.currentMana < m_config.minManaToCombo) {
        m_state.previousHP = player.currentHP;
        return false;
    }

    // Giãn cách combo
    if (nowMs - m_state.lastSkillCastMs < m_config.comboIntervalMs) {
        if (takingDamage) {
            m_state.previousHP = player.currentHP;
        }
        return false;
    }

    // 2. ƯU TIÊN CAO NHẤT: Quét tìm quái vật bị Stagger trong phạm vi tấn công
    if (m_config.autoStaggerFinisher && (nowMs - m_state.lastFinisherCastMs >= 1500)) {
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            const auto& ent = packet.entities[i];
            if (ent.type == 1 && !(ent.extraFlags & 4)) { // Monster còn sống
                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 dist = (len > 0.001f) ? len : ent.distanceToPlayer;

                if (dist <= 180.0f && ent.staggerProgress >= m_config.staggerThreshold) {
                    TriggerFinisher(kmbox, nowMs, ent.name[0] ? ent.name : "Elite Boss", dx, dy);
                    m_state.previousHP = player.currentHP;
                    return true;
                }
            }
        }
    }

    // 3. Tìm mục tiêu tối ưu (Weighted Target Selection kết hợp Line of Sight)
    float bestScore = -99999.0f;
    float bestDist = 9999.0f;
    float bestDx = 1.0f, bestDy = 0.0f;
    bool hasRealTarget = false;
    bool targetHasLoS = false;
    const EntityTelemetryData* bestTargetEnt = nullptr;

    const float maxScanDist = (m_config.isRangedMode || m_config.primarySkillVk == VK_RBUTTON) ? m_config.rangedEngageDist : 480.0f;

    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type == 1 && !(ent.extraFlags & 4)) { // Monster còn sống
            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 dist = (len > 0.001f) ? len : ent.distanceToPlayer;

            if (dist <= maxScanDist) {
                bool los = m_terrainGrid ? m_terrainGrid->HasLineOfSight(player.posX, player.posY, ent.posX, ent.posY) : true;
                
                // Điểm ưu tiên:
                // +1000 nếu có Line of Sight (tầm nhìn thông thoáng)
                // -dist (ưu tiên quái gần hơn)
                // +300 nếu là Boss / Unique
                // +2000 nếu Boss/Monster đang bị Staggered/Vulnerable
                float score = (los ? 1000.0f : 0.0f) - dist;
                if ((ent.extraFlags & 1) || (ent.rarity >= 3)) {
                    score += 300.0f;
                    if (m_state.activeCombatProfile == CombatProfile::BOSS_BURST) {
                        score += 3000.0f; // Tối đa hóa ưu tiên Boss khi ở profile BOSS_BURST
                    }
                }
                if (ent.staggerProgress >= m_config.bossStaggerThreshold || (ent.extraFlags & (1 << 7))) score += 2000.0f;

                if (score > bestScore) {
                    bestScore = score;
                    bestDist = dist;
                    bestDx = (dx != 0.0f || dy != 0.0f) ? dx : 1.0f;
                    bestDy = (dx != 0.0f || dy != 0.0f) ? dy : 0.0f;
                    hasRealTarget = true;
                    targetHasLoS = los;
                    bestTargetEnt = &ent;
                }
            }
        }
    }

    const bool isBoss = hasRealTarget && bestTargetEnt && ((bestTargetEnt->extraFlags & 1) || (bestTargetEnt->rarity >= 3));
    m_state.isBossEncounter = isBoss;

    // 3a. Quản lý trạng thái Boss & Boss Rage Flask (INV-COMBAT-BOSS-RAGE-FLASK)
    if (isBoss && bestTargetEnt) {
        if (m_state.activeBossId != bestTargetEnt->id) {
            m_state.activeBossId = bestTargetEnt->id;
            m_state.bossLifeFlaskUsed = false;
            m_state.lastTCastMs = 0;
            CoreLog("[Boss Combat] Giao tranh Boss moi (ID: " + std::to_string(bestTargetEnt->id) + ") -> San sang chu trinh Rage & Life Flask!");
        }
    } else if (m_state.activeBossId != 0) {
        bool bossStillAlive = false;
        for (uint32_t i = 0; i < packet.entityCount; ++i) {
            if (packet.entities[i].id == m_state.activeBossId && !(packet.entities[i].extraFlags & 4) && packet.entities[i].currentHP > 0) {
                bossStillAlive = true;
                break;
            }
        }
        if (!bossStillAlive) {
            m_state.activeBossId = 0;
            m_state.bossLifeFlaskUsed = false;
            m_state.lastTCastMs = 0;
        }
    }

    // Nhận diện phím T (Bear Warcry / Rage Buff) được kích hoạt thủ công hoặc từ macro/hotkey
    if (isBoss && m_state.lastTCastMs == 0 && (GetAsyncKeyState(m_config.rageBuffSkillVk) & 0x8000)) {
        m_state.lastTCastMs = nowMs;
        CoreLog("[Boss Combat] Nhan dien phim T kich hoat! Bat dau dem " + std::to_string(m_config.rageFlaskDelayAfterTMs) + "ms de bam Life Flask duy tri Rage...");
    }

    // Kích hoạt Life Flask [1] duy nhất 1 lần sau 4s (4000ms) kích hoạt chiêu T để duy trì Rage khi đánh Boss
    if (m_config.bossRageFlaskEnabled && isBoss && m_state.lastTCastMs > 0 && !m_state.bossLifeFlaskUsed) {
        if (nowMs - m_state.lastTCastMs >= m_config.rageFlaskDelayAfterTMs) {
            SendKey(m_config.lifeFlaskVk, kmbox, 40);
            m_state.bossLifeFlaskUsed = true;
            const std::string flaskLog = "[Boss Rage Flask] Da troi qua " + std::to_string(nowMs - m_state.lastTCastMs)
                + "ms sau chieu T -> Bam Life Flask [1] (1 lan duy nhat) duy tri Rage danh Boss!";
            std::cout << "\n" << flaskLog << std::endl;
            CoreLog(flaskLog);
        }
    }

    uint32_t realEnemyCount = 0;
    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type == 1 && !(ent.extraFlags & 4) && ent.maxHP > 0 && ent.currentHP > 0) {
            float edx = ent.posX - player.posX;
            float edy = ent.posY - player.posY;
            if (std::sqrt(edx * edx + edy * edy) <= 450.0f) {
                ++realEnemyCount;
            }
        }
    }

    // 4. Quyết định kỹ năng thi triển
    uint8_t selectedSkillVk = 0;
    uint32_t holdMs = 40;
    bool shouldCast = false;

    // 4-pre. Dedicated Skill Engine (ARCH-SKILL-ENGINE-FLEXIBLE-2026-P6)
    if (m_skillEngine && m_skillEngine->ActiveProfile() && !m_skillEngine->ActiveProfile()->Skills().empty() && hasRealTarget && bestTargetEnt) {
        CombatContext ctx{};
        ctx.vitals.curLife = player.currentHP;
        ctx.vitals.maxLife = player.maxHP;
        ctx.vitals.curES = player.currentES;
        ctx.vitals.maxES = player.maxES;
        ctx.vitals.curMana = player.currentMana;
        ctx.vitals.maxMana = player.maxMana;
        ctx.vitals.curRage = m_state.currentRage;
        ctx.vitals.comboPoints = m_state.currentComboPoints;
        ctx.vitals.staffCharges = m_state.currentCharges;

        ctx.target.id = bestTargetEnt->id;
        ctx.target.rarity = static_cast<uint8_t>(bestTargetEnt->rarity);
        ctx.target.distance = bestDist;
        ctx.target.posX = bestTargetEnt->posX;
        ctx.target.posY = bestTargetEnt->posY;
        ctx.target.hpPct = (bestTargetEnt->maxHP > 0) ? static_cast<float>(bestTargetEnt->currentHP) / static_cast<float>(bestTargetEnt->maxHP) : 1.0f;
        ctx.target.staggerPct = bestTargetEnt->staggerProgress;

        ctx.gear.isChaosInoculation = (player.maxHP == 1);
        ctx.nearbyEnemyCount = realEnemyCount;

        SkillDecision decision = m_skillEngine->SelectBestAction(ctx, nowMs);
        if (decision.kind == SkillDecisionKind::EmergencyCancel) {
            TriggerDoubleDodge(kmbox, nowMs, bestDx, bestDy, decision.reason);
            return true;
        }
        if (decision.kind == SkillDecisionKind::CastSkill && decision.vkCode != 0) {
            if (decision.vkCode == m_config.rageBuffSkillVk && isBoss && m_state.lastTCastMs == 0) {
                m_state.lastTCastMs = nowMs;
                CoreLog("[Boss Combat] SkillEngine cast chieu T! Bat dau dem " + std::to_string(m_config.rageFlaskDelayAfterTMs) + "ms de bam Life Flask duy tri Rage...");
            }
            // GAP-4: Gọi OnSkillCastStarted để nạp đúng hoạt ảnh windup/active/recovery
            m_skillEngine->OnSkillCastStarted(decision.vkCode, nowMs);
            AimAndCast(decision.vkCode, bestDx, bestDy, kmbox, 40);
            m_skillEngine->OnSkillExecuted(decision.vkCode, nowMs);

            // Đồng bộ trạng thái trackers về m_state
            auto prof = m_skillEngine->ActiveProfile();
            if (prof) {
                m_state.currentComboPoints = static_cast<uint32_t>(prof->GetTracker("combo_points"));
                m_state.currentRage = static_cast<uint32_t>(prof->GetTracker("rage"));
            }
            m_state.lastSkillCastMs = nowMs;
            m_state.previousHP = player.currentHP;
            const std::string logMsg = "[SkillEngine] " + decision.reason;
            std::cout << logMsg << std::endl;
            CoreLog(logMsg);
            return true;
        }
        if (decision.kind == SkillDecisionKind::WaitAnimation) {
            return false;
        }
    }

    // 4a. Bảng kỹ năng table-driven skills.toml (Doc 29 mục 13 - Phase P4)
    if (!m_skillsTable.IsEmpty() && hasRealTarget && bestTargetEnt) {
        if (bestDist > 250.0f && !targetHasLoS) {
            CoreLog("[CombatTable] Quai bi tuong che khuat Line-of-Sight -> Khong tung chieu!");
            return false;
        }

        const float lifePct = (player.maxHP > 0) ? static_cast<float>(player.currentHP) / static_cast<float>(player.maxHP) : 1.0f;
        const float manaPct = (player.maxMana > 0) ? static_cast<float>(player.currentMana) / static_cast<float>(player.maxMana) : 1.0f;
        const float esPct = (player.maxES > 0) ? static_cast<float>(player.currentES) / static_cast<float>(player.maxES) : 1.0f;

        // Ưu tiên 1: Kỹ năng tự buff chiến đấu (CombatBuff)
        SkillDefinition* buffSkill = m_skillsTable.SelectBestSkill(nowMs, 0.0f, 0, lifePct, manaPct, esPct, SkillKind::CombatBuff, realEnemyCount);
        if (buffSkill && buffSkill->vkCode != 0) {
            if (buffSkill->vkCode == m_config.rageBuffSkillVk && isBoss && m_state.lastTCastMs == 0) {
                m_state.lastTCastMs = nowMs;
                CoreLog("[Boss Combat] SkillsTable cast chieu T! Bat dau dem " + std::to_string(m_config.rageFlaskDelayAfterTMs) + "ms de bam Life Flask duy tri Rage...");
            }
            AimAndCast(buffSkill->vkCode, bestDx, bestDy, kmbox, 40);
            buffSkill->lastCastMs = nowMs;
            m_state.lastSkillCastMs = nowMs;
            m_state.previousHP = player.currentHP;
            const std::string logMsg = "[CombatTable] Tung CombatBuff: " + buffSkill->name + " via " + buffSkill->key;
            std::cout << logMsg << std::endl;
            CoreLog(logMsg);
            return true;
        }

        // Ưu tiên 2: Kỹ năng tấn công mục tiêu (Attack)
        SkillDefinition* attackSkill = m_skillsTable.SelectBestSkill(nowMs, bestDist, static_cast<uint8_t>(bestTargetEnt->rarity), lifePct, manaPct, esPct, SkillKind::Attack, realEnemyCount);
        if (attackSkill && attackSkill->vkCode != 0) {
            AimAndCast(attackSkill->vkCode, bestDx, bestDy, kmbox, 40);
            attackSkill->lastCastMs = nowMs;
            m_state.lastSkillCastMs = nowMs;
            m_state.previousHP = player.currentHP;
            const std::string logMsg = "[CombatTable] Tung Attack: " + attackSkill->name + " via " + attackSkill->key + " (dist=" + std::to_string(static_cast<int>(bestDist)) + ")";
            std::cout << logMsg << std::endl;
            CoreLog(logMsg);
            return true;
        }
    }

    // 4-pre-boss. Tự động thi triển chiêu T (Bear Warcry / Rage Buff) mở màn giao tranh Boss nếu chưa có profile/table
    if (isBoss && m_state.lastTCastMs == 0 && bestDist <= 350.0f && m_config.bossRageFlaskEnabled && m_config.rageBuffSkillVk != 0) {
        AimAndCast(m_config.rageBuffSkillVk, bestDx, bestDy, kmbox, 40);
        m_state.lastTCastMs = nowMs;
        m_state.lastSkillCastMs = nowMs;
        m_state.previousHP = player.currentHP;
        ++m_state.totalCombosExecuted;
        CoreLog("[Boss Combat] Tu dong thi trien chieu T mo man giao tranh Boss! Bat dau dem " + std::to_string(m_config.rageFlaskDelayAfterTMs) + "ms de bam Life Flask duy tri Rage...");
        return true;
    }

    if (m_config.isCiChargedStaffMode) {
        if (hasRealTarget) {
            if (bestDist > 250.0f && !targetHasLoS) {
                CoreLog("[CI Staff] Quai o xa bi tuong che khuat Line-of-Sight -> Khong danh vao tuong!");
                return false;
            }

            // Tối ưu hóa Mana: nếu mana quá thấp, chủ động yêu cầu ReflexManager nhấp Mana Flask (nếu manaEnabled bật)
            if (player.currentMana < m_config.minManaForHeavySkill && m_reflexManager && m_reflexManager->GetConfig().manaEnabled) {
                m_reflexManager->TriggerManaFlask(kmbox, nowMs, "Pre-Combo CI Mana Recovery");
            }

            // 1. Kỹ năng Buff Chuột Phải [Charged Staff] (RMB):
            // Kích hoạt khi có 4 đến 5 charges VÀ buff đã hết hạn hoặc sắp hết (còn <= 3s trong 40s)
            const bool buffNeedsRefresh = (nowMs >= m_state.chargedStaffBuffExpiresMs) ||
                                          (m_state.chargedStaffBuffExpiresMs > nowMs && m_state.chargedStaffBuffExpiresMs - nowMs <= 3000);
            if (m_state.currentCharges >= m_config.requiredChargesForBuff && buffNeedsRefresh) {
                selectedSkillVk = m_config.staffBuffSkillVk; // VK_RBUTTON
                holdMs = 40;
                AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
                m_state.chargedStaffBuffExpiresMs = nowMs + m_config.chargedStaffBuffDurationMs;
                m_state.currentCharges = 0; // Tiêu hao charges cho 40s buff
                m_state.lastSkillCastMs = nowMs;
                m_state.previousHP = player.currentHP;
                const std::string logMsg = "[CI Staff] Kích hoạt Chuột Phải [Charged Staff] với 4-5 charges! Buff 40s ACTIVE (hết hạn sau " + std::to_string(m_config.chargedStaffBuffDurationMs / 1000) + "s)";
                std::cout << logMsg << std::endl;
                CoreLog(logMsg);
                return true;
            }

            // 2. Kỹ năng Kết Liễu Cực Đại [R] (Finisher 4-Combo):
            // Kích hoạt khi tích lũy đủ 4 Combo points
            if (m_state.currentComboPoints >= m_config.requiredComboForR) {
                selectedSkillVk = m_config.finisherRSkillVk; // Phím 'R' (0x52)
                holdMs = 50;
                AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
                m_state.currentComboPoints = 0; // Đã xả đòn kết liễu
                m_state.lastSkillCastMs = nowMs;
                m_state.lastFinisherCastMs = nowMs;
                m_state.previousHP = player.currentHP;
                const std::string logMsg = "[CI Staff] Đạt 4 Combo points -> Tung chiêu kết liễu cực đại [R]!";
                std::cout << logMsg << std::endl;
                CoreLog(logMsg);
                return true;
            }

            // 3. Kỹ năng Tích Lũy [Q] (Builder: Rage & Combo):
            // Khi chưa đủ 4 combo points, dùng Q để tích lũy Rage và Combo
            if (m_state.currentComboPoints < m_config.requiredComboForR) {
                selectedSkillVk = m_config.builderSkillVk; // Phím 'Q' (0x51)
                holdMs = 40;
                AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
                m_state.currentComboPoints++;
                m_state.currentRage += 10;
                m_state.lastSkillCastMs = nowMs;
                m_state.previousHP = player.currentHP;
                const std::string logMsg = "[CI Staff] Tung chiêu [Q] tích lũy (Combo: " + std::to_string(m_state.currentComboPoints) + "/4, Rage: " + std::to_string(m_state.currentRage) + ", Charges: " + std::to_string(m_state.currentCharges) + "/5)";
                std::cout << logMsg << std::endl;
                CoreLog(logMsg);
                return true;
            }

            // 4. Kỹ năng Tấn công Cơ bản Chuột Trái (Whirling Assault):
            selectedSkillVk = m_config.staffMeleeSkillVk; // VK_LBUTTON
            holdMs = 40;
            AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
            m_state.lastSkillCastMs = nowMs;
            m_state.previousHP = player.currentHP;
            CoreLog("[CI Staff] Tung Chuột Trái [Whirling Assault] áp sát mục tiêu");
            return true;
        }
    } else if (m_config.isRangedMode || m_config.primarySkillVk == VK_RBUTTON) {
        if (hasRealTarget) {
            if (bestDist > 180.0f && !targetHasLoS) {
                CoreLog("[CombatCombo] Quai o xa bi tuong che khuat Line-of-Sight -> Khong ban vao tuong!");
                return false;
            }

            shouldCast = true;
            selectedSkillVk = m_config.primarySkillVk; // VK_RBUTTON
            holdMs = 40;
            CoreLog("[Ranged Combat] Ban Chuot Phai [RMB] vao quai (dist=" + std::to_string(static_cast<int>(bestDist)) + ")");
            AimAndCast(m_config.primarySkillVk, bestDx, bestDy, kmbox, holdMs);
            m_state.consecutiveBlindCasts = 0;
        } else if (isUnderAttack && m_state.consecutiveBlindCasts < 1) {
            shouldCast = true;
            selectedSkillVk = m_config.primarySkillVk;
            holdMs = 40;
            CoreLog("[Ranged Defense] Dang bi danh ngoai tam radar (HP=" + std::to_string(player.currentHP) + "/" + std::to_string(player.maxHP) + ") -> Ban RMB phong thu");
            SendKey(selectedSkillVk, kmbox, holdMs);
            ++m_state.consecutiveBlindCasts;
        } else if ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) || (GetAsyncKeyState(0x51) & 0x8000) || (GetAsyncKeyState(0x45) & 0x8000)) {
            shouldCast = true;
            selectedSkillVk = m_config.primarySkillVk;
            SendKey(selectedSkillVk, kmbox, 40);
        }
    } else if (m_config.useMonkRotation) {
        if (hasRealTarget) {
            if (bestDist > 180.0f && !targetHasLoS) {
                CoreLog("[CombatCombo] Quai o xa bi tuong che khuat Line-of-Sight -> Khong ban vao tuong!");
                return false;
            }

            shouldCast = true;
            if (isBoss) {
                // BOSS ROTATION MONK (ABSTRACT COMBO BUILDER -> SPENDER):
                // Tích lũy đủ điểm combo qua Chiêu Tích Combo -> Xả đòn kết liễu / Đặt chuông bão vào chân Boss
                if (m_state.bossComboHits < m_config.bossStrikeGoal) {
                    selectedSkillVk = m_config.primarySkillVk; // Đòn tích combo
                    holdMs = 40;
                    ++m_state.bossComboHits;
                    m_state.bossIceStrikeCount = m_state.bossComboHits;
                    m_state.currentComboPoints = (std::min)(4u, m_state.currentComboPoints + 1);
                    CoreLog("[Combo Engine] Chiêu Tích Combo #" + std::to_string(m_state.bossComboHits)
                        + "/" + std::to_string(m_config.bossStrikeGoal) + " vao Boss!");
                } else {
                    // Đã đủ 4 điểm combo: Đặt chuông / Thi triển Spender nếu chưa có hoặc chuông đã hết hạn
                    bool needBell = (m_state.lastBellPlacedMs == 0) || (nowMs - m_state.lastBellPlacedMs >= m_config.bellDurationMs);
                    if (needBell) {
                        selectedSkillVk = m_config.bellSkillVk; // Phím Q / R tùy cấu hình
                        holdMs = 50;
                        m_state.lastBellPlacedMs = nowMs;
                        m_state.lastBellPosX = bestTargetEnt->posX;
                        m_state.lastBellPosY = bestTargetEnt->posY;
                        ++m_state.totalBellsPlaced;
                        m_state.bossComboHits = 0;
                        m_state.bossIceStrikeCount = 0; // Reset đếm để chu kỳ sau tiếp tục tích combo
                        m_state.currentComboPoints = 0;
                        CoreLog("[Combo Engine] >> Thi triển Chiêu Xả Combo / Tempest Bell vào chân Boss! (Tong: "
                            + std::to_string(m_state.totalBellsPlaced) + ") <<");
                    } else {
                        selectedSkillVk = m_config.primarySkillVk;
                        holdMs = 40;
                        CoreLog("[Combo Engine] Resonance Shockwaves: Đánh chiêu tích combo vào Tempest Bell / Boss!");
                    }
                }
            } else {
                // DỌN QUÁI THƯỜNG:
                // Chiêu đánh chính là Chuột Trái (Ice Strike). Tuyệt đối không dùng E liên tục.
                selectedSkillVk = m_config.primarySkillVk;
                holdMs = 40;
                CoreLog("[Monk Clear] Ice Strike [LMB] vao quai (dist=" + std::to_string(static_cast<int>(bestDist)) + ")");
            }
            AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
            m_state.consecutiveBlindCasts = 0;
        } else if (isUnderAttack && m_state.consecutiveBlindCasts < 1 && m_config.primarySkillVk != VK_LBUTTON) {
            // Khi chiêu đánh chính là Chuột Trái (VK_LBUTTON - gắn liền với Click-to-Move),
            // tuyệt đối KHÔNG đấm gió mù quáng vào tâm màn hình khi không có quái trên radar
            // vì sẽ cắt ngang lệnh di chuyển khiến nhân vật bị khựng lại giữa đường!
            shouldCast = true;
            selectedSkillVk = m_config.primarySkillVk;
            holdMs = 40;
            CoreLog("[Monk Defense] Dang bi danh ngoai tam radar (HP=" + std::to_string(player.currentHP) + "/" + std::to_string(player.maxHP) + ") -> Phong thu 1 nhip");
            SendKey(selectedSkillVk, kmbox, holdMs);
            ++m_state.consecutiveBlindCasts;
        } else if ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) || (GetAsyncKeyState(0x51) & 0x8000) || (GetAsyncKeyState(0x45) & 0x8000)) {
            shouldCast = true;
            selectedSkillVk = m_config.primarySkillVk;
            SendKey(selectedSkillVk, kmbox, 40);
        }
    } else {
        // Chế độ cũ (Legacy Range-based):
        if (hasRealTarget) {
            if (bestDist > m_config.closeRangeDistance && !targetHasLoS) {
                CoreLog("[CombatCombo] Quai o xa bi tuong che khuat Line-of-Sight -> Khong ban vao tuong!");
                return false;
            }

            shouldCast = true;
            if (bestDist <= m_config.closeRangeDistance) {
                selectedSkillVk = m_config.closeSkillVk;
                holdMs = 40;
                CoreLog("[CombatCombo] Quai gan (dist=" + std::to_string(static_cast<int>(bestDist)) + ") -> Tung chieu [E]");
            } else {
                selectedSkillVk = m_config.rangedSkillVk;
                holdMs = 60;
                CoreLog("[CombatCombo] Quai xa LoS ro (dist=" + std::to_string(static_cast<int>(bestDist)) + ") -> Tung chieu [Chuot Phai]");
            }
            AimAndCast(selectedSkillVk, bestDx, bestDy, kmbox, holdMs);
            m_state.consecutiveBlindCasts = 0;
        } else if (isUnderAttack) {
            shouldCast = true;
            selectedSkillVk = m_config.closeSkillVk;
            holdMs = 40;
            CoreLog("[CombatCombo] Dang bi danh (HP=" + std::to_string(player.currentHP) + "/" + std::to_string(player.maxHP) + ") -> Tung chieu [E] tu ve");
            SendKey(selectedSkillVk, kmbox, holdMs);
            ++m_state.consecutiveBlindCasts;
        } else if ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) || (GetAsyncKeyState(0x51) & 0x8000) || (GetAsyncKeyState(0x45) & 0x8000)) {
            shouldCast = true;
            selectedSkillVk = m_config.rangedSkillVk;
            SendKey(selectedSkillVk, kmbox, 50);
        }
    }

    if (!shouldCast) {
        m_state.previousHP = player.currentHP;
        return false;
    }

    m_state.currentRotationStep = (m_state.currentRotationStep == 0) ? 1 : 0;
    m_state.previousHP = player.currentHP;
    m_state.lastSkillCastMs = nowMs;
    ++m_state.totalCombosExecuted;
    return true;
}

void ComboManager::TriggerFinisher(KMBoxNet& kmbox, uint64_t nowMs, const std::string& targetName, float dx, float dy) {
    if (!EnsurePoe2WindowFocus()) return;
    ++m_state.totalFinishersExecuted;
    m_state.lastFinisherCastMs = nowMs;
    m_state.lastSkillCastMs = nowMs;

    // Tung đòn kết liễu cực đại ('R') nhắm thẳng vào mục tiêu
    AimAndCast(m_config.finisherSkillVk, dx, dy, kmbox, 60);

    const std::string logMsg = "[StaggerFinisher #" + std::to_string(m_state.totalFinishersExecuted)
        + "] Quai bi STAGGER -> Tung Heavy Finisher [R] vao: " + targetName;
    std::cout << logMsg << std::endl;
    CoreLog(logMsg);
}

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

    float closestMonsterDist = 9999.0f;
    uint32_t closestMonsterId = 0;
    float closestX = 0.0f, closestY = 0.0f, closestZ = 0.0f;

    float closestBossDist = 9999.0f;
    uint32_t closestBossId = 0;
    float bossX = 0.0f, bossY = 0.0f, bossZ = 0.0f;

    for (uint32_t i = 0; i < packet.entityCount; ++i) {
        const auto& ent = packet.entities[i];
        if (ent.type != 1) continue; // Monster
        if (ent.extraFlags & 4) continue; // Dead
        if (ent.maxHP == 0) continue;

        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 dist = (len > 0.001f) ? len : ent.distanceToPlayer;

        const bool isBoss = (ent.rarity == 3 || (ent.extraFlags & 1));
        if (isBoss && dist < closestBossDist) {
            closestBossDist = dist;
            closestBossId = ent.id;
            bossX = ent.posX;
            bossY = ent.posY;
            bossZ = ent.posZ;
        }

        if (dist < closestMonsterDist) {
            closestMonsterDist = dist;
            closestMonsterId = ent.id;
            closestX = ent.posX;
            closestY = ent.posY;
            closestZ = ent.posZ;
        }
    }

    // 0. Phân tích cụm bãi quái thời gian thực 120Hz (Tier 1 Hot Path DOD)
    const auto packResult = m_packClusterer.ClusterPacks(packet, m_terrainGrid);
    const auto* bestCluster = packResult.BestCluster();

    // 1. Quái sống trong cự ly nguy hiểm safeRange (<= 50) -> EMERGENCY_ATTACK (7500)
    if (closestMonsterDist <= m_config.safeRange && closestMonsterId != 0) {
        ActionProposal p{};
        p.kind = BotActionKind::EmergencyAttack;
        p.priority = bot_priority::EMERGENCY_ATTACK;
        p.priorityName = "EMERGENCY_ATTACK";
        p.entityId = closestMonsterId;
        p.targetX = closestX;
        p.targetY = closestY;
        p.targetZ = closestZ;
        p.distance = closestMonsterDist;
        return p;
    }

    // 2. Boss nhìn thấy trên radar -> COMBAT (7000)
    if (closestBossId != 0) {
        ActionProposal p{};
        p.kind = BotActionKind::Combat;
        p.priority = bot_priority::COMBAT;
        p.priorityName = "COMBAT";
        p.entityId = closestBossId;
        p.targetX = bossX;
        p.targetY = bossY;
        p.targetZ = bossZ;
        p.distance = closestBossDist;
        return p;
    }

    // 3. Ưu tiên bãi quái mật độ cao (Pack Centroid AoE Targeting)
    if (bestCluster && bestCluster->monsterCount >= 2 && bestCluster->distanceToPlayer <= m_config.engageRange) {
        ActionProposal p{};
        p.kind = BotActionKind::Combat;
        p.entityId = bestCluster->primaryTargetId;
        p.targetX = bestCluster->centroidX;
        p.targetY = bestCluster->centroidY;
        p.targetZ = player.posZ;
        p.distance = bestCluster->distanceToPlayer;

        if (bestCluster->distanceToPlayer <= m_config.monstersAttackRange) {
            p.priority = bot_priority::COMBAT;
            p.priorityName = "COMBAT_PACK";
        } else {
            p.priority = bot_priority::COMBAT_ENGAGE;
            p.priorityName = "COMBAT_ENGAGE_PACK";
        }
        return p;
    }

    // 4. Quái sống đơn lẻ trong cự ly giao chiến engageRange (<= 380u)
    if (closestMonsterDist <= m_config.engageRange && closestMonsterId != 0) {
        ActionProposal p{};
        p.kind = BotActionKind::Combat;
        p.entityId = closestMonsterId;
        p.targetX = closestX;
        p.targetY = closestY;
        p.targetZ = closestZ;
        p.distance = closestMonsterDist;

        if (closestMonsterDist <= m_config.monstersAttackRange) {
            // Trong tầm đánh trực tiếp -> COMBAT (7000)
            p.priority = bot_priority::COMBAT;
            p.priorityName = "COMBAT";
        } else {
            // Ngoài tầm đánh, đang tiếp cận -> COMBAT_ENGAGE (5800)
            // Nhường quyền nhặt đồ quý gần chân (PICKUP 6000) trước khi lao vào bãi quái xa!
            p.priority = bot_priority::COMBAT_ENGAGE;
            p.priorityName = "COMBAT_ENGAGE";
        }
        return p;
    }

    return {};
}

void ComboManager::ReleaseEngageKeys(KMBoxNet& kmbox) {
    if (m_moveCtrl) {
        m_moveCtrl->Stop(kmbox);
    }
    if (!m_heldEngageKeys.empty()) {
        for (uint8_t k : m_heldEngageKeys) {
            kmbox.KeyUp(k);
        }
        m_heldEngageKeys.clear();
    }
}

bool ComboManager::ExecuteEngageMovement(const ActionProposal& chosen, const TelemetryPacket& packet, KMBoxNet& kmbox, uint64_t nowMs) {
    if (!EnsurePoe2WindowFocus()) return false;

    const float dx = chosen.targetX - packet.player.posX;
    const float dy = chosen.targetY - packet.player.posY;
    const float dist = std::sqrt(dx * dx + dy * dy);
    if (dist < 0.001f) return false;

    // 1. Nếu có Gap-Closer (MMB Wolf Leap) và cự ly phù hợp (>= 100u) -> Nhảy áp sát yểm bùa
    if (m_config.useGapCloserOnEngage && dist >= 100.0f && (nowMs - m_lastGapCloserMs >= m_config.gapCloserCooldownMs)) {
        ReleaseEngageKeys(kmbox);
        AimAndCast(m_config.gapCloserSkillVk, dx, dy, kmbox, 40);
        m_lastGapCloserMs = nowMs;
        CoreLog("[CombatEngage] Tung Gap-Closer (MMB Wolf Leap) ap sat quai cach " + std::to_string(static_cast<int>(dist)) + "u");
        return true;
    }

    // 2. Tiếp cận phi khóa (Non-Blocking Stateful) qua MovementController
    if (m_moveCtrl) {
        return m_moveCtrl->SteerTowards(dx, dy, kmbox, nowMs);
    }

    // Fallback phi khóa (Non-Blocking WASD):
    common::WasdVector wasd = common::WorldToWasd(dx, dy);
    if (!wasd.empty()) {
        std::vector<uint8_t> keys;
        if (wasd.up) keys.push_back(0x1A);
        if (wasd.left) keys.push_back(0x04);
        if (wasd.down) keys.push_back(0x16);
        if (wasd.right) keys.push_back(0x07);

        if (keys != m_heldEngageKeys) {
            for (uint8_t oldK : m_heldEngageKeys) {
                if (std::find(keys.begin(), keys.end(), oldK) == keys.end()) {
                    kmbox.KeyUp(oldK);
                }
            }
            for (uint8_t newK : keys) {
                if (std::find(m_heldEngageKeys.begin(), m_heldEngageKeys.end(), newK) == m_heldEngageKeys.end()) {
                    kmbox.KeyDown(newK);
                }
            }
            m_heldEngageKeys = keys;
        }
        return true;
    }
    return false;
}

bool ComboManager::Execute(const ActionProposal& chosen, const TelemetryPacket& packet, KMBoxNet& kmbox, uint64_t nowMs) {
    if (chosen.kind != BotActionKind::EmergencyAttack && chosen.kind != BotActionKind::Combat) {
        ReleaseEngageKeys(kmbox);
        return false;
    }

    // Nếu quái ở cự ly tiếp cận (150u < dist <= engageRange 380u): Kích hoạt bước tiếp cận giao tranh
    if (chosen.distance > m_config.monstersAttackRange) {
        return ExecuteEngageMovement(chosen, packet, kmbox, nowMs);
    }

    ReleaseEngageKeys(kmbox);
    return Update(packet, kmbox, nowMs);
}

bool ComboManager::LoadSkillProfile(const std::string& tomlPath) {
    if (!m_skillEngine) {
        m_skillEngine = std::make_unique<SkillEngine>();
    }
    return m_skillEngine->LoadProfile(tomlPath);
}

void ComboManager::SetCombatProfile(CombatProfile profile, uint32_t targetEntityId) {
    if (m_state.activeCombatProfile == profile && m_state.activeBossId == targetEntityId) {
        return;
    }
    m_state.activeCombatProfile = profile;
    if (profile == CombatProfile::BOSS_BURST) {
        m_state.isBossBurstWindow = true;
        m_state.burstWindowStartMs = 0; // Khi được chỉ thị từ Agent, giữ mở liên tục
        m_state.isBossEncounter = true;
        if (targetEntityId != 0) {
            m_state.activeBossId = targetEntityId;
        }
        m_config.bossStaggerBurstEnabled = true;
        m_config.autoStaggerFinisher = true;
    } else if (profile == CombatProfile::MOB_CLEARING) {
        m_state.isBossBurstWindow = false;
        m_state.isBossEncounter = false;
        m_state.activeBossId = 0;
    } else if (profile == CombatProfile::SURVIVAL_KITING) {
        m_state.isBossBurstWindow = false;
    }
}
