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

#include "debris_manager.hpp"
#include "core/defaults.hpp"
#include "core/rendering/line_renderer.hpp"
#include <cmath>
#include <cstdlib>
#include <iostream>

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 {rotated_x + posicio.x, 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) {
  if (!shape || !shape->es_valida()) {
    return;
  }

  // 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.push_back({primitive.points[i], primitive.points[i + 1]});
      }
    } else { // PrimitiveType::LINE
      // Line: un únic segment
      if (primitive.points.size() >= 2) {
        segments.push_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) {
        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ó perpendicular
      Punt direccio = calcular_direccio_perpendicular(world_p1, world_p2);

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

      debris->velocitat.x = direccio.x * speed;
      debris->velocitat.y = direccio.y * speed;
      debris->acceleracio = Defaults::Physics::Debris::ACCELERACIO;

      // 6. Rotació lenta aleatòria
      debris->velocitat_rot =
          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 = -debris->velocitat_rot;
      }

      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. 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;
      if (nova_speed < 0.0f)
        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;
    }

    // 3. Calcular centre del segment
    Punt centre = {(debris.p1.x + debris.p2.x) / 2.0f,
                   (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ó
    debris.angle_rotacio += debris.velocitat_rot * 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
    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 *DebrisManager::trobar_slot_lliure() {
  for (auto &debris : debris_pool_) {
    if (!debris.actiu) {
      return &debris;
    }
  }
  return nullptr; // Pool ple
}

Punt DebrisManager::calcular_direccio_perpendicular(const Punt &p1,
                                                     const Punt &p2) const {
  // 1. Calcular vector de la línia (p1 → p2)
  float dx = p2.x - p1.x;
  float dy = p2.y - p1.y;

  // 2. Normalitzar (obtenir vector unitari)
  float length = std::sqrt(dx * dx + dy * dy);
  if (length < 0.001f) {
    // Línia degenerada, retornar direcció aleatòria
    float angle_rand =
        (std::rand() / static_cast<float>(RAND_MAX)) * 2.0f * Defaults::Math::PI;
    return {std::cos(angle_rand), std::sin(angle_rand)};
  }

  dx /= length;
  dy /= length;

  // 3. Rotar 90° (perpendicular)
  // Rotació 90° sentit antihorari: (x,y) → (-y, x)
  float perp_x = -dy;
  float perp_y = dx;

  // 4. Afegir variació aleatòria petita (±15°)
  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 = perp_x * cos_v - perp_y * sin_v;
  float final_y = perp_x * sin_v + perp_y * cos_v;

  // 5. Afegir ± direcció aleatòria (50% probabilitat d'invertir)
  if (std::rand() % 2 == 0) {
    final_x = -final_x;
    final_y = -final_y;
  }

  return {final_x, 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
