// firework_manager.cpp - Implementació del gestor de fireworks
// © 2026 JailDesigner

#include "firework_manager.hpp"

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

#include "core/defaults.hpp"
#include "core/rendering/line_renderer.hpp"

namespace Effects {

    namespace {
        // Random float in [-1, 1].
        auto randSigned() -> float {
            return ((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F) - 1.0F;
        }

        // Rebot del head contra els límits del PLAYAREA (mateix patró que
        // DebrisManager::bounceOffPlayArea).
        void bounceOffPlayArea(Vec2& head, Vec2& velocity) {
            const auto& bounds = Defaults::Zones::PLAYAREA;
            constexpr float REST = Defaults::FX::Firework::RESTITUTION_BOUNDS;
            const float MIN_X = bounds.x;
            const float MAX_X = bounds.x + bounds.w;
            const float MIN_Y = bounds.y;
            const float MAX_Y = bounds.y + bounds.h;
            if (head.x < MIN_X) {
                head.x = MIN_X;
                if (velocity.x < 0.0F) {
                    velocity.x = -velocity.x * REST;
                }
            }
            if (head.x > MAX_X) {
                head.x = MAX_X;
                if (velocity.x > 0.0F) {
                    velocity.x = -velocity.x * REST;
                }
            }
            if (head.y < MIN_Y) {
                head.y = MIN_Y;
                if (velocity.y < 0.0F) {
                    velocity.y = -velocity.y * REST;
                }
            }
            if (head.y > MAX_Y) {
                head.y = MAX_Y;
                if (velocity.y > 0.0F) {
                    velocity.y = -velocity.y * REST;
                }
            }
        }
    }  // namespace

    FireworkManager::FireworkManager(Rendering::Renderer* renderer)
        : renderer_(renderer) {
        for (auto& fw : pool_) {
            fw.active = false;
        }
    }

    void FireworkManager::spawn(const Vec2& origen,
        SDL_Color color,
        float initial_speed,
        int n_points,
        float initial_brightness,
        bool glow,
        SDL_Color glow_color) {
        if (n_points <= 0) {
            return;
        }

        // Notificar als subscriptors (playfield pulses, etc.).
        if (spawn_callback_) {
            spawn_callback_(origen);
        }

        const float ANGLE_STEP = 2.0F * Defaults::Math::PI / static_cast<float>(n_points);
        const float JITTER_RAD =
            Defaults::FX::Firework::ANGULAR_JITTER_DEG * Defaults::Math::PI / 180.0F;

        for (int i = 0; i < n_points; i++) {
            Firework* fw = findFreeSlot();
            if (fw == nullptr) {
                return;  // Pool ple
            }

            const float BASE_ANGLE = ANGLE_STEP * static_cast<float>(i);
            const float JITTER = randSigned() * JITTER_RAD;
            const float ANGLE = BASE_ANGLE + JITTER;

            const float SPEED =
                initial_speed + (randSigned() * Defaults::FX::Firework::SPEED_VARIATION);

            fw->head = origen;
            fw->velocity = {.x = std::cos(ANGLE) * SPEED, .y = std::sin(ANGLE) * SPEED};
            fw->acceleration = Defaults::FX::Firework::FRICTION;

            fw->current_length = 0.0F;
            fw->max_length = Defaults::FX::Firework::MAX_LENGTH;
            fw->grow_duration = Defaults::FX::Firework::GROW_DURATION;

            fw->temps_vida = 0.0F;
            fw->initial_speed = SPEED;

            fw->brightness = initial_brightness;
            fw->color = color;
            fw->glow = glow;
            fw->glow_color = glow_color;
            fw->active = true;
        }
    }

    void FireworkManager::update(float delta_time) {
        for (auto& fw : pool_) {
            if (!fw.active) {
                continue;
            }

            fw.temps_vida += delta_time;

            // 1. Fricció lineal (aplicar en la direcció del movement).
            const float SPEED = std::sqrt(
                (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));

            if (SPEED > 1.0F) {
                const float DIR_X = fw.velocity.x / SPEED;
                const float DIR_Y = fw.velocity.y / SPEED;
                float new_speed = SPEED + (fw.acceleration * delta_time);
                new_speed = std::max(new_speed, 0.0F);
                fw.velocity.x = DIR_X * new_speed;
                fw.velocity.y = DIR_Y * new_speed;
            } else {
                fw.velocity = {.x = 0.0F, .y = 0.0F};
            }

            // 2. Avançar head.
            fw.head.x += fw.velocity.x * delta_time;
            fw.head.y += fw.velocity.y * delta_time;

            // 3. Rebot contra PLAYAREA.
            bounceOffPlayArea(fw.head, fw.velocity);

            // 4. Calcular longitud i brillor segons fase.
            if (fw.temps_vida < fw.grow_duration) {
                // Fase 1: creixement lineal de 0 a max_length.
                const float T = fw.temps_vida / fw.grow_duration;
                fw.current_length = fw.max_length * T;
                fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS;
            } else {
                // Fase 2: longitud i brillor proporcionals a la velocity actual.
                const float CURRENT_SPEED = std::sqrt(
                    (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));
                const float RATIO = (fw.initial_speed > 0.01F)
                    ? std::min(CURRENT_SPEED / fw.initial_speed, 1.0F)
                    : 0.0F;
                fw.current_length = fw.max_length * RATIO;
                fw.brightness = Defaults::FX::Firework::INITIAL_BRIGHTNESS * RATIO;
            }

            // 5. Morir si la longitud o el brillor cauen sota llindar.
            if (fw.current_length < Defaults::FX::Firework::MIN_LENGTH ||
                fw.brightness < Defaults::FX::Firework::MIN_BRIGHTNESS) {
                fw.active = false;
            }
        }
    }

    void FireworkManager::draw() const {
        for (const auto& fw : pool_) {
            if (!fw.active) {
                continue;
            }

            // tail = head − velocity_normalitzada × current_length.
            const float SPEED = std::sqrt(
                (fw.velocity.x * fw.velocity.x) + (fw.velocity.y * fw.velocity.y));
            if (SPEED < 0.01F) {
                continue;  // Sense direcció no podem orientar la línia.
            }

            const float DIR_X = fw.velocity.x / SPEED;
            const float DIR_Y = fw.velocity.y / SPEED;

            const Vec2 TAIL = {
                .x = fw.head.x - (DIR_X * fw.current_length),
                .y = fw.head.y - (DIR_Y * fw.current_length),
            };

            if (fw.glow) {
                Rendering::lineaGlow(renderer_,
                    static_cast<int>(fw.head.x),
                    static_cast<int>(fw.head.y),
                    static_cast<int>(TAIL.x),
                    static_cast<int>(TAIL.y),
                    fw.brightness,
                    0.0F,
                    fw.color,
                    fw.glow_color);
            } else {
                Rendering::linea(renderer_,
                    static_cast<int>(fw.head.x),
                    static_cast<int>(fw.head.y),
                    static_cast<int>(TAIL.x),
                    static_cast<int>(TAIL.y),
                    fw.brightness,
                    0.0F,
                    fw.color);
            }
        }
    }

    void FireworkManager::reset() {
        for (auto& fw : pool_) {
            fw.active = false;
        }
    }

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

    auto FireworkManager::findFreeSlot() -> Firework* {
        for (auto& fw : pool_) {
            if (!fw.active) {
                return &fw;
            }
        }
        return nullptr;
    }

}  // namespace Effects
