#pragma once

#include <memory>  // for unique_ptr, shared_ptr
#include <vector>  // for vector

#include "ball.hpp"     // for Ball
#include "defines.hpp"  // for GravityDirection

// Forward declarations
class Texture;
class ThemeManager;

/**
 * @class SceneManager
 * @brief Gestiona toda la lógica de creación, física y actualización de bolas
 *
 * Responsabilidad única: Manejo de la escena (bolas, gravedad, física)
 *
 * Características:
 * - Crea y destruye bolas según escenario seleccionado
 * - Controla la dirección y estado de la gravedad
 * - Actualiza física de todas las bolas cada frame
 * - Proporciona acceso controlado a las bolas para rendering
 * - Mantiene el Engine desacoplado de la lógica de física
 */
class SceneManager {
    public:
        /**
         * @brief Constructor
         * @param screen_width Ancho lógico de la pantalla
         * @param screen_height Alto lógico de la pantalla
         */
        SceneManager(int screen_width, int screen_height);

        /**
         * @brief Inicializa el manager con configuración inicial
         * @param scenario Escenario inicial (índice de BALL_COUNT_SCENARIOS)
         * @param texture Textura compartida para sprites de bolas
         * @param theme_manager Puntero al gestor de temas (para colores)
         */
        void initialize(int scenario, std::shared_ptr<Texture> texture, ThemeManager* theme_manager);

        /**
         * @brief Actualiza física de todas las bolas
         * @param delta_time Tiempo transcurrido desde último frame (segundos)
         */
        void update(float delta_time);

        // === Gestión de bolas ===

        /**
         * @brief Cambia el número de bolas según escenario
         * @param scenario_id Índice del escenario (0-7 para 10 a 50,000 bolas; 8 = custom)
         * @param mode Modo de simulación actual (afecta inicialización)
         */
        void changeScenario(int scenario_id, SimulationMode mode);

        /**
         * @brief Configura el número de bolas para el escenario custom (índice 8)
         * @param n Número de bolas del escenario custom
         */
        void setCustomBallCount(int n) { custom_ball_count_ = n; }

        /**
         * @brief Actualiza textura y tamaño de todas las bolas
         * @param new_texture Nueva textura compartida
         * @param new_ball_size Nuevo tamaño de bolas (píxeles)
         */
        void updateBallTexture(std::shared_ptr<Texture> new_texture, int new_ball_size);

        // === Control de gravedad ===

        /**
         * @brief Aplica impulso a todas las bolas alejándolas de la superficie de gravedad
         */
        void pushBallsAwayFromGravity();

        /**
         * @brief Alterna el estado de gravedad (ON/OFF) en todas las bolas
         */
        void switchBallsGravity();

        /**
         * @brief Reactiva gravedad solo si estaba desactivada
         */
        void enableBallsGravityIfDisabled();

        /**
         * @brief Fuerza gravedad ON en todas las bolas
         */
        void forceBallsGravityOn();

        /**
         * @brief Fuerza gravedad OFF en todas las bolas (con impulso si >50% en superficie)
         */
        void forceBallsGravityOff();

        /**
         * @brief Cambia la dirección de la gravedad
         * @param direction Nueva dirección (UP/DOWN/LEFT/RIGHT)
         */
        void changeGravityDirection(GravityDirection direction);

        // === Acceso a datos (read-only) ===

        /**
         * @brief Obtiene referencia constante al vector de bolas (para rendering)
         */
        const std::vector<std::unique_ptr<Ball>>& getBalls() const { return balls_; }

        /**
         * @brief Obtiene referencia mutable al vector de bolas (para ShapeManager/BoidManager)
         * NOTA: Usar con cuidado, solo para sistemas que necesitan modificar estado de bolas
         */
        std::vector<std::unique_ptr<Ball>>& getBallsMutable() { return balls_; }

        /**
         * @brief Activa o desactiva la atracción de figura en todas las bolas
         * @param enabled true para activar, false para desactivar
         */
        void enableShapeAttractionAll(bool enabled);

        /**
         * @brief Resetea la escala de profundidad a 1.0 en todas las bolas
         */
        void resetDepthScalesAll();

        /**
         * @brief Obtiene número total de bolas
         */
        size_t getBallCount() const { return balls_.size(); }

        /**
         * @brief Verifica si hay al menos una bola
         */
        bool hasBalls() const { return !balls_.empty(); }

        /**
         * @brief Obtiene puntero a la primera bola (para debug info)
         * @return Puntero constante o nullptr si no hay bolas
         */
        const Ball* getFirstBall() const { return balls_.empty() ? nullptr : balls_[0].get(); }

        /**
         * @brief Obtiene dirección actual de gravedad
         */
        GravityDirection getCurrentGravity() const { return current_gravity_; }

        /**
         * @brief Obtiene escenario actual
         */
        int getCurrentScenario() const { return scenario_; }

        /**
         * @brief Actualiza resolución de pantalla (para resize/fullscreen)
         * @param width Nuevo ancho lógico
         * @param height Nuevo alto lógico
         */
        void updateScreenSize(int width, int height);

    private:
        // === Datos de escena ===
        std::vector<std::unique_ptr<Ball>> balls_;
        GravityDirection current_gravity_;
        int scenario_;
        int custom_ball_count_ = 0;  // Número de bolas para escenario custom (índice 8)

        // === Configuración de pantalla ===
        int screen_width_;
        int screen_height_;
        int current_ball_size_;

        // === Referencias a otros sistemas (no owned) ===
        std::shared_ptr<Texture> texture_;
        ThemeManager* theme_manager_;

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

        /**
         * @brief Ajusta posiciones de bolas al cambiar tamaño de sprite
         * @param old_size Tamaño anterior
         * @param new_size Tamaño nuevo
         */
        void updateBallSizes(int old_size, int new_size);
};
