// starfield.cpp - Implementació del starfield 3D
// © 2026 JailDesigner

#include "core/graphics/starfield.hpp"

#include <algorithm>
#include <cmath>
#include <cstdlib>

#include "core/defaults.hpp"

namespace Graphics {

    namespace {

        // Helper: número aleatori en [0, 1) usant rand()/RAND_MAX (mateixa convenció
        // que la resta del joc — veure starfield.cpp).
        auto randFloat01() -> float {
            return static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
        }

        auto randRange(float lo, float hi) -> float {
            return lo + (randFloat01() * (hi - lo));
        }

    }  // namespace

    Starfield::Starfield(Rendering::Renderer* renderer, const Camera3D* camera, int density)
        : renderer_(renderer),
          camera_(camera),
          octahedron_(makeOctahedron()) {
        stars_.resize(static_cast<std::size_t>(std::max(0, density)));
        for (auto& star : stars_) {
            // Pre-omplir amb estrelles distribuïdes per tot el rang Z, no només al far.
            initStar(star, /*spawn_at_far=*/false);
        }
    }

    void Starfield::initStar(Star& star, bool spawn_at_far) {
        star.position.x = randRange(-HALF_SPAWN_X, HALF_SPAWN_X);
        star.position.y = randRange(-HALF_SPAWN_Y, HALF_SPAWN_Y);
        star.position.z = spawn_at_far
            ? Z_FAR_SPAWN
            : randRange(Z_NEAR_RESPAWN, Z_FAR_SPAWN);

        star.velocity_z = -randRange(MIN_VELOCITY_Z, MAX_VELOCITY_Z);
        star.rot_phase_y = randFloat01() * Defaults::Math::PI * 2.0F;
        star.rot_phase_x = randFloat01() * Defaults::Math::PI * 2.0F;
        star.rot_speed_y = randRange(MIN_ROT_SPEED, MAX_ROT_SPEED);
        star.rot_speed_x = randRange(MIN_ROT_SPEED, MAX_ROT_SPEED);
        star.scale = STAR_BASE_SCALE + (randRange(-1.0F, 1.0F) * STAR_SCALE_JITTER);
    }

    auto Starfield::computeBrightness(const Star& star) const -> float {
        // Lerp segons distància Z normalitzada [Z_NEAR_RESPAWN .. Z_FAR_SPAWN].
        const float SPAN = Z_FAR_SPAWN - Z_NEAR_RESPAWN;
        const float T_RAW = (star.position.z - Z_NEAR_RESPAWN) / SPAN;
        const float T = std::clamp(T_RAW, 0.0F, 1.0F);
        const float BRIGHTNESS = BRIGHTNESS_NEAR + (T * (BRIGHTNESS_FAR - BRIGHTNESS_NEAR));
        return std::clamp(BRIGHTNESS * brightness_mult_, 0.0F, 1.0F);
    }

    void Starfield::update(float delta_time) {
        for (auto& star : stars_) {
            star.position.z += star.velocity_z * delta_time;
            star.rot_phase_y += star.rot_speed_y * delta_time;
            star.rot_phase_x += star.rot_speed_x * delta_time;

            if (star.position.z < Z_NEAR_RESPAWN) {
                initStar(star, /*spawn_at_far=*/true);
            }
        }
    }

    void Starfield::draw() const {
        if (camera_ == nullptr || renderer_ == nullptr) {
            return;
        }
        // Ordena de més lluny a més a prop perquè el blending additiu acumule
        // brightness sense que els elements de davant queden tapats pels de
        // darrere. Còpia d'índexs per no modificar l'ordre intern de stars_.
        std::vector<std::size_t> order(stars_.size());
        for (std::size_t i = 0; i < order.size(); ++i) {
            order[i] = i;
        }
        std::ranges::sort(order, [&](std::size_t a, std::size_t b) {
            return stars_[a].position.z > stars_[b].position.z;
        });

        for (std::size_t idx : order) {
            const Star& star = stars_[idx];
            const Transform3D TRANSFORM{
                .position = star.position,
                .rotation_euler = Vec3{.x = star.rot_phase_x, .y = star.rot_phase_y, .z = 0.0F},
                .scale = star.scale,
            };
            drawWireframe(renderer_, *camera_, octahedron_, TRANSFORM, computeBrightness(star), color_);
        }
    }

    void Starfield::setBrightness(float multiplier) {
        brightness_mult_ = std::max(0.0F, multiplier);
    }

    void Starfield::setColor(SDL_Color color) {
        color_ = color;
    }

}  // namespace Graphics
