/**
 * @file math3d.hpp
 * @brief Minimal 3D math library for render pipeline visualization
 */

#pragma once

#include <cmath>
#include <algorithm>

constexpr float PI = 3.14159265358979323846f;

struct Vec3 {
    float x = 0, y = 0, z = 0;
    Vec3 operator+(Vec3 o) const { return {x+o.x, y+o.y, z+o.z}; }
    Vec3 operator-(Vec3 o) const { return {x-o.x, y-o.y, z-o.z}; }
    Vec3 operator*(float s) const { return {x*s, y*s, z*s}; }
    float dot(Vec3 o) const { return x*o.x + y*o.y + z*o.z; }
    Vec3 cross(Vec3 o) const { return {y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x}; }
    float length() const { return std::sqrt(x*x + y*y + z*z); }
    Vec3 normalized() const {
        float l = length();
        return l > 0 ? Vec3{x/l, y/l, z/l} : Vec3{0, 0, 0};
    }
};

inline Vec3 lerp(Vec3 a, Vec3 b, float t) {
    return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
}

inline float smoothstep(float t) {
    return t * t * (3.0f - 2.0f * t);
}

struct Vec4 {
    float x = 0, y = 0, z = 0, w = 1;
    Vec3 xyz() const { return {x, y, z}; }
    Vec3 perspDiv() const {
        return w != 0 ? Vec3{x/w, y/w, z/w} : Vec3{x, y, z};
    }
};

struct Mat4 {
    float m[4][4] = {};

    static Mat4 identity() {
        Mat4 r;
        r.m[0][0] = r.m[1][1] = r.m[2][2] = r.m[3][3] = 1;
        return r;
    }

    Vec4 operator*(Vec4 v) const {
        return {
            m[0][0]*v.x + m[0][1]*v.y + m[0][2]*v.z + m[0][3]*v.w,
            m[1][0]*v.x + m[1][1]*v.y + m[1][2]*v.z + m[1][3]*v.w,
            m[2][0]*v.x + m[2][1]*v.y + m[2][2]*v.z + m[2][3]*v.w,
            m[3][0]*v.x + m[3][1]*v.y + m[3][2]*v.z + m[3][3]*v.w,
        };
    }

    Mat4 operator*(const Mat4& o) const {
        Mat4 r;
        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++)
                for (int k = 0; k < 4; k++)
                    r.m[i][j] += m[i][k] * o.m[k][j];
        return r;
    }

    static Mat4 translate(float x, float y, float z) {
        Mat4 r = identity();
        r.m[0][3] = x; r.m[1][3] = y; r.m[2][3] = z;
        return r;
    }

    static Mat4 scale(float sx, float sy, float sz) {
        Mat4 r = identity();
        r.m[0][0] = sx; r.m[1][1] = sy; r.m[2][2] = sz;
        return r;
    }

    static Mat4 rotateX(float angle) {
        float c = std::cos(angle), s = std::sin(angle);
        Mat4 r = identity();
        r.m[1][1] = c; r.m[1][2] = -s;
        r.m[2][1] = s; r.m[2][2] = c;
        return r;
    }

    static Mat4 rotateY(float angle) {
        float c = std::cos(angle), s = std::sin(angle);
        Mat4 r = identity();
        r.m[0][0] = c;  r.m[0][2] = s;
        r.m[2][0] = -s; r.m[2][2] = c;
        return r;
    }

    static Mat4 rotateZ(float angle) {
        float c = std::cos(angle), s = std::sin(angle);
        Mat4 r = identity();
        r.m[0][0] = c; r.m[0][1] = -s;
        r.m[1][0] = s; r.m[1][1] = c;
        return r;
    }

    // Standard OpenGL-style perspective (right-handed, -Z forward)
    static Mat4 perspective(float fov, float aspect, float near, float far) {
        float t = std::tan(fov * 0.5f);
        Mat4 r;
        r.m[0][0] = 1.0f / (aspect * t);
        r.m[1][1] = 1.0f / t;
        r.m[2][2] = -(far + near) / (far - near);
        r.m[2][3] = -(2.0f * far * near) / (far - near);
        r.m[3][2] = -1.0f;
        return r;
    }

    // Standard lookAt (right-handed)
    static Mat4 lookAt(Vec3 eye, Vec3 target, Vec3 up) {
        Vec3 f = (target - eye).normalized();
        Vec3 r = f.cross(up).normalized();
        Vec3 u = r.cross(f);
        Mat4 mat = identity();
        mat.m[0][0] = r.x;  mat.m[0][1] = r.y;  mat.m[0][2] = r.z;  mat.m[0][3] = -r.dot(eye);
        mat.m[1][0] = u.x;  mat.m[1][1] = u.y;  mat.m[1][2] = u.z;  mat.m[1][3] = -u.dot(eye);
        mat.m[2][0] = -f.x; mat.m[2][1] = -f.y; mat.m[2][2] = -f.z; mat.m[2][3] = f.dot(eye);
        return mat;
    }
};
