#pragma once

#include <SDL3/SDL.h>

#include <array>    // Para array
#include <cstdint>  // Para uint8_t
#include <limits>   // Para numeric_limits
#include <memory>   // Para shared_ptr, __shared_ptr_access
#include <string>   // Para string
#include <utility>

#include "core/rendering/sprite/animated_sprite.hpp"  // Para SAnimatedSprite
#include "game/gameplay/room.hpp"
#include "game/options.hpp"   // Para Cheat, Options, options
#include "utils/defines.hpp"  // Para BORDER_TOP, BLOCK
#include "utils/utils.hpp"    // Para Color
namespace Ja {
    struct Sound;
}

class Player {
   public:
    // --- Enums y Structs ---
    enum class State : std::uint8_t {
        ON_GROUND,  // En suelo plano o conveyor belt
        ON_SLOPE,   // En rampa/pendiente
        JUMPING,
        FALLING,
    };

    enum class Direction : std::uint8_t {
        LEFT,
        RIGHT,
        UP,
        DOWN,
        NONE
    };

    // --- Constantes de física (públicas para permitir cálculos en structs) ---
    static constexpr float HORIZONTAL_VELOCITY = 40.0F;  // Velocidad horizontal en pixels/segundo (0.6 * 66.67fps)
    static constexpr float MAX_VY = 80.0F;               // Velocidad vertical máxima en pixels/segundo (1.2 * 66.67fps)
    static constexpr float JUMP_VELOCITY = -80.0F;       // Velocidad inicial del salto en pixels/segundo
    static constexpr float GRAVITY_FORCE = 155.6F;       // Fuerza de gravedad en pixels/segundo² (0.035 * 66.67²)

    struct SpawnData {
        float x = 0;
        float y = 0;
        float vx = 0;
        float vy = 0;
        int last_grounded_position = 0;
        State state = State::ON_GROUND;
        SDL_FlipMode flip = SDL_FLIP_NONE;
    };

    struct Data {
        SpawnData spawn_data;
        std::string animations_path;
        std::shared_ptr<Room> room = nullptr;
    };

    struct JumpSoundController {
        // Duración del salto calculada automáticamente con física: t = 2 * v0 / g
        static constexpr float JUMP_DURATION = (2.0F * MAX_VY) / GRAVITY_FORCE;
        static constexpr size_t FIRST_SOUND = 1;  // Primer sonido a reproducir (índice 1)
        static constexpr size_t LAST_SOUND = 17;  // Último sonido a reproducir (índice 17)
        static constexpr float SECONDS_PER_SOUND = JUMP_DURATION / (LAST_SOUND - FIRST_SOUND + 1);

        size_t current_index = 0;   // Índice del sonido actual
        float elapsed_time = 0.0F;  // Tiempo transcurrido durante el salto
        bool active = false;        // Indica si el controlador está activo

        void start();                                                  // Inicia el controlador
        void reset();                                                  // Resetea el controlador
        auto shouldPlay(float delta_time, size_t& out_index) -> bool;  // Comprueba si debe reproducir un sonido
    };

    struct FallSoundController {
        static constexpr float PIXELS_PER_SOUND = 5.0F;  // Intervalo de píxeles por sonido (configurable)
        static constexpr size_t FIRST_SOUND = 1;         // Primer sonido a reproducir (índice 1)
        static constexpr size_t LAST_SOUND = 13;         // Último sonido a reproducir (índice 13)

        size_t current_index = 0;        // Índice del sonido actual
        float distance_traveled = 0.0F;  // Distancia acumulada durante la caída
        float last_y = 0.0F;             // Última posición Y registrada
        bool active = false;             // Indica si el controlador está activo

        void start(float start_y);                                                      // Inicia el controlador
        void reset();                                                                   // Resetea el controlador
        auto shouldPlay(float delta_time, float current_y, size_t& out_index) -> bool;  // Comprueba si debe reproducir un sonido
    };

    // --- Constructor y Destructor ---
    explicit Player(const Data& player);
    ~Player() = default;

    // --- Funciones ---
    void render();                                                                                                                                                                           // Pinta el enemigo en pantalla
    void update(float delta_time);                                                                                                                                                           // Actualiza las variables del objeto
    [[nodiscard]] auto isOnBorder() const -> bool { return border_ != Room::Border::NONE; }                                                                                                  // Indica si el jugador esta en uno de los cuatro bordes de la pantalla
    [[nodiscard]] auto getBorder() const -> Room::Border { return border_; }                                                                                                                 // Indica en cual de los cuatro bordes se encuentra
    void switchBorders();                                                                                                                                                                    // Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
    auto getRect() -> SDL_FRect { return {.x = x_, .y = y_, .w = WIDTH, .h = HEIGHT}; }                                                                                                      // Obtiene el rectangulo que delimita al jugador
    auto getCollider() -> SDL_FRect& { return collider_box_; }                                                                                                                               // Obtiene el rectangulo de colision del jugador
    auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; }  // Obtiene el estado de reaparición del jugador
    void setColor(Uint8 color = 0);                                                                                                                                                          // Establece el color del jugador (0 = automático según cheats)
    void setSkin(const std::string& skin_name);                                                                                                                                              // Cambia la skin del jugador en caliente ("default" o nombre de enemigo)
    static auto skinToAnimationPath(const std::string& skin_name) -> std::string;                                                                                                            // Resuelve nombre de skin a fichero de animación
    void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); }                                                                                                                    // Establece la habitación en la que se encuentra el jugador
    //[[nodiscard]] auto isAlive() const -> bool { return is_alive_ || (Options::cheats.invincible == Options::Cheat::State::ENABLED); }                                                       // Comprueba si el jugador esta vivo
    [[nodiscard]] auto isAlive() const -> bool { return is_alive_; }  // Comprueba si el jugador esta vivo
    void setPaused(bool value) { is_paused_ = value; }                // Pone el jugador en modo pausa
    void setIgnoreInput(bool value) { ignore_input_ = value; }        // Ignora inputs del jugador (física sigue activa)
    [[nodiscard]] auto getIgnoreInput() const -> bool { return ignore_input_; }

#ifdef _DEBUG
    // --- Funciones de debug ---
    void setDebugPosition(float x, float y);  // Establece la posición del jugador directamente (debug)
    void finalizeDebugTeleport();             // Fija estado ON_GROUND, velocidades a 0, actualiza last_grounded_position_ (debug)
#endif

   private:
    // --- Constantes ---
    static constexpr int WIDTH = 8;                            // Ancho del jugador
    static constexpr int HEIGHT = 16;                          // ALto del jugador
    static constexpr int MAX_FALLING_HEIGHT = Tile::SIZE * 4;  // Altura maxima permitida de caída en pixels

    // --- Objetos y punteros ---
    std::shared_ptr<Room> room_;              // Objeto encargado de gestionar cada habitación del juego
    std::unique_ptr<AnimatedSprite> sprite_;  // Sprite del jugador

    // --- Variables de posición y física ---
    float x_ = 0.0F;       // Posición del jugador en el eje X
    float y_ = 0.0F;       // Posición del jugador en el eje Y
    float y_prev_ = 0.0F;  // Posición Y del frame anterior (para detectar hitos de distancia en sonidos)
    float vx_ = 0.0F;      // Velocidad/desplazamiento del jugador en el eje X
    float vy_ = 0.0F;      // Velocidad/desplazamiento del jugador en el eje Y

    Direction wanna_go_ = Direction::NONE;
    bool wanna_jump_ = false;

    // --- Variables de estado ---
    State state_ = State::ON_GROUND;           // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo
    State previous_state_ = State::ON_GROUND;  // Estado previo en el que se encontraba el jugador

    // --- Variables de colisión ---
    SDL_FRect collider_box_{};                              // Caja de colisión con los enemigos u objetos
    std::array<SDL_FPoint, 8> collider_points_{};           // Puntos de colisión con el mapa
    SDL_FPoint under_left_foot_ = {.x = 0.0F, .y = 0.0F};   // El punto bajo la esquina inferior izquierda del jugador
    SDL_FPoint under_right_foot_ = {.x = 0.0F, .y = 0.0F};  // El punto bajo la esquina inferior derecha del jugador
    const LineDiagonal* current_slope_{nullptr};            // Rampa actual sobe la que está el jugador

    // --- Variables de juego ---
    bool is_alive_ = true;                     // Indica si el jugador esta vivo o no
    bool is_paused_ = false;                   // Indica si el jugador esta en modo pausa
    bool ignore_input_ = false;                // Ignora inputs pero mantiene la física activa
    bool auto_movement_ = false;               // Indica si esta siendo arrastrado por una superficie automatica
    Room::Border border_ = Room::Border::TOP;  // Indica en cual de los cuatro bordes se encuentra
    int last_grounded_position_ = 0;           // Ultima posición en Y en la que se estaba en contacto con el suelo (hace doble función: tracking de caída + altura inicial del salto)

    // --- Variables de renderizado y sonido ---
    Uint8 color_ = 0;                             // Color del jugador
    std::array<Ja::Sound*, 24> jumping_sound_{};  // Array con todos los sonidos del salto
    std::array<Ja::Sound*, 14> falling_sound_{};  // Array con todos los sonidos de la caída
    JumpSoundController jump_sound_ctrl_;         // Controlador de sonidos de salto
    FallSoundController fall_sound_ctrl_;         // Controlador de sonidos de caída
    int fall_start_position_ = 0;                 // Posición Y al iniciar la caída

    void handleConveyorBelts();
    void handleShouldFall();
    void updateState(float delta_time);

    // --- Métodos de actualización por estado ---
    void updateOnGround(float delta_time);  // Actualización lógica estado ON_GROUND
    void updateOnSlope(float delta_time);   // Actualización lógica estado ON_SLOPE
    void updateJumping(float delta_time);   // Actualización lógica estado JUMPING
    void updateFalling(float delta_time);   // Actualización lógica estado FALLING

    // --- Métodos de movimiento por estado ---
    void moveOnGround(float delta_time);  // Movimiento físico estado ON_GROUND
    void moveOnSlope(float delta_time);   // Movimiento físico estado ON_SLOPE
    void moveJumping(float delta_time);   // Movimiento físico estado JUMPING
    void moveFalling(float delta_time);   // Movimiento físico estado FALLING

    // --- Funciones de inicialización ---
    void initSprite(const std::string& animations_path);  // Inicializa el sprite del jugador
    void initSounds();                                    // Inicializa los sonidos de salto y caida
    void applySpawnValues(const SpawnData& spawn);        // Aplica los valores de spawn al jugador

    // --- Funciones de procesamiento de entrada ---
    void handleInput();  // Comprueba las entradas y modifica variables

    // --- Funciones de gestión de estado ---
    void transitionToState(State state);  // Cambia el estado del jugador

    // --- Funciones de física ---
    void applyGravity(float delta_time);  // Aplica gravedad al jugador

    // --- Funciones de movimiento y colisión ---
    void move(float delta_time);                                                         // Orquesta el movimiento del jugador
    auto getProjection(Direction direction, float displacement) -> SDL_FRect;            // Devuelve el rectangulo de proyeccion
    void applyHorizontalMovement(float delta_time);                                      // Aplica movimiento horizontal con colisión de muros
    auto handleLandingFromAir(float displacement, const SDL_FRect& projection) -> bool;  // Detecta aterrizaje en superficies y rampas
    void resetSoundControllersOnLanding();                                               // Resetea los controladores de sonido al aterrizar

    // --- Funciones de detección de superficies ---
    auto isOnFloor() -> bool;         // Comprueba si el jugador tiene suelo debajo de los pies
    auto isOnTopSurface() -> bool;    // Comprueba si el jugador está sobre una superficie
    auto isOnConveyorBelt() -> bool;  // Comprueba si el jugador esta sobre una cinta transportadora
    auto isOnSlope() -> bool;         // Comprueba si el jugador está sobre una rampa
    auto isLeftSlope() -> bool;       // Comprueba si current_slope_ es una rampa izquierda (ascendente a la izquierda)
    void updateCurrentSlope();        // Actualiza current_slope_ con la rampa correcta y muestra debug info

    // --- Funciones de actualización de geometría ---
    void syncSpriteAndCollider();  // Actualiza collider_box y collision points
    void updateColliderPoints();   // Actualiza los puntos de colisión
    void updateFeet();             // Actualiza los puntos de los pies
    void placeSprite();            // Coloca el sprite en la posición del jugador

    // --- Funciones de finalización ---
    void animate(float delta_time);        // Establece la animación del jugador
    auto handleBorders() -> Room::Border;  // Comprueba si se halla en alguno de los cuatro bordes
    void handleJumpEnd();                  // Comprueba si ha finalizado el salto al alcanzar la altura de inicio
    auto handleKillingTiles() -> bool;     // Comprueba que el jugador no toque ningun tile de los que matan
    void playJumpSound(float delta_time);  // Calcula y reproduce el sonido de salto
    void playFallSound(float delta_time);  // Calcula y reproduce el sonido de caer
    void handleDeathByFalling();           // Gestiona la muerte al caer desde muy alto
    void updateVelocity();                 // Calcula la velocidad en x
    void markAsDead();                     // Marca al jugador como muerto
};