#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/gamepad_config_manager.hpp"  // for GamepadConfig (ptr only), GamepadConfigs
#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;     // Scancode asociado
        bool is_held;       // Está pulsada ahora mismo
        bool just_pressed;  // Se acaba de pulsar en este fotograma

        KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
            : scancode(scancode),
              is_held(is_held),
              just_pressed(just_pressed) {}
    };

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

        ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
            : button(btn),
              is_held(is_held),
              just_pressed(just_pressed),
              axis_active(axis_act) {}
    };

    struct Keyboard {
        std::unordered_map<Action, KeyState> bindings{
            // Movimiento del jugador
            {Action::UP, KeyState(SDL_SCANCODE_UP)},
            {Action::DOWN, KeyState(SDL_SCANCODE_DOWN)},
            {Action::LEFT, KeyState(SDL_SCANCODE_LEFT)},
            {Action::RIGHT, KeyState(SDL_SCANCODE_RIGHT)},

            // Disparo del jugador
            {Action::FIRE_LEFT, KeyState(SDL_SCANCODE_Q)},
            {Action::FIRE_CENTER, KeyState(SDL_SCANCODE_W)},
            {Action::FIRE_RIGHT, KeyState(SDL_SCANCODE_E)},

            // Interfaz
            {Action::START, KeyState(SDL_SCANCODE_RETURN)},

            // Menu de servicio
            {Action::SERVICE, KeyState(SDL_SCANCODE_F12)},
            {Action::SM_SELECT, KeyState(SDL_SCANCODE_RETURN)},
            {Action::SM_BACK, KeyState(SDL_SCANCODE_BACKSPACE)},

            // Control del programa
            {Action::EXIT, KeyState(SDL_SCANCODE_ESCAPE)},
            {Action::PAUSE, KeyState(SDL_SCANCODE_P)},
            {Action::BACK, KeyState(SDL_SCANCODE_BACKSPACE)},

            {Action::WINDOW_DEC_SIZE, KeyState(SDL_SCANCODE_F1)},
            {Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
            {Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
            {Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
            {Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
            {Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
            {Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
            {Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
            {Action::TOGGLE_VIDEO_VSYNC, KeyState(SDL_SCANCODE_F6)},

            {Action::TOGGLE_AUDIO, KeyState(SDL_SCANCODE_F7)},
            {Action::TOGGLE_AUTO_FIRE, KeyState(SDL_SCANCODE_F8)},
            {Action::CHANGE_LANG, KeyState(SDL_SCANCODE_F9)},

            {Action::RESET, KeyState(SDL_SCANCODE_F10)},
            {Action::SHOW_INFO, KeyState(SDL_SCANCODE_F11)}};

        Keyboard() = default;
    };

    struct Gamepad {
        SDL_Gamepad* pad;
        SDL_JoystickID instance_id;
        std::string name;
        std::string path;
        std::unordered_map<Action, ButtonState> bindings;

        // 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;
        }

        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::UP, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_UP))},
                  {Action::DOWN, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_DOWN))},
                  {Action::LEFT, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_LEFT))},
                  {Action::RIGHT, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_DPAD_RIGHT))},

                  // Disparo del jugador
                  {Action::FIRE_LEFT, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_WEST))},
                  {Action::FIRE_CENTER, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_NORTH))},
                  {Action::FIRE_RIGHT, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_EAST))},

                  // Interfaz
                  {Action::START, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_START))},
                  {Action::SERVICE, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_BACK))},

                  // Menu de servicio
                  {Action::SM_SELECT, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_WEST))},
                  {Action::SM_BACK, ButtonState(static_cast<int>(SDL_GAMEPAD_BUTTON_NORTH))}} {}

        ~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] = static_cast<int>(new_button);
        }
    };

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

    // --- Métodos de singleton ---
    static void init(const std::string& game_controller_db_path, const std::string& gamepad_configs_file);
    static void destroy();
    static auto get() -> Input*;

    // --- Métodos de configuración de controles ---
    void bindKey(Action action, SDL_Scancode code);
    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);

    // --- Métodos de consulta de entrada ---
    void update();
    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;

    // --- Métodos de gestión de mandos ---
    [[nodiscard]] auto gameControllerFound() const -> bool;
    static auto getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::string;
    [[nodiscard]] auto getControllerNames() const -> std::vector<std::string>;
    [[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_; }

    // --- Métodos de consulta y utilidades ---
    [[nodiscard]] static auto getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton;
    [[nodiscard]] static auto inputToString(Action action) -> std::string;
    [[nodiscard]] static auto stringToInput(const std::string& name) -> Action;

    // --- Métodos de reseteo de estado de entrada ---
    void resetInputStates();

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

    void printConnectedGamepads() const;

    auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Gamepad>;
    void saveGamepadConfigFromGamepad(std::shared_ptr<Gamepad> gamepad);

   private:
    // --- Constantes ---
    static constexpr Sint16 AXIS_THRESHOLD = 30000;
    static constexpr Sint16 TRIGGER_THRESHOLD = 16384;                                                                                   // Umbral para triggers (aproximadamente 50% del rango)
    static constexpr std::array<Action, 4> BUTTON_INPUTS = {Action::FIRE_LEFT, Action::FIRE_CENTER, Action::FIRE_RIGHT, Action::START};  // Listado de los inputs para jugar que utilizan botones, ni palancas ni crucetas

    // --- Variables internas ---
    Gamepads gamepads_;
    Keyboard keyboard_;
    std::string gamepad_mappings_file_;
    std::string gamepad_configs_file_;
    GamepadConfigs gamepad_configs_;

    // --- Métodos internos ---
    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();

    // --- Métodos para integración con GamepadConfigManager ---
    void loadGamepadConfigs();
    void saveGamepadConfigs();
    void applyGamepadConfig(std::shared_ptr<Gamepad> gamepad);

    // Métodos auxiliares opcionales
    void setGamepadConfigsFile(const std::string& filename);
    auto getGamepadConfig(const std::string& gamepad_name) -> GamepadConfig*;
    auto removeGamepadConfig(const std::string& gamepad_name) -> bool;

    // --- Constructor y destructor ---
    explicit Input(std::string game_controller_db_path, std::string gamepad_configs_file);
    ~Input() = default;

    // --- Singleton ---
    static Input* instance;
};