// trail_manager.cpp - Implementació de l'estela de partícules
// © 2026 JailDesigner

#include "trail_manager.hpp"

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

#include "core/defaults.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/rendering/shape_renderer.hpp"

namespace Effects {

    namespace {

        constexpr float TAU = 2.0F * Defaults::Math::PI;

        auto randUniform(float min_v, float max_v) -> float {
            const float NORM = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
            return min_v + (NORM * (max_v - min_v));
        }

        auto lerpU8(unsigned char a, unsigned char b, float t) -> unsigned char {
            const float MIX = std::clamp(t, 0.0F, 1.0F);
            const float OUT = static_cast<float>(a) + (MIX * (static_cast<float>(b) - static_cast<float>(a)));
            return static_cast<unsigned char>(std::round(OUT));
        }

    }  // namespace

    TrailManager::TrailManager(Rendering::Renderer* renderer)
        : renderer_(renderer),
          star_shape_(Graphics::ShapeLoader::load("star.shp")) {
        if (!star_shape_ || !star_shape_->isValid()) {
            std::cerr << "[TrailManager] Warning: no s'ha pogut load star.shp\n";
        }
        for (auto& particle : pool_) {
            particle.active = false;
        }
    }

    void TrailManager::update(float delta_time, const std::array<Ship, 2>& ships) {
        time_accumulator_ += delta_time;

        for (auto& particle : pool_) {
            if (!particle.active) {
                continue;
            }
            particle.age += delta_time;
            if (particle.age >= particle.lifetime) {
                particle.active = false;
            }
        }

        for (std::size_t i = 0; i < ships.size(); i++) {
            tryEmitFromShip(ships[i], static_cast<std::uint8_t>(i), delta_time);
        }
    }

    void TrailManager::tryEmitFromShip(const Ship& ship, std::uint8_t player_id, float delta_time) {
        if (!ship.isActive()) {
            emit_cooldown_[player_id] = 0.0F;
            return;
        }
        if (ship.getSpeed() < Defaults::Trail::SPEED_THRESHOLD_PX_S) {
            emit_cooldown_[player_id] = 0.0F;
            return;
        }

        emit_cooldown_[player_id] -= delta_time;
        if (emit_cooldown_[player_id] > 0.0F) {
            return;
        }

        // Cua = center - REAR_OFFSET * forward, on forward = (cos(angle-π/2), sin(angle-π/2))
        const float FORWARD_ANGLE = ship.getAngle() - (Defaults::Math::PI / 2.0F);
        const float COS_F = std::cos(FORWARD_ANGLE);
        const float SIN_F = std::sin(FORWARD_ANGLE);
        const Vec2 CENTER = ship.getCenter();

        const float JITTER_X = randUniform(-Defaults::Trail::POSITION_JITTER_PX, Defaults::Trail::POSITION_JITTER_PX);
        const float JITTER_Y = randUniform(-Defaults::Trail::POSITION_JITTER_PX, Defaults::Trail::POSITION_JITTER_PX);

        const Vec2 REAR = {
            .x = CENTER.x - (Defaults::Trail::REAR_OFFSET_PX * COS_F) + JITTER_X,
            .y = CENTER.y - (Defaults::Trail::REAR_OFFSET_PX * SIN_F) + JITTER_Y};

        emitAt(REAR, player_id);

        emit_cooldown_[player_id] = Defaults::Trail::EMIT_INTERVAL_S +
            randUniform(-Defaults::Trail::EMIT_JITTER_S, Defaults::Trail::EMIT_JITTER_S);
    }

    void TrailManager::emitAt(Vec2 pos, std::uint8_t player_id) {
        const int SLOT = findFreeSlot();
        if (SLOT < 0) {
            return;  // pool ple — descart silenciós
        }

        Particle& particle = pool_[static_cast<std::size_t>(SLOT)];
        particle.active = true;
        particle.player_id = player_id;
        particle.origin = pos;
        particle.phase_x = randUniform(0.0F, TAU);
        particle.phase_y = randUniform(0.0F, TAU);
        particle.phase_pulse = randUniform(0.0F, TAU);
        particle.age = 0.0F;
        particle.lifetime = Defaults::Trail::LIFETIME_BASE_S +
            randUniform(-Defaults::Trail::LIFETIME_JITTER_S, Defaults::Trail::LIFETIME_JITTER_S);
        particle.scale = randUniform(Defaults::Trail::SCALE_MIN, Defaults::Trail::SCALE_MAX);
    }

    auto TrailManager::findFreeSlot() -> int {
        for (std::size_t i = 0; i < pool_.size(); i++) {
            if (!pool_[i].active) {
                return static_cast<int>(i);
            }
        }
        return -1;
    }

    void TrailManager::draw() const {
        if (!star_shape_ || !star_shape_->isValid()) {
            return;
        }
        for (const auto& particle : pool_) {
            if (!particle.active) {
                continue;
            }
            drawParticle(particle);
        }
    }

    void TrailManager::drawParticle(const Particle& particle) const {
        const float T_NORM = std::clamp(particle.age / particle.lifetime, 0.0F, 1.0F);
        const float FADE = 1.0F - T_NORM;

        const float OSC_DX = std::sin((TAU * Defaults::Trail::OSCILLATION_FREQ_HZ * time_accumulator_) + particle.phase_x) *
            Defaults::Trail::OSCILLATION_AMP_PX * FADE;
        const float OSC_DY = std::sin((TAU * Defaults::Trail::OSCILLATION_FREQ_HZ * time_accumulator_) + particle.phase_y) *
            Defaults::Trail::OSCILLATION_AMP_PX * FADE;

        const Vec2 POS = {.x = particle.origin.x + OSC_DX, .y = particle.origin.y + OSC_DY};

        const float MIX = 0.5F + (0.5F * std::sin((TAU * Defaults::Trail::PULSE_FREQ_HZ * time_accumulator_) + particle.phase_pulse));

        const bool IS_P2 = particle.player_id == 1;
        const unsigned char A_R = IS_P2 ? Defaults::Trail::COLOR_P2_A_R : Defaults::Trail::COLOR_A_R;
        const unsigned char A_G = IS_P2 ? Defaults::Trail::COLOR_P2_A_G : Defaults::Trail::COLOR_A_G;
        const unsigned char A_B = IS_P2 ? Defaults::Trail::COLOR_P2_A_B : Defaults::Trail::COLOR_A_B;
        const unsigned char B_R = IS_P2 ? Defaults::Trail::COLOR_P2_B_R : Defaults::Trail::COLOR_B_R;
        const unsigned char B_G = IS_P2 ? Defaults::Trail::COLOR_P2_B_G : Defaults::Trail::COLOR_B_G;
        const unsigned char B_B = IS_P2 ? Defaults::Trail::COLOR_P2_B_B : Defaults::Trail::COLOR_B_B;

        const SDL_Color COLOR = {
            .r = lerpU8(A_R, B_R, MIX),
            .g = lerpU8(A_G, B_G, MIX),
            .b = lerpU8(A_B, B_B, MIX),
            .a = 255};

        const float CURRENT_SCALE = particle.scale * FADE;
        const float BRIGHTNESS = FADE;

        Rendering::renderShape(
            renderer_,
            star_shape_,
            POS,
            0.0F,  // sense rotació
            CURRENT_SCALE,
            1.0F,  // progress (totalment visible)
            BRIGHTNESS,
            COLOR);
    }

    void TrailManager::reset() {
        for (auto& particle : pool_) {
            particle.active = false;
        }
        emit_cooldown_[0] = 0.0F;
        emit_cooldown_[1] = 0.0F;
        time_accumulator_ = 0.0F;
    }

    auto TrailManager::getActiveCount() const -> int {
        int count = 0;
        for (const auto& particle : pool_) {
            if (particle.active) {
                count++;
            }
        }
        return count;
    }

}  // namespace Effects
