#pragma once
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <unordered_set>
#include <array>
#include <glm/glm.hpp>

// Modern input system
class Input {
public:
    enum class KeyState : int {
        Released = GLFW_RELEASE,
        Pressed  = GLFW_PRESS,
        Held     = GLFW_REPEAT
    };

    enum class MouseButton : int {
        Left   = GLFW_MOUSE_BUTTON_LEFT,
        Right  = GLFW_MOUSE_BUTTON_RIGHT,
        Middle = GLFW_MOUSE_BUTTON_MIDDLE
    };

    // PS4/PS5 button mapping (matches GLFW gamepad buttons)
    enum class GamepadButton : int {
        Cross    = GLFW_GAMEPAD_BUTTON_A,        // X / Cross
        Circle   = GLFW_GAMEPAD_BUTTON_B,        // O / Circle
        Square   = GLFW_GAMEPAD_BUTTON_X,        // Square
        Triangle = GLFW_GAMEPAD_BUTTON_Y,        // Triangle
        L1       = GLFW_GAMEPAD_BUTTON_LEFT_BUMPER,
        R1       = GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER,
        Share    = GLFW_GAMEPAD_BUTTON_BACK,     // Share / Create
        Options  = GLFW_GAMEPAD_BUTTON_START,    // Options
        L3       = GLFW_GAMEPAD_BUTTON_LEFT_THUMB,
        R3       = GLFW_GAMEPAD_BUTTON_RIGHT_THUMB,
        DPadUp   = GLFW_GAMEPAD_BUTTON_DPAD_UP,
        DPadRight= GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,
        DPadDown = GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
        DPadLeft = GLFW_GAMEPAD_BUTTON_DPAD_LEFT,
        PS       = GLFW_GAMEPAD_BUTTON_GUIDE     // PS button
    };

    enum class GamepadAxis : int {
        LeftX    = GLFW_GAMEPAD_AXIS_LEFT_X,
        LeftY    = GLFW_GAMEPAD_AXIS_LEFT_Y,
        RightX   = GLFW_GAMEPAD_AXIS_RIGHT_X,
        RightY   = GLFW_GAMEPAD_AXIS_RIGHT_Y,
        L2       = GLFW_GAMEPAD_AXIS_LEFT_TRIGGER,
        R2       = GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
    };

    static void Initialize(GLFWwindow* window);
    static void Update();

    [[nodiscard]] static bool IsKeyPressed(int key);
    [[nodiscard]] static bool IsKeyHeld(int key);
    [[nodiscard]] static bool IsKeyReleased(int key);

    [[nodiscard]] static bool IsMouseButtonPressed(MouseButton button);
    [[nodiscard]] static glm::vec2 GetMousePosition();
    [[nodiscard]] static glm::vec2 GetMouseDelta();
    [[nodiscard]] static float     GetScrollOffset();

    // Gamepad
    [[nodiscard]] static bool  IsGamepadConnected();
    [[nodiscard]] static bool  IsGamepadButtonPressed(GamepadButton button);
    [[nodiscard]] static bool  IsGamepadButtonDown(GamepadButton button);
    [[nodiscard]] static float GetGamepadAxis(GamepadAxis axis, float deadzone = 0.15f);
    [[nodiscard]] static glm::vec2 GetLeftStick(float deadzone = 0.15f);
    [[nodiscard]] static glm::vec2 GetRightStick(float deadzone = 0.15f);
    [[nodiscard]] static float GetL2();
    [[nodiscard]] static float GetR2();

    [[nodiscard]] static constexpr bool IsValidKey(int key) noexcept {
        return key >= 0 && key < GLFW_KEY_LAST;
    }

private:
    static GLFWwindow*                 window_;
    static std::unordered_set<int>     pressed_keys_;
    static std::unordered_set<int>     held_keys_;
    static std::unordered_set<int>     released_keys_;
    static std::array<bool, GLFW_MOUSE_BUTTON_LAST> mouse_buttons_;
    static glm::vec2                   mouse_position_;
    static glm::vec2                   mouse_delta_;
    static float                       scroll_offset_;

    // Gamepad state
    static int                         gamepad_id_;
    static GLFWgamepadstate            gamepad_state_;
    static GLFWgamepadstate            gamepad_prev_state_;
    static bool                        gamepad_connected_;

    static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
    static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
    static void CursorPositionCallback(GLFWwindow* window, double xpos, double ypos);
    static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
};

// Out-of-class definitions
inline bool Input::IsKeyPressed(int key) {
    return IsValidKey(key) && pressed_keys_.contains(key);
}
inline bool Input::IsKeyHeld(int key) {
    return IsValidKey(key) && held_keys_.contains(key);
}
inline bool Input::IsKeyReleased(int key) {
    return IsValidKey(key) && released_keys_.contains(key);
}
