#pragma once

#include <SDL3/SDL.h>  // Para SDL_Scancode, SDL_GamepadButton, SDL_JoystickID, SDL_CloseGamepad, SDL_Gamepad, SDL_GetGamepadJoystick, SDL_GetGamepadName, SDL_GetGamepadPath, SDL_GetJoystickID, Sint16, Uint8, SDL_Event

#include <array>          // Para array
#include <memory>         // Para shared_ptr
#include <string>         // Para string, basic_string
#include <unordered_map>  // Para unordered_map
#include <utility>        // Para pair
#include <vector>         // Para vector

#include "core/input/input_types.hpp"  // for InputAction

// --- Clase Input: gestiona la entrada de teclado y mandos (singleton) ---
class Input {
   public:
    // --- Constantes ---
    static constexpr bool ALLOW_REPEAT = true;            // Permite repetición
    static constexpr bool DO_NOT_ALLOW_REPEAT = false;    // No permite repetición
    static constexpr bool CHECK_KEYBOARD = true;          // Comprueba teclado
    static constexpr bool DO_NOT_CHECK_KEYBOARD = false;  // No comprueba teclado
    static constexpr int TRIGGER_L2_AS_BUTTON = 100;      // L2 como botón
    static constexpr int TRIGGER_R2_AS_BUTTON = 101;      // R2 como botón

    // --- Tipos ---
    using Action = InputAction;  // Alias para mantener compatibilidad

    // --- Estructuras ---
    struct KeyState {
        Uint8 scancode{0};         // Scancode asociado
        bool is_held{false};       // Está pulsada ahora mismo
        bool just_pressed{false};  // Se acaba de pulsar en este fotograma
    };

    struct ButtonState {
        int button{static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)};  // GameControllerButton asociado
        bool is_held{false};                                       // Está pulsada ahora mismo
        bool just_pressed{false};                                  // Se acaba de pulsar en este fotograma
        bool axis_active{false};                                   // Estado del eje
        bool trigger_active{false};                                // Estado del trigger como botón digital
    };

    struct Keyboard {
        std::unordered_map<Action, KeyState> bindings;  // Mapa de acciones a estados de tecla
    };

    struct Gamepad {
        SDL_Gamepad* pad{nullptr};                         // Puntero al gamepad SDL
        SDL_JoystickID instance_id{0};                     // ID de instancia del joystick
        std::string name;                                  // Nombre del gamepad
        std::string path;                                  // Ruta del dispositivo
        std::unordered_map<Action, ButtonState> bindings;  // Mapa de acciones a estados de botón

        // Recorta el nombre del mando hasta el primer '(' o '[' y elimina espacios finales.
        // Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones.
        static auto trimName(const char* raw) -> std::string {
            std::string s(raw != nullptr ? raw : "");
            const auto pos = s.find_first_of("([");
            if (pos != std::string::npos) { s.erase(pos); }
            while (!s.empty() && s.back() == ' ') { s.pop_back(); }
            return s;
        }

        explicit Gamepad(SDL_Gamepad* gamepad)
            : pad(gamepad),
              instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
              name(trimName(SDL_GetGamepadName(gamepad))),
              path(std::string(SDL_GetGamepadPath(pad))),
              bindings{
                  // Movimiento del jugador
                  {Action::LEFT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT)}},
                  {Action::RIGHT, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)}},
                  {Action::JUMP, ButtonState{.button = static_cast<int>(SDL_GAMEPAD_BUTTON_WEST)}}} {}

        ~Gamepad() {
            if (pad != nullptr) {
                SDL_CloseGamepad(pad);
            }
        }

        // Reasigna un botón a una acción
        void rebindAction(Action action, SDL_GamepadButton new_button) {
            bindings[action].button = static_cast<int>(new_button);
        }
    };

    // --- Tipos ---
    using Gamepads = std::vector<std::shared_ptr<Gamepad>>;  // Vector de gamepads

    // --- Singleton ---
    static void init(const std::string& game_controller_db_path);
    static void destroy();
    static auto get() -> Input*;

    // --- Actualización del sistema ---
    void update();  // Actualiza estados de entrada

    // --- Configuración de controles ---
    void bindKey(Action action, SDL_Scancode code);
    void applyKeyboardBindingsFromOptions();
    void applyGamepadBindingsFromOptions();
    static void bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action, SDL_GamepadButton button);
    static void bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action_target, Action action_source);

    // --- Consulta de entrada ---
    auto checkAction(Action action, bool repeat = true, bool check_keyboard = true, const std::shared_ptr<Gamepad>& gamepad = nullptr) -> bool;
    auto checkAnyInput(bool check_keyboard = true, const std::shared_ptr<Gamepad>& gamepad = nullptr) -> bool;
    auto checkAnyButton(bool repeat = DO_NOT_ALLOW_REPEAT) -> bool;
    void resetInputStates();

    // --- Gestión de gamepads ---
    [[nodiscard]] auto gameControllerFound() const -> bool;
    [[nodiscard]] auto getNumGamepads() const -> int;
    [[nodiscard]] auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Gamepad>;
    [[nodiscard]] auto getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad>;
    [[nodiscard]] auto getGamepads() const -> const Gamepads& { return gamepads_; }
    auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Gamepad>;
    static auto getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::string;
    [[nodiscard]] auto getControllerNames() const -> std::vector<std::string>;
    [[nodiscard]] static auto getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton;
    void printConnectedGamepads() const;

    // --- Eventos ---
    auto handleEvent(const SDL_Event& event) -> std::string;

   private:
    // --- Constantes ---
    static constexpr Sint16 AXIS_THRESHOLD = 20000;                         // Umbral on/off para ejes analógicos
    static constexpr Sint16 TRIGGER_THRESHOLD = 20000;                      // Umbral on/off para triggers
    static constexpr std::array<Action, 1> BUTTON_INPUTS = {Action::JUMP};  // Inputs que usan botones

    // --- Métodos ---
    explicit Input(std::string game_controller_db_path);
    ~Input() = default;

    void initSDLGamePad();
    static auto checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool;
    static auto checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool;
    auto addGamepad(int device_index) -> std::string;
    auto removeGamepad(SDL_JoystickID id) -> std::string;
    void addGamepadMappingsFromFile();
    void discoverGamepads();

    // --- Variables miembro ---
    static Input* instance;  // Instancia única del singleton

    Gamepads gamepads_;                  // Lista de gamepads conectados
    Keyboard keyboard_{};                // Estado del teclado
    std::string gamepad_mappings_file_;  // Ruta al archivo de mappings
};