#pragma once

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <tlhelp32.h>

#include <cstdint>
#include <functional>
#include <string>
#include <vector>

// ==========================================================
// IMemoryReader - Giao diện trừu tượng tầng đọc bộ nhớ game
// (Giai đoạn 2 - Nhiệm vụ 2.2 theo WBS)
//
// Cho phép hoán đổi linh hoạt các back-end:
//   1. ReadProcessMemoryReader - User-mode, dễ debug (mặc định)
//   2. DMA Reader (vmmdll.h)   - PCIe DMA Card   (tương lai)
//   3. Kernel Driver Reader    - MmCopyVirtualMemory (tương lai)
//
// LƯU Ý: docs/research/01 đã chứng minh client POE2 chạy với
// High Integrity Token nên back-end User-mode thường bị
// ERROR_ACCESS_DENIED - cần chạy tool với quyền Admin hoặc
// dùng back-end phần cứng.
// ==========================================================

class IMemoryReader {
public:
    // Callback liệt kê region: (base, size). Trả về false để dừng.
    using RegionFn = std::function<bool(uintptr_t, size_t)>;

    virtual ~IMemoryReader() = default;

    virtual const char* BackendName() const = 0;

    virtual bool Attach(uint32_t pid) = 0;
    virtual void Detach() = 0;

    virtual bool IsAttached() const { return m_pid != 0; }
    uint32_t AttachedPid() const { return m_pid; }

    // Đọc mảng byte thô từ không gian địa chỉ của tiến trình
    virtual bool Read(uintptr_t address, void* buffer, size_t size) = 0;

    // Liệt kê các region memory readable (phục vụ quét pattern).
    // Backend SIM: 1 region duy nhất. Backend RPM: VirtualQueryEx walk.
    virtual bool ForEachReadableRegion(const RegionFn& fn) = 0;

    // Liệt kê các region heap động riêng tư (MEM_COMMIT + MEM_PRIVATE + PAGE_READWRITE, 64KB - 32MB)
    // Tối ưu hóa tốc độ quét HP/Mana, loại trừ 95% RAM đồ họa và file mapped.
    virtual bool ForEachHeapRegion(const RegionFn& fn) {
        return ForEachReadableRegion(fn);
    }

    // Đọc 1 giá trị kiểu T tại địa chỉ
    template <typename T>
    bool ReadValue(uintptr_t address, T& outValue) {
        return Read(address, &outValue, sizeof(T));
    }

    // Ghi mảng byte thô vào không gian địa chỉ của tiến trình
    virtual bool Write(uintptr_t /*address*/, const void* /*buffer*/, size_t /*size*/) {
        return false;
    }

    // Ghi 1 giá trị kiểu T tại địa chỉ
    template <typename T>
    bool WriteValue(uintptr_t address, const T& value) {
        return Write(address, &value, sizeof(T));
    }

    // Giải chuỗi con trỏ nhiều cấp (pointer chain):
    // base -> [base + off0] -> [.. + off1] -> ... -> giá trị cuối
    bool DerefPointerChain(uintptr_t base, const std::vector<uintptr_t>& offsets,
                           uintptr_t& outAddress) {
        uintptr_t current = base;
        for (size_t i = 0; i < offsets.size(); ++i) {
            if (!ReadValue<uintptr_t>(current, current)) return false;
            if (current == 0) return false;
            current += offsets[i];
        }
        outAddress = current;
        return true;
    }

protected:
    uint32_t m_pid = 0;
};

// Tìm PID của tiến trình theo tên file exe (không phân biệt hoa thường).
// Trả về 0 nếu không thấy.
inline uint32_t FindProcessId(const std::wstring& processName) {
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) return 0;

    PROCESSENTRY32W entry{};
    entry.dwSize = sizeof(entry);

    uint32_t foundPid = 0;
    if (Process32FirstW(snapshot, &entry)) {
        do {
            if (_wcsicmp(entry.szExeFile, processName.c_str()) == 0) {
                foundPid = entry.th32ProcessID;
                break;
            }
        } while (Process32NextW(snapshot, &entry));
    }
    CloseHandle(snapshot);
    return foundPid;
}

// Tự động tìm PID và tên tiến trình POE2 (hỗ trợ cả Standalone lẫn Steam, 64-bit)
inline uint32_t FindPoeProcessId(std::wstring* outName = nullptr) {
    const std::wstring candidateNames[] = {
        L"PathOfExile.exe",
        L"PathOfExileSteam.exe",
        L"PathOfExile_x64.exe",
        L"PathOfExile_x64Steam.exe",
        L"PathOfExile2.exe"
    };
    for (const auto& name : candidateNames) {
        uint32_t pid = FindProcessId(name);
        if (pid != 0) {
            if (outName) *outName = name;
            return pid;
        }
    }
    return 0;
}