//
// Created by sebastian on 02.08.26.
//

#include "Mouse.h"

#include "imgui_impl_glfw.h"
#include "core/Window.h"
#include "GLFW/glfw3.h"
#include "spdlog/spdlog.h"

engine::Mouse::Mouse(Window &window) {
    window.setMouse(this);
    addClickListener(window);
    addMoveListener(window);
    addScrollListener(window);
}

bool engine::Mouse::isButtonDown(const MouseButton button) const {
    return buttonsDown.contains(MouseButtonToInt(button));
}

bool engine::Mouse::isClickEvent(const MouseButton button) const {
    return buttonsClickedThisFrame.contains(MouseButtonToInt(button));
}

bool engine::Mouse::isReleaseEvent(const MouseButton button) const {
    return buttonsReleasedThisFrame.contains(MouseButtonToInt(button));
}

float engine::Mouse::getX(const bool ndc) const {
    return ndc ? 2 * x - 1 : x;
}

float engine::Mouse::getY(const bool ndc) const {
    return ndc ? 1 - 2 * y : y;
}

float engine::Mouse::getDX() const {
    return dx;
}

float engine::Mouse::getDY() const {
    return dy;
}

float engine::Mouse::getScroll() const {
    return scroll;
}

void engine::Mouse::update() {
    updateDetlas();
    scroll = 0;

    buttonsClickedThisFrame.clear();
    buttonsReleasedThisFrame.clear();
}

glm::vec2 engine::Mouse::getXY(bool ndc) const {
    return {getX(ndc), getY(ndc)};
}

void engine::Mouse::reportButtonClick(int button) {
    spdlog::debug("Button {} clicked", button);
    buttonsDown.insert(button);
    buttonsClickedThisFrame.insert(button);
}

void engine::Mouse::reportButtonRelease(int button) {
    buttonsDown.erase(button);
    buttonsReleasedThisFrame.insert(button);
}

void engine::Mouse::updateDetlas() {
    dx = x - lastX;
    dy = y - lastY;
    lastX = x;
    lastY = y;
}

void engine::Mouse::addMoveListener(Window &window) {
    glfwSetCursorPosCallback(window.getHandle(), [](GLFWwindow* handle, double xPos, double yPos) {
        ImGui_ImplGlfw_CursorPosCallback(handle, xPos, yPos);

        auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
        if (!target || !target->mouse) return;

        int width,  height;
        glfwGetFramebufferSize(handle, &width, &height);
        target->mouse->x = static_cast<float>(xPos / width);
        target->mouse->y = static_cast<float>(yPos / height);
    });
}

void engine::Mouse::addScrollListener(Window &window) {
    glfwSetScrollCallback(window.getHandle(), [](GLFWwindow* handle, double xoff, double yoff) {
            ImGui_ImplGlfw_ScrollCallback(handle, xoff, yoff);

            auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
            if (target && target->mouse) target->mouse->scroll = static_cast<float>(yoff);
        });
}

void engine::Mouse::addClickListener(Window &window) {
    glfwSetMouseButtonCallback(window.getHandle(), [](GLFWwindow* handle, int button, int action, int mods) {
        ImGui_ImplGlfw_MouseButtonCallback(handle, button, action, mods);

        auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
        if (!target || !target->mouse) return;

        if (action == GLFW_PRESS)        target->mouse->reportButtonClick(button);
        else if (action == GLFW_RELEASE) target->mouse->reportButtonRelease(button);
    });
}

int engine::Mouse::MouseButtonToInt(MouseButton button) {
    int glfwButton = 0;
    switch (button) {
        case MouseButton::LEFT: glfwButton = GLFW_MOUSE_BUTTON_LEFT; break;
        case MouseButton::RIGHT: glfwButton = GLFW_MOUSE_BUTTON_RIGHT; break;
        case MouseButton::MIDDLE: glfwButton = GLFW_MOUSE_BUTTON_MIDDLE; break;
    }
    return glfwButton;
}
