// debris_manager.cpp - Implementació del gestor de fragments
// © 2025 Port a C++20 amb SDL3

#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 punt amb rotació, escala i trasllació
// (Copiat de shape_renderer.cpp:12-34)
static Punt transform_point(const Punt& point, const Punt& shape_centre, const Punt& posicio, float angle, float escala) {
    // 1. Centrar el punt respecte al centre de la forma
    float centered_x = point.x - shape_centre.x;
    float centered_y = point.y - shape_centre.y;

    // 2. Aplicar escala al punt centrat
    float scaled_x = centered_x * escala;
    float scaled_y = centered_y * escala;

    // 3. Aplicar rotació
    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 trasllació a posició mundial
    return {.x = rotated_x + posicio.x, .y = rotated_y + posicio.y};
}

DebrisManager::DebrisManager(SDL_Renderer* renderer)
    : renderer_(renderer) {
    // Inicialitzar tots els debris com inactius
    for (auto& debris : debris_pool_) {
        debris.actiu = false;
    }
}

void DebrisManager::explotar(const std::shared_ptr<Graphics::Shape>& shape,
    const Punt& centre,
    float angle,
    float escala,
    float velocitat_base,
    float brightness,
    const Punt& velocitat_objecte,
    float velocitat_angular,
    float factor_herencia_visual,
    const std::string& sound) {
    if (!shape || !shape->es_valida()) {
        return;
    }

    // Reproducir sonido de explosión
    Audio::get()->playSound(sound, Audio::Group::GAME);

    // Obtenir centre de la forma per a transformacions
    const Punt& shape_centre = shape->get_centre();

    // Iterar sobre totes les primitives de la forma
    for (const auto& primitive : shape->get_primitives()) {
        // Processar cada segment de línia
        std::vector<std::pair<Punt, Punt>> segments;

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

        // Crear debris per a cada segment
        for (const auto& [local_p1, local_p2] : segments) {
            // 1. Transformar punts locals → coordenades mundials
            Punt world_p1 =
                transform_point(local_p1, shape_centre, centre, angle, escala);
            Punt world_p2 =
                transform_point(local_p2, shape_centre, centre, angle, escala);

            // 2. Trobar slot lliure
            Debris* debris = trobar_slot_lliure();
            if (debris == nullptr) {
                std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
                return;  // Pool ple
            }

            // 3. Inicialitzar geometria
            debris->p1 = world_p1;
            debris->p2 = world_p2;

            // 4. Calcular direcció d'explosió (radial, des del centre cap a fora)
            Punt direccio = calcular_direccio_explosio(world_p1, world_p2, centre);

            // 5. Velocitat inicial (base ± variació aleatòria + velocitat heretada)
            float speed =
                velocitat_base +
                (((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
                    Defaults::Physics::Debris::VARIACIO_VELOCITAT);

            // Heredar velocitat de l'objecte original (suma vectorial)
            debris->velocitat.x = (direccio.x * speed) + velocitat_objecte.x;
            debris->velocitat.y = (direccio.y * speed) + velocitat_objecte.y;
            debris->acceleracio = Defaults::Physics::Debris::ACCELERACIO;

            // 6. Herència de velocitat angular amb cap + conversió d'excés

            // 6a. Rotació de TRAYECTORIA amb cap + conversió tangencial
            if (std::abs(velocitat_angular) > 0.01F) {
                // FASE 1: Aplicar herència i variació (igual que abans)
                float factor_herencia =
                    Defaults::Physics::Debris::FACTOR_HERENCIA_MIN +
                    ((std::rand() / static_cast<float>(RAND_MAX)) *
                        (Defaults::Physics::Debris::FACTOR_HERENCIA_MAX -
                            Defaults::Physics::Debris::FACTOR_HERENCIA_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: Aplicar cap i calcular excés
                constexpr float CAP = Defaults::Physics::Debris::VELOCITAT_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) {
                    // Excés: convertir a velocitat tangencial
                    float excess = abs_ang - CAP;

                    // Radi de la forma (enemics = 20 px)
                    float radius = 20.0F;

                    // Velocitat tangencial = ω_excés × radi
                    float v_tangential = excess * radius;

                    // Direcció tangencial: perpendicular a la radial (90° CCW)
                    // Si direccio = (dx, dy), tangent = (-dy, dx)
                    float tangent_x = -direccio.y;
                    float tangent_y = direccio.x;

                    // Afegir velocitat tangencial (suma vectorial)
                    debris->velocitat.x += tangent_x * v_tangential;
                    debris->velocitat.y += tangent_y * v_tangential;

                    // Aplicar cap a velocitat angular (preservar signe)
                    debris->velocitat_rot = sign_ang * CAP;
                } else {
                    // Per sota del cap: comportament normal
                    debris->velocitat_rot = velocitat_ang_heretada;
                }
            } else {
                debris->velocitat_rot = 0.0F;  // Nave: sin curvas
            }

            // 6b. Rotació VISUAL (proporcional según factor_herencia_visual)
            if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
                // Heredar rotación visual con factor proporcional
                debris->velocitat_rot_visual = debris->velocitat_rot * factor_herencia_visual;

                // Variació aleatòria petita (±5%) per naturalitat
                float variacio_visual =
                    ((std::rand() / static_cast<float>(RAND_MAX)) * 0.1F) - 0.05F;
                debris->velocitat_rot_visual *= (1.0F + variacio_visual);
            } else {
                // Rotació visual aleatòria (factor = 0.0 o sin velocidad angular)
                debris->velocitat_rot_visual =
                    Defaults::Physics::Debris::ROTACIO_MIN +
                    ((std::rand() / static_cast<float>(RAND_MAX)) *
                        (Defaults::Physics::Debris::ROTACIO_MAX -
                            Defaults::Physics::Debris::ROTACIO_MIN));

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

            debris->angle_rotacio = 0.0F;

            // 7. Configurar vida i shrinking
            debris->temps_vida = 0.0F;
            debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA;
            debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;

            // 8. Heredar brightness
            debris->brightness = brightness;

            // 9. Activar
            debris->actiu = true;
        }
    }
}

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

        // 1. Actualitzar temps de vida
        debris.temps_vida += delta_time;

        // Desactivar si ha superat temps màxim
        if (debris.temps_vida >= debris.temps_max) {
            debris.actiu = false;
            continue;
        }

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

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

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

            debris.velocitat.x = dir_x * nova_speed;
            debris.velocitat.y = dir_y * nova_speed;
        } else {
            // Velocitat molt baixa, aturar
            debris.velocitat.x = 0.0F;
            debris.velocitat.y = 0.0F;
        }

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

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

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

            debris.velocitat.x = (vel_x_old * cos_a) - (vel_y_old * sin_a);
            debris.velocitat.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. Calcular centre del segment
        Punt centre = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
            .y = (debris.p1.y + debris.p2.y) / 2.0F};

        // 4. Actualitzar posició del centre
        centre.x += debris.velocitat.x * delta_time;
        centre.y += debris.velocitat.y * delta_time;

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

        // 6. Aplicar shrinking (reducció de distància entre punts)
        float shrink_factor =
            1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
        shrink_factor = std::max(0.0F, shrink_factor);  // No negatiu

        // Calcular distància original entre punts
        float dx = debris.p2.x - debris.p1.x;
        float dy = debris.p2.y - debris.p1.y;

        // 7. Reconstruir segment amb nova mida i rotació
        float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F;
        float original_angle = std::atan2(dy, dx);
        float new_angle = original_angle + debris.angle_rotacio;

        debris.p1.x = centre.x - (half_length * std::cos(new_angle));
        debris.p1.y = centre.y - (half_length * std::sin(new_angle));
        debris.p2.x = centre.x + (half_length * std::cos(new_angle));
        debris.p2.y = centre.y + (half_length * std::sin(new_angle));
    }
}

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

        // Dibuixar segment de línia amb brightness heretat
        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),
            true,
            debris.brightness);
    }
}

Debris* DebrisManager::trobar_slot_lliure() {
    for (auto& debris : debris_pool_) {
        if (!debris.actiu) {
            return &debris;
        }
    }
    return nullptr;  // Pool ple
}

Punt DebrisManager::calcular_direccio_explosio(const Punt& p1,
    const Punt& p2,
    const Punt& centre_objecte) const {
    // 1. Calcular centre 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 centre de l'objecte cap al centre del segment
    // Això garanteix que la direcció sempre apunte cap a fora (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 centre (cas extrem molt 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. Afegir variació aleatòria petita (±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::reiniciar() {
    for (auto& debris : debris_pool_) {
        debris.actiu = false;
    }
}

int DebrisManager::get_num_actius() const {
    int count = 0;
    for (const auto& debris : debris_pool_) {
        if (debris.actiu) {
            count++;
        }
    }
    return count;
}

}  // namespace Effects
