#include "adapters/windows/rendering/WindowHook.h"

#include "application/lifecycle/RuntimeState.h"
#include "application/lifecycle/CallbackBarrier.h"
#include "adapters/windows/rendering/ImGuiHost.h"

#include <imgui_impl_win32.h>

namespace
{
    HWND g_window = nullptr;
    WNDPROC g_original_proc = nullptr;

    LRESULT call_original(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
    {
        if (g_original_proc)
            return CallWindowProc(g_original_proc, window, message, wparam, lparam);

        return DefWindowProc(window, message, wparam, lparam);
    }

    bool is_mouse_input_message(const UINT message) noexcept
    {
        switch (message)
        {
        case WM_MOUSEMOVE:
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_RBUTTONDBLCLK:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
        case WM_MBUTTONDBLCLK:
        case WM_XBUTTONDOWN:
        case WM_XBUTTONUP:
        case WM_XBUTTONDBLCLK:
        case WM_MOUSEWHEEL:
        case WM_MOUSEHWHEEL:
            return true;
        default:
            return false;
        }
    }

    bool is_keyboard_input_message(const UINT message) noexcept
    {
        switch (message)
        {
        case WM_KEYDOWN:
        case WM_KEYUP:
        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_CHAR:
        case WM_DEADCHAR:
        case WM_SYSCHAR:
        case WM_SYSDEADCHAR:
        case WM_UNICHAR:
            return true;
        default:
            return false;
        }
    }

    bool should_block_game_input(const UINT message) noexcept
    {
        return message == WM_INPUT ||
            is_mouse_input_message(message) ||
            is_keyboard_input_message(message);
    }
}

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

namespace ta::adapters::windows::rendering::window_hook
{
    LRESULT WINAPI hooked_wnd_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
    {
        ta::application::runtime::CallbackGuard callback_guard;
        if (!callback_guard || !ta::application::runtime::should_accept_callbacks())
            return call_original(window, message, wparam, lparam);

        const bool menu_active = ta::adapters::windows::rendering::imgui_host::menu_active();
        if (!menu_active)
            return call_original(window, message, wparam, lparam);

        // Always feed the event to ImGui first. The Win32 backend return value is
        // not a complete input-capture decision and must not be used on its own
        // to decide whether the game receives the event.
        ImGui_ImplWin32_WndProcHandler(window, message, wparam, lparam);

        if (should_block_game_input(message))
        {
            // Foreground WM_INPUT requires DefWindowProc for system cleanup, but
            // it must bypass the original game WndProc while the menu is open.
            if (message == WM_INPUT)
                return DefWindowProcW(window, message, wparam, lparam);

            return 0;
        }

        return call_original(window, message, wparam, lparam);
    }

    bool install(HWND window) noexcept
    {
        if (g_original_proc)
            return true;

        if (!window || !IsWindow(window))
            return false;

        SetLastError(ERROR_SUCCESS);
        const auto previous = SetWindowLongPtrW(window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(hooked_wnd_proc));
        const DWORD error = GetLastError();
        if (!previous && error != ERROR_SUCCESS)
            return false;

        g_window = window;
        g_original_proc = reinterpret_cast<WNDPROC>(previous);
        return true;
    }

    void uninstall() noexcept
    {
        if (g_window && g_original_proc && IsWindow(g_window))
            SetWindowLongPtrW(g_window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(g_original_proc));

        g_window = nullptr;
        g_original_proc = nullptr;
    }

}
