#pragma once

#include <SDL3/SDL.h>

#include <cstdint>  // for uint8_t
#include <string>   // for string, basic_string
#include <vector>   // for vector

// Valores de repetición
constexpr bool REPEAT_TRUE = true;
constexpr bool REPEAT_FALSE = false;

// Métodos de entrada
constexpr int INPUT_USE_KEYBOARD = 0;
constexpr int INPUT_USE_GAMECONTROLLER = 1;
constexpr int INPUT_USE_ANY = 2;

enum InputAction : std::uint8_t {
    // Inputs obligatorios
    INVALID,
    UP,
    DOWN,
    LEFT,
    RIGHT,
    PAUSE,
    EXIT,
    ACCEPT,
    CANCEL,

    // Inputs personalizados
    FIRE_LEFT,
    FIRE_CENTER,
    FIRE_RIGHT,
    WINDOW_FULLSCREEN,
    WINDOW_INC_ZOOM,
    WINDOW_DEC_ZOOM,

    // GPU / shaders (hotkeys provisionales hasta que haya menú de opciones)
    NEXT_SHADER_PRESET,
    TOGGLE_SHADER,
    TOGGLE_SHADER_TYPE,

    // Input obligatorio
    NUMBER_OF_INPUTS
};

enum InputDisable : std::uint8_t {
    NOT_DISABLED,
    FOREVER,
    KEY_PRESSED
};

class Input {
   public:
    // Singleton API
    static void init(const std::string &game_controller_db_path);  // Crea la instancia
    static void destroy();                                         // Libera la instancia
    static auto get() -> Input *;                                  // Obtiene el puntero a la instancia

    ~Input();  // Destructor

    void update();                                                         // Actualiza el estado del objeto
    void bindKey(Uint8 input, SDL_Scancode code);                          // Asigna inputs a teclas
    void bindGameControllerButton(Uint8 input, SDL_GamepadButton button);  // Asigna inputs a botones del mando

    auto checkInput(Uint8 input, bool repeat = true, int device = INPUT_USE_ANY, int index = 0) -> bool;  // Comprueba si un input esta activo
    auto checkAnyInput(int device = INPUT_USE_ANY, int index = 0) -> bool;                                // Comprueba si hay almenos un input activo

    auto discoverGameController() -> bool;  // Busca si hay un mando conectado

    // Procesa un evento SDL_EVENT_GAMEPAD_ADDED. Devuelve true si el mando se ha añadido
    // (no estaba ya registrado) y escribe el nombre visible en outName.
    auto handleGamepadAdded(SDL_JoystickID jid, std::string &out_name) -> bool;

    // Procesa un evento SDL_EVENT_GAMEPAD_REMOVED. Devuelve true si se ha encontrado y
    // eliminado, y escribe el nombre visible en outName.
    auto handleGamepadRemoved(SDL_JoystickID jid, std::string &out_name) -> bool;

    [[nodiscard]] auto gameControllerFound() const -> bool;  // Comprueba si hay algun mando conectado
    [[nodiscard]] auto getNumControllers() const -> int;     // Obten el numero de mandos conectados
    auto getControllerName(int index) -> std::string;        // Obten el nombre de un mando de juego

    void setVerbose(bool value);            // Establece si ha de mostrar mensajes
    void disableUntil(InputDisable value);  // Deshabilita las entradas durante un periodo de tiempo
    void enable();                          // Hablita las entradas

   private:
    struct KeyBindings {
        Uint8 scancode;  // Scancode asociado
        bool active;     // Indica si está activo
    };

    struct GameControllerBindings {
        SDL_GamepadButton button;  // GameControllerButton asociado
        bool active;               // Indica si está activo
    };

    // Objetos y punteros
    std::vector<SDL_Gamepad *> connected_controllers_;      // Vector con todos los mandos conectados
    std::vector<SDL_JoystickID> connected_controller_ids_;  // Instance IDs paralelos para mapear eventos

    // Variables
    std::vector<KeyBindings> key_bindings_;                         // Vector con las teclas asociadas a los inputs predefinidos
    std::vector<GameControllerBindings> game_controller_bindings_;  // Vector con las teclas asociadas a los inputs predefinidos
    std::vector<std::string> controller_names_;                     // Vector con los nombres de los mandos
    int num_gamepads_{0};                                           // Numero de mandos conectados
    std::string db_path_;                                           // Ruta al archivo gamecontrollerdb.txt
    bool verbose_{true};                                            // Indica si ha de mostrar mensajes
    InputDisable disabled_until_{NOT_DISABLED};                     // Tiempo que esta deshabilitado
    bool enabled_{true};                                            // Indica si está habilitado

    static Input *instance;  // Instancia única

    explicit Input(std::string file);  // Constructor privado (usar Input::init)

    // Construye el nombre visible de un mando (name truncado + sufijo #N)
    static auto buildControllerName(SDL_Gamepad *pad, int pad_index) -> std::string;

    // Helpers de checkInput
    auto checkKeyboardInput(Uint8 input, bool repeat) -> bool;
    auto checkGameControllerInput(Uint8 input, bool repeat, int index) -> bool;

    // Helpers de discoverGameController
    void resetGameControllerState();
    void ensureGamepadSubsystem();
    auto openGamepad(SDL_JoystickID joystick_id, int pad_index) -> bool;
};
