#include "GlfwWindow.hpp"

#include "GlfwSingleton.hpp"
#include "MayaFlux/Journal/Archivist.hpp"

#ifdef GLFW_BACKEND

#ifdef MAYAFLUX_PLATFORM_LINUX
#include <GLFW/glfw3native.h>
#elif MAYAFLUX_PLATFORM_MACOS
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#endif

namespace MayaFlux::Core {

GlfwWindow::GlfwWindow(const WindowCreateInfo& create_info,
    const GraphicsSurfaceInfo& surface_info, GlobalGraphicsConfig::GraphicsApi api, GlfwPreInitConfig pre_init_config)
    : m_create_info(create_info)
{
    GLFWSingleton::configure(pre_init_config);

    if (!GLFWSingleton::initialize()) {
        error<std::runtime_error>(Journal::Component::Core, Journal::Context::WindowingSubsystem, std::source_location::current(),
            "Failed to initialize GLFW for window creation");
    }

    configure_window_hints(surface_info, api);

    GLFWmonitor* monitor = nullptr;
    if (create_info.fullscreen) {
        if (create_info.monitor_id >= 0) {
            int count {};
            std::span<GLFWmonitor*> monitors(glfwGetMonitors(&count), count);
            if (create_info.monitor_id < count) {
                monitor = monitors[create_info.monitor_id];
            }
        } else {
            monitor = glfwGetPrimaryMonitor();
        }
    }

    m_window = glfwCreateWindow(
        (int32_t)create_info.width,
        (int32_t)create_info.height,
        create_info.title.c_str(),
        monitor,
        nullptr);

    if (!m_window) {
        error<std::runtime_error>(Journal::Component::Core, Journal::Context::WindowingSubsystem, std::source_location::current(),
            "Failed to create GLFW window: {}", create_info.title);
    }

    glfwSetWindowUserPointer(m_window, this);
    setup_callbacks();

    int w {}, h {};
    glfwGetWindowSize(m_window, &w, &h);
    m_state.current_width = w;
    m_state.current_height = h;
    m_state.is_visible = false;

    GLFWSingleton::mark_window_created();

    MF_INFO(Journal::Component::Core, Journal::Context::WindowingSubsystem,
        "Created window '{}' ({}x{})", create_info.title, w, h);
}

GlfwWindow::~GlfwWindow()
{
    destroy();
}

void GlfwWindow::destroy()
{
    if (m_window) {
        glfwDestroyWindow(m_window);
        GLFWSingleton::mark_window_destroyed();
        m_window = nullptr;
    }
}

GlfwWindow::GlfwWindow(GlfwWindow&& other) noexcept
    : m_window(other.m_window)
    , m_create_info(std::move(other.m_create_info))
    , m_state(other.m_state)
    , m_input_config(other.m_input_config)
    , m_event_callback(std::move(other.m_event_callback))
{
    other.m_window = nullptr;
    if (m_window) {
        glfwSetWindowUserPointer(m_window, this);
    }
}

GlfwWindow& GlfwWindow::operator=(GlfwWindow&& other) noexcept
{
    if (this != &other) {
        if (m_window) {
            glfwDestroyWindow(m_window);
            GLFWSingleton::mark_window_destroyed();
        }

        m_window = other.m_window;
        m_create_info = std::move(other.m_create_info);
        m_state = other.m_state;
        m_input_config = other.m_input_config;
        m_event_callback = std::move(other.m_event_callback);

        other.m_window = nullptr;
        if (m_window) {
            glfwSetWindowUserPointer(m_window, this);
        }
    }
    return *this;
}

void GlfwWindow::set_title(const std::string& title)
{
    if (m_window) {
        glfwSetWindowTitle(m_window, title.c_str());
        m_create_info.title = title;
    }
}

void GlfwWindow::configure_window_hints(const GraphicsSurfaceInfo& /*surface_info*/, GlobalGraphicsConfig::GraphicsApi api) const
{
    glfwDefaultWindowHints();

#ifdef MAYAFLUX_PLATFORM_MACOS
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
#else
    glfwWindowHint(GLFW_RESIZABLE, m_create_info.resizable ? GLFW_TRUE : GLFW_FALSE);
#endif // MAYAFLUX_PLATFORM_MACOS

    glfwWindowHint(GLFW_DECORATED, m_create_info.decorated ? GLFW_TRUE : GLFW_FALSE);
    glfwWindowHint(GLFW_FLOATING, m_create_info.floating ? GLFW_TRUE : GLFW_FALSE);
    glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, m_create_info.transparent ? GLFW_TRUE : GLFW_FALSE);
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

    if (api == GlobalGraphicsConfig::GraphicsApi::VULKAN) {
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    } else if (api == GlobalGraphicsConfig::GraphicsApi::OPENGL) {
        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
    }
}

void GlfwWindow::setup_callbacks()
{
    glfwSetWindowSizeCallback(m_window, glfw_window_size_callback);
    glfwSetWindowCloseCallback(m_window, glfw_window_close_callback);
    glfwSetWindowFocusCallback(m_window, glfw_window_focus_callback);
    glfwSetFramebufferSizeCallback(m_window, glfw_framebuffer_size_callback);
    glfwSetKeyCallback(m_window, glfw_key_callback);
    glfwSetCursorPosCallback(m_window, glfw_cursor_pos_callback);
    glfwSetMouseButtonCallback(m_window, glfw_mouse_button_callback);
    glfwSetScrollCallback(m_window, glfw_scroll_callback);
}

void GlfwWindow::show()
{
    if (m_window) {
        glfwShowWindow(m_window);
        m_state.is_visible = true;
    }
}

void GlfwWindow::hide()
{
    if (m_window) {
        glfwHideWindow(m_window);
        m_state.is_visible = false;
    }
}

bool GlfwWindow::should_close() const
{
    return m_window ? glfwWindowShouldClose(m_window) : true;
}

void GlfwWindow::set_event_callback(WindowEventCallback callback)
{
    m_event_callback = std::move(callback);
}

void* GlfwWindow::get_native_handle() const
{
    if (!m_window)
        return nullptr;

#ifdef MAYAFLUX_PLATFORM_LINUX
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
    if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
        return glfwGetWaylandWindow(m_window);
    }
#endif
#if defined(GLFW_EXPOSE_NATIVE_X11)
    if (glfwGetPlatform() == GLFW_PLATFORM_X11) {
        return reinterpret_cast<void*>(glfwGetX11Window(m_window));
    }
#endif
    return nullptr;
#elif MAYAFLUX_PLATFORM_MACOS
    return glfwGetCocoaWindow(m_window);
#else
    return m_window;
#endif
}

void* GlfwWindow::get_native_display() const
{
#ifdef MAYAFLUX_PLATFORM_LINUX
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
    if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
        return glfwGetWaylandDisplay();
    }
#endif
#if defined(GLFW_EXPOSE_NATIVE_X11)
    if (glfwGetPlatform() == GLFW_PLATFORM_X11) {
        return glfwGetX11Display();
    }
#endif
#endif
    return nullptr;
}

void GlfwWindow::glfw_window_size_callback(GLFWwindow* window, int width, int height)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (win && win->m_event_callback) {
        win->m_state.current_width = width;
        win->m_state.current_height = height;

        if (win->m_event_callback) {
            WindowEvent event;
            event.type = WindowEventType::WINDOW_RESIZED;
            event.timestamp = glfwGetTime();
            event.data = WindowEvent::ResizeData {
                .width = static_cast<uint32_t>(width),
                .height = static_cast<uint32_t>(height)
            };
            win->m_event_callback(event);
        }
    }
}

void GlfwWindow::set_size(uint32_t width, uint32_t height)
{
    if (m_window) {
        glfwSetWindowSize(m_window, static_cast<int>(width), static_cast<int>(height));
    }

    m_create_info.width = width;
    m_create_info.height = height;
}

void GlfwWindow::set_position(uint32_t x, uint32_t y)
{
    if (m_window) {
        glfwSetWindowPos(m_window, static_cast<int>(x), static_cast<int>(y));
    }
}

void GlfwWindow::set_color(const std::array<float, 4>& color)
{
    m_create_info.clear_color = color;
}

void GlfwWindow::glfw_window_close_callback(GLFWwindow* window)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEvent event;
    event.type = WindowEventType::WINDOW_CLOSED;
    event.timestamp = glfwGetTime();

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_window_focus_callback(GLFWwindow* window, int focused)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    win->m_state.is_focused = (focused == GLFW_TRUE);

    WindowEvent event;
    event.type = focused ? WindowEventType::WINDOW_FOCUS_GAINED
                         : WindowEventType::WINDOW_FOCUS_LOST;
    event.timestamp = glfwGetTime();

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEvent event;
    event.type = WindowEventType::FRAMEBUFFER_RESIZED;
    event.timestamp = glfwGetTime();
    event.data = WindowEvent::ResizeData {
        .width = static_cast<uint32_t>(width),
        .height = static_cast<uint32_t>(height)
    };

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEventType type {};
    switch (action) {
    case GLFW_PRESS:
        type = WindowEventType::KEY_PRESSED;
        break;
    case GLFW_RELEASE:
        type = WindowEventType::KEY_RELEASED;
        break;
    case GLFW_REPEAT:
        type = WindowEventType::KEY_REPEAT;
        break;
    default:
        return;
    }

    WindowEvent event;
    event.type = type;
    event.timestamp = glfwGetTime();
    event.data = WindowEvent::KeyData {
        .key = static_cast<int16_t>(key),
        .scancode = scancode,
        .mods = mods
    };

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_cursor_pos_callback(GLFWwindow* window, double xpos, double ypos)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEvent event;
    event.type = WindowEventType::MOUSE_MOTION;
    event.timestamp = glfwGetTime();

    event.data = WindowEvent::MousePosData {
        .x = xpos,
        .y = ypos
    };

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEvent event;
    event.type = (action == GLFW_PRESS) ? WindowEventType::MOUSE_BUTTON_PRESSED
                                        : WindowEventType::MOUSE_BUTTON_RELEASED;
    event.timestamp = glfwGetTime();

    event.data = WindowEvent::MouseButtonData {
        .button = static_cast<int8_t>(button),
        .mods = mods
    };

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    auto* win = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
    if (!win)
        return;

    WindowEvent event;
    event.type = WindowEventType::MOUSE_SCROLLED;
    event.timestamp = glfwGetTime();
    event.data = WindowEvent::ScrollData {
        .x_offset = xoffset,
        .y_offset = yoffset
    };

    win->m_event_source.signal(event);

    if (win->m_event_callback) {
        win->m_event_callback(event);
    }
}

void GlfwWindow::set_graphics_registered(bool registered)
{
    m_graphics_registered.store(registered, std::memory_order_release);
}

void GlfwWindow::register_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
{
    for (const auto& weak_buf : m_rendering_buffers) {
        if (auto buf = weak_buf.lock()) {
            if (buf == buffer) {
                return;
            }
        }
    }

    std::lock_guard lock(m_render_tracking_mutex);
    m_rendering_buffers.push_back(buffer);

    MF_RT_DEBUG(Journal::Component::Core, Journal::Context::GraphicsCallback,
        "Window '{}': registered VKBuffer for rendering (total: {})",
        m_create_info.title, m_rendering_buffers.size());
}

void GlfwWindow::unregister_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buffer)
{
    auto it = std::ranges::find_if(m_rendering_buffers,
        [&buffer](const std::weak_ptr<Buffers::VKBuffer>& weak_buf) {
            auto buf = weak_buf.lock();
            return buf == buffer;
        });

    if (it != m_rendering_buffers.end()) {
        std::lock_guard lock(m_render_tracking_mutex);
        m_rendering_buffers.erase(it);
    }
}

void GlfwWindow::track_frame_command(uint64_t cmd_id)
{
    std::lock_guard lock(m_render_tracking_mutex);
    m_frame_commands.push_back(cmd_id);

    MF_RT_TRACE(Journal::Component::Core, Journal::Context::GraphicsCallback,
        "Window '{}': tracked command buffer {} (total this frame: {})",
        m_create_info.title, cmd_id, m_frame_commands.size());
}

const std::vector<uint64_t>& GlfwWindow::get_frame_commands() const
{
    return m_frame_commands;
}

void GlfwWindow::clear_frame_commands()
{
    std::lock_guard lock(m_render_tracking_mutex);
    m_frame_commands.clear();
}

std::vector<std::shared_ptr<Buffers::VKBuffer>> GlfwWindow::get_rendering_buffers() const
{
    std::lock_guard lock(m_render_tracking_mutex);

    std::vector<std::shared_ptr<Buffers::VKBuffer>> buffers;
    for (const auto& weak_buf : m_rendering_buffers) {
        if (auto buf = weak_buf.lock()) {
            buffers.push_back(buf);
        }
    }
    return buffers;
}

}

#endif // GLFW_BACKEND
