// debris_manager.cpp - Implementació del gestor de fragments
// © 2026 JailDesigner

#include "debris_manager.hpp"

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

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

namespace Effects {

    // Helper: transformar point con rotación, scale i traslación
    // (Copiat de shape_renderer.cpp:12-34)
    static auto transformPoint(const Vec2& point, const Vec2& shape_centre, const Vec2& position, float angle, float scale) -> Vec2 {
        // 1. Centrar el point respecte al centro de la shape
        float centered_x = point.x - shape_centre.x;
        float centered_y = point.y - shape_centre.y;

        // 2. Aplicar scale al point centrat
        float scaled_x = centered_x * scale;
        float scaled_y = centered_y * scale;

        // 3. Aplicar rotación
        float cos_a = std::cos(angle);
        float sin_a = std::sin(angle);

        float rotated_x = (scaled_x * cos_a) - (scaled_y * sin_a);
        float rotated_y = (scaled_x * sin_a) + (scaled_y * cos_a);

        // 4. Aplicar traslación a posición mundial
        return {.x = rotated_x + position.x, .y = rotated_y + position.y};
    }

    DebrisManager::DebrisManager(Rendering::Renderer* renderer)
        : renderer_(renderer) {
        // Inicialitzar todos los debris como inactius
        for (auto& debris : debris_pool_) {
            debris.active = false;
        }
    }

    void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
        const Vec2& centro,
        float angle,
        float scale,
        float velocitat_base,
        float brightness,
        const Vec2& velocitat_objecte,
        float velocitat_angular,
        float factor_herencia_visual,
        const std::string& sound,
        SDL_Color color,
        float lifetime,
        float friction,
        int segment_multiplier,
        const Vec2& bullet_impulse_velocity,
        float piece_scale) {
        if (!shape || !shape->isValid()) {
            return;
        }

        // Reproducir sonido de explosión. Cadena buida = explosió silenciosa
        // (p. ex. el logo dins el cicle d'atracció).
        if (!sound.empty()) {
            Audio::get()->playSound(sound, Audio::Group::GAME);
        }

        // Notificar als subscriptors (border, playfield, etc.).
        if (explosion_callback_) {
            explosion_callback_(centro);
        }

        const Vec2& shape_centre = shape->getCenter();

        // Multiplier: cada segment s'emet N vegades amb direccions aleatòries
        // distintes (la variació ±15° de computeExplosionDirection ho garanteix).
        const int COPIES = std::max(1, segment_multiplier);

        for (int copy = 0; copy < COPIES; copy++) {
            for (const auto& primitive : shape->getPrimitives()) {
                for (const auto& [local_p1, local_p2] : extractSegments(primitive)) {
                    // Transformar points locals → coordenades mundials
                    Vec2 world_p1 = transformPoint(local_p1, shape_centre, centro, angle, scale);
                    Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale);

                    // Si el pool es ple, no té sentit continuar amb la resta de segments
                    if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, bullet_impulse_velocity, piece_scale)) {
                        return;
                    }
                }
            }
        }
    }

    auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive)
        -> std::vector<std::pair<Vec2, Vec2>> {
        std::vector<std::pair<Vec2, Vec2>> segments;

        if (primitive.type == Graphics::PrimitiveType::POLYLINE) {
            // Polyline: extreure segments consecutius
            for (size_t i = 0; i + 1 < primitive.points.size(); i++) {
                segments.emplace_back(primitive.points[i], primitive.points[i + 1]);
            }
            return segments;
        }
        // PrimitiveType::LINE: un únic segment (si té els 2 punts)
        if (primitive.points.size() >= 2) {
            segments.emplace_back(primitive.points[0], primitive.points[1]);
        }
        return segments;
    }

    auto DebrisManager::spawnDebris(const Vec2& world_p1_in, const Vec2& world_p2_in, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity, float piece_scale) -> bool {
        Debris* debris = findFreeSlot();
        if (debris == nullptr) {
            std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
            return false;
        }

        // Escala el segment al voltant del seu punt mitjà segons piece_scale
        // (1.0 = original; 0.3 = "esquerda petita"). La resta del càlcul (angle,
        // half_length, p1/p2) en deriva naturalment.
        const Vec2 MID = {.x = (world_p1_in.x + world_p2_in.x) / 2.0F,
            .y = (world_p1_in.y + world_p2_in.y) / 2.0F};
        const Vec2 WORLD_P1 = {.x = MID.x + ((world_p1_in.x - MID.x) * piece_scale),
            .y = MID.y + ((world_p1_in.y - MID.y) * piece_scale)};
        const Vec2 WORLD_P2 = {.x = MID.x + ((world_p2_in.x - MID.x) * piece_scale),
            .y = MID.y + ((world_p2_in.y - MID.y) * piece_scale)};

        // Geometria autoritaritzada: centro + original_angle + original_half_length.
        // p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades.
        const float DX = WORLD_P2.x - WORLD_P1.x;
        const float DY = WORLD_P2.y - WORLD_P1.y;
        debris->centro = {.x = (WORLD_P1.x + WORLD_P2.x) / 2.0F,
            .y = (WORLD_P1.y + WORLD_P2.y) / 2.0F};
        debris->original_angle = std::atan2(DY, DX);
        debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F;
        debris->p1 = WORLD_P1;
        debris->p2 = WORLD_P2;

        // Direcció radial (desde el centro hacia el segment)
        Vec2 direccio = computeExplosionDirection(WORLD_P1, WORLD_P2, centro);

        // Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte +
        // velocitat de la bala escalada per BULLET_IMPULSE_FACTOR).
        float speed =
            velocitat_base +
            (((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
                Defaults::Physics::Debris::VARIACIO_SPEED);
        debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x +
            (bullet_impulse_velocity.x * Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR);
        debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y +
            (bullet_impulse_velocity.y * Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR);
        debris->acceleration = friction;

        // Rotación de trayectoria (con conversió a tangencial si excedeix cap)
        applyAngularVelocity(*debris, direccio, velocitat_angular);

        // Rotación visual (proporcional o aleatòria)
        applyVisualRotation(*debris, velocitat_angular, factor_herencia_visual);

        debris->angle_rotacio = 0.0F;

        // Vida i shrinking — min_lifetime és el temps mínim garantit; després
        // el fragment mor quan |velocity| < MIN_SPEED_TO_DIE.
        debris->elapsed_time = 0.0F;
        debris->min_lifetime = lifetime;
        debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;

        // Visuals heretades
        debris->brightness = brightness;
        debris->color = color;

        debris->active = true;
        return true;
    }

    void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular) {
        if (std::abs(velocitat_angular) <= 0.01F) {
            debris.velocitat_rot = 0.0F;  // Nave: sin curvas
            return;
        }

        // FASE 1: Aplicar herència i variació
        float factor_herencia =
            Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN +
            ((std::rand() / static_cast<float>(RAND_MAX)) *
                (Defaults::Physics::Debris::INHERITANCE_FACTOR_MAX -
                    Defaults::Physics::Debris::INHERITANCE_FACTOR_MIN));
        float velocitat_ang_heretada = velocitat_angular * factor_herencia;
        float variacio = ((std::rand() / static_cast<float>(RAND_MAX)) * 0.2F) - 0.1F;
        velocitat_ang_heretada *= (1.0F + variacio);

        // FASE 2: Cap a la velocity màxima; l'excés es converteix en tangencial
        constexpr float CAP = Defaults::Physics::Debris::SPEED_ROT_MAX;
        float abs_ang = std::abs(velocitat_ang_heretada);
        float sign_ang = (velocitat_ang_heretada >= 0.0F) ? 1.0F : -1.0F;

        if (abs_ang <= CAP) {
            debris.velocitat_rot = velocitat_ang_heretada;
            return;
        }

        // Excés: converteix l'excés de velocitat angular en velocitat tangencial lineal
        float excess = abs_ang - CAP;
        constexpr float RADIUS = 20.0F;  // Radi típic de la shape (enemigos = 20 px)
        float v_tangential = excess * RADIUS;

        // Direcció tangencial: perpendicular a la radial (90° CCW): tangent = (-dy, dx)
        debris.velocity.x += -direccio.y * v_tangential;
        debris.velocity.y += direccio.x * v_tangential;

        // Velocitat angular limitada al cap (preservant el signe)
        debris.velocitat_rot = sign_ang * CAP;
    }

    void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual) {
        if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
            // Heredar rotación visual con factor proporcional + ±5% de variació
            debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual;
            float variacio_visual =
                ((std::rand() / static_cast<float>(RAND_MAX)) * 0.1F) - 0.05F;
            debris.velocitat_rot_visual *= (1.0F + variacio_visual);
            return;
        }

        // Rotación visual aleatòria (factor = 0.0 o sin velocidad angular)
        debris.velocitat_rot_visual =
            Defaults::Physics::Debris::ROTATION_MIN +
            ((std::rand() / static_cast<float>(RAND_MAX)) *
                (Defaults::Physics::Debris::ROTATION_MAX -
                    Defaults::Physics::Debris::ROTATION_MIN));

        // 50% probabilitat de rotación en sentit contrari
        if (std::rand() % 2 == 0) {
            debris.velocitat_rot_visual = -debris.velocitat_rot_visual;
        }
    }

    // Rebot del fragment contra els límits del PLAYAREA (mateix patró que
    // PhysicsWorld::resolveBoundsCollisions per a enemics i ships).
    static void bounceOffPlayArea(Vec2& centro, Vec2& velocity) {
        const auto& bounds = Defaults::Zones::PLAYAREA;
        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;
        constexpr float REST = Defaults::Physics::Debris::RESTITUTION_BOUNDS;
        if (centro.x < MIN_X) {
            centro.x = MIN_X;
            if (velocity.x < 0.0F) {
                velocity.x = -velocity.x * REST;
            }
        }
        if (centro.x > MAX_X) {
            centro.x = MAX_X;
            if (velocity.x > 0.0F) {
                velocity.x = -velocity.x * REST;
            }
        }
        if (centro.y < MIN_Y) {
            centro.y = MIN_Y;
            if (velocity.y < 0.0F) {
                velocity.y = -velocity.y * REST;
            }
        }
        if (centro.y > MAX_Y) {
            centro.y = MAX_Y;
            if (velocity.y > 0.0F) {
                velocity.y = -velocity.y * REST;
            }
        }
    }

    void DebrisManager::update(float delta_time) {
        for (auto& debris : debris_pool_) {
            if (!debris.active) {
                continue;
            }

            // 1. Actualitzar time de vida
            debris.elapsed_time += delta_time;

            // Política de mort: viu sí o sí durant min_lifetime; després mor
            // quan la velocity cau per sota d'un llindar. Així els fragments
            // ràpids no desapareixen en moviment.
            if (debris.elapsed_time >= debris.min_lifetime) {
                const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) +
                    (debris.velocity.y * debris.velocity.y);
                if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) {
                    debris.active = false;
                    continue;
                }
            }

            // 2. Actualitzar velocity (desacceleració)
            // Aplicar fricció en la direcció del movement
            float speed = std::sqrt((debris.velocity.x * debris.velocity.x) +
                (debris.velocity.y * debris.velocity.y));

            if (speed > 1.0F) {
                // Calcular direcció normalitzada
                float dir_x = debris.velocity.x / speed;
                float dir_y = debris.velocity.y / speed;

                // Aplicar aceleración negativa (fricció)
                float nova_speed = speed + (debris.acceleration * delta_time);
                nova_speed = std::max(nova_speed, 0.0F);

                debris.velocity.x = dir_x * nova_speed;
                debris.velocity.y = dir_y * nova_speed;
            } else {
                // Velocidad mucho baixa, aturar
                debris.velocity.x = 0.0F;
                debris.velocity.y = 0.0F;
            }

            // 2b. Rotar vector de velocity (trayectoria curva)
            if (std::abs(debris.velocitat_rot) > 0.01F) {
                // Calcular angle de rotación este frame
                float dangle = debris.velocitat_rot * delta_time;

                // Rotar vector de velocity usant matriu de rotación 2D
                float vel_x_old = debris.velocity.x;
                float vel_y_old = debris.velocity.y;

                float cos_a = std::cos(dangle);
                float sin_a = std::sin(dangle);

                debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a);
                debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a);
            }

            // 2c. Aplicar fricció angular (desacceleració gradual)
            if (std::abs(debris.velocitat_rot) > 0.01F) {
                float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F;
                float reduccion =
                    Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time;
                debris.velocitat_rot -= sign * reduccion;

                // Evitar canvi de signe (no pot passar de CW a CCW)
                if ((debris.velocitat_rot > 0) != (sign > 0)) {
                    debris.velocitat_rot = 0.0F;
                }
            }

            // 3. Actualitzar posició del centre (integra velocity).
            debris.centro.x += debris.velocity.x * delta_time;
            debris.centro.y += debris.velocity.y * delta_time;

            // 4. Rebot contra els límits del PLAYAREA.
            bounceOffPlayArea(debris.centro, debris.velocity);

            // 5. Actualitzar rotació visual acumulada.
            debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;

            // 6. Shrink lineal sobre la longitud ORIGINAL (no iteratiu).
            // SHRINK_T va de 0 a 1 al llarg de min_lifetime; després queda
            // a 1 i el shrink_factor manté el valor mínim (1 - factor_shrink).
            const float SHRINK_T = std::min(debris.elapsed_time / debris.min_lifetime, 1.0F);
            const float SHRINK_FACTOR = std::max(0.0F, 1.0F - (debris.factor_shrink * SHRINK_T));

            // 7. Reconstruir p1/p2 des de la geometria autoritaritzada:
            //   centro + (cos/sin(original_angle + angle_rotacio)) × original_half_length × shrink_factor
            // No iteratiu — evita la rotació quadràtica i el shrink exponencial.
            const float CURRENT_ANGLE = debris.original_angle + debris.angle_rotacio;
            const float HALF_LEN = debris.original_half_length * SHRINK_FACTOR;
            const float COS_A = std::cos(CURRENT_ANGLE);
            const float SIN_A = std::sin(CURRENT_ANGLE);
            debris.p1.x = debris.centro.x - (HALF_LEN * COS_A);
            debris.p1.y = debris.centro.y - (HALF_LEN * SIN_A);
            debris.p2.x = debris.centro.x + (HALF_LEN * COS_A);
            debris.p2.y = debris.centro.y + (HALF_LEN * SIN_A);
        }
    }

    void DebrisManager::draw() const {
        for (const auto& debris : debris_pool_) {
            if (!debris.active) {
                continue;
            }

            // Dibujar segmento con brightness y color heredados del padre.
            Rendering::linea(renderer_,
                static_cast<int>(debris.p1.x),
                static_cast<int>(debris.p1.y),
                static_cast<int>(debris.p2.x),
                static_cast<int>(debris.p2.y),
                debris.brightness,
                0.0F,
                debris.color);
        }
    }

    auto DebrisManager::findFreeSlot() -> Debris* {
        for (auto& debris : debris_pool_) {
            if (!debris.active) {
                return &debris;
            }
        }
        return nullptr;  // Pool ple
    }

    auto DebrisManager::computeExplosionDirection(const Vec2& p1,
        const Vec2& p2,
        const Vec2& centre_objecte) -> Vec2 {
        // 1. Calcular centro del segment
        float centro_seg_x = (p1.x + p2.x) / 2.0F;
        float centro_seg_y = (p1.y + p2.y) / 2.0F;

        // 2. Calcular vector des del centro de l'objecte hacia el centro del segment
        // Això garanteix que la direcció siempre apunte hacia fuera (direcció radial)
        float dx = centro_seg_x - centre_objecte.x;
        float dy = centro_seg_y - centre_objecte.y;

        // 3. Normalitzar (obtenir vector unitari)
        float length = std::sqrt((dx * dx) + (dy * dy));
        if (length < 0.001F) {
            // Segment al centro (cas extrem mucho improbable), retornar direcció aleatòria
            float angle_rand =
                (std::rand() / static_cast<float>(RAND_MAX)) * 2.0F * Defaults::Math::PI;
            return {.x = std::cos(angle_rand), .y = std::sin(angle_rand)};
        }

        dx /= length;
        dy /= length;

        // 4. Añadir variació aleatòria pequeña (±15°) per varietat visual
        float angle_variacio =
            ((std::rand() % 30) - 15) * Defaults::Math::PI / 180.0F;

        float cos_v = std::cos(angle_variacio);
        float sin_v = std::sin(angle_variacio);

        float final_x = (dx * cos_v) - (dy * sin_v);
        float final_y = (dx * sin_v) + (dy * cos_v);

        return {.x = final_x, .y = final_y};
    }

    void DebrisManager::reset() {
        for (auto& debris : debris_pool_) {
            debris.active = false;
        }
    }

    auto DebrisManager::getActiveCount() const -> int {
        int count = 0;
        for (const auto& debris : debris_pool_) {
            if (debris.active) {
                count++;
            }
        }
        return count;
    }

}  // namespace Effects
