/**
 * RustOffsets.com — C++ Auto-Update Example
 *
 * Fetches offsets from the API at runtime so your cheat stays up-to-date
 * when Rust updates. No need to recompile or manually update headers.
 *
 * Requirements:
 *   - Windows (WinHTTP) or replace with cURL/cpp-httplib
 *   - nlohmann/json (single header: https://github.com/nlohmann/json)
 *     Add json.hpp to your include path.
 *
 * Usage:
 *   1. Copy this file into your project
 *   2. Add nlohmann/json (include/json.hpp)
 *   3. Call RustOffsets::Init() at startup
 *   4. Use RustOffsets::Get("BaseNetworkable::client_entities") etc.
 */

#include <cstdint>
#include <string>
#include <unordered_map>

#ifdef _WIN32
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
#endif

// Add nlohmann/json single header to your project
#include "json.hpp"
using json = nlohmann::json;


namespace RustOffsets {

constexpr const char* DEFAULT_API = "https://rustoffsets.com/api/offsets";

struct OffsetsData {
    std::string build_id;
    std::unordered_map<std::string, std::uintptr_t> offsets;
    json decryptions;
    bool loaded = false;
};

static OffsetsData g_data;

#ifdef _WIN32
static std::string HttpGet(const char* url) {
    std::string result;
    HINTERNET hSession = WinHttpOpen(L"RustOffsets/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hSession) return result;

    // Parse URL (convert narrow to wide)
    int len = MultiByteToWideChar(CP_UTF8, 0, url, -1, NULL, 0);
    if (len <= 0) { WinHttpCloseHandle(hSession); return result; }
    std::wstring wurl(len, 0);
    MultiByteToWideChar(CP_UTF8, 0, url, -1, wurl.data(), len);

    URL_COMPONENTS urlComp = {};
    urlComp.dwStructSize = sizeof(urlComp);
    wchar_t host[256] = {}, path[1024] = {};
    urlComp.lpszHostName = host;
    urlComp.dwHostNameLength = (DWORD)(sizeof(host) / sizeof(wchar_t));
    urlComp.lpszUrlPath = path;
    urlComp.dwUrlPathLength = (DWORD)(sizeof(path) / sizeof(wchar_t));

    if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) {
        WinHttpCloseHandle(hSession);
        return result;
    }

    HINTERNET hConnect = WinHttpConnect(hSession, host, urlComp.nPort, 0);
    if (!hConnect) {
        WinHttpCloseHandle(hSession);
        return result;
    }

    DWORD flags = (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
    if (!hRequest) {
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return result;
    }

    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return result;
    }

    if (!WinHttpReceiveResponse(hRequest, NULL)) {
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return result;
    }

    DWORD size = 0, read = 0;
    do {
        WinHttpQueryDataAvailable(hRequest, &size);
        if (size == 0) break;
        std::string buf(size, 0);
        WinHttpReadData(hRequest, buf.data(), size, &read);
        result.append(buf.data(), read);
    } while (size > 0);

    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return result;
}
#else
static std::string HttpGet(const char* url) {
    (void)url;
    // Implement with cURL, cpp-httplib, or similar for your platform
    return "";
}
#endif

/// Flatten namespaces to "Namespace::field" or "field" for _root
static void ParseOffsets(const json& namespaces,
    std::unordered_map<std::string, std::uintptr_t>& out) {
    if (!namespaces.is_object()) return;

    for (auto& [ns_name, ns_data] : namespaces.items()) {
        if (!ns_data.contains("fields") || !ns_data["fields"].is_object()) continue;

        std::string prefix = (ns_name == "_root") ? "" : (ns_name + "::");

        for (auto& [fname, fdata] : ns_data["fields"].items()) {
            if (!fdata.is_object() || !fdata.contains("offset")) continue;
            auto& off = fdata["offset"];
            if (off.is_null()) continue;

            std::string offset_str = off.get<std::string>();
            if (offset_str.size() < 3 || offset_str[0] != '0' || (offset_str[1] != 'x' && offset_str[1] != 'X'))
                continue;

            std::uintptr_t val = std::stoull(offset_str, nullptr, 16);
            std::string key = prefix + fname;
            out[key] = val;
        }
    }
}

/**
 * Initialize: fetch offsets from the API.
 * Call once at startup (e.g. before attaching to the game).
 *
 * @param api_url Optional custom API URL (default: rustoffsets.com)
 * @return true if offsets loaded successfully
 */
bool Init(const char* api_url = nullptr) {
    const char* url = api_url ? api_url : DEFAULT_API;
    std::string body = HttpGet(url);
    if (body.empty()) return false;

    try {
        json j = json::parse(body);
        if (j.contains("error")) return false;

        g_data.offsets.clear();
        g_data.build_id = j.value("build_id", "");
        if (j.contains("namespaces")) {
            ParseOffsets(j["namespaces"], g_data.offsets);
        }
        if (j.contains("decryptions") && j["decryptions"].is_object()) {
            g_data.decryptions = j["decryptions"];
        }
        g_data.loaded = true;
        return true;
    } catch (...) {
        return false;
    }
}

/**
 * Get an offset by key. Keys match the JSON structure:
 *   - "get_handle"              (il2cpp handle)
 *   - "basenetworkable_pointer"  (TypeInfo for BaseNetworkable)
 *   - "BaseNetworkable::client_entities"
 *   - "BaseNetworkable::entity_list"
 *   - "BasePlayer::playerFlags"
 *   - "BaseCamera::position"
 *   - etc.
 *
 * @param key Field key (see offsets.hpp or API JSON for full list)
 * @return Offset value or 0 if not found / not loaded
 */
std::uintptr_t Get(const char* key) {
    if (!g_data.loaded) return 0;
    auto it = g_data.offsets.find(key);
    return (it != g_data.offsets.end()) ? it->second : 0;
}

/// Alias for Get() with std::string
std::uintptr_t Get(const std::string& key) {
    return Get(key.c_str());
}

/// Check if offsets were loaded successfully
bool IsLoaded() { return g_data.loaded; }

/// Get the build ID these offsets are for (e.g. "22359278")
const std::string& BuildId() { return g_data.build_id; }

/// Access decryption routines if needed (BaseNetworkable, BasePlayer, etc.)
const json& Decryptions() {
    return g_data.decryptions;
}

} // namespace RustOffsets


// ============== USAGE EXAMPLE ==============
/*
int main() {
    if (!RustOffsets::Init()) {
        printf("Failed to fetch offsets\n");
        return 1;
    }

    printf("Loaded offsets for build %s\n", RustOffsets::BuildId().c_str());

    std::uintptr_t get_handle = RustOffsets::Get("get_handle");
    std::uintptr_t client_entities = RustOffsets::Get("BaseNetworkable::client_entities");
    std::uintptr_t player_flags = RustOffsets::Get("BasePlayer::playerFlags");

    // Use with GameAssembly base:
    // uintptr_t base = GetModuleBase("GameAssembly.dll");
    // uintptr_t handle_ptr = base + get_handle;
    // uintptr_t handle = *(uintptr_t*)handle_ptr;
    // ... il2cpp resolution, entity list, etc.

    return 0;
}
*/
