// ==========================================================
// world_to_screen.cpp - 3D World To Screen 4x4 Projection Math
// Poe2VisualTool v1.2.0 Pro In-Game HUD Engine
// C++23 Native - High-precision SIMD / Scalar Projection
// ==========================================================

#include "visual/world_to_screen.hpp"
#include <cmath>

namespace visual {

bool WorldToScreen::Project(
    const Matrix4x4& viewProj,
    const Vector3& worldPos,
    int screenWidth,
    int screenHeight,
    Vector2& outScreenPos,
    bool columnMajor
) {
    return Project(viewProj.m, worldPos.x, worldPos.y, worldPos.z, screenWidth, screenHeight, outScreenPos.x, outScreenPos.y, columnMajor);
}

bool WorldToScreen::Project(
    const float matrix[16],
    float x, float y, float z,
    int screenWidth,
    int screenHeight,
    float& outX,
    float& outY,
    bool columnMajor
) {
    if (screenWidth <= 0 || screenHeight <= 0) {
        return false;
    }

    float clipX = 0.0f;
    float clipY = 0.0f;
    float clipZ = 0.0f;
    float clipW = 0.0f;

    if (!columnMajor) {
        // Row-major: v * M (DirectX HLSL default)
        // clip = [x, y, z, 1.0] * M
        clipX = x * matrix[0] + y * matrix[4] + z * matrix[8]  + matrix[12];
        clipY = x * matrix[1] + y * matrix[5] + z * matrix[9]  + matrix[13];
        clipZ = x * matrix[2] + y * matrix[6] + z * matrix[10] + matrix[14];
        clipW = x * matrix[3] + y * matrix[7] + z * matrix[11] + matrix[15];
    } else {
        // Column-major: M * v (OpenGL / standard math)
        clipX = matrix[0] * x + matrix[1] * y + matrix[2]  * z + matrix[3];
        clipY = matrix[4] * x + matrix[5] * y + matrix[6]  * z + matrix[7];
        clipZ = matrix[8] * x + matrix[9] * y + matrix[10] * z + matrix[11];
        clipW = matrix[12] * x + matrix[13] * y + matrix[14] * z + matrix[15];
    }

    // IEEE 754 NaN/Inf check + Near plane check
    if (!std::isfinite(clipW) || !std::isfinite(clipX) || !std::isfinite(clipY) || !std::isfinite(clipZ)) {
        return false;
    }
    if (clipW <= 0.001f || clipZ < 0.0f) {
        return false;
    }

    // Normalized Device Coordinates (NDC) in [-1.0, 1.0]
    const float invW = 1.0f / clipW;
    const float ndcX = clipX * invW;
    const float ndcY = clipY * invW;

    // Viewport mapping:
    // Screen X: [-1.0, 1.0] -> [0.0, screenWidth]
    // Screen Y: [-1.0, 1.0] -> [0.0, screenHeight] (Y inverted for top-left screen origin)
    outX = (ndcX + 1.0f) * 0.5f * static_cast<float>(screenWidth);
    outY = (1.0f - ndcY) * 0.5f * static_cast<float>(screenHeight);

    return true;
}

bool WorldToScreen::IsOnScreen(const Vector2& screenPos, int screenWidth, int screenHeight, float margin) {
    return screenPos.x >= -margin && screenPos.x <= static_cast<float>(screenWidth) + margin &&
           screenPos.y >= -margin && screenPos.y <= static_cast<float>(screenHeight) + margin;
}

Vector2 WorldToScreen::ProjectToMinimapRadar(
    const Vector3& playerPos,
    const Vector3& targetPos,
    float cameraYawRad,
    float radarScale,
    float maxRadiusPixels
) {
    // Delta in world coordinates (X = East/West, Y = North/South or Z depending on POE2 terrain axes)
    // POE2 world coords: X and Y are plane coordinates, Z is height
    const float dx = targetPos.x - playerPos.x;
    const float dy = targetPos.y - playerPos.y;

    // Rotate by camera yaw so forward in camera matches radar up
    const float cosYaw = std::cos(cameraYawRad);
    const float sinYaw = std::sin(cameraYawRad);

    const float rotX = dx * cosYaw - dy * sinYaw;
    const float rotY = dx * sinYaw + dy * cosYaw;

    // Scale to radar pixels
    float radarX = rotX * radarScale;
    float radarY = -rotY * radarScale; // Invert Y for screen display (up is negative Y)

    // Clamp to circular radar radius if outside
    const float dist = std::sqrt(radarX * radarX + radarY * radarY);
    if (dist > maxRadiusPixels && dist > 0.001f) {
        const float factor = maxRadiusPixels / dist;
        radarX *= factor;
        radarY *= factor;
    }

    return Vector2(radarX, radarY);
}

} // namespace visual
