#pragma once

#include <SDL3/SDL.h>

#include <string>  // Para string, basic_string
#include <vector>  // Para vector

// Definiciones de repetición
constexpr bool INPUT_ALLOW_REPEAT = true;
constexpr bool INPUT_DO_NOT_ALLOW_REPEAT = false;

// Tipos de entrada
enum class InputDeviceToUse : int {
    KEYBOARD = 0,
    CONTROLLER = 1,
    ANY = 2,
};

enum class InputAction {
    // Inputs obligatorios
    UP,
    DOWN,
    LEFT,
    RIGHT,
    PAUSE,
    EXIT,
    ACCEPT,
    CANCEL,

    // Inputs personalizados
    JUMP,
    WINDOW_INC_ZOOM,
    WINDOW_DEC_ZOOM,
    TOGGLE_VIDEOMODE,
    TOGGLE_INTEGER_SCALE,
    TOGGLE_BORDER,
    TOGGLE_MUSIC,
    NEXT_PALETTE,
    PREVIOUS_PALETTE,
    TOGGLE_SHADERS,
    SHOW_DEBUG_INFO,

    // Input obligatorio
    NONE,
    SIZE
};

class Input {
    private:
        // [SINGLETON] Objeto privado
        static Input* input;

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

                // Constructor
                explicit KeyBindings(Uint8 sc = 0, bool act = false)
                    : scancode(sc),
                      active(act) {}
        };

        struct ControllerBindings {
                SDL_GamepadButton button;  // GameControllerButton asociado
                bool active;               // Indica si está activo
                bool axis_active;          // Estado del eje

                // Constructor
                explicit ControllerBindings(SDL_GamepadButton btn = SDL_GAMEPAD_BUTTON_INVALID, bool act = false, bool axis_act = false)
                    : button(btn),
                      active(act),
                      axis_active(axis_act) {}
        };

        // Variables
        std::vector<SDL_Gamepad*> connected_controllers_;                   // Vector con todos los mandos conectados
        std::vector<SDL_Joystick*> joysticks_;                              // Vector con todos los joysticks conectados
        std::vector<KeyBindings> key_bindings_;                             // Vector con las teclas asociadas a los inputs predefinidos
        std::vector<std::vector<ControllerBindings>> controller_bindings_;  // Vector con los botones asociadas a los inputs predefinidos para cada mando
        std::vector<std::string> controller_names_;                         // Vector con los nombres de los mandos
        std::vector<InputAction> button_inputs_;                            // Inputs asignados al jugador y a botones, excluyendo direcciones
        int num_joysticks_ = 0;                                             // Número de joysticks conectados
        int num_gamepads_ = 0;                                              // Número de mandos conectados
        std::string game_controller_db_path_;                               // Ruta al archivo gamecontrollerdb.txt

        // Comprueba el eje del mando
        auto checkAxisInput(InputAction input, int controller_index, bool repeat) -> bool;

        // Constructor
        explicit Input(const std::string& game_controller_db_path);

        // Destructor
        ~Input() = default;

    public:
        // [SINGLETON] Crearemos el objeto con esta función estática
        static void init(const std::string& game_controller_db_path);

        // [SINGLETON] Destruiremos el objeto con esta función estática
        static void destroy();

        // [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
        static auto get() -> Input*;

        // Asigna inputs a teclas
        void bindKey(InputAction input, SDL_Scancode code);

        // Asigna inputs a botones del mando
        void bindGameControllerButton(int controller_index, InputAction input, SDL_GamepadButton button);
        void bindGameControllerButton(int controller_index, InputAction input_target, InputAction input_source);

        // Comprueba si un input esta activo
        auto checkInput(InputAction input, bool repeat = true, InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0) -> bool;

        // Comprueba si hay almenos un input activo
        auto checkAnyInput(InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0) -> bool;

        // Busca si hay mandos conectados
        auto discoverGameControllers() -> bool;

        // Comprueba si hay algun mando conectado
        [[nodiscard]] auto gameControllerFound() const -> bool;

        // Obten el número de mandos conectados
        [[nodiscard]] auto getNumControllers() const -> int;

        // Obten el nombre de un mando de juego
        [[nodiscard]] auto getControllerName(int controller_index) const -> std::string;

        // Obtiene el indice del controlador a partir de un event.id
        [[nodiscard]] auto getJoyIndex(SDL_JoystickID id) const -> int;

        // Obtiene el SDL_GamepadButton asignado a un input
        [[nodiscard]] auto getControllerBinding(int controller_index, InputAction input) const -> SDL_GamepadButton;

        // Obtiene el indice a partir del nombre del mando
        [[nodiscard]] auto getIndexByName(const std::string& name) const -> int;
};