#pragma once

#include <SDL3/SDL.h>  // Para Uint8, Uint16, SDL_FRect, Uint32

#include <array>        // Para array
#include <memory>       // Para allocator, shared_ptr, unique_ptr
#include <string>       // Para basic_string, string
#include <string_view>  // Para string_view
#include <vector>       // Para vector

#include "core/rendering/sprite/animated_sprite.hpp"  // Para AnimatedSprite
#include "utils/utils.hpp"                            // Para Circle

class Texture;

// --- Clase Balloon ---
class Balloon {
   public:
    // --- Constantes relacionadas con globos ---
    static constexpr int MAX_BOUNCE = 10;  // Cantidad de elementos del vector de deformación

    static constexpr std::array<int, 4> SCORE = {50, 100, 200, 400};
    static constexpr std::array<int, 4> POWER = {1, 3, 7, 15};
    static constexpr std::array<int, 4> MENACE = {1, 2, 4, 8};
    static constexpr std::array<int, 5> WIDTH = {10, 16, 26, 48, 49};

    static constexpr std::array<std::string_view, 4> BOUNCING_SOUND = {
        "balloon_bounce0.wav",
        "balloon_bounce1.wav",
        "balloon_bounce2.wav",
        "balloon_bounce3.wav"};

    static constexpr std::array<std::string_view, 4> POPPING_SOUND = {
        "balloon_pop0.wav",
        "balloon_pop1.wav",
        "balloon_pop2.wav",
        "balloon_pop3.wav"};

    // Velocidades horizontales en pixels/segundo (convertidas desde 0.7 pixels/frame a 60fps)
    static constexpr float VELX_POSITIVE = 0.7F * 60.0F;   // 42 pixels/segundo
    static constexpr float VELX_NEGATIVE = -0.7F * 60.0F;  // -42 pixels/segundo

    // Multiplicadores de tempo del juego (sin cambios, son puros multiplicadores)
    static constexpr std::array<float, 5> GAME_TEMPO = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};

    static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10;
    static constexpr int POWERBALL_COUNTER = 8;

    // --- Enums ---
    enum class Size : Uint8 {
        SMALL = 0,       // Tamaño pequeño
        MEDIUM = 1,      // Tamaño mediano
        LARGE = 2,       // Tamaño grande
        EXTRALARGE = 3,  // Tamaño extra grande
    };

    enum class Type : Uint8 {
        BALLOON = 0,    // Globo normal
        FLOATER = 1,    // Globo flotante
        POWERBALL = 2,  // Globo de poder
    };

    // --- Estructura para manejo de sonido ---
    struct Sound {
        std::string bouncing_file;      // Archivo de sonido al rebotar
        std::string popping_file;       // Archivo de sonido al explotar
        bool bouncing_enabled = false;  // Si debe sonar el globo al rebotar
        bool poping_enabled = true;     // Si debe sonar el globo al explotar
        bool enabled = true;            // Indica si los globos deben hacer algun sonido
    };

    // --- Estructura de configuración para inicialización ---
    struct Config {
        float x = 0.0F;
        float y = 0.0F;
        Type type = Type::BALLOON;
        Size size = Size::EXTRALARGE;
        float vel_x = VELX_POSITIVE;
        float game_tempo = GAME_TEMPO.at(0);
        float creation_counter = 0.0F;
        SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
        std::shared_ptr<Texture> texture = nullptr;
        std::vector<std::string> animation;
        Sound sound;
    };

    // --- Constructores y destructor ---
    Balloon(const Config& config);
    ~Balloon() = default;

    // --- Métodos principales ---
    void alignTo(int x);                  // Centra el globo en la posición X
    void render();                        // Pinta el globo en la pantalla
    void move(float delta_time);          // Actualiza la posición y estados del globo (time-based)
    void update(float delta_time);        // Actualiza el globo (posición, animación, contadores) (time-based)
    void stop();                          // Detiene el globo
    void start();                         // Pone el globo en movimiento
    void pop(bool should_sound = false);  // Explota el globo

    // --- Métodos de color ---
    void useReverseColor();  // Pone el color alternativo en el globo
    void useNormalColor();   // Pone el color normal en el globo

    // --- Getters ---
    [[nodiscard]] auto getPosX() const -> float { return x_; }
    [[nodiscard]] auto getPosY() const -> float { return y_; }
    [[nodiscard]] auto getWidth() const -> int { return w_; }
    [[nodiscard]] auto getHeight() const -> int { return h_; }
    [[nodiscard]] auto getSize() const -> Size { return size_; }
    [[nodiscard]] auto getType() const -> Type { return type_; }
    [[nodiscard]] auto getScore() const -> Uint16 { return score_; }
    auto getCollider() -> Circle& { return collider_; }
    [[nodiscard]] auto getMenace() const -> Uint8 { return isEnabled() ? menace_ : 0; }
    [[nodiscard]] auto getPower() const -> Uint8 { return power_; }
    [[nodiscard]] auto isStopped() const -> bool { return stopped_; }
    [[nodiscard]] auto isPowerBall() const -> bool { return type_ == Type::POWERBALL; }
    [[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
    [[nodiscard]] auto isBeingCreated() const -> bool { return being_created_; }
    [[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
    [[nodiscard]] auto isUsingReversedColor() const -> bool { return use_reversed_colors_; }
    [[nodiscard]] auto canBePopped() const -> bool { return !isBeingCreated(); }

    // --- Setters ---
    void setVelY(float vel_y) { vy_ = vel_y; }
    void setVelX(float vel_x) { vx_ = vel_x; }
    void alterVelX(float percent) { vx_ *= percent; }
    void setGameTempo(float tempo) { game_tempo_ = tempo; }
    void setInvulnerable(bool value) { invulnerable_ = value; }
    void setBouncingSound(bool value) { sound_.bouncing_enabled = value; }
    void setPoppingSound(bool value) { sound_.poping_enabled = value; }
    void setSound(bool value) { sound_.enabled = value; }

   private:
    // --- Estructura para el efecto de rebote ---
    struct BounceEffect {
       private:
        static constexpr int BOUNCE_FRAMES = 10;  // Cantidad de elementos del vector de deformación

        // Tablas de valores predefinidos para el efecto de rebote
        static constexpr std::array<float, BOUNCE_FRAMES> HORIZONTAL_ZOOM_VALUES = {
            1.10F,
            1.05F,
            1.00F,
            0.95F,
            0.90F,
            0.95F,
            1.00F,
            1.02F,
            1.05F,
            1.02F};

        static constexpr std::array<float, BOUNCE_FRAMES> VERTICAL_ZOOM_VALUES = {
            0.90F,
            0.95F,
            1.00F,
            1.05F,
            1.10F,
            1.05F,
            1.00F,
            0.98F,
            0.95F,
            0.98F};

        // Estado del efecto
        bool enabled_ = false;  // Si el efecto está activo
        Uint8 counter_ = 0;     // Contador para el efecto
        Uint8 speed_ = 2;       // Velocidad del efecto

        // Valores actuales de transformación
        float horizontal_zoom_ = 1.0F;  // Zoom en anchura
        float verical_zoom_ = 1.0F;     // Zoom en altura
        float x_offset_ = 0.0F;         // Desplazamiento X antes de pintar
        float y_offset_ = 0.0F;         // Desplazamiento Y antes de pintar

       public:
        // Constructor por defecto
        BounceEffect() = default;

        // Reinicia el efecto a sus valores iniciales
        void reset() {
            counter_ = 0;
            horizontal_zoom_ = 1.0F;
            verical_zoom_ = 1.0F;
            x_offset_ = 0.0F;
            y_offset_ = 0.0F;
        }

        // Aplica la deformación visual al sprite
        void apply(AnimatedSprite* sprite) const {
            if (sprite != nullptr) {
                sprite->setHorizontalZoom(horizontal_zoom_);
                sprite->setVerticalZoom(verical_zoom_);
            }
        }

        // Activa el efecto de rebote
        void enable(AnimatedSprite* sprite, Size balloon_size) {
            // Los globos pequeños no tienen efecto de rebote
            if (balloon_size == Size::SMALL) {
                return;
            }
            enabled_ = true;
            reset();
            apply(sprite);
        }

        // Detiene el efecto de rebote
        void disable(AnimatedSprite* sprite) {
            enabled_ = false;
            reset();
            apply(sprite);
        }

        // Actualiza el efecto en cada frame
        void update(AnimatedSprite* sprite) {
            if (!enabled_) {
                return;
            }

            // Calcula el índice basado en el contador y velocidad
            const int INDEX = counter_ / speed_;

            // Actualiza los valores de zoom desde las tablas predefinidas
            horizontal_zoom_ = HORIZONTAL_ZOOM_VALUES.at(INDEX);
            verical_zoom_ = VERTICAL_ZOOM_VALUES.at(INDEX);

            // Aplica la deformación al sprite
            apply(sprite);

            // Incrementa el contador y verifica si el efecto debe terminar
            if (++counter_ / speed_ >= BOUNCE_FRAMES) {
                disable(sprite);
            }
        }

        // Getters para acceso a los valores actuales
        [[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
        [[nodiscard]] auto getHorizontalZoom() const -> float { return horizontal_zoom_; }
        [[nodiscard]] auto getVerticalZoom() const -> float { return verical_zoom_; }
        [[nodiscard]] auto getXOffset() const -> float { return x_offset_; }
        [[nodiscard]] auto getYOffset() const -> float { return y_offset_; }
    };

    // --- Objetos y punteros ---
    std::unique_ptr<AnimatedSprite> sprite_;  // Sprite del objeto globo

    // --- Variables de estado y físicas ---
    float x_;                            // Posición X
    float y_;                            // Posición Y
    float w_;                            // Ancho
    float h_;                            // Alto
    float vx_;                           // Velocidad X
    float vy_;                           // Velocidad Y
    float gravity_;                      // Aceleración en Y
    float default_vy_;                   // Velocidad inicial al rebotar
    float max_vy_;                       // Máxima velocidad en Y
    bool being_created_;                 // Si el globo se está creando
    bool enabled_ = true;                // Si el globo está activo
    bool invulnerable_;                  // Si el globo es invulnerable
    bool stopped_;                       // Si el globo está parado
    bool use_reversed_colors_ = false;   // Si se usa el color alternativo
    Circle collider_;                    // Círculo de colisión
    float creation_counter_;             // Temporizador de creación
    float creation_counter_ini_;         // Valor inicial del temporizador de creación
    Uint16 score_;                       // Puntos al destruir el globo
    Type type_;                          // Tipo de globo
    Size size_;                          // Tamaño de globo
    Uint8 menace_;                       // Amenaza que genera el globo
    Uint32 counter_ = 0;                 // Contador interno
    float game_tempo_;                   // Multiplicador de tempo del juego
    float movement_accumulator_ = 0.0F;  // Acumulador para movimiento durante creación (deltaTime)
    Uint8 power_;                        // Poder que alberga el globo
    SDL_FRect play_area_;                // Zona de movimiento del globo
    Sound sound_;                        // Configuración de sonido del globo
    BounceEffect bounce_effect_;         // Efecto de rebote

    // --- Posicionamiento y transformación ---
    void shiftColliders();  // Alinea el círculo de colisión con el sprite
    void shiftSprite();     // Alinea el sprite en pantalla

    // --- Animación y sonido ---
    void setAnimation();             // Establece la animación correspondiente
    void playBouncingSound() const;  // Reproduce el sonido de rebote
    void playPoppingSound() const;   // Reproduce el sonido de reventar

    // --- Movimiento y física ---
    void handleHorizontalMovement(float delta_time);  // Maneja el movimiento horizontal (time-based)
    void handleVerticalMovement(float delta_time);    // Maneja el movimiento vertical (time-based)
    void applyGravity(float delta_time);              // Aplica la gravedad al objeto (time-based)

    // --- Rebote ---
    void enableBounceEffect();                              // Activa el efecto de rebote
    void disableBounceEffect();                             // Detiene el efecto de rebote
    void updateBounceEffect();                              // Actualiza el estado del rebote
    void handleHorizontalBounce(float min_x, float max_x);  // Maneja el rebote horizontal dentro de límites

    // --- Colisiones ---
    [[nodiscard]] auto isOutOfHorizontalBounds(float min_x, float max_x) const -> bool;  // Verifica si está fuera de los límites horizontales
    [[nodiscard]] auto shouldCheckTopCollision() const -> bool;                          // Determina si debe comprobarse la colisión superior
    void handleTopCollision();                                                           // Maneja la colisión superior
    void handleBottomCollision();                                                        // Maneja la colisión inferior

    // --- Lógica de estado ---
    void updateState(float delta_time);  // Actualiza los estados del globo (time-based)
};