#include "core/orbit_camera.h"
#include "imgui.h"

#include <cmath>

namespace fn::core {

namespace {

constexpr float kPi = 3.14159265358979323846f;

// row-major identity
void mat4_identity(float m[16]) {
    for (int i = 0; i < 16; ++i) m[i] = 0.0f;
    m[0] = m[5] = m[10] = m[15] = 1.0f;
}

// row-major: m[row*4 + col]
void make_lookat(float view[16],
                 float ex, float ey, float ez,
                 float cx, float cy, float cz,
                 float ux, float uy, float uz) {
    // forward = normalize(center - eye)
    float fx = cx - ex, fy = cy - ey, fz = cz - ez;
    float fl = std::sqrt(fx*fx + fy*fy + fz*fz);
    if (fl > 0) { fx /= fl; fy /= fl; fz /= fl; }
    // side = normalize(cross(forward, up))
    float sx = fy*uz - fz*uy;
    float sy = fz*ux - fx*uz;
    float sz = fx*uy - fy*ux;
    float sl = std::sqrt(sx*sx + sy*sy + sz*sz);
    if (sl > 0) { sx /= sl; sy /= sl; sz /= sl; }
    // up' = cross(side, forward)
    float ux2 = sy*fz - sz*fy;
    float uy2 = sz*fx - sx*fz;
    float uz2 = sx*fy - sy*fx;

    mat4_identity(view);
    view[0] = sx;   view[1] = sy;   view[2] = sz;   view[3]  = -(sx*ex + sy*ey + sz*ez);
    view[4] = ux2;  view[5] = uy2;  view[6] = uz2;  view[7]  = -(ux2*ex + uy2*ey + uz2*ez);
    view[8] = -fx;  view[9] = -fy;  view[10] = -fz; view[11] = (fx*ex + fy*ey + fz*ez);
    view[12] = 0;   view[13] = 0;   view[14] = 0;   view[15] = 1;
}

void make_perspective(float proj[16], float fov_deg, float aspect, float n, float f) {
    float fov_rad = fov_deg * kPi / 180.0f;
    float t = 1.0f / std::tan(fov_rad * 0.5f);
    for (int i = 0; i < 16; ++i) proj[i] = 0.0f;
    proj[0]  = t / aspect;
    proj[5]  = t;
    proj[10] = (f + n) / (n - f);
    proj[11] = (2.0f * f * n) / (n - f);
    proj[14] = -1.0f;
    proj[15] = 0.0f;
}

} // namespace

CameraMatrices orbit_camera_matrices(const OrbitCamera& cam) {
    CameraMatrices m{};
    // Convert spherical (azimuth, elevation, distance) -> cartesian eye position.
    // azimuth rotates around Y; elevation tilts up/down.
    float ce = std::cos(cam.elevation);
    float se = std::sin(cam.elevation);
    float ca = std::cos(cam.azimuth);
    float sa = std::sin(cam.azimuth);
    float ex = cam.distance * ce * sa;
    float ey = cam.distance * se;
    float ez = cam.distance * ce * ca;

    make_lookat(m.view, ex, ey, ez, 0, 0, 0, 0, 1, 0);
    make_perspective(m.proj, cam.fov, cam.aspect, cam.near_plane, cam.far_plane);
    return m;
}

void orbit_camera_handle_drag(OrbitCamera& cam, ImVec2 drag_delta, float wheel) {
    constexpr float kDragSens = 0.01f;
    cam.azimuth   += drag_delta.x * kDragSens;
    cam.elevation += drag_delta.y * kDragSens;

    // clamp elevation to ±π/2 - eps to avoid gimbal flip
    constexpr float kElevMax = kPi * 0.5f - 1e-3f;
    if (cam.elevation >  kElevMax) cam.elevation =  kElevMax;
    if (cam.elevation < -kElevMax) cam.elevation = -kElevMax;

    if (wheel != 0.0f) {
        cam.distance *= (1.0f - wheel * 0.1f);
        if (cam.distance < 0.1f) cam.distance = 0.1f;
    }
}

} // namespace fn::core
