#include "scene_manager.hpp"

#include <cstdlib>  // for rand
#include <utility>

#include "defines.hpp"           // for BALL_COUNT_SCENARIOS, GRAVITY_MASS_MIN, etc
#include "external/texture.hpp"  // for Texture
#include "theme_manager.hpp"     // for ThemeManager

SceneManager::SceneManager(int screen_width, int screen_height)
    : current_gravity_(GravityDirection::DOWN),
      scenario_(0),
      screen_width_(screen_width),
      screen_height_(screen_height),
      current_ball_size_(10),
      texture_(nullptr),
      theme_manager_(nullptr) {
}

void SceneManager::initialize(int scenario, std::shared_ptr<Texture> texture, ThemeManager* theme_manager) {
    scenario_ = scenario;
    texture_ = std::move(texture);
    theme_manager_ = theme_manager;
    current_ball_size_ = texture_->getWidth();

    // Crear bolas iniciales (siempre en modo PHYSICS al inicio)
    changeScenario(scenario_, SimulationMode::PHYSICS);
}

void SceneManager::update(float delta_time) {
    // Actualizar física de todas las bolas
    for (auto& ball : balls_) {
        ball->update(delta_time);
    }
}

void SceneManager::changeScenario(int scenario_id, SimulationMode mode) {
    // Guardar escenario
    scenario_ = scenario_id;

    // Limpiar las bolas actuales
    balls_.clear();

    // Resetear gravedad al estado por defecto (DOWN) al cambiar escenario
    changeGravityDirection(GravityDirection::DOWN);

    // Crear las bolas según el escenario
    int ball_count = (scenario_id == CUSTOM_SCENARIO_IDX)
        ? custom_ball_count_
        : BALL_COUNT_SCENARIOS[scenario_id];
    for (int i = 0; i < ball_count; ++i) {
        float x;
        float y;
        float vx;
        float vy;

        // Inicialización según SimulationMode (RULES.md líneas 23-26)
        switch (mode) {
            case SimulationMode::PHYSICS: {
                // PHYSICS: Parte superior, 75% distribución central en X
                const int SIGN = ((rand() % 2) * 2) - 1;
                const int MARGIN = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
                const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN);
                x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN;
                y = 0.0f;  // Parte superior
                vx = (((rand() % 20) + 10) * 0.1f) * SIGN;
                vy = ((rand() % 60) - 30) * 0.1f;
                break;
            }

            case SimulationMode::SHAPE: {
                // SHAPE: Centro de pantalla, sin velocidad inicial
                x = screen_width_ / 2.0f;
                y = screen_height_ / 2.0f;  // Centro vertical
                vx = 0.0f;
                vy = 0.0f;
                break;
            }

            case SimulationMode::BOIDS: {
                // BOIDS: Posiciones aleatorias, velocidades aleatorias
                const int SIGN_X = ((rand() % 2) * 2) - 1;
                const int SIGN_Y = ((rand() % 2) * 2) - 1;
                x = static_cast<float>(rand() % screen_width_);
                y = static_cast<float>(rand() % screen_height_);  // Posición Y aleatoria
                vx = (((rand() % 40) + 10) * 0.1f) * SIGN_X;      // 1.0 - 5.0 px/frame
                vy = (((rand() % 40) + 10) * 0.1f) * SIGN_Y;
                break;
            }

            default:
                // Fallback a PHYSICS por seguridad
                const int SIGN = ((rand() % 2) * 2) - 1;
                const int MARGIN = static_cast<int>(screen_width_ * BALL_SPAWN_MARGIN);
                const int SPAWN_ZONE_WIDTH = screen_width_ - (2 * MARGIN);
                x = (rand() % SPAWN_ZONE_WIDTH) + MARGIN;
                y = 0.0f;  // Parte superior
                vx = (((rand() % 20) + 10) * 0.1f) * SIGN;
                vy = ((rand() % 60) - 30) * 0.1f;
                break;
        }

        // Seleccionar color de la paleta del tema actual (delegado a ThemeManager)
        int random_index = rand();
        Color color = theme_manager_->getInitialBallColor(random_index);

        // Generar factor de masa aleatorio (0.7 = ligera, 1.3 = pesada)
        float mass_factor = GRAVITY_MASS_MIN + ((rand() % 1000) / 1000.0f * (GRAVITY_MASS_MAX - GRAVITY_MASS_MIN));

        balls_.emplace_back(std::make_unique<Ball>(
            x,
            y,
            vx,
            vy,
            color,
            texture_,
            screen_width_,
            screen_height_,
            current_ball_size_,
            current_gravity_,
            mass_factor));
    }
}

void SceneManager::updateBallTexture(std::shared_ptr<Texture> new_texture, int new_ball_size) {
    if (balls_.empty()) {
        return;
    }

    // Guardar tamaño antiguo
    int old_size = current_ball_size_;

    // Actualizar textura y tamaño
    texture_ = std::move(new_texture);
    current_ball_size_ = new_ball_size;

    // Actualizar texturas de todas las pelotas
    for (auto& ball : balls_) {
        ball->setTexture(texture_);
    }

    // Ajustar posiciones según el cambio de tamaño
    updateBallSizes(old_size, new_ball_size);
}

void SceneManager::pushBallsAwayFromGravity() {
    for (auto& ball : balls_) {
        const int SIGNO = ((rand() % 2) * 2) - 1;
        const float LATERAL = (((rand() % 20) + 10) * 0.1f) * SIGNO;
        const float MAIN = ((rand() % 40) * 0.1f) + 5;

        float vx = 0;
        float vy = 0;
        switch (current_gravity_) {
            case GravityDirection::DOWN:  // Impulsar ARRIBA
                vx = LATERAL;
                vy = -MAIN;
                break;
            case GravityDirection::UP:  // Impulsar ABAJO
                vx = LATERAL;
                vy = MAIN;
                break;
            case GravityDirection::LEFT:  // Impulsar DERECHA
                vx = MAIN;
                vy = LATERAL;
                break;
            case GravityDirection::RIGHT:  // Impulsar IZQUIERDA
                vx = -MAIN;
                vy = LATERAL;
                break;
        }
        ball->modVel(vx, vy);  // Modifica la velocidad según dirección de gravedad
    }
}

void SceneManager::switchBallsGravity() {
    for (auto& ball : balls_) {
        ball->switchGravity();
    }
}

void SceneManager::enableBallsGravityIfDisabled() {
    for (auto& ball : balls_) {
        ball->enableGravityIfDisabled();
    }
}

void SceneManager::forceBallsGravityOn() {
    for (auto& ball : balls_) {
        ball->forceGravityOn();
    }
}

void SceneManager::forceBallsGravityOff() {
    // Contar cuántas pelotas están en superficie (suelo/techo/pared)
    int balls_on_surface = 0;
    for (const auto& ball : balls_) {
        if (ball->isOnSurface()) {
            balls_on_surface++;
        }
    }

    // Si la mayoría (>50%) están en superficie, aplicar impulso para que se vea el efecto
    float surface_ratio = static_cast<float>(balls_on_surface) / static_cast<float>(balls_.size());
    if (surface_ratio > 0.5f) {
        pushBallsAwayFromGravity();  // Dar impulso contrario a gravedad
    }

    // Desactivar gravedad
    for (auto& ball : balls_) {
        ball->forceGravityOff();
    }
}

void SceneManager::changeGravityDirection(GravityDirection direction) {
    current_gravity_ = direction;
    for (auto& ball : balls_) {
        ball->setGravityDirection(direction);
        ball->applyRandomLateralPush();  // Aplicar empuje lateral aleatorio
    }
}

void SceneManager::updateScreenSize(int width, int height) {
    screen_width_ = width;
    screen_height_ = height;

    // NOTA: No actualizamos las bolas existentes, solo afecta a futuras creaciones
    // Si se requiere reposicionar bolas existentes, implementar aquí
}

// === Métodos privados ===

void SceneManager::updateBallSizes(int old_size, int new_size) {
    for (auto& ball : balls_) {
        SDL_FRect pos = ball->getPosition();

        // Ajustar posición para compensar cambio de tamaño
        // Si aumenta tamaño, mover hacia centro; si disminuye, alejar del centro
        float center_x = screen_width_ / 2.0f;
        float center_y = screen_height_ / 2.0f;

        float dx = pos.x - center_x;
        float dy = pos.y - center_y;

        // Ajustar proporcionalmente (evitar divisiones por cero)
        if (old_size > 0) {
            float scale_factor = static_cast<float>(new_size) / static_cast<float>(old_size);
            pos.x = center_x + dx * scale_factor;
            pos.y = center_y + dy * scale_factor;
        }

        // Actualizar tamaño del hitbox
        ball->updateSize(new_size);
    }
}

void SceneManager::enableShapeAttractionAll(bool enabled) {
    for (auto& ball : balls_) {
        ball->enableShapeAttraction(enabled);
    }
}

void SceneManager::resetDepthScalesAll() {
    for (auto& ball : balls_) {
        ball->setDepthScale(1.0f);
    }
}
