// wireframe3d.cpp - Implementació dels meshos 3D wireframe
// © 2026 JailDesigner

#include "core/graphics/wireframe3d.hpp"

#include <cmath>
#include <cstdint>

#include "core/rendering/line_renderer.hpp"

namespace Graphics {

    auto applyTransform(const Transform3D& transform, const Vec3& local) -> Vec3 {
        // 1. Escala uniforme.
        Vec3 v{
            .x = local.x * transform.scale,
            .y = local.y * transform.scale,
            .z = local.z * transform.scale,
        };

        // Ordre X → Y → Z: amb aquest ordre, una rotació pitch+yaw pot dur el
        // vector local (0,-1,0) a qualsevol direcció mundial — necessari perquè
        // les naus calculen pitch+yaw look-at per alinear-se amb el seu path.
        // L'ordre invers (Y→X) no permet X arbitrari en vectors sobre l'eix Y.

        // 2. Rotació X (pitch): Y i Z.
        const float CX = std::cos(transform.rotation_euler.x);
        const float SX = std::sin(transform.rotation_euler.x);
        {
            const float NY = (v.y * CX) - (v.z * SX);
            const float NZ = (v.y * SX) + (v.z * CX);
            v.y = NY;
            v.z = NZ;
        }

        // 3. Rotació Y (yaw): X i Z.
        const float CY = std::cos(transform.rotation_euler.y);
        const float SY = std::sin(transform.rotation_euler.y);
        {
            const float NX = (v.x * CY) + (v.z * SY);
            const float NZ = (-v.x * SY) + (v.z * CY);
            v.x = NX;
            v.z = NZ;
        }

        // 4. Rotació Z (roll): X i Y.
        const float CZ = std::cos(transform.rotation_euler.z);
        const float SZ = std::sin(transform.rotation_euler.z);
        {
            const float NX = (v.x * CZ) - (v.y * SZ);
            const float NY = (v.x * SZ) + (v.y * CZ);
            v.x = NX;
            v.y = NY;
        }

        // 5. Translació final.
        v.x += transform.position.x;
        v.y += transform.position.y;
        v.z += transform.position.z;
        return v;
    }

    void drawWireframe(Rendering::Renderer* renderer, const Camera3D& camera, const Mesh3D& mesh, const Transform3D& transform, float brightness, SDL_Color color) {
        if (renderer == nullptr || mesh.edges.empty() || mesh.vertices.empty()) {
            return;
        }

        // Projecta tots els vèrtexs un cop; cau-en si queden darrere del near.
        std::vector<std::optional<Camera3D::ProjectedPoint>> projected;
        projected.reserve(mesh.vertices.size());
        for (const auto& vertex : mesh.vertices) {
            const Vec3 WORLD = applyTransform(transform, vertex);
            projected.push_back(camera.project(WORLD));
        }

        for (const auto& edge : mesh.edges) {
            const auto& a_proj = projected[edge.first];
            const auto& b_proj = projected[edge.second];
            if (!a_proj.has_value() || !b_proj.has_value()) {
                continue;
            }
            Rendering::linea(renderer,
                static_cast<int>(a_proj->screen.x),
                static_cast<int>(a_proj->screen.y),
                static_cast<int>(b_proj->screen.x),
                static_cast<int>(b_proj->screen.y),
                brightness,
                0.0F,
                color);
        }
    }

    auto makeOctahedron() -> Mesh3D {
        // 6 vèrtexs als eixos: ±X, ±Y, ±Z.
        Mesh3D mesh;
        mesh.vertices = {
            {.x = 1.0F, .y = 0.0F, .z = 0.0F},   // 0: +X
            {.x = -1.0F, .y = 0.0F, .z = 0.0F},  // 1: -X
            {.x = 0.0F, .y = 1.0F, .z = 0.0F},   // 2: +Y
            {.x = 0.0F, .y = -1.0F, .z = 0.0F},  // 3: -Y
            {.x = 0.0F, .y = 0.0F, .z = 1.0F},   // 4: +Z
            {.x = 0.0F, .y = 0.0F, .z = -1.0F},  // 5: -Z
        };
        // 12 arestes: cada vèrtex axial connecta amb els 4 vèrtexs no oposats.
        mesh.edges = {
            // "Equador" XY al voltant de Z.
            {2, 0},
            {0, 3},
            {3, 1},
            {1, 2},
            // Piràmide superior (cap a +Z).
            {2, 4},
            {0, 4},
            {3, 4},
            {1, 4},
            // Piràmide inferior (cap a -Z).
            {2, 5},
            {0, 5},
            {3, 5},
            {1, 5},
        };
        return mesh;
    }

    auto extrudeShape2D(const Shape& shape, float depth) -> Mesh3D {
        Mesh3D mesh;
        if (!shape.isValid()) {
            return mesh;
        }

        const float HALF = depth * 0.5F;
        const Vec2 CENTRE = shape.getCenter();
        // Si depth <= 0, emetem només un pla (sense vèrtexs back ni connexions)
        // per evitar arestes degenerades i acumulació additiva de brightness.
        const bool FLAT = (depth <= 0.0F);

        for (const auto& primitive : shape.getPrimitives()) {
            if (primitive.points.size() < 2) {
                continue;
            }

            const auto BASE = static_cast<std::uint16_t>(mesh.vertices.size());
            const auto N = static_cast<std::uint16_t>(primitive.points.size());

            // Vèrtexs frontals (z = +HALF, o z = 0 si FLAT).
            for (const auto& p : primitive.points) {
                mesh.vertices.push_back(Vec3{
                    .x = p.x - CENTRE.x,
                    .y = p.y - CENTRE.y,
                    .z = HALF,
                });
            }
            // Arestes "frontals": connecten punts consecutius de la polyline.
            for (std::uint16_t i = 0; i + 1 < N; ++i) {
                mesh.edges.emplace_back(BASE + i, BASE + i + 1);
            }

            if (FLAT) {
                continue;
            }

            // Vèrtexs posteriors (z = -HALF) i arestes corresponents.
            for (const auto& p : primitive.points) {
                mesh.vertices.push_back(Vec3{
                    .x = p.x - CENTRE.x,
                    .y = p.y - CENTRE.y,
                    .z = -HALF,
                });
            }
            for (std::uint16_t i = 0; i + 1 < N; ++i) {
                mesh.edges.emplace_back(BASE + N + i, BASE + N + i + 1);
            }
            // Arestes de connexió front↔posterior per cada vèrtex.
            // Per polylines tancades (primer == últim punt), el bucle igualment
            // genera N connexions; el parell duplicat (primer i últim) cau en una
            // línia idèntica sense efecte visible.
            for (std::uint16_t i = 0; i < N; ++i) {
                mesh.edges.emplace_back(BASE + i, BASE + N + i);
            }
        }

        return mesh;
    }

}  // namespace Graphics
