#pragma once

#include <SDL3/SDL.h>

#include <memory>  // Para shared_ptr, __shared_ptr_access
#include <string>  // Para string
#include <utility>
#include <vector>  // Para vector

#include "core/rendering/surface_animated_sprite.hpp"  // Para SAnimatedSprite
#include "game/gameplay/room.hpp"
#include "utils/defines.hpp"  // Para BORDER_TOP, BLOCK
#include "utils/utils.hpp"    // Para Color
struct JA_Sound_t;            // lines 13-13

enum class PlayerState {
    STANDING,
    JUMPING,
    FALLING,
};

struct PlayerSpawn {
        float x;
        float y;
        float vx;
        float vy;
        int jump_init_pos;
        PlayerState state;
        SDL_FlipMode flip;

        // Constructor por defecto
        PlayerSpawn()
            : x(0),
              y(0),
              vx(0),
              vy(0),
              jump_init_pos(0),
              state(PlayerState::STANDING),
              flip(SDL_FLIP_NONE) {}

        // Constructor
        PlayerSpawn(float x, float y, float vx, float vy, int jump_init_pos, PlayerState state, SDL_FlipMode flip)
            : x(x),
              y(y),
              vx(vx),
              vy(vy),
              jump_init_pos(jump_init_pos),
              state(state),
              flip(flip) {}
};

struct PlayerData {
        PlayerSpawn spawn;
        std::string texture_path;
        std::string animations_path;
        std::shared_ptr<Room> room;

        // Constructor
        PlayerData(PlayerSpawn spawn, std::string texture_path, std::string animations_path, std::shared_ptr<Room> room)
            : spawn(spawn),
              texture_path(std::move(std::move(texture_path))),
              animations_path(std::move(std::move(animations_path))),
              room(std::move(std::move(room))) {}
};

class Player {
    private:
        // Constantes
        static constexpr int WIDTH = 8;                       // Ancho del jugador
        static constexpr int HEIGHT = 16;                     // ALto del jugador
        static constexpr int MAX_FALLING_HEIGHT = BLOCK * 4;  // Altura maxima permitida de caída.
        static constexpr float MAX_VY = 1.2F;                 // Velocidad máxima que puede alcanzar al desplazarse en vertical

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

        // Variables
        float x_;                                  // Posición del jugador en el eje X
        float y_;                                  // Posición del jugador en el eje Y
        float vx_;                                 // Velocidad/desplazamiento del jugador en el eje X
        float vy_;                                 // Velocidad/desplazamiento del jugador en el eje Y
        Uint8 color_;                              // Color del jugador
        SDL_FRect collider_box_;                   // Caja de colisión con los enemigos u objetos
        std::vector<SDL_FPoint> collider_points_;  // Puntos de colisión con el mapa
        std::vector<SDL_FPoint> under_feet_;       // Contiene los puntos que hay bajo cada pie del jugador
        std::vector<SDL_FPoint> feet_;             // Contiene los puntos que hay en el pie del jugador
        PlayerState state_;                        // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo
        PlayerState previous_state_;               // Estado previo en el que se encontraba el jugador
        bool is_on_border_ = false;                // Indica si el jugador esta en uno de los cuatro bordes de la pantalla
        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 auto_movement_ = false;               // Indica si esta siendo arrastrado por una superficie automatica
        RoomBorder border_ = RoomBorder::TOP;      // Indica en cual de los cuatro bordes se encuentra
        SDL_FRect last_position_;                  // Contiene la ultima posición del jugador, por si hay que deshacer algun movimiento
        int jump_init_pos_;                        // Valor del eje Y en el que se inicia el salto
        std::vector<JA_Sound_t*> jumping_sound_;   // Vecor con todos los sonidos del salto
        std::vector<JA_Sound_t*> falling_sound_;   // Vecor con todos los sonidos de la caída
        int jumping_counter_ = 0;                  // Cuenta el tiempo de salto
        int falling_counter_ = 0;                  // Cuenta el tiempo de caida

#ifdef _DEBUG
        SDL_FRect debug_rect_x_;  // Rectangulo de desplazamiento para el modo debug
        SDL_FRect debug_rect_y_;  // Rectangulo de desplazamiento para el modo debug
        Uint8 debug_color_;       // Color del recuadro de debug del jugador
        SDL_FPoint debug_point_;  // Punto para debug
#endif

        // Comprueba las entradas y modifica variables
        void checkInput();

        // Comprueba si se halla en alguno de los cuatro bordes
        void checkBorders();

        // Comprueba el estado del jugador
        void checkState();

        // Aplica gravedad al jugador
        void applyGravity();

        // Recalcula la posición del jugador y su animación
        void move();

        // Maneja el movimiento horizontal hacia la izquierda
        void moveHorizontalLeft();

        // Maneja el movimiento horizontal hacia la derecha
        void moveHorizontalRight();

        // Maneja el movimiento vertical hacia arriba
        void moveVerticalUp();

        // Maneja el movimiento vertical hacia abajo
        void moveVerticalDown();

        // Establece la animación del jugador
        void animate();

        // Comprueba si ha finalizado el salto al alcanzar la altura de inicio
        void checkJumpEnd();

        // Calcula y reproduce el sonido de salto
        void playJumpSound();

        // Calcula y reproduce el sonido de caer
        void playFallSound();

        // Comprueba si el jugador tiene suelo debajo de los pies
        auto isOnFloor() -> bool;

        // Comprueba si el jugador esta sobre una superficie automática
        auto isOnAutoSurface() -> bool;

        // Comprueba si el jugador está sobre una rampa hacia abajo
        auto isOnDownSlope() -> bool;

        // Comprueba que el jugador no toque ningun tile de los que matan
        auto checkKillingTiles() -> bool;

        // Actualiza los puntos de colisión
        void updateColliderPoints();

        // Actualiza los puntos de los pies
        void updateFeet();

        // Cambia el estado del jugador
        void setState(PlayerState value);

        // Inicializa los sonidos de salto y caida
        void initSounds();

        // Coloca el sprite en la posición del jugador
        void placeSprite() { sprite_->setPos(x_, y_); }

        // Aplica los valores de spawn al jugador
        void applySpawnValues(const PlayerSpawn& spawn);

        // Inicializa el sprite del jugador
        void initSprite(const std::string& surface_path, const std::string& animations_path);

#ifdef _DEBUG
        // Pinta la información de debug del jugador
        void renderDebugInfo();
#endif

public:
        // Constructor
        explicit Player(const PlayerData& player);

        // Destructor
        ~Player() = default;

        // Pinta el enemigo en pantalla
        void render();

        // Actualiza las variables del objeto
        void update();

        // Indica si el jugador esta en uno de los cuatro bordes de la pantalla
        [[nodiscard]] auto getOnBorder() const -> bool { return is_on_border_; }

        // Indica en cual de los cuatro bordes se encuentra
        [[nodiscard]] auto getBorder() const -> RoomBorder { return border_; }

        // Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
        void switchBorders();

        // Obtiene el rectangulo que delimita al jugador
        auto getRect() -> SDL_FRect { return {x_, y_, WIDTH, HEIGHT}; }

        // Obtiene el rectangulo de colision del jugador
        auto getCollider() -> SDL_FRect& { return collider_box_; }

        // Obtiene el estado de reaparición del jugador
        auto getSpawnParams() -> PlayerSpawn { return {x_, y_, vx_, vy_, jump_init_pos_, state_, sprite_->getFlip()}; }

        // Establece el color del jugador
        void setColor();

        // Establece la habitación en la que se encuentra el jugador
        void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); }

        // Comprueba si el jugador esta vivo
        [[nodiscard]] auto isAlive() const -> bool { return is_alive_; }

        // Pone el jugador en modo pausa
        void setPaused(bool value) { is_paused_ = value; }
};