#include "game/entities/moving_platform.hpp"

#include <cmath>    // Para std::sqrt
#include <cstdlib>  // Para rand

#include "core/rendering/sprite/animated_sprite.hpp"  // Para AnimatedSprite
#include "core/resources/resource_cache.hpp"          // Para Resource
#include "utils/easing_functions.hpp"                 // Para Easing::*

// Resuelve el nombre de un easing a su función
auto MovingPlatform::resolveEasing(const std::string& name) -> EasingFunc {
    if (name == "quadIn") { return Easing::quadIn; }
    if (name == "quadOut") { return Easing::quadOut; }
    if (name == "quadInOut") { return Easing::quadInOut; }
    if (name == "cubicIn") { return Easing::cubicIn; }
    if (name == "cubicOut") { return Easing::cubicOut; }
    if (name == "cubicInOut") { return Easing::cubicInOut; }
    if (name == "sineIn") { return Easing::sineIn; }
    if (name == "sineOut") { return Easing::sineOut; }
    if (name == "sineInOut") { return Easing::sineInOut; }
    return Easing::linear;
}

// Constructor
MovingPlatform::MovingPlatform(const Data& data)
    : sprite_(std::make_shared<AnimatedSprite>(Resource::Cache::get()->getAnimationData(data.animation_path))),
      path_(data.path),
      speed_(data.speed),
      loop_mode_(data.loop),
      easing_(resolveEasing(data.easing)) {
    // Flags del SolidActor: jump-through desde abajo, carry al Player encima.
    // NOTA: sin BLOCKS_PLAYER, las plataformas móviles no bloquean lateralmente
    // ni por arriba. ONEWAY_TOP hace que los sweeps verticales solo las vean
    // al caer desde arriba.
    flags_ = CARRY_ON_TOP | ONEWAY_TOP;

    // Colocar el sprite en el primer waypoint
    if (!path_.empty()) {
        sprite_->setPosX(path_[0].x);
        sprite_->setPosY(path_[0].y);
    }

    aabb_ = getRect();

    // Frame inicial
    sprite_->setCurrentAnimationFrame((data.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : data.frame);

    // Calcular longitud del primer segmento
    recalcSegmentLength();
}

// Índice del waypoint origen del segmento actual
auto MovingPlatform::getSegmentFrom() const -> int {
    if (direction_ == 1) {
        return current_segment_;
    }
    // Pingpong retrocediendo: segmento N va de path[N+1] a path[N]
    return current_segment_ + 1;
}

// Índice del waypoint destino del segmento actual
auto MovingPlatform::getSegmentTo() const -> int {
    if (direction_ == 1) {
        if (loop_mode_ == LoopMode::CIRCULAR) {
            return (current_segment_ + 1) % static_cast<int>(path_.size());
        }
        return current_segment_ + 1;
    }
    // Pingpong retrocediendo
    return current_segment_;
}

// Recalcula la longitud del segmento actual
void MovingPlatform::recalcSegmentLength() {
    if (path_.size() < 2) {
        segment_length_ = 0.0F;
        return;
    }

    int from = getSegmentFrom();
    int to = getSegmentTo();

    float dx = path_[to].x - path_[from].x;
    float dy = path_[to].y - path_[from].y;
    segment_length_ = std::sqrt((dx * dx) + (dy * dy));
}

// Avanza al siguiente segmento
void MovingPlatform::advanceSegment() {
    int path_size = static_cast<int>(path_.size());

    if (loop_mode_ == LoopMode::PINGPONG) {
        if (direction_ == 1) {
            if (current_segment_ + 1 >= path_size - 1) {
                // Llegamos al final, invertir dirección
                direction_ = -1;
                current_segment_ = path_size - 2;
            } else {
                current_segment_++;
            }
        } else {
            if (current_segment_ <= 0) {
                // Llegamos al inicio, invertir dirección
                direction_ = 1;
                current_segment_ = 0;
            } else {
                current_segment_--;
            }
        }
    } else {
        // CIRCULAR
        current_segment_ = (current_segment_ + 1) % path_size;
    }

    segment_progress_ = 0.0F;
    recalcSegmentLength();
}

// Actualiza posición de la plataforma siguiendo la ruta
void MovingPlatform::update(float delta_time) {
    sprite_->animate(delta_time);

    if (path_.size() < 2) {
        last_delta_.x = 0.0F;
        last_delta_.y = 0.0F;
        return;
    }

    float old_x = sprite_->getPosX();
    float old_y = sprite_->getPosY();

    // Si estamos esperando en un waypoint
    if (waiting_) {
        wait_timer_ -= delta_time;
        if (wait_timer_ <= 0.0F) {
            waiting_ = false;
            advanceSegment();
        } else {
            last_delta_.x = 0.0F;
            last_delta_.y = 0.0F;
            return;
        }
    }

    // Avanzar por el segmento
    if (segment_length_ > 0.0F) {
        float distance = speed_ * delta_time;
        float delta_progress = distance / segment_length_;
        segment_progress_ += delta_progress;
    } else {
        // Segmento de longitud 0: saltar al siguiente
        segment_progress_ = 1.0F;
    }

    // Comprobar si llegamos al final del segmento
    if (segment_progress_ >= 1.0F) {
        segment_progress_ = 1.0F;

        // Colocar en el waypoint destino exacto
        int to = getSegmentTo();
        sprite_->setPosX(path_[to].x);
        sprite_->setPosY(path_[to].y);

        // Comprobar si hay espera en este waypoint
        if (path_[to].wait > 0.0F) {
            waiting_ = true;
            wait_timer_ = path_[to].wait;
        } else {
            advanceSegment();
        }
    } else {
        // Interpolar posición con easing
        float t = easing_(segment_progress_);
        int from = getSegmentFrom();
        int to = getSegmentTo();

        float new_x = path_[from].x + ((path_[to].x - path_[from].x) * t);
        float new_y = path_[from].y + ((path_[to].y - path_[from].y) * t);
        sprite_->setPosX(new_x);
        sprite_->setPosY(new_y);
    }

    last_delta_.x = sprite_->getPosX() - old_x;
    last_delta_.y = sprite_->getPosY() - old_y;
    aabb_ = getRect();
}

// Pinta la plataforma en pantalla
void MovingPlatform::render() {
    sprite_->render();
}

#ifdef _DEBUG
// Solo actualiza la animación sin mover la plataforma
void MovingPlatform::updateAnimation(float delta_time) {
    sprite_->animate(delta_time);
}

// Resetea la plataforma a su posición inicial (para editor)
void MovingPlatform::resetToInitialPosition(const Data& data) {
    path_ = data.path;
    speed_ = data.speed;
    loop_mode_ = data.loop;
    easing_ = resolveEasing(data.easing);

    current_segment_ = 0;
    direction_ = 1;
    segment_progress_ = 0.0F;
    waiting_ = false;
    wait_timer_ = 0.0F;

    if (!path_.empty()) {
        sprite_->setPosX(path_[0].x);
        sprite_->setPosY(path_[0].y);
    }

    aabb_ = getRect();
    recalcSegmentLength();
}
#endif

// Devuelve el rectangulo que contiene a la plataforma
auto MovingPlatform::getRect() -> SDL_FRect {
    return sprite_->getRect();
}
