#pragma once

#include <SDL3/SDL.h>

#include <cstdint>           // Para uint8_t
#include <initializer_list>  // Para initializer_list
#include <memory>            // Para shared_ptr
#include <string>            // Para string
#include <unordered_map>     // Para unordered_map
#include <vector>            // Para vector

#include "game/entities/player.hpp"  // Para PlayerSpawn
#include "game/gameplay/room.hpp"    // Para Room, Room::Border
#include "utils/delta_timer.hpp"     // Para DeltaTimer
class RoomTracker;                   // lines 13-13
class Scoreboard;                    // lines 14-14
class Surface;

class Game {
   public:
    // --- Estructuras ---
    enum class Mode : std::uint8_t {
        DEMO,
        GAME
    };

    enum class State : std::uint8_t {
        PLAYING,           // Normal gameplay
        BLACK_SCREEN,      // Black screen after death (0.30s)
        GAME_OVER,         // Intermediate state before changing scene
        FADE_TO_ENDING,    // Fade out transition
        POST_FADE_ENDING,  // Black screen delay before ending
    };

    // --- Constructor y Destructor ---
    explicit Game(Mode mode);
    ~Game();

    // --- Frame (llamados desde Director::iterate) ---
    void update();
    void render();

   private:
    // --- Constantes de tiempo ---
    static constexpr float BLACK_SCREEN_DURATION = 0.30F;  // Duración de la pantalla negra en segundos (20 frames a 66.67fps)
    static constexpr float GAME_OVER_THRESHOLD = 0.255F;   // Tiempo antes del game over en segundos (17 frames a 66.67fps)
    static constexpr float DEMO_ROOM_DURATION = 6.0F;      // Duración de cada habitación en modo demo en segundos (400 frames)
    static constexpr float FADE_STEP_INTERVAL = 0.05F;     // Intervalo entre pasos de fade en segundos
    static constexpr float POST_FADE_DELAY = 2.0F;         // Duración de la pantalla negra después del fade
    static constexpr float TRANSITION_DURATION = 0.5F;     // Duración de la transición entre pantallas en segundos

    // --- Estructuras ---
    struct DemoData {
        float time_accumulator{0.0F};    // Acumulador de tiempo para el modo demo
        int room_index{0};               // Índice para el vector de habitaciones
        std::vector<std::string> rooms;  // Listado con los mapas de la demo
    };

    // --- Métodos ---
    static void handleEvents();                                                                 // Comprueba los eventos de la cola
    void transitionToState(State new_state);                                                    // Cambia al estado especificado y resetea los timers
    void updatePlaying(float delta_time);                                                       // Actualiza el juego en estado PLAYING
    void updateBlackScreen(float delta_time);                                                   // Actualiza el juego en estado BLACK_SCREEN
    void updateGameOver(float delta_time);                                                      // Actualiza el juego en estado GAME_OVER
    void updateFadeToEnding(float delta_time);                                                  // Actualiza el juego en estado FADE_TO_ENDING
    void updatePostFadeEnding(float delta_time);                                                // Actualiza el juego en estado POST_FADE_ENDING
    void renderPlaying();                                                                       // Renderiza el juego en estado PLAYING (directo a pantalla)
    static void renderBlackScreen();                                                            // Renderiza el juego en estado BLACK_SCREEN (pantalla negra)
    static void renderGameOver();                                                               // Renderiza el juego en estado GAME_OVER (pantalla negra)
    void renderFadeToEnding();                                                                  // Renderiza el juego en estado FADE_TO_ENDING (via backbuffer)
    static void renderPostFadeEnding();                                                         // Renderiza el juego en estado POST_FADE_ENDING (pantalla negra)
    auto changeRoom(const std::string& room_path) -> bool;                                      // Cambia de habitación
    auto getOrCreateRoom(const std::string& room_path) -> std::shared_ptr<Room>;                // Obtiene una habitación del caché o la crea
    void buildCollisionBorders();                                                               // Rellena el tilemap extendido de la room actual con bordes adyacentes
    auto getAdjacentCollision(Room::Border border) -> const std::vector<int>*;                  // Collision tilemap de la room adyacente o nullptr
    auto getDiagCollision(Room::Border first, Room::Border second) -> const std::vector<int>*;  // Collision tilemap de la room diagonal o nullptr
    auto getAdjacentSolidActors(Room::Border border) -> SolidActorManager*;                     // SolidActorManager de la room adyacente o nullptr
    void updateAdjacentRooms(float delta_time);                                                 // Actualiza enemigos de las habitaciones adyacentes
    void handleInput();                                                                         // Comprueba el teclado
    void checkPlayerIsOnBorder();                                                               // Comprueba si el jugador esta en el borde de la pantalla y actua
    auto checkPlayerAndEnemies() -> bool;                                                       // Comprueba las colisiones del jugador con los enemigos
    void checkPlayerAndItems();                                                                 // Comprueba las colisiones del jugador con los objetos
    void checkPlayerAndKeys();                                                                  // Comprueba las colisiones del jugador con las llaves
    void checkIfPlayerIsAlive();                                                                // Comprueba si el jugador esta vivo
    void killPlayer();                                                                          // Mata al jugador
    void togglePause();                                                                         // Pone el juego en pausa
    void initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room);          // Inicializa al jugador
    void endTransition();                                                                       // Finaliza la transición entre pantallas
    void keepMusicPlaying();                                                                    // Hace sonar la música (safety net, delega en updateMusicForRoom)
    void updateMusicForRoom();                                                                  // Sincroniza la música con la zona de la room actual
    void demoInit();                                                                            // DEMO MODE: Inicializa las variables para el modo demo
    void demoCheckRoomChange(float delta_time);                                                 // DEMO MODE: Comprueba si se ha de cambiar de habitación
#ifdef _DEBUG
    static void renderDebugInfo();                   // Pone la información de debug en pantalla
    void handleDebugEvents(const SDL_Event& event);  // Comprueba los eventos
    void handleDebugMouseDrag(float delta_time);     // Maneja el arrastre del jugador con el ratón (debug)
#endif

    // --- Variables miembro ---
    // Objetos y punteros a recursos
    std::shared_ptr<Scoreboard::Data> scoreboard_data_;  // Estructura con los datos del marcador
    std::shared_ptr<Scoreboard> scoreboard_;             // Objeto encargado de gestionar el marcador
    std::shared_ptr<RoomTracker> room_tracker_;          // Lleva el control de las habitaciones visitadas
    std::shared_ptr<Room> room_;                         // Objeto encargado de gestionar cada habitación del juego
    std::shared_ptr<Player> player_;                     // Objeto con el jugador
    std::shared_ptr<Surface> game_backbuffer_surface_;   // Backbuffer para efectos de fade

    // Variables de estado del juego
    Mode mode_;                     // Modo del juego
    State state_{State::PLAYING};   // Estado actual de la escena
    DeltaTimer delta_timer_;        // Timer para calcular delta time
    std::string current_room_;      // Fichero de la habitación actual
    Player::SpawnData spawn_data_;  // Lugar de la habitación donde aparece el jugador
    bool paused_{false};            // Indica si el juego se encuentra en pausa
    float state_time_{0.0F};        // Tiempo acumulado en el estado actual
    float fade_accumulator_{0.0F};  // Acumulador de tiempo para el fade

    // Caché de habitaciones vivas (enemigos mantienen su posición)
    std::unordered_map<std::string, std::shared_ptr<Room>> room_cache_;

    // Transición animada entre pantallas
    bool transitioning_{false};                              // Indica si hay una transición en curso
    float transition_timer_{0.0F};                           // Tiempo transcurrido en la transición
    std::shared_ptr<Room> transition_old_room_;              // Habitación saliente (se mantiene viva durante la transición)
    Room::Border transition_direction_{Room::Border::NONE};  // Dirección de la transición
    bool transition_just_ended_{false};                      // Cooldown de 1 frame tras finalizar transición

    // Variables de demo mode
    DemoData demo_;  // Variables para el modo demo

#ifdef _DEBUG
    // Variables de debug para arrastre con ratón
    bool debug_dragging_player_{false};  // Indica si estamos arrastrando al jugador con el ratón
    float debug_drag_speed_{0.0F};       // Velocidad actual del arrastre (ease-in)
    // Estado previo de invencibilidad antes de entrar en modo debug
    bool invincible_before_debug_{false};
#endif
};