// IWYU pragma: no_include <bits/std_abs.h>
#include "game/entities/tabe.hpp"

#include <SDL3/SDL.h>  // Para SDL_FlipMode, SDL_GetTicks

#include <algorithm>  // Para max
#include <array>      // Para array
#include <cstdlib>    // Para rand, abs
#include <string>     // Para basic_string

#include "core/audio/audio.hpp"         // Para Audio
#include "core/resources/resource.hpp"  // Para Resource
#include "utils/param.hpp"              // Para Param, param, ParamGame, ParamTabe
#include "utils/utils.hpp"              // Para Zone

// Constructor
Tabe::Tabe()
    : sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))),
      timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {}

// Actualiza la lógica (time-based)
void Tabe::update(float delta_time) {
    if (enabled_ && !timer_.is_paused) {
        sprite_->update(delta_time);
        move(delta_time);
        updateState(delta_time);
    }

    timer_.update();
    if (timer_.shouldSpawn()) {
        enable();
    }
}

// Dibuja el objeto
void Tabe::render() {
    if (enabled_) {
        sprite_->render();
    }
}

// Mueve el objeto (time-based)
void Tabe::move(float delta_time) {
    const int X = static_cast<int>(x_);
    speed_ += accel_ * delta_time;
    x_ += speed_ * delta_time;
    fly_distance_ -= std::abs(X - static_cast<int>(x_));

    // Comprueba si sale por los bordes
    const float MIN_X = param.game.game_area.rect.x - WIDTH;
    const float MAX_X = param.game.game_area.rect.x + param.game.game_area.rect.w;
    switch (destiny_) {
        case Direction::TO_THE_LEFT: {
            if (x_ < MIN_X) {
                disable();
            }
            if (x_ > param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH && direction_ == Direction::TO_THE_RIGHT) {
                setRandomFlyPath(Direction::TO_THE_LEFT, 80);
                x_ = param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH;
            }
            break;
        }

        case Direction::TO_THE_RIGHT: {
            if (x_ > MAX_X) {
                disable();
            }
            if (x_ < param.game.game_area.rect.x && direction_ == Direction::TO_THE_LEFT) {
                setRandomFlyPath(Direction::TO_THE_RIGHT, 80);
                x_ = param.game.game_area.rect.x;
            }
            break;
        }
        default:
            break;
    }

    if (fly_distance_ <= 0) {
        if (waiting_counter_ > 0) {
            accel_ = speed_ = 0.0F;
            waiting_counter_ -= delta_time;
            waiting_counter_ = std::max<float>(waiting_counter_, 0);
        } else {
            constexpr int CHOICES = 4;
            const std::array<Direction, CHOICES> LEFT = {
                Direction::TO_THE_LEFT,
                Direction::TO_THE_LEFT,
                Direction::TO_THE_LEFT,
                Direction::TO_THE_RIGHT};

            const std::array<Direction, CHOICES> RIGHT = {
                Direction::TO_THE_LEFT,
                Direction::TO_THE_RIGHT,
                Direction::TO_THE_RIGHT,
                Direction::TO_THE_RIGHT};

            const Direction DIRECTION = destiny_ == Direction::TO_THE_LEFT
                ? LEFT[rand() % CHOICES]
                : RIGHT[rand() % CHOICES];

            setRandomFlyPath(DIRECTION, 20 + (rand() % 40));
        }
    }

    shiftSprite();
}

// Habilita el objeto
void Tabe::enable() {
    if (!enabled_) {
        enabled_ = true;
        has_bonus_ = true;
        hit_counter_ = 0;
        number_of_hits_ = 0;
        y_ = param.game.game_area.rect.y + 20.0F;

        // Establece una dirección aleatoria
        destiny_ = direction_ = rand() % 2 == 0 ? Direction::TO_THE_LEFT : Direction::TO_THE_RIGHT;

        // Establece la posición inicial
        x_ = (direction_ == Direction::TO_THE_LEFT) ? param.game.game_area.rect.x + param.game.game_area.rect.w : param.game.game_area.rect.x - WIDTH;

        // Crea una ruta de vuelo
        setRandomFlyPath(direction_, 60);
        shiftSprite();
    }
}

// Establece un vuelo aleatorio
void Tabe::setRandomFlyPath(Direction direction, int length) {
    direction_ = direction;
    fly_distance_ = length;
    waiting_counter_ = 0.083F + ((rand() % 15) * 0.0167F);  // 5-20 frames converted to seconds (5/60 to 20/60)
    Audio::get()->playSound("tabe.wav");

    constexpr float SPEED = 120.0F;  // 2 pixels/frame * 60fps = 120 pixels/second

    switch (direction) {
        case Direction::TO_THE_LEFT: {
            speed_ = -1.0F * SPEED;
            accel_ = -1.0F * (1 + (rand() % 10)) * 2.0F;  // Converted from frame-based to seconds
            sprite_->setFlip(SDL_FLIP_NONE);
            break;
        }

        case Direction::TO_THE_RIGHT: {
            speed_ = SPEED;
            accel_ = (1 + (rand() % 10)) * 2.0F;  // Converted from frame-based to seconds
            sprite_->setFlip(SDL_FLIP_HORIZONTAL);
            break;
        }

        default:
            break;
    }
}

// Establece el estado
void Tabe::setState(State state) {
    if (enabled_) {
        state_ = state;

        switch (state) {
            case State::FLY:
                sprite_->setCurrentAnimation("fly");
                break;

            case State::HIT:
                sprite_->setCurrentAnimation("hit");
                hit_counter_ = 0.083F;  // 5 frames converted to seconds (5/60)
                ++number_of_hits_;
                break;

            default:
                break;
        }
    }
}

// Actualiza el estado (time-based)
void Tabe::updateState(float delta_time) {
    if (state_ == State::HIT) {
        hit_counter_ -= delta_time;
        if (hit_counter_ <= 0) {
            setState(State::FLY);
        }
    }
}

// Intenta obtener el bonus
auto Tabe::tryToGetBonus() -> bool {
    if (has_bonus_ && rand() % std::max(1, 15 - number_of_hits_) == 0) {
        has_bonus_ = false;
        return true;
    }
    return false;
}

// Actualiza el temporizador
void Tabe::updateTimer() {
    timer_.current_time = SDL_GetTicks();
    timer_.delta_time = timer_.current_time - timer_.last_time;
    timer_.last_time = timer_.current_time;
}

// Deshabilita el objeto
void Tabe::disable() {
    enabled_ = false;
    timer_.reset();
}

// Detiene/activa el timer
void Tabe::pauseTimer(bool value) {
    timer_.setPaused(value);
}

// Deshabilita el spawning permanentemente
void Tabe::disableSpawning() {
    timer_.setSpawnDisabled(true);
}

// Habilita el spawning nuevamente
void Tabe::enableSpawning() {
    timer_.setSpawnDisabled(false);
}