#pragma once

#include <cstdint>
#include <string>
#include <vector>

#include "memory/imemory_reader.hpp"

// ==========================================================
// vitals_fingerprint - Khóa LifeComponent bằng pool HUD thật
// (CI: maxHP=1, maxES/Ward/Mana/Spirit do người chơi đọc trên HUD)
//
// Không dùng overlay PlayerStateMemory giả lập (XYZ float + HP +16).
// Layout sống đã đối chiếu PID 36104, 10/09/2026 15:19:27, HUD CI:
//   +0x00 curHP=1, +0x04 maxHP=1, +0x08 unreserved=1
//   +0x0C/+0x10 ES 2416/2416, +0x14 unreservedES=1
//   +0x18=0, +0x1C=0xFFFFFFFF (KHÔNG phải Mana)
//   +0x20/+0x24 Ward 826/826
// Mana 828/828 tại +0x420, Spirit 8/138 tại +0x44C (cùng object Life, dump
// 10/09/2026 18:21:59 PID 36104). +0x18/+0x1C vẫn KHÔNG phải Mana.
// INV-OFFSET-01: chỉ trả về địa chỉ heap trong report tạm, không persist.
// ==========================================================
namespace vitals_fingerprint {

// Offset tương đối từ LifeComponent.curHP — khóa live 10/09/2026 18:21.
constexpr int kHpRel = 0;
constexpr int kEsRel = 12;       // +0x0C
constexpr int kWardRel = 32;     // +0x20
constexpr int kLegacyManaRel = 24; // +0x18 — sentinel 0/0xFFFFFFFF trên dump CI
constexpr int kManaRel = 0x420;  // +1056, HUD 828/828
constexpr int kSpiritRel = 0x44C; // +1100, HUD 8/138

// Cặp ES hợp lệ trên Life (HUD CI 4713/4713, buff Endurance charges >5500).
// cur == 0 là trạng thái hợp lệ khi nhân vật bị quái đánh vỡ khiên (broken shield).
inline bool IsPlausibleEsPair(uint32_t cur, uint32_t mx) {
    return mx >= 20 && mx <= 50000 && cur <= mx * 2;
}
inline bool IsInvertedEsPair(uint32_t cur, uint32_t mx) {
    return mx > 0 && mx <= 50000 && (cur > mx * 3 || (mx < 100 && cur > mx));
}

struct Expected {
    uint32_t maxHP = 1;
    uint32_t maxES = 0;
    uint32_t maxWard = 0;
    uint32_t maxMana = 0;
    uint32_t curSpirit = 0;
    uint32_t maxSpirit = 0;

    bool Enabled() const { return maxES != 0 || maxWard != 0 || maxMana != 0 || maxSpirit != 0; }
};

struct PoolHit {
    int offset = -1;   // byte offset từ lifeAddr
    uint32_t cur = 0;
    uint32_t max = 0;
};

struct LifeMatch {
    uintptr_t lifeAddr = 0;
    bool ecsSignature = false;
    uint32_t hp = 0;
    uint32_t maxHP = 0;
    PoolHit es;
    PoolHit ward;
    PoolHit mana;
    PoolHit spirit;
    int score = 0;
};

struct ScanStats {
    size_t regionsVisited = 0;
    size_t chunkReadsOk = 0;
    size_t chunkReadsFail = 0;
    size_t esDwords = 0;
    size_t matchAttempts = 0;
};

struct PairHit {
    uintptr_t addr = 0; // địa chỉ cur (max tại +4)
    uint32_t cur = 0;
    uint32_t max = 0;
};

bool MatchAt(IMemoryReader& reader, uintptr_t lifeAddr, const Expected& exp, LifeMatch& out);

// Quét cặp {cur, max} u32 aligned. expectCur = UINT32_MAX → mọi cur <= max*2.
std::vector<PairHit> ScanCurMaxPair(IMemoryReader& reader, uint32_t expectCur,
                                    uint32_t expectMax, size_t maxHits = 32,
                                    ScanStats* stats = nullptr);

// Quét mọi region readable. Không phụ thuộc Read() một cửa sổ 1088-byte liền mạch
// (Life sát đầu heap slab thì life-64 là PAGE_NOACCESS).
std::vector<LifeMatch> ScanHeap(IMemoryReader& reader, const Expected& exp,
                                size_t maxHits = 16, ScanStats* stats = nullptr);

// Từ heap InGameState: *(+0x10) rồi cửa sổ / 1 cấp pointer tới Life khớp fingerprint.
bool FindLifeFromInGame(IMemoryReader& reader, uintptr_t inGame, const Expected& exp, LifeMatch& out);

std::string Describe(const LifeMatch& hit);

} // namespace vitals_fingerprint
