use dear_imgui_rs::{Id, Ui};
use dear_imgui_sys as imgui_sys;
use dear_imguizmo_sys as sys;

use crate::mat::Mat4Like;
use crate::style::Style;
use crate::types::{AxisMask, DrawListTarget, GuizmoId, Mode, MoveType, Operation, Vec3Like};

/// Context handle; lightweight wrapper to bind ImGui context and build a GizmoUi
#[derive(Default, Clone, Copy)]
pub struct GuizmoContext;

impl GuizmoContext {
    pub fn new() -> Self {
        Self
    }

    /// Begin an ImGuizmo frame for the given ImGui `Ui`.
    /// Call exactly once per frame before using GizmoUi functions.
    pub fn begin_frame<'ui>(&self, ui: &'ui Ui) -> GizmoUi<'ui> {
        let imgui_ctx_raw = ui.with_bound_context(|| {
            let imgui_ctx_raw = unsafe { imgui_sys::igGetCurrentContext() };
            assert!(
                !imgui_ctx_raw.is_null(),
                "dear-imguizmo: begin_frame requires an active ImGui context"
            );
            unsafe {
                sys::ImGuizmo_SetImGuiContext(imgui_ctx_raw);
                // ImGuizmo::BeginFrame() creates a helper ImGui window named "gizmo".
                // When ImGui viewports are enabled, this helper window can accidentally
                // get its own platform viewport on some backends, resulting in an extra
                // OS window (often rendered as a black fullscreen window).
                //
                // Force the helper window to stay on the main viewport by setting the
                // next-window viewport ID before calling into ImGuizmo.
                let main_vp = imgui_sys::igGetMainViewport();
                if !main_vp.is_null() {
                    imgui_sys::igSetNextWindowViewport((*main_vp).ID);
                }
                sys::ImGuizmo_BeginFrame();
            }
            imgui_ctx_raw
        });
        GizmoUi {
            _ui: ui,
            imgui_ctx_raw,
        }
    }
}

pub struct GizmoUi<'ui> {
    pub(crate) _ui: &'ui Ui,
    imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct GridColors {
    pub major: u32,
    pub minor: u32,
    pub center: u32,
}

impl<'ui> GizmoUi<'ui> {
    #[inline]
    pub(crate) fn bind(&self) -> BoundGizmoContext {
        assert!(
            !self.imgui_ctx_raw.is_null(),
            "dear-imguizmo: GizmoUi requires an active ImGui context"
        );
        let previous = unsafe { imgui_sys::igGetCurrentContext() };
        unsafe {
            if previous != self.imgui_ctx_raw {
                imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
            }
            sys::ImGuizmo_SetImGuiContext(self.imgui_ctx_raw);
        }
        BoundGizmoContext {
            previous,
            current: self.imgui_ctx_raw,
        }
    }

    pub fn set_rect(&self, x: f32, y: f32, width: f32, height: f32) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetRect(x, y, width, height) }
    }
    pub fn set_orthographic(&self, is_ortho: bool) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetOrthographic(is_ortho) }
    }
    pub fn allow_axis_flip(&self, enable: bool) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_AllowAxisFlip(enable) }
    }
    pub fn set_gizmo_size_clip_space(&self, value: f32) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetGizmoSizeClipSpace(value) }
    }
    pub fn set_axis_limit(&self, value: f32) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetAxisLimit(value) }
    }
    pub fn set_plane_limit(&self, value: f32) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetPlaneLimit(value) }
    }
    pub fn set_axis_mask(&self, mask: AxisMask) {
        let x = mask.contains(AxisMask::X);
        let y = mask.contains(AxisMask::Y);
        let z = mask.contains(AxisMask::Z);
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetAxisMask(x, y, z) }
    }

    pub fn draw_grid<T: Mat4Like>(&self, view: &T, projection: &T, model: &T, grid_size: f32) {
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_DrawGrid(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                model.to_cols_array().as_ptr(),
                grid_size,
            )
        }
    }

    pub fn draw_grid_custom<T: Mat4Like>(
        &self,
        view: &T,
        projection: &T,
        model: &T,
        grid_size: f32,
        major_step: f32,
        subdivision: u32,
    ) {
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_DrawGridCustom(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                model.to_cols_array().as_ptr(),
                grid_size,
                major_step,
                subdivision,
            )
        }
    }

    pub fn draw_grid_custom_color<T: Mat4Like>(
        &self,
        view: &T,
        projection: &T,
        model: &T,
        grid_size: f32,
        major_step: f32,
        subdivision: u32,
        colors: GridColors,
    ) {
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_DrawGridCustomColor(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                model.to_cols_array().as_ptr(),
                grid_size,
                major_step,
                subdivision,
                colors.major,
                colors.minor,
                colors.center,
            )
        }
    }

    pub fn draw_axes<T: Mat4Like>(&self, view: &T, projection: &T, matrices: &[T]) {
        let count = match i32::try_from(matrices.len()) {
            Ok(v) => v,
            Err(_) => return,
        };
        if count == 0 {
            return;
        }
        let cap = match matrices.len().checked_mul(16) {
            Some(v) => v,
            None => return,
        };
        let mut flat: Vec<f32> = Vec::with_capacity(cap);
        for m in matrices {
            flat.extend_from_slice(&m.to_cols_array());
        }
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_DrawAxes(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                flat.as_ptr(),
                count,
            )
        }
    }

    pub fn draw_cubes<T: Mat4Like>(&self, view: &T, projection: &T, matrices: &[T]) {
        let count = match i32::try_from(matrices.len()) {
            Ok(v) => v,
            Err(_) => return,
        };
        if count == 0 {
            return;
        }
        let cap = match matrices.len().checked_mul(16) {
            Some(v) => v,
            None => return,
        };
        let mut flat: Vec<f32> = Vec::with_capacity(cap);
        for m in matrices {
            flat.extend_from_slice(&m.to_cols_array());
        }
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_DrawCubes(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                flat.as_ptr(),
                count,
            )
        }
    }

    pub fn set_drawlist_window(&self) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetDrawlist(imgui_sys::igGetWindowDrawList()) }
    }
    pub fn set_drawlist_background(&self) {
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_SetDrawlist(imgui_sys::igGetBackgroundDrawList(std::ptr::null_mut()))
        }
    }
    pub fn set_drawlist_foreground(&self) {
        let _guard = self.bind();
        unsafe {
            sys::ImGuizmo_SetDrawlist(imgui_sys::igGetForegroundDrawList_ViewportPtr(
                std::ptr::null_mut(),
            ))
        }
    }
    pub fn set_drawlist(&self, target: DrawListTarget) {
        match target {
            DrawListTarget::Window => self.set_drawlist_window(),
            DrawListTarget::Background => self.set_drawlist_background(),
            DrawListTarget::Foreground => self.set_drawlist_foreground(),
        }
    }

    /// Set an alternative ImGui window to consider hovered for ImGuizmo interactions.
    ///
    /// This corresponds to `ImGuizmo::SetAlternativeWindow(ImGuiWindow*)`.
    ///
    /// Notes:
    /// - `window_name` must match the exact ImGui window name (including any `###id` suffix).
    /// - Returns `true` if the window was found; otherwise clears the alternative window and returns `false`.
    pub fn set_alternative_window_by_name(&self, window_name: &str) -> bool {
        assert!(!window_name.contains('\0'), "window_name contained NUL");
        let _guard = self.bind();
        dear_imgui_rs::with_scratch_txt(window_name, |ptr| {
            let window = unsafe { imgui_sys::igFindWindowByName(ptr) } as *mut sys::ImGuiWindow;
            unsafe { sys::ImGuizmo_SetAlternativeWindow(window) };
            !window.is_null()
        })
    }

    /// Clear the alternative hovered window for ImGuizmo.
    pub fn clear_alternative_window(&self) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetAlternativeWindow(std::ptr::null_mut()) }
    }

    /// Unsafe: set a raw `ImGuiWindow*` as ImGuizmo's alternative hovered window.
    ///
    /// Prefer `set_alternative_window_by_name` unless you are already working with internal ImGui window pointers.
    pub unsafe fn set_alternative_window_raw(&self, window: *mut sys::ImGuiWindow) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetAlternativeWindow(window) }
    }

    /// Unsafe: set a raw ImGui drawlist pointer for ImGuizmo to render into.
    ///
    /// Prefer using `set_drawlist_*` safe variants. Only use this when you
    /// have a valid `*mut ImDrawList` whose lifetime is at least the duration
    /// of the current frame.
    pub unsafe fn set_drawlist_raw(&self, drawlist: *mut imgui_sys::ImDrawList) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_SetDrawlist(drawlist) }
    }

    /// Manipulate using the currently bound draw list.
    /// Call one of `set_drawlist_*` before invoking.
    pub fn manipulate<T: Mat4Like>(
        &self,
        view: &T,
        projection: &T,
        operation: Operation,
        mode: Mode,
        model_matrix: &mut T,
        mut delta_matrix: Option<&mut T>,
        snap: Option<&[f32; 3]>,
        local_bounds: Option<&[f32; 6]>,
        bounds_snap: Option<&[f32; 3]>,
    ) -> bool {
        let _guard = self.bind();
        let mut model_arr = model_matrix.to_cols_array();
        let mut delta_arr = match &delta_matrix {
            Some(dm) => dm.to_cols_array(),
            None => T::identity().to_cols_array(),
        };
        let used = unsafe {
            sys::ImGuizmo_Manipulate(
                view.to_cols_array().as_ptr(),
                projection.to_cols_array().as_ptr(),
                operation.into(),
                mode.into(),
                model_arr.as_mut_ptr(),
                delta_arr.as_mut_ptr(),
                snap.map(|s| s.as_ptr()).unwrap_or(std::ptr::null()),
                local_bounds.map(|b| b.as_ptr()).unwrap_or(std::ptr::null()),
                bounds_snap.map(|b| b.as_ptr()).unwrap_or(std::ptr::null()),
            )
        };
        model_matrix.set_from_cols_array(model_arr);
        if let Some(dm) = &mut delta_matrix {
            dm.set_from_cols_array(delta_arr);
        }
        used
    }

    pub fn view_manipulate<T: Mat4Like>(
        &self,
        view: &mut T,
        length: f32,
        position: impl Into<[f32; 2]>,
        size: impl Into<[f32; 2]>,
        background_color: u32,
    ) -> bool {
        let _guard = self.bind();
        let mut arr = view.to_cols_array();
        let position = position.into();
        let size = size.into();
        unsafe {
            sys::ImGuizmo_ViewManipulate_Float(
                arr.as_mut_ptr(),
                length,
                sys::ImVec2_c {
                    x: position[0],
                    y: position[1],
                },
                sys::ImVec2_c {
                    x: size[0],
                    y: size[1],
                },
                background_color,
            );
        }
        view.set_from_cols_array(arr);
        unsafe { sys::ImGuizmo_IsUsingViewManipulate() }
    }

    /// Extended view manipulator that also takes projection and edits a target matrix.
    pub fn view_manipulate_with_camera<T: Mat4Like>(
        &self,
        view: &mut T,
        projection: &T,
        operation: Operation,
        mode: Mode,
        matrix: &mut T,
        length: f32,
        position: impl Into<[f32; 2]>,
        size: impl Into<[f32; 2]>,
        background_color: u32,
    ) -> bool {
        let _guard = self.bind();
        let mut view_arr = view.to_cols_array();
        let mut matrix_arr = matrix.to_cols_array();
        let position = position.into();
        let size = size.into();
        unsafe {
            sys::ImGuizmo_ViewManipulate_FloatPtr(
                view_arr.as_mut_ptr(),
                projection.to_cols_array().as_ptr(),
                operation.into(),
                mode.into(),
                matrix_arr.as_mut_ptr(),
                length,
                sys::ImVec2_c {
                    x: position[0],
                    y: position[1],
                },
                sys::ImVec2_c {
                    x: size[0],
                    y: size[1],
                },
                background_color,
            );
        }
        view.set_from_cols_array(view_arr);
        matrix.set_from_cols_array(matrix_arr);
        unsafe { sys::ImGuizmo_IsUsingViewManipulate() }
    }

    /// Convenience: set rect from pos/size vectors
    pub fn set_rect_pos_size(&self, pos: impl Into<[f32; 2]>, size: impl Into<[f32; 2]>) {
        let pos = pos.into();
        let size = size.into();
        self.set_rect(pos[0], pos[1], size[0], size[1]);
    }

    pub fn enable(&self, enable: bool) {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_Enable(enable) }
    }
    pub fn is_over(&self) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsOver_Nil() }
    }
    pub fn is_using(&self) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsUsing() }
    }
    pub fn is_using_any(&self) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsUsingAny() }
    }
    pub fn is_using_view_manipulate(&self) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsUsingViewManipulate() }
    }
    pub fn is_view_manipulate_hovered(&self) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsViewManipulateHovered() }
    }
    pub fn is_over_operation(&self, operation: Operation) -> bool {
        let _guard = self.bind();
        unsafe { sys::ImGuizmo_IsOver_OPERATION(operation.into()) }
    }

    pub fn active_handle_type(&self) -> MoveType {
        let _guard = self.bind();
        MoveType::from_raw(unsafe { sys::ImGuizmo_GetActiveHandleType() })
    }

    pub fn hovered_handle_type(&self) -> MoveType {
        let _guard = self.bind();
        MoveType::from_raw(unsafe { sys::ImGuizmo_GetHoveredHandleType() })
    }

    pub fn active_move_type(&self) -> MoveType {
        let _guard = self.bind();
        MoveType::from_raw(unsafe { sys::ImGuizmo_GetActiveMoveType() })
    }

    pub fn hovered_move_type(&self) -> MoveType {
        let _guard = self.bind();
        MoveType::from_raw(unsafe { sys::ImGuizmo_GetHoveredMoveType() })
    }

    /// Test if the mouse is within `pixel_radius` of a world position once
    /// ImGuizmo has a valid view/projection context for the current frame.
    ///
    /// Notes:
    /// - This uses ImGuizmo internal `ViewProjection` from the last compute
    ///   (e.g. after calling `manipulate`, `draw_grid`, etc.).
    /// - `position` is a world-space 3D point.
    pub fn is_over_at<V: Vec3Like>(&self, position: V, pixel_radius: f32) -> bool {
        let _guard = self.bind();
        let mut p = position.to_array();
        unsafe { sys::ImGuizmo_IsOver_FloatPtr(p.as_mut_ptr(), pixel_radius) }
    }

    /// Push an ID for ImGuizmo's own ID stack and return a guard that pops on drop.
    pub fn push_id<'a, I>(&self, id: I) -> IdToken<'ui>
    where
        I: Into<GuizmoId<'a>>,
    {
        let _guard = self.bind();
        let id: GuizmoId<'a> = id.into();
        unsafe {
            match id {
                GuizmoId::Int(i) => sys::ImGuizmo_PushID_Int(i),
                GuizmoId::Str(s) => {
                    assert!(!s.contains('\0'), "string contained NUL");
                    dear_imgui_rs::with_scratch_txt(s, |ptr| sys::ImGuizmo_PushID_Str(ptr));
                }
                GuizmoId::Bytes(bytes) => {
                    let range = bytes.as_ptr_range();
                    sys::ImGuizmo_PushID_StrStr(
                        range.start as *const std::os::raw::c_char,
                        range.end as *const std::os::raw::c_char,
                    )
                }
                GuizmoId::Ptr(p) => sys::ImGuizmo_PushID_Ptr(p),
            }
        }
        IdToken {
            _ui: self._ui,
            imgui_ctx_raw: self.imgui_ctx_raw,
        }
    }
    /// Convenience for string ID push without needing to keep the guard name verbose.
    pub fn push_id_str(&self, id: &str) -> IdToken<'ui> {
        self.push_id(GuizmoId::Str(id))
    }
    /// Push an ID using a non-NUL-terminated byte slice (ImGuizmo `str_begin/str_end` form).
    pub fn push_id_bytes<'a>(&self, bytes: &'a [u8]) -> IdToken<'ui> {
        self.push_id(GuizmoId::Bytes(bytes))
    }
    /// Obtain a hashed ID value following ImGuizmo's ID scheme.
    pub fn get_id_str(&self, id: &str) -> Id {
        let _guard = self.bind();
        assert!(!id.contains('\0'), "string contained NUL");
        Id::from(dear_imgui_rs::with_scratch_txt(id, |ptr| unsafe {
            sys::ImGuizmo_GetID_Str(ptr)
        }))
    }
    /// Obtain a hashed ID value using a non-NUL-terminated byte slice (ImGuizmo `str_begin/str_end` form).
    pub fn get_id_bytes(&self, bytes: &[u8]) -> Id {
        let _guard = self.bind();
        let range = bytes.as_ptr_range();
        Id::from(unsafe {
            sys::ImGuizmo_GetID_StrStr(
                range.start as *const std::os::raw::c_char,
                range.end as *const std::os::raw::c_char,
            )
        })
    }

    /// Obtain a hashed ID from a pointer following ImGuizmo's ID scheme.
    pub fn get_id_ptr<T>(&self, ptr: *const T) -> Id {
        let _guard = self.bind();
        Id::from(unsafe { sys::ImGuizmo_GetID_Ptr(ptr as *const std::ffi::c_void) })
    }

    /// Access ImGuizmo global style through a safe wrapper bound to this UI lifetime.
    pub fn style(&self) -> Style<'ui> {
        let _guard = self.bind();
        let ptr = unsafe { sys::ImGuizmo_GetStyle() };
        Style {
            ptr,
            _phantom: std::marker::PhantomData,
        }
    }

    /// Start a builder-style manipulation call
    pub fn manipulate_config<T: Mat4Like>(
        &'ui self,
        view: &'ui T,
        projection: &'ui T,
        model: &'ui mut T,
    ) -> crate::op::Manipulate<'ui, T> {
        crate::op::Manipulate::new(self, view, projection, model)
    }
}

pub(crate) struct BoundGizmoContext {
    previous: *mut imgui_sys::ImGuiContext,
    current: *mut imgui_sys::ImGuiContext,
}

impl Drop for BoundGizmoContext {
    fn drop(&mut self) {
        if self.previous != self.current {
            unsafe {
                imgui_sys::igSetCurrentContext(self.previous);
                sys::ImGuizmo_SetImGuiContext(self.previous);
            }
        }
    }
}

/// RAII token that pops an ImGuizmo ID when dropped.
pub struct IdToken<'ui> {
    pub(crate) _ui: &'ui Ui,
    imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
}
impl<'ui> Drop for IdToken<'ui> {
    fn drop(&mut self) {
        if self.imgui_ctx_raw.is_null() {
            return;
        }
        let previous = unsafe { imgui_sys::igGetCurrentContext() };
        unsafe {
            if previous != self.imgui_ctx_raw {
                imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
            }
            sys::ImGuizmo_SetImGuiContext(self.imgui_ctx_raw);
            sys::ImGuizmo_PopID();
            if previous != self.imgui_ctx_raw {
                imgui_sys::igSetCurrentContext(previous);
                sys::ImGuizmo_SetImGuiContext(previous);
            }
        }
    }
}

/// Extension methods on dear-imgui's Ui to access ImGuizmo in a unified way
pub trait GuizmoExt {
    fn guizmo(&self) -> GizmoUi<'_>;
}
impl GuizmoExt for Ui {
    fn guizmo(&self) -> GizmoUi<'_> {
        GuizmoContext::new().begin_frame(self)
    }
}

#[cfg(test)]
mod tests {
    use super::{GuizmoExt, sys};
    use dear_imgui_rs::{BackendFlags, Context};
    use dear_imgui_sys as imgui_sys;
    use std::sync::{Mutex, OnceLock};

    fn test_guard() -> std::sync::MutexGuard<'static, ()> {
        static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
        GUARD.get_or_init(|| Mutex::new(())).lock().unwrap()
    }

    fn prepare_imgui(imgui: &mut Context) {
        let io = imgui.io_mut();
        io.set_display_size([800.0, 600.0]);
        io.set_delta_time(1.0 / 60.0);
        io.set_backend_flags(io.backend_flags() | BackendFlags::RENDERER_HAS_TEXTURES);
    }

    #[test]
    fn id_token_drops_with_matching_imgui_context() {
        let _guard = test_guard();
        let mut imgui = Context::create();
        prepare_imgui(&mut imgui);

        {
            let ui = imgui.frame();
            let giz = ui.guizmo();
            let id = giz.push_id(7);
            drop(id);
        }
        let _ = imgui.render();
    }

    #[test]
    fn id_token_drops_on_owner_context_and_restores_previous_current_context() {
        let _guard = test_guard();
        let mut imgui_a = Context::create();
        prepare_imgui(&mut imgui_a);
        let raw_a = imgui_a.as_raw();

        let ui = imgui_a.frame();
        let giz = ui.guizmo();
        let id = giz.push_id(7);

        unsafe { imgui_sys::igSetCurrentContext(std::ptr::null_mut()) };
        drop(id);
        assert_eq!(
            unsafe { imgui_sys::igGetCurrentContext() },
            std::ptr::null_mut()
        );

        unsafe {
            imgui_sys::igSetCurrentContext(raw_a);
            sys::ImGuizmo_SetImGuiContext(raw_a);
        }
        let _ = imgui_a.render();
    }

    #[test]
    fn get_id_helpers_return_typed_imgui_ids() {
        let _guard = test_guard();
        let mut imgui = Context::create();
        prepare_imgui(&mut imgui);

        let ptr_value = 1234_u32;
        {
            let ui = imgui.frame();
            let giz = ui.guizmo();

            let str_id = giz.get_id_str("typed-id");
            let expected_str = dear_imgui_rs::with_scratch_txt("typed-id", |ptr| unsafe {
                sys::ImGuizmo_GetID_Str(ptr)
            });
            assert_eq!(str_id.raw(), expected_str);

            let bytes = b"typed-id";
            let bytes_id = giz.get_id_bytes(bytes);
            let range = bytes.as_ptr_range();
            let expected_bytes = unsafe {
                sys::ImGuizmo_GetID_StrStr(
                    range.start.cast::<std::os::raw::c_char>(),
                    range.end.cast::<std::os::raw::c_char>(),
                )
            };
            assert_eq!(bytes_id.raw(), expected_bytes);

            let ptr_id = giz.get_id_ptr(&ptr_value);
            let expected_ptr =
                unsafe { sys::ImGuizmo_GetID_Ptr((&ptr_value as *const u32).cast()) };
            assert_eq!(ptr_id.raw(), expected_ptr);
        }
        let _ = imgui.render();
    }
}
