#pragma once
#include <cstdint>
#include <cmath>
#include <string>
#include <algorithm>
#include <numbers>    // std::numbers::pi (C++20)

#include "../Core/Models.h"

// -------------------------------------------------------------------------
// Header-only port of C# MemoryValidation.cs
// -------------------------------------------------------------------------

namespace MemoryValidation {

// -------------------------------------------------------------------------
// Address sanity
// -------------------------------------------------------------------------

/// Returns true if the address falls in the valid user-mode range:
/// [0x0000000100000000, 0x0000FFFF00000000)
[[nodiscard]] inline bool IsValidUserAddress(uint64_t address) noexcept {
    constexpr uint64_t kLow  = 0x0000000100000000ULL;
    constexpr uint64_t kHigh = 0x0000FFFF00000000ULL;
    return address >= kLow && address < kHigh;
}

// -------------------------------------------------------------------------
// Vector plausibility
// -------------------------------------------------------------------------

/// Returns true when all three components are finite and within ±100 000 units.
[[nodiscard]] inline bool IsValidVector(const Vector3& v) noexcept {
    constexpr float kLimit = 100000.0f;
    return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z)
        && std::abs(v.x) < kLimit
        && std::abs(v.y) < kLimit
        && std::abs(v.z) < kLimit;
}

// -------------------------------------------------------------------------
// String helpers
// -------------------------------------------------------------------------

/// Returns true when the string is non-empty and not all whitespace.
[[nodiscard]] inline bool IsUsableString(const std::string& s) noexcept {
    if (s.empty()) return false;
    for (char c : s) {
        if (c != ' ' && c != '\t' && c != '\r' && c != '\n') return true;
    }
    return false;
}

namespace detail {

/// Classifies a char as "readable": printable ASCII (0x20-0x7E) or high-byte
/// (Latin-1 / Windows-1251 printable range 0xA0-0xFF).
[[nodiscard]] inline bool IsReadableChar(unsigned char c) noexcept {
    return (c >= 0x20 && c <= 0x7E) || (c >= 0xA0);
}

/// Returns the fraction of chars that are readable (0.0–1.0).
[[nodiscard]] inline double ReadableRatio(const std::string& s) noexcept {
    if (s.empty()) return 0.0;
    int readable = 0;
    for (unsigned char c : s) {
        if (IsReadableChar(c)) ++readable;
    }
    return static_cast<double>(readable) / static_cast<double>(s.size());
}

/// Returns true when the string contains at least one control character
/// (excluding normal whitespace: space, tab, CR, LF).
[[nodiscard]] inline bool HasUnwantedControlChar(const std::string& s) noexcept {
    for (unsigned char c : s) {
        if (c < 0x20 && c != '\t' && c != '\r' && c != '\n') return true;
    }
    return false;
}

} // namespace detail

/// Returns true when the server name is plausible:
///   - length 3–96
///   - no control characters
///   - readable-char ratio > 50 %
///   - contains at least one ASCII alphanumeric character
[[nodiscard]] inline bool IsPlausibleServerName(const std::string& s) noexcept {
    if (s.size() < 3 || s.size() > 96) return false;
    if (detail::HasUnwantedControlChar(s)) return false;
    if (detail::ReadableRatio(s) < 0.5) return false;
    for (char c : s) {
        if (std::isalnum(static_cast<unsigned char>(c))) return true;
    }
    return false;
}

/// Returns true when the game version string is plausible:
///   - length 3–32
///   - contains at least one digit
///   - contains at least one of '.', '-', '_'
[[nodiscard]] inline bool IsPlausibleGameVersion(const std::string& s) noexcept {
    if (s.size() < 3 || s.size() > 32) return false;
    bool hasDigit     = false;
    bool hasSeparator = false;
    for (char c : s) {
        if (std::isdigit(static_cast<unsigned char>(c))) hasDigit     = true;
        if (c == '.' || c == '-' || c == '_')            hasSeparator = true;
    }
    return hasDigit && hasSeparator;
}

/// Sanitizes an entity string read from memory.
/// Returns empty string if the content has unwanted control characters
/// or if the readable-char ratio is below 50 %.
/// Otherwise returns the original string.
[[nodiscard]] inline std::string SanitizeEntityString(const std::string& s) {
    if (s.empty()) return {};
    if (detail::HasUnwantedControlChar(s)) return {};
    if (detail::ReadableRatio(s) < 0.5) return {};
    return s;
}

// -------------------------------------------------------------------------
// Heading helpers
// -------------------------------------------------------------------------

/// Normalises an arbitrary heading (degrees) into [0, 360).
[[nodiscard]] inline float NormalizeHeading(float degrees) noexcept {
    float result = std::fmod(degrees, 360.0f);
    if (result < 0.0f) result += 360.0f;
    return result;
}

/// Computes a world heading (degrees, 0 = north/+Z) from a 2-D direction
/// vector (dirX, dirY) where dirY typically corresponds to the forward axis.
///
/// Formula mirrors the C# original:
///   heading = atan2(dirY, dirX) * 180 / PI + 90
/// then normalized to [0, 360).
///
/// Returns false when the direction vector is degenerate (near-zero length).
[[nodiscard]] inline bool TryGetCorrectedHeadingFromDirection(
    float dirX, float dirY, float& heading) noexcept
{
    constexpr float kEpsilon = 1e-6f;
    if (std::abs(dirX) < kEpsilon && std::abs(dirY) < kEpsilon) {
        heading = 0.0f;
        return false;
    }
    constexpr float kRadToDeg = 180.0f / static_cast<float>(std::numbers::pi);
    heading = NormalizeHeading(std::atan2(dirY, dirX) * kRadToDeg + 90.0f);
    return true;
}

} // namespace MemoryValidation
