#include "game/entities/balloon.hpp"

#include <algorithm>  // Para clamp
#include <array>      // Para array
#include <cmath>      // Para fabs

#include "core/audio/audio.hpp"                       // Para Audio
#include "core/rendering/sprite/animated_sprite.hpp"  // Para AnimatedSprite
#include "core/rendering/sprite/sprite.hpp"           // Para Sprite
#include "core/rendering/texture.hpp"                 // Para Texture
#include "utils/param.hpp"                            // Para Param, ParamBalloon, param

// Constructor
Balloon::Balloon(const Config& config)
    : sprite_(std::make_unique<AnimatedSprite>(config.texture, config.animation)),
      x_(config.x),
      y_(config.y),
      vx_(config.vel_x),
      being_created_(config.creation_counter > 0),
      invulnerable_(config.creation_counter > 0),
      stopped_(config.creation_counter > 0),
      creation_counter_(config.creation_counter),
      creation_counter_ini_(config.creation_counter),
      type_(config.type),
      size_(config.size),
      game_tempo_(config.game_tempo),
      play_area_(config.play_area),
      sound_(config.sound) {
    switch (type_) {
        case Type::BALLOON: {
            vy_ = 0;
            max_vy_ = 3.0F * 60.0F;  // Convert from frames to seconds (180 pixels/s)

            const int INDEX = static_cast<int>(size_);
            gravity_ = param.balloon.settings.at(INDEX).grav;
            default_vy_ = param.balloon.settings.at(INDEX).vel;
            h_ = w_ = WIDTH.at(INDEX);
            power_ = POWER.at(INDEX);
            menace_ = MENACE.at(INDEX);
            score_ = SCORE.at(INDEX);
            sound_.bouncing_file = BOUNCING_SOUND.at(INDEX);
            sound_.popping_file = POPPING_SOUND.at(INDEX);
            break;
        }

        case Type::FLOATER: {
            default_vy_ = max_vy_ = vy_ = std::fabs(vx_ * 2.0F);
            gravity_ = 0.00F;

            const int INDEX = static_cast<int>(size_);
            h_ = w_ = WIDTH.at(INDEX);
            power_ = POWER.at(INDEX);
            menace_ = MENACE.at(INDEX);
            score_ = SCORE.at(INDEX);
            sound_.bouncing_file = BOUNCING_SOUND.at(INDEX);
            sound_.popping_file = POPPING_SOUND.at(INDEX);
            break;
        }

        case Type::POWERBALL: {
            constexpr int INDEX = 3;
            h_ = w_ = WIDTH.at(4);
            sound_.bouncing_file = BOUNCING_SOUND.at(3);
            sound_.popping_file = "power_ball_explosion.wav";
            power_ = score_ = menace_ = 0;

            vy_ = 0;
            max_vy_ = 3.0F * 60.0F;  // Convert from frames to seconds (180 pixels/s)
            gravity_ = param.balloon.settings.at(INDEX).grav;
            default_vy_ = param.balloon.settings.at(INDEX).vel;

            sprite_->setRotate(config.creation_counter <= 0);
            sprite_->setRotateAmount(vx_ > 0.0F ? 120.0 : -120.0);  // Convert from 2 degrees/frame to 120 degrees/second
            break;
        }

        default:
            break;
    }

    // Configura el sprite
    sprite_->setWidth(w_);
    sprite_->setHeight(h_);
    shiftSprite();

    // Alinea el circulo de colisión con el objeto
    collider_.r = w_ / 2;
    shiftColliders();

    // Establece la animación a usar
    setAnimation();

    // Si no se está creando (creation_counter = 0), asegurar estado activo
    if (!being_created_) {
        start();
        setInvulnerable(false);
    }
}

// Centra el globo en la posición X
void Balloon::alignTo(int x) {
    const float MIN_X = play_area_.x;
    const float MAX_X = play_area_.w - w_;
    x_ = std::clamp(x - (w_ / 2), MIN_X, MAX_X);
}

// Pinta el globo en la pantalla
void Balloon::render() {
    if (type_ == Type::POWERBALL) {
        // Renderiza el fondo azul
        {
            auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
            sp->setSpriteClip(0, 0, WIDTH.at(4), WIDTH.at(4));
            sp->render();
        }

        // Renderiza la estrella
        if (!invulnerable_) {
            SDL_FPoint p = {.x = 24.0F, .y = 24.0F};
            sprite_->setRotatingCenter(p);
            sprite_->render();
        }

        // Añade la máscara del borde y los reflejos
        {
            auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
            sp->setSpriteClip(WIDTH.at(4) * 2, 0, WIDTH.at(4), WIDTH.at(4));
            sp->render();
        }
    } else {
        // Renderizado para el resto de globos
        if (isBeingCreated()) {
            // Renderizado con transparencia
            sprite_->getTexture()->setAlpha(255 - static_cast<int>(creation_counter_ * (255.0F / creation_counter_ini_)));
            sprite_->render();
            sprite_->getTexture()->setAlpha(255);
        } else {
            // Renderizado normal
            sprite_->render();
        }
    }
}

// Actualiza la posición y estados del globo (time-based)
void Balloon::move(float delta_time) {
    if (isStopped()) {
        return;
    }

    handleHorizontalMovement(delta_time);
    handleVerticalMovement(delta_time);
    applyGravity(delta_time);
}

void Balloon::handleHorizontalMovement(float delta_time) {
    // DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
    x_ += vx_ * game_tempo_ * delta_time;

    const int CLIP = 2;
    const float MIN_X = play_area_.x - CLIP;
    const float MAX_X = play_area_.x + play_area_.w - w_ + CLIP;

    if (isOutOfHorizontalBounds(MIN_X, MAX_X)) {
        handleHorizontalBounce(MIN_X, MAX_X);
    }
}

void Balloon::handleVerticalMovement(float delta_time) {
    // DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
    y_ += vy_ * game_tempo_ * delta_time;

    if (shouldCheckTopCollision()) {
        handleTopCollision();
    }

    handleBottomCollision();
}

auto Balloon::isOutOfHorizontalBounds(float min_x, float max_x) const -> bool {
    return x_ < min_x || x_ > max_x;
}

void Balloon::handleHorizontalBounce(float min_x, float max_x) {
    playBouncingSound();
    x_ = std::clamp(x_, min_x, max_x);
    vx_ = -vx_;

    if (type_ == Type::POWERBALL) {
        sprite_->switchRotate();
    } else {
        enableBounceEffect();
    }
}

auto Balloon::shouldCheckTopCollision() const -> bool {
    // Colisión en la parte superior solo si el globo va de subida
    return vy_ < 0;
}

void Balloon::handleTopCollision() {
    const int MIN_Y = play_area_.y;
    if (y_ < MIN_Y) {
        playBouncingSound();
        y_ = MIN_Y;
        vy_ = -vy_;
        enableBounceEffect();
    }
}

void Balloon::handleBottomCollision() {
    const int MAX_Y = play_area_.y + play_area_.h - h_;
    if (y_ > MAX_Y) {
        playBouncingSound();
        y_ = MAX_Y;
        vy_ = -default_vy_;

        if (type_ != Type::POWERBALL) {
            enableBounceEffect();
        } else {
            setInvulnerable(false);
        }
    }
}

void Balloon::applyGravity(float delta_time) {
    // DeltaTime en segundos: aceleración (pixels/s²) * tempo * tiempo (s)
    vy_ += gravity_ * game_tempo_ * delta_time;
}

void Balloon::playBouncingSound() const {
    if (sound_.enabled && sound_.bouncing_enabled) {
        Audio::get()->playSound(sound_.bouncing_file);
    }
}

void Balloon::playPoppingSound() const {
    if (sound_.enabled && sound_.poping_enabled) {
        Audio::get()->playSound(sound_.popping_file);
    }
}

// Actualiza al globo a su posicion, animación y controla los contadores (time-based)
void Balloon::update(float delta_time) {
    move(delta_time);
    updateState(delta_time);
    updateBounceEffect();
    shiftSprite();
    shiftColliders();
    sprite_->update(delta_time);
    // Contador interno con deltaTime en segundos
    counter_ += delta_time;
}

// Actualiza los estados del globo (time-based)
void Balloon::updateState(float delta_time) {
    // Si se está creando
    if (isBeingCreated()) {
        // Actualiza el valor de las variables
        stop();
        setInvulnerable(true);

        if (creation_counter_ > 0) {
            // Desplaza lentamente el globo hacia abajo y hacia un lado
            // Cada 10/60 segundos (equivalente a 10 frames a 60fps)
            movement_accumulator_ += delta_time;

            constexpr float MOVEMENT_INTERVAL_S = 10.0F / 60.0F;  // 10 frames = ~0.167s
            if (movement_accumulator_ >= MOVEMENT_INTERVAL_S) {
                movement_accumulator_ -= MOVEMENT_INTERVAL_S;
                y_++;
                x_ += vx_ / 60.0F;  // Convierte de pixels/segundo a pixels/frame para movimiento discreto

                // Comprueba no se salga por los laterales
                const int MIN_X = play_area_.x;
                const int MAX_X = play_area_.w - w_;

                if (x_ < MIN_X || x_ > MAX_X) {
                    // Corrige y cambia el sentido de la velocidad
                    x_ -= vx_ / 60.0F;
                    vx_ = -vx_;
                }
            }
            creation_counter_ -= delta_time;
            creation_counter_ = std::max<float>(creation_counter_, 0);
        }

        else {
            // El contador ha llegado a cero
            being_created_ = false;
            start();
            setInvulnerable(false);
            setAnimation();
        }
    }
}

// Establece la animación correspondiente al estado
void Balloon::setAnimation() {
    std::string creating_animation;
    std::string normal_animation;

    switch (type_) {
        case Type::POWERBALL:
            creating_animation = "powerball";
            normal_animation = "powerball";
            break;
        case Type::FLOATER:
            creating_animation = param.balloon.color.at(2);
            normal_animation = param.balloon.color.at(3);
            break;
        default:
            creating_animation = param.balloon.color.at(0);
            normal_animation = param.balloon.color.at(1);
            break;
    }

    // Establece el frame de animación
    std::string chosen_animation;
    if (use_reversed_colors_) {
        chosen_animation = creating_animation;
    } else {
        chosen_animation = isBeingCreated() ? creating_animation : normal_animation;
    }

    sprite_->setCurrentAnimation(chosen_animation);
}

// Detiene el globo
void Balloon::stop() {
    stopped_ = true;
    if (isPowerBall()) {
        sprite_->setRotate(!stopped_);
    }
}

// Pone el globo en movimiento
void Balloon::start() {
    stopped_ = false;
    if (isPowerBall()) {
        sprite_->setRotate(!stopped_);
    }
}

// Alinea el circulo de colisión con la posición del objeto globo
void Balloon::shiftColliders() {
    collider_.x = static_cast<int>(x_) + collider_.r;
    collider_.y = static_cast<int>(y_) + collider_.r;
}

// Alinea el sprite con la posición del objeto globo
void Balloon::shiftSprite() {
    sprite_->setPosX(x_);
    sprite_->setPosY(y_);
}

void Balloon::enableBounceEffect() {
    bounce_effect_.enable(sprite_.get(), size_);
}

void Balloon::disableBounceEffect() {
    bounce_effect_.disable(sprite_.get());
}

void Balloon::updateBounceEffect() {
    bounce_effect_.update(sprite_.get());
}

// Pone el color alternativo en el globo
void Balloon::useReverseColor() {
    if (!isBeingCreated()) {
        use_reversed_colors_ = true;
        setAnimation();
    }
}

// Pone el color normal en el globo
void Balloon::useNormalColor() {
    use_reversed_colors_ = false;
    setAnimation();
}

// Explota el globo
void Balloon::pop(bool should_sound) {
    if (should_sound) { playPoppingSound(); }
    enabled_ = false;
}