// dear imgui: Platform Backend for SDL3 (*EXPERIMENTAL*)
// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)

// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN**)

// Implemented features:
//  [X] Platform: Clipboard support.
//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.

// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ                  https://dearimgui.com/faq
// - Getting Started      https://dearimgui.com/getting-started
// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp

// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
//  2024-07-22: Update for SDL3 api changes: SDL_GetGamepads() memory ownership change. (#7807)
//  2024-07-18: Update for SDL3 api changes: SDL_GetClipboardText() memory ownership change. (#7801)
//  2024-07-15: Update for SDL3 api changes: SDL_GetProperty() change to SDL_GetPointerProperty(). (#7794)
//  2024-07-02: Update for SDL3 api changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762).
//  2024-07-01: Update for SDL3 api changes: SDL_SetTextInputRect() changed to SDL_SetTextInputArea().
//  2024-06-26: Update for SDL3 api changes: SDL_StartTextInput()/SDL_StopTextInput()/SDL_SetTextInputRect() functions signatures.
//  2024-06-24: Update for SDL3 api changes: SDL_EVENT_KEY_DOWN/SDL_EVENT_KEY_UP contents.
//  2024-06-03; Update for SDL3 api changes: SDL_SYSTEM_CURSOR_ renames.
//  2024-05-15: Update for SDL3 api changes: SDLK_ renames.
//  2024-04-15: Inputs: Re-enable calling SDL_StartTextInput()/SDL_StopTextInput() as SDL3 no longer enables it by default and should play nicer with IME.
//  2024-02-13: Inputs: Fixed gamepad support. Handle gamepad disconnection. Added ImGui_ImplSDL3_SetGamepadMode().
//  2023-11-13: Updated for recent SDL3 API changes.
//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
//  2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
//  2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
//  2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
//  2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.

// SDL
// Let Jai's SDL3 module handle this
//#include <SDL3/SDL.h>

#if OS == .MACOS {
    //#include <TargetConditionals.h>
    //#assert false "Please include the platform header TargetConditionals.h somehow.";
}
#if OS == .WINDOWS {
// Let Jai's Windows module handle this

//    #if !exists(WIN32_LEAN_AND_MEAN) {
//        //WIN32_LEAN_AND_MEAN :: 1;
//    }
//    //#include <windows.h>
//    // you should have included this in your main file
}

#if !#exists(__EMSCRIPTEN__) && !#exists(__ANDROID__) && !(#exists(__APPLE__) && #exists(TARGET_OS_IOS)) &&
    !#exists(__amigaos4__) {
    SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE :: true;
} else {
    SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE :: false;
}

ImGui_ImplSDL3_GamepadMode :: enum {
    AutoFirst;
    AutoAll;
    Manual;
}

// SDL Data
ImGui_ImplSDL3_Data :: struct {
    Window : *SDL_Window;
    Renderer : *SDL_Renderer;
    Time : Uint64;
    ClipboardTextData : *u8;

    // IME handling
    ImeWindow : *SDL_Window;

    // Mouse handling
    MouseWindowID : Uint32;
    MouseButtonsDown : s32;
    MouseCursors : [ImGui.MouseCursor.ImGuiMouseCursor_COUNT]*SDL_Cursor;
    MouseLastCursor : *SDL_Cursor;
    MousePendingLeaveFrame : s32;
    MouseCanUseGlobalState : bool;

    // Gamepad handling
    Gamepads : [..]*SDL_Gamepad;
    GamepadMode : ImGui_ImplSDL3_GamepadMode;
    WantUpdateGamepadsList : bool;
}

// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
ImGui_ImplSDL3_GetBackendData :: () -> *ImGui_ImplSDL3_Data #c_call {
    return ifx ImGui.GetCurrentContext() then cast(*ImGui_ImplSDL3_Data) ImGui.GetIO().BackendPlatformUserData else null;
}

// Functions
ImGui_ImplSDL3_GetClipboardText :: (user_data: *void) -> *u8 #c_call {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    if bd.ClipboardTextData
        SDL_free(bd.ClipboardTextData);
    sdl_clipboard_text : *u8 = SDL_GetClipboardText();
    bd.ClipboardTextData = ifx sdl_clipboard_text then SDL_strdup(sdl_clipboard_text) else null;
    return bd.ClipboardTextData;
}

ImGui_ImplSDL3_SetClipboardText :: (user_data: *void, text: *u8) #c_call {
    SDL_SetClipboardText(text);
}

ImGui_ImplSDL3_PlatformSetImeData :: (ctx: *ImGui.ImGuiContext, viewport: *ImGui.Viewport, data: *ImGui.PlatformImeData) #c_call {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    window : *SDL_Window = cast(*SDL_Window) viewport.PlatformHandle;
    if ((data.WantVisible == false || bd.ImeWindow != window) && bd.ImeWindow != null) {
        SDL_StopTextInput(bd.ImeWindow);
        bd.ImeWindow = null;
    }

    if (data.WantVisible) {
        r : SDL_Rect;
        r.x = cast(s32) data.InputPos.x;
        r.y = cast(s32) data.InputPos.y;
        r.w = 1;
        r.h = cast(s32) data.InputLineHeight;
        SDL_SetTextInputArea(window, *r, 0);
        SDL_StartTextInput(window);
        bd.ImeWindow = window;
    }
}

ImGui_ImplSDL3_KeyEventToImGuiKey :: (keycode: SDL_Keycode, scancode: SDL_Scancode) -> ImGui.Key {
    // Keypad doesn't have individual key values in SDL3
    if scancode == {
        case SDL_Scancode.SDL_SCANCODE_KP_0; return ImGui.Key.Keypad0;
        case SDL_Scancode.SDL_SCANCODE_KP_1; return ImGui.Key.Keypad1;
        case SDL_Scancode.SDL_SCANCODE_KP_2; return ImGui.Key.Keypad2;
        case SDL_Scancode.SDL_SCANCODE_KP_3; return ImGui.Key.Keypad3;
        case SDL_Scancode.SDL_SCANCODE_KP_4; return ImGui.Key.Keypad4;
        case SDL_Scancode.SDL_SCANCODE_KP_5; return ImGui.Key.Keypad5;
        case SDL_Scancode.SDL_SCANCODE_KP_6; return ImGui.Key.Keypad6;
        case SDL_Scancode.SDL_SCANCODE_KP_7; return ImGui.Key.Keypad7;
        case SDL_Scancode.SDL_SCANCODE_KP_8; return ImGui.Key.Keypad8;
        case SDL_Scancode.SDL_SCANCODE_KP_9; return ImGui.Key.Keypad9;
        case SDL_Scancode.SDL_SCANCODE_KP_PERIOD; return ImGui.Key.KeypadDecimal;
        case SDL_Scancode.SDL_SCANCODE_KP_DIVIDE; return ImGui.Key.KeypadDivide;
        case SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY; return ImGui.Key.KeypadMultiply;
        case SDL_Scancode.SDL_SCANCODE_KP_MINUS; return ImGui.Key.KeypadSubtract;
        case SDL_Scancode.SDL_SCANCODE_KP_PLUS; return ImGui.Key.KeypadAdd;
        case SDL_Scancode.SDL_SCANCODE_KP_ENTER; return ImGui.Key.KeypadEnter;
        case SDL_Scancode.SDL_SCANCODE_KP_EQUALS; return ImGui.Key.KeypadEqual;
    }

    if keycode == {
        case SDLK_TAB; return ImGui.Key.Tab;
        case SDLK_LEFT; return ImGui.Key.LeftArrow;
        case SDLK_RIGHT; return ImGui.Key.RightArrow;
        case SDLK_UP; return ImGui.Key.UpArrow;
        case SDLK_DOWN; return ImGui.Key.DownArrow;
        case SDLK_PAGEUP; return ImGui.Key.PageUp;
        case SDLK_PAGEDOWN; return ImGui.Key.PageDown;
        case SDLK_HOME; return ImGui.Key.Home;
        case SDLK_END; return ImGui.Key.End;
        case SDLK_INSERT; return ImGui.Key.Insert;
        case SDLK_DELETE; return ImGui.Key.Delete;
        case SDLK_BACKSPACE; return ImGui.Key.Backspace;
        case SDLK_SPACE; return ImGui.Key.Space;
        case SDLK_RETURN; return ImGui.Key.Enter;
        case SDLK_ESCAPE; return ImGui.Key.Escape;
        case SDLK_APOSTROPHE; return ImGui.Key.Apostrophe;
        case SDLK_COMMA; return ImGui.Key.Comma;
        case SDLK_MINUS; return ImGui.Key.Minus;
        case SDLK_PERIOD; return ImGui.Key.Period;
        case SDLK_SLASH; return ImGui.Key.Slash;
        case SDLK_SEMICOLON; return ImGui.Key.Semicolon;
        case SDLK_EQUALS; return ImGui.Key.Equal;
        case SDLK_LEFTBRACKET; return ImGui.Key.LeftBracket;
        case SDLK_BACKSLASH; return ImGui.Key.Backslash;
        case SDLK_RIGHTBRACKET; return ImGui.Key.RightBracket;
        case SDLK_GRAVE; return ImGui.Key.GraveAccent;
        case SDLK_CAPSLOCK; return ImGui.Key.CapsLock;
        case SDLK_SCROLLLOCK; return ImGui.Key.ScrollLock;
        case SDLK_NUMLOCKCLEAR; return ImGui.Key.NumLock;
        case SDLK_PRINTSCREEN; return ImGui.Key.PrintScreen;
        case SDLK_PAUSE; return ImGui.Key.Pause;
        case SDLK_LCTRL; return ImGui.Key.LeftCtrl;
        case SDLK_LSHIFT; return ImGui.Key.LeftShift;
        case SDLK_LALT; return ImGui.Key.LeftAlt;
        case SDLK_LGUI; return ImGui.Key.LeftSuper;
        case SDLK_RCTRL; return ImGui.Key.RightCtrl;
        case SDLK_RSHIFT; return ImGui.Key.RightShift;
        case SDLK_RALT; return ImGui.Key.RightAlt;
        case SDLK_RGUI; return ImGui.Key.RightSuper;
        case SDLK_APPLICATION; return ImGui.Key.Menu;
        case SDLK_0; return ImGui.Key._0;
        case SDLK_1; return ImGui.Key._1;
        case SDLK_2; return ImGui.Key._2;
        case SDLK_3; return ImGui.Key._3;
        case SDLK_4; return ImGui.Key._4;
        case SDLK_5; return ImGui.Key._5;
        case SDLK_6; return ImGui.Key._6;
        case SDLK_7; return ImGui.Key._7;
        case SDLK_8; return ImGui.Key._8;
        case SDLK_9; return ImGui.Key._9;
        case SDLK_A; return ImGui.Key.A;
        case SDLK_B; return ImGui.Key.B;
        case SDLK_C; return ImGui.Key.C;
        case SDLK_D; return ImGui.Key.D;
        case SDLK_E; return ImGui.Key.E;
        case SDLK_F; return ImGui.Key.F;
        case SDLK_G; return ImGui.Key.G;
        case SDLK_H; return ImGui.Key.H;
        case SDLK_I; return ImGui.Key.I;
        case SDLK_J; return ImGui.Key.J;
        case SDLK_K; return ImGui.Key.K;
        case SDLK_L; return ImGui.Key.L;
        case SDLK_M; return ImGui.Key.M;
        case SDLK_N; return ImGui.Key.N;
        case SDLK_O; return ImGui.Key.O;
        case SDLK_P; return ImGui.Key.P;
        case SDLK_Q; return ImGui.Key.Q;
        case SDLK_R; return ImGui.Key.R;
        case SDLK_S; return ImGui.Key.S;
        case SDLK_T; return ImGui.Key.T;
        case SDLK_U; return ImGui.Key.U;
        case SDLK_V; return ImGui.Key.V;
        case SDLK_W; return ImGui.Key.W;
        case SDLK_X; return ImGui.Key.X;
        case SDLK_Y; return ImGui.Key.Y;
        case SDLK_Z; return ImGui.Key.Z;
        case SDLK_F1; return ImGui.Key.F1;
        case SDLK_F2; return ImGui.Key.F2;
        case SDLK_F3; return ImGui.Key.F3;
        case SDLK_F4; return ImGui.Key.F4;
        case SDLK_F5; return ImGui.Key.F5;
        case SDLK_F6; return ImGui.Key.F6;
        case SDLK_F7; return ImGui.Key.F7;
        case SDLK_F8; return ImGui.Key.F8;
        case SDLK_F9; return ImGui.Key.F9;
        case SDLK_F10; return ImGui.Key.F10;
        case SDLK_F11; return ImGui.Key.F11;
        case SDLK_F12; return ImGui.Key.F12;
        case SDLK_F13; return ImGui.Key.F13;
        case SDLK_F14; return ImGui.Key.F14;
        case SDLK_F15; return ImGui.Key.F15;
        case SDLK_F16; return ImGui.Key.F16;
        case SDLK_F17; return ImGui.Key.F17;
        case SDLK_F18; return ImGui.Key.F18;
        case SDLK_F19; return ImGui.Key.F19;
        case SDLK_F20; return ImGui.Key.F20;
        case SDLK_F21; return ImGui.Key.F21;
        case SDLK_F22; return ImGui.Key.F22;
        case SDLK_F23; return ImGui.Key.F23;
        case SDLK_F24; return ImGui.Key.F24;
        case SDLK_AC_BACK; return ImGui.Key.AppBack;
        case SDLK_AC_FORWARD; return ImGui.Key.AppForward;
    }
    return ImGui.Key.None;
}

ImGui_ImplSDL3_UpdateKeyModifiers :: (sdl_key_mods: SDL_Keymod) {
    io : *ImGui.IO = ImGui.GetIO();
    io.AddKeyEvent(io, ImGui.Key.Mod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
    io.AddKeyEvent(io, ImGui.Key.Mod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
    io.AddKeyEvent(io, ImGui.Key.Mod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
    io.AddKeyEvent(io, ImGui.Key.Mod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
}

// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
ImGui_ImplSDL3_ProcessEvent :: (event: *SDL_Event) -> bool {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    assert(bd != null && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
    io : *ImGui.IO = ImGui.GetIO();

    if event.type == {
        case xx SDL_EVENT_MOUSE_MOTION;
            mouse_pos : ImGui.ImVec2 = .{cast(float) event.motion.x, cast(float) event.motion.y};
            io.AddMouseSourceEvent(io, ifx event.motion.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
            io.AddMousePosEvent(io, mouse_pos.x, mouse_pos.y);
            return true;

        case xx SDL_EVENT_MOUSE_WHEEL;
            //IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event.wheel.x, (float)event.wheel.y, event.wheel.preciseX, event.wheel.preciseY);
            wheel_x : float = -event.wheel.x;
            wheel_y : float = event.wheel.y;
#if #exists (__EMSCRIPTEN__) {
            wheel_x /= 100.0;
}
            io.AddMouseSourceEvent(io, ifx event.wheel.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
            io.AddMouseWheelEvent(io, wheel_x, wheel_y);
            return true;

        case xx SDL_EVENT_MOUSE_BUTTON_DOWN;
            #through;

        case xx SDL_EVENT_MOUSE_BUTTON_UP;
            mouse_button : s32 = -1;
            if (event.button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
            if (event.button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
            if (event.button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
            if (event.button.button == SDL_BUTTON_X1) { mouse_button = 3; }
            if (event.button.button == SDL_BUTTON_X2) { mouse_button = 4; }
            io.AddMouseSourceEvent(io, ifx event.button.which == SDL_TOUCH_MOUSEID then ImGui.MouseSource.TouchScreen else ImGui.MouseSource.Mouse);
            io.AddMouseButtonEvent(io, mouse_button, (event.type == xx SDL_EVENT_MOUSE_BUTTON_DOWN));
            bd.MouseButtonsDown = xx ifx (event.type == xx SDL_EVENT_MOUSE_BUTTON_DOWN) then (bd.MouseButtonsDown | (1 << mouse_button)) else (bd.MouseButtonsDown & ~(1 << mouse_button));
            return true;

        case xx SDL_EVENT_TEXT_INPUT;
            io.AddInputCharactersUTF8(io, event.text.text);
            return true;

        case xx SDL_EVENT_KEY_DOWN;
            #through;

        case xx SDL_EVENT_KEY_UP;
            //IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%d: key=%d, scancode=%d, mod=%X\n", (event.type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP", event.key.key, event.key.scancode, event.key.mod);
            ImGui_ImplSDL3_UpdateKeyModifiers(cast(SDL_Keymod) event.key.mod);
            key : ImGui.Key = ImGui_ImplSDL3_KeyEventToImGuiKey(event.key.key, event.key.scancode);
            io.AddKeyEvent(io, key, (event.type == xx SDL_EVENT_KEY_DOWN));
            io.SetKeyEventNativeData(io, key, xx event.key.key, xx event.key.scancode, xx event.key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
            return true;

        case xx SDL_EVENT_WINDOW_MOUSE_ENTER;
            bd.MouseWindowID = event.window.windowID;
            bd.MousePendingLeaveFrame = 0;
            return true;

        // - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
        //   causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
        //   we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
        // FIXME: Unconfirmed whether this is still needed with SDL3.
        case xx SDL_EVENT_WINDOW_MOUSE_LEAVE;
            bd.MousePendingLeaveFrame = ImGui.GetFrameCount() + 1;
            return true;

        case xx SDL_EVENT_WINDOW_FOCUS_GAINED;
            io.AddFocusEvent(io, true);
            return true;

        case xx SDL_EVENT_WINDOW_FOCUS_LOST;
            io.AddFocusEvent(io, false);
            return true;

        case xx SDL_EVENT_GAMEPAD_ADDED;
            #through;

        case xx SDL_EVENT_GAMEPAD_REMOVED;
            bd.WantUpdateGamepadsList = true;
            return true;
    }
    return false;
}

ImGui_ImplSDL3_SetupPlatformHandles :: (viewport: *ImGui.Viewport, window: *SDL_Window) {
    viewport.PlatformHandle = window;
    viewport.PlatformHandleRaw = null;
#if #exists(_WIN32) && !#exists(__WINRT__) {
    viewport.PlatformHandleRaw = cast(HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window),
                                                                   SDL_PROP_WINDOW_WIN32_HWND_POINTER, null);
} else #if #exists (__APPLE__) && #exists (SDL_VIDEO_DRIVER_COCOA) {
    viewport.PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window),
                                                        SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, null);
}
}

ImGui_ImplSDL3_Init :: (window: *SDL_Window, renderer: *SDL_Renderer, sdl_gl_context: *void) -> bool {
    io : *ImGui.IO = ImGui.GetIO();
    ImGui.IMGUI_CHECKVERSION();
    assert(io.BackendPlatformUserData == null && "Already initialized a platform backend!");

    // Check and store if we are on a SDL backend that supports global mouse position
    // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
    mouse_can_use_global_state : bool = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE {
    //sdl_backend : *u8 = SDL_GetCurrentVideoDriver();
    //global_mouse_whitelist : []*u8 = .[ "windows", "cocoa", "x11", "DIVE", "VMAN" ];
    //for n : 0..global_mouse_whitelist.count {
    //    //if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
    //       // mouse_can_use_global_state = true;
    //}
}

    // Setup backend capabilities flags
    bd : *ImGui_ImplSDL3_Data = New(ImGui_ImplSDL3_Data);
    io.BackendPlatformUserData = cast(*void) bd;
    io.BackendPlatformName = "imgui_impl_sdl3";
    io.BackendFlags_ |= ImGui.BackendFlags.HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
    io.BackendFlags_ |= ImGui.BackendFlags.HasSetMousePos;            // We can honor io.WantSetMousePos requests (optional, rarely used)

    bd.Window = window;
    bd.Renderer = renderer;
    bd.MouseCanUseGlobalState = mouse_can_use_global_state;

    io.SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
    io.GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
    io.ClipboardUserData = null;
    io.PlatformSetImeDataFn = ImGui_ImplSDL3_PlatformSetImeData;

    // Gamepad handling
    bd.GamepadMode = ImGui_ImplSDL3_GamepadMode.AutoFirst;
    bd.WantUpdateGamepadsList = true;

    // Load mouse cursors
    bd.MouseCursors[ImGui.MouseCursor.Arrow] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_DEFAULT);
    bd.MouseCursors[ImGui.MouseCursor.TextInput] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_TEXT);
    bd.MouseCursors[ImGui.MouseCursor.ResizeAll] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_MOVE);
    bd.MouseCursors[ImGui.MouseCursor.ResizeNS] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NS_RESIZE);
    bd.MouseCursors[ImGui.MouseCursor.ResizeEW] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_EW_RESIZE);
    bd.MouseCursors[ImGui.MouseCursor.ResizeNESW] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NESW_RESIZE);
    bd.MouseCursors[ImGui.MouseCursor.ResizeNWSE] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NWSE_RESIZE);
    bd.MouseCursors[ImGui.MouseCursor.Hand] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_POINTER);
    bd.MouseCursors[ImGui.MouseCursor.NotAllowed] = SDL_CreateSystemCursor(.SDL_SYSTEM_CURSOR_NOT_ALLOWED);

    // Set platform dependent data in viewport
    // Our mouse update function expect PlatformHandle to be filled for the main viewport
    main_viewport : *ImGui.Viewport = ImGui.GetMainViewport();
    ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);

    // From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
    // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
    // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
    // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
    // you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
#if SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH {
    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
}

    // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
#if SDL_HINT_MOUSE_AUTO_CAPTURE {
    SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
}

    return true;
}

ImGui_ImplSDL3_InitForOpenGL :: (window: *SDL_Window, sdl_gl_context: *void) -> bool {
    return ImGui_ImplSDL3_Init(window, null, sdl_gl_context);
}

ImGui_ImplSDL3_InitForVulkan :: (window: *SDL_Window) -> bool {
    return ImGui_ImplSDL3_Init(window, null, null);
}

ImGui_ImplSDL3_InitForD3D :: (window: *SDL_Window) -> bool {
#if #exists (_WIN32) {
    assert(0 && "Unsupported");
}
    return ImGui_ImplSDL3_Init(window, null, null);
}

ImGui_ImplSDL3_InitForMetal :: (window: *SDL_Window) -> bool {
    return ImGui_ImplSDL3_Init(window, null, null);
}

ImGui_ImplSDL3_InitForSDLRenderer :: (window: *SDL_Window, renderer: *SDL_Renderer) -> bool {
    return ImGui_ImplSDL3_Init(window, renderer, null);
}

ImGui_ImplSDL3_InitForOther :: (window: *SDL_Window) -> bool {
    return ImGui_ImplSDL3_Init(window, null, null);
}

ImGui_ImplSDL3_Shutdown :: () {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    assert(bd != null && "No platform backend to shutdown, or already shutdown?");
    io : *ImGui.IO = ImGui.GetIO();

    if (bd.ClipboardTextData)
        SDL_free(bd.ClipboardTextData);
    for cursor_n : 0..ImGui.MouseCursor.ImGuiMouseCursor_COUNT - 1
        SDL_DestroyCursor(bd.MouseCursors[cursor_n]);
    ImGui_ImplSDL3_CloseGamepads();

    io.BackendPlatformName = null;
    io.BackendPlatformUserData = null;
    io.BackendFlags_ &= ~(ImGui.BackendFlags.HasMouseCursors | ImGui.BackendFlags.HasSetMousePos | ImGui.BackendFlags.HasGamepad);
    free(bd);
}

ImGui_ImplSDL3_UpdateMouseData :: () {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    io : *ImGui.IO = ImGui.GetIO();

    // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE {
    // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries
    // shouldn't e.g. trigger other operations outside
    SDL_CaptureMouse(xx ifx (bd.MouseButtonsDown != 0) then SDL_TRUE else SDL_FALSE);
    focused_window : *SDL_Window = SDL_GetKeyboardFocus();
    is_app_focused : bool = (bd.Window == focused_window);
} else {
    focused_window : *SDL_Window = bd.Window;
    is_app_focused : bool = (SDL_GetWindowFlags(bd.Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
    // SDL 2.0.3 and non-windowed systems: single-viewport only
}
    if (is_app_focused)
    {
        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
        if (io.WantSetMousePos)
            SDL_WarpMouseInWindow(bd.Window, io.MousePos.x, io.MousePos.y);

        // (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
        if (bd.MouseCanUseGlobalState && bd.MouseButtonsDown == 0)
        {
            // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
            mouse_x_global, mouse_y_global : float;
            window_x, window_y : s32;
            SDL_GetGlobalMouseState(*mouse_x_global, *mouse_y_global);
            SDL_GetWindowPosition(focused_window, *window_x, *window_y);
            io.AddMousePosEvent(io, mouse_x_global - window_x, mouse_y_global - window_y);
        }
    }
}

ImGui_ImplSDL3_UpdateMouseCursor :: () {
    io : *ImGui.IO = ImGui.GetIO();
    if (io.ConfigFlags_ & ImGui.ConfigFlags.NoMouseCursorChange)
        return;
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();

    imgui_cursor : ImGui.MouseCursor = ImGui.GetMouseCursor();
    if (io.MouseDrawCursor || imgui_cursor == ImGui.MouseCursor.None)
    {
        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
        SDL_HideCursor();
    }
    else
    {
        // Show OS mouse cursor
        expected_cursor : *SDL_Cursor = ifx bd.MouseCursors[imgui_cursor] then bd.MouseCursors[imgui_cursor] else bd.MouseCursors[ImGui.MouseCursor.Arrow];
        if (bd.MouseLastCursor != expected_cursor)
        {
            SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
            bd.MouseLastCursor = expected_cursor;
        }
        SDL_ShowCursor();
    }
}

ImGui_ImplSDL3_CloseGamepads :: () {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    if (bd.GamepadMode != ImGui_ImplSDL3_GamepadMode.Manual)
        for  gamepad : bd.Gamepads
            SDL_CloseGamepad(gamepad);
    array_reset(*bd.Gamepads);
}

ImGui_ImplSDL3_SetGamepadMode :: (mode: ImGui_ImplSDL3_GamepadMode, manual_gamepads_array: **SDL_Gamepad, manual_gamepads_count: s32) {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    ImGui_ImplSDL3_CloseGamepads();
    if (mode == ImGui_ImplSDL3_GamepadMode.Manual) {
        assert(manual_gamepads_array != null && manual_gamepads_count > 0);
        for n : 0..manual_gamepads_count - 1
            array_add(*bd.Gamepads, manual_gamepads_array[n]);
    } else {
        assert(manual_gamepads_array == null && manual_gamepads_count <= 0);
        bd.WantUpdateGamepadsList = true;
    }
    bd.GamepadMode = mode;
}

ImGui_ImplSDL3_UpdateGamepadButton :: (bd : *ImGui_ImplSDL3_Data, io : *ImGui.IO, key:ImGui.Key, button_no: SDL_GamepadButton) {
    merged_value : bool = false;
    for gamepad : bd.Gamepads
        merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
    io.AddKeyEvent(io, key, merged_value);
}

Saturate :: (v: float) -> float {
    return ifx v < 0.0 then 0.0 else ifx v > 1.0 then 1.0 else v;
}

ImGui_ImplSDL3_UpdateGamepadAnalog :: (bd : *ImGui_ImplSDL3_Data, io : *ImGui.IO, key: ImGui.Key, axis_no: SDL_GamepadAxis, v0: float, v1: float) {
    merged_value : float = 0.0;
    for gamepad : bd.Gamepads{
        vn : float = Saturate(cast(float) (SDL_GetGamepadAxis(gamepad, axis_no) - v0) / cast(float)(v1 - v0));
        if (merged_value < vn)
            merged_value = vn;
    }
    io.AddKeyAnalogEvent(io, key, merged_value > 0.1, merged_value);
}

ImGui_ImplSDL3_UpdateGamepads :: () {
    io : *ImGui.IO = ImGui.GetIO();
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();

    // Update list of gamepads to use
    if (bd.WantUpdateGamepadsList && bd.GamepadMode != ImGui_ImplSDL3_GamepadMode.Manual) {
        ImGui_ImplSDL3_CloseGamepads();
        sdl_gamepads_count : s32 = 0;
        sdl_gamepads : *SDL_JoystickID = SDL_GetGamepads(*sdl_gamepads_count);
        for n : 0..sdl_gamepads_count - 1 {
            gamepad : *SDL_Gamepad = SDL_OpenGamepad(sdl_gamepads[n]);
            if gamepad {
                array_add(*bd.Gamepads, gamepad);
                if (bd.GamepadMode == ImGui_ImplSDL3_GamepadMode.AutoFirst)
                    break;
            }
        }
        bd.WantUpdateGamepadsList = false;
    }

    // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
    if ((io.ConfigFlags_ & ImGui.ConfigFlags.NavEnableGamepad) == 0)
        return;
    io.BackendFlags_ &= ~ImGui.BackendFlags.HasGamepad;
    if (bd.Gamepads.count == 0)
        return;
    io.BackendFlags_ |= ImGui.BackendFlags.HasGamepad;

    // Update gamepad inputs
    thumb_dead_zone : s32 = 8000;           // SDL_gamepad.h suggests using this value.
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadStart,       SDL_GAMEPAD_BUTTON_START);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadBack,        SDL_GAMEPAD_BUTTON_BACK);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceLeft,    SDL_GAMEPAD_BUTTON_WEST);           // Xbox X, PS Square
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceRight,   SDL_GAMEPAD_BUTTON_EAST);           // Xbox B, PS Circle
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceUp,      SDL_GAMEPAD_BUTTON_NORTH);          // Xbox Y, PS Triangle
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadFaceDown,    SDL_GAMEPAD_BUTTON_SOUTH);          // Xbox A, PS Cross
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadLeft,    SDL_GAMEPAD_BUTTON_DPAD_LEFT);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadRight,   SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadUp,      SDL_GAMEPAD_BUTTON_DPAD_UP);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadDpadDown,    SDL_GAMEPAD_BUTTON_DPAD_DOWN);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadL1,          SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadR1,          SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadL2,          SDL_GAMEPAD_AXIS_LEFT_TRIGGER,  0.0, 32767);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadR2,          SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0, 32767);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadL3,          SDL_GAMEPAD_BUTTON_LEFT_STICK);
    ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGui.Key.GamepadR3,          SDL_GAMEPAD_BUTTON_RIGHT_STICK);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickLeft,  SDL_GAMEPAD_AXIS_LEFTX,  xx -thumb_dead_zone, -32768);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX,  xx +thumb_dead_zone, +32767);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickUp,    SDL_GAMEPAD_AXIS_LEFTY,  xx -thumb_dead_zone, -32768);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadLStickDown,  SDL_GAMEPAD_AXIS_LEFTY,  xx +thumb_dead_zone, +32767);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickLeft,  SDL_GAMEPAD_AXIS_RIGHTX, xx -thumb_dead_zone, -32768);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, xx +thumb_dead_zone, +32767);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickUp,    SDL_GAMEPAD_AXIS_RIGHTY, xx -thumb_dead_zone, -32768);
    ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGui.Key.GamepadRStickDown,  SDL_GAMEPAD_AXIS_RIGHTY, xx +thumb_dead_zone, +32767);
}

ImGui_ImplSDL3_NewFrame :: () {
    bd : *ImGui_ImplSDL3_Data = ImGui_ImplSDL3_GetBackendData();
    assert(bd != null && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
    io : *ImGui.IO = ImGui.GetIO();

    // Setup display size (every frame to accommodate for window resizing)
    w, h : s32;
    display_w, display_h : s32;
    SDL_GetWindowSize(bd.Window, *w, *h);
    if (SDL_GetWindowFlags(bd.Window) & SDL_WINDOW_MINIMIZED) {
        w = 0;
        h = 0;
    }
    SDL_GetWindowSizeInPixels(bd.Window, *display_w, *display_h);
    io.DisplaySize = ImGui.ImVec2.{cast(float)w, cast(float)h};
    if (w > 0 && h > 0)
        io.DisplayFramebufferScale = ImGui.ImVec2.{cast(float) display_w / w, cast(float) display_h / h};

    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
    // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
    frequency : Uint64 = SDL_GetPerformanceFrequency();
    current_time : Uint64 = SDL_GetPerformanceCounter();
    if (current_time <= bd.Time)
        current_time = bd.Time + 1;
    io.DeltaTime = ifx bd.Time > 0 then cast(float)(cast(float64)(current_time - bd.Time) / frequency) else cast(float)(1.0 / 60.0);
    bd.Time = current_time;

    if (bd.MousePendingLeaveFrame && bd.MousePendingLeaveFrame >= ImGui.GetFrameCount() && bd.MouseButtonsDown == 0) {
        bd.MouseWindowID = 0;
        bd.MousePendingLeaveFrame = 0;
        io.AddMousePosEvent(io, FLOAT32_MIN, FLOAT32_MAX);
    }

    ImGui_ImplSDL3_UpdateMouseData();
    ImGui_ImplSDL3_UpdateMouseCursor();

    // Update game controllers (if enabled and available)
    ImGui_ImplSDL3_UpdateGamepads();
}
