/** \file
 * \brief Menu Resources
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdlib.h>
#include <stdio.h>

#include "iup.h"

#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drv.h"
#include "iup_menu.h"

#include "iupgtk4_drv.h"
#include "iupgtk4_x11.h"

#ifdef GDK_WINDOWING_WIN32
#include <gdk/win32/gdkwin32.h>
#endif

#ifdef GDK_WINDOWING_MACOS
#include <gdk/macos/gdkmacos.h>
#endif

typedef struct _ImenuPos
{
  int x, y;
  Ihandle* ih;
} ImenuPos;

static int gtk4IsWin32Backend(void)
{
#ifdef GDK_WINDOWING_WIN32
  GdkDisplay *display = gdk_display_get_default();
  return (display && GDK_IS_WIN32_DISPLAY(display));
#else
  return 0;
#endif
}

static int gtk4IsMacosBackend(void)
{
#ifdef GDK_WINDOWING_MACOS
  GdkDisplay *display = gdk_display_get_default();
  return (display && GDK_IS_MACOS_DISPLAY(display));
#else
  return 0;
#endif
}

static void gtk4MenuActionActivated(GSimpleAction* action, GVariant* parameter, gpointer user_data)
{
  Ihandle* ih = (Ihandle*)user_data;
  Icallback cb;
  GVariant* state;

  if (!ih)
    return;

  /* Handle AUTOTOGGLE for checkable items */
  state = g_action_get_state(G_ACTION(action));
  if (state != NULL)
  {
    /* This is a stateful (checkable) action */
    gboolean checked = g_variant_get_boolean(state);
    g_variant_unref(state);

    if (iupAttribGetBoolean(ih, "AUTOTOGGLE"))
    {
      /* Toggle the state */
      checked = !checked;
      g_simple_action_set_state(action, g_variant_new_boolean(checked));

      /* Update IUP's VALUE attribute */
      iupAttribSet(ih, "VALUE", checked ? "ON" : "OFF");

    }
  }

  cb = IupGetCallback(ih, "ACTION");
  if (cb)
  {
    int ret = cb(ih);
    if (ret == IUP_CLOSE)
    {
      IupExitLoop();
    }
  }

  (void)parameter;
}

static void gtk4MenuRadioActivated(GSimpleAction* action, GVariant* parameter, gpointer user_data)
{
  Ihandle* parent_menu = (Ihandle*)user_data;
  const char* target;
  Ihandle* picked = NULL;
  Ihandle* child;
  Icallback cb;

  if (!parameter || !parent_menu)
    return;

  g_simple_action_set_state(action, parameter);

  target = g_variant_get_string(parameter, NULL);
  for (child = parent_menu->firstchild; child; child = child->brother)
  {
    if (!iupStrEqual(child->iclass->name, "menuitem"))
      continue;
    const char* item_target = iupAttribGet(child, "_IUPGTK4_RADIO_TARGET");
    if (item_target && strcmp(item_target, target) == 0)
    {
      picked = child;
      iupAttribSet(child, "VALUE", "ON");
    }
    else
      iupAttribSet(child, "VALUE", "OFF");
  }

  if (!picked)
    return;
  cb = IupGetCallback(picked, "ACTION");
  if (cb && cb(picked) == IUP_CLOSE)
    IupExitLoop();
}

/* Recursively build GMenu model from IUP menu hierarchy
 * is_root: TRUE if this is the root menu bar level (requires submenus only), FALSE if this is inside a submenu */
static GMenu* gtk4BuildMenuModel(Ihandle* ih_menu, GSimpleActionGroup* action_group, int is_root)
{
  GMenu* menu;
  GMenu* section;
  Ihandle* child;

  if (!ih_menu)
    return NULL;

  menu = g_menu_new();
  section = g_menu_new();

  gboolean is_radio_menu = !is_root && iupAttribGetBoolean(ih_menu, "RADIO");
  char radio_action_name[64] = {0};
  Ihandle* radio_initial = NULL;

  if (is_radio_menu)
  {
    Ihandle* c;
    for (c = ih_menu->firstchild; c; c = c->brother)
    {
      if (!iupStrEqual(c->iclass->name, "menuitem"))
        continue;
      if (!radio_initial) radio_initial = c;
      if (iupAttribGetBoolean(c, "VALUE")) { radio_initial = c; break; }
    }

    snprintf(radio_action_name, sizeof(radio_action_name), "radio-%p", (void*)ih_menu);

    char init_target[32] = {0};
    if (radio_initial)
      snprintf(init_target, sizeof(init_target), "%p", (void*)radio_initial);

    GSimpleAction* radio_action = g_simple_action_new_stateful(
        radio_action_name, G_VARIANT_TYPE_STRING, g_variant_new_string(init_target));
    g_signal_connect(radio_action, "activate", G_CALLBACK(gtk4MenuRadioActivated), ih_menu);
    g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(radio_action));
    g_object_unref(radio_action);

    iupAttribSetStr(ih_menu, "_IUPGTK4_RADIO_ACTION_NAME", radio_action_name);
  }

  for (child = ih_menu->firstchild; child; child = child->brother)
  {
    char* title;
    char* processed_title;
    char c = '_';

    if (iupStrEqual(child->iclass->name, "submenu"))
    {
      /* Submenu: get title and recursively build submenu */
      Ihandle* submenu_ih = child->firstchild;  /* The Menu child of Submenu */
      GMenu* submenu_model;

      title = iupAttribGet(child, "TITLE");
      if (!title) title = "";

      /* Process mnemonic: convert & to _ for GTK */
      processed_title = iupStrProcessMnemonic(title, &c, 1);

      if (submenu_ih)
      {
        /* Submenus are never at root, so pass FALSE */
        submenu_model = gtk4BuildMenuModel(submenu_ih, action_group, FALSE);
        if (submenu_model)
        {
          g_menu_append_submenu(section, processed_title, G_MENU_MODEL(submenu_model));
          /* Store GMenu reference on the IUP menu handle for later access (e.g., recent files) */
          iupAttribSet(submenu_ih, "_IUP_RECENT_GMENU", (char*)submenu_model);
        }
      }

      /* Free processed title if it was allocated */
      if (processed_title != title)
        free(processed_title);
    }
    else if (iupStrEqual(child->iclass->name, "menuitem"))
    {
      /* Menu item: create action and add to menu */
      GSimpleAction* action;
      char action_name[64];
      int is_checkable = 0;
      int is_checked = 0;
      int is_active = 1;

      title = iupAttribGet(child, "TITLE");
      if (!title) title = "";

      /* Process mnemonic: convert & to _ for GTK */
      processed_title = iupStrProcessMnemonic(title, &c, 1);

      if (is_radio_menu)
      {
        char target[32];
        snprintf(target, sizeof(target), "%p", (void*)child);
        iupAttribSetStr(child, "_IUPGTK4_RADIO_TARGET", target);
        iupAttribSetStr(child, "_IUPGTK4_RADIO_ACTION_NAME", radio_action_name);
        iupAttribSet(child, "VALUE", child == radio_initial ? "ON" : "OFF");

        char full_action_name[96];
        snprintf(full_action_name, sizeof(full_action_name), "menu.%s", radio_action_name);

        GMenuItem* mitem = g_menu_item_new(processed_title, NULL);
        g_menu_item_set_action_and_target(mitem, full_action_name, "s", target);
        g_menu_append_item(section, mitem);
        g_object_unref(mitem);

        if (processed_title != title)
          free(processed_title);
        continue;
      }

      /* Create unique action name using ih pointer */
      snprintf(action_name, sizeof(action_name), "item-%p", (void*)child);

      /* Check if this is a checkable item (VALUE attribute present or HIDEMARK not set) */
      char* value_str = iupAttribGetStr(child, "VALUE");

      if (value_str || !iupAttribGetBoolean(child, "HIDEMARK"))
      {
        is_checkable = 1;
        is_checked = iupStrBoolean(value_str);
      }

      if (iupAttribGet(child, "ACTIVE"))
        is_active = iupAttribGetBoolean(child, "ACTIVE");

      /* Create action with state for checkable items */
      if (is_checkable)
      {
        GVariant* state = g_variant_new_boolean(is_checked);
        action = g_simple_action_new_stateful(action_name, NULL, state);

        /* Store attributes needed for VALUE get/set, use SetStr to duplicate strings */
        iupAttribSetStr(child, "_IUPGTK4_CHECKABLE", "1");
        iupAttribSetStr(child, "_IUPGTK4_ACTION_NAME", action_name);
      }
      else
      {
        action = g_simple_action_new(action_name, NULL);
      }

      g_signal_connect(action, "activate", G_CALLBACK(gtk4MenuActionActivated), child);
      g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action));
      g_simple_action_set_enabled(action, is_active);
      g_object_unref(action);  /* action_group takes ownership */

      /* Add to menu with full action name */
      char full_action_name[96];
      snprintf(full_action_name, sizeof(full_action_name), "menu.%s", action_name);

      if (is_root)
      {
        /* GtkPopoverMenuBar requires submenus at root level.
         * For root-level items, create a submenu with single unlabeled item.
         * Menu bar shows the title, clicking opens dropdown and immediately triggers the action. */
        GMenu* item_submenu = g_menu_new();
        /* Add item without label, will just trigger the action */
        g_menu_append(item_submenu, NULL, full_action_name);
        g_menu_append_submenu(section, processed_title, G_MENU_MODEL(item_submenu));
        g_object_unref(item_submenu);
      }
      else
      {
        /* Regular submenu item, add directly */
        g_menu_append(section, processed_title, full_action_name);
      }

      /* Free processed title if it was allocated */
      if (processed_title != title)
        free(processed_title);
    }
    else if (iupStrEqual(child->iclass->name, "menuseparator"))
    {
      if (!is_root)
      {
        /* GMenu uses sections for separators, finish the current section and start a new one */
        g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
        g_object_unref(section);
        section = g_menu_new();
      }
    }
  }

  /* Append the final section */
  g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
  g_object_unref(section);

  return menu;
}

static void gtk4ApplyPendingRecentMenus(Ihandle* ih_menu)
{
  Ihandle* child;

  for (child = ih_menu->firstchild; child; child = child->brother)
  {
    if (iupStrEqual(child->iclass->name, "submenu") && child->firstchild)
    {
      Ihandle* submenu_ih = child->firstchild;

      if (iupAttribGet(submenu_ih, "_IUP_CONFIG") && iupAttribGet(submenu_ih, "_IUP_RECENT_GMENU"))
      {
        Icallback map_cb = IupGetCallback(submenu_ih, "MAP_CB");
        if (map_cb)
          map_cb(submenu_ih);
      }

      gtk4ApplyPendingRecentMenus(submenu_ih);
    }
  }
}

IUP_DRV_API void iupgtk4DialogSetMenuBar(Ihandle* ih_dialog, Ihandle* ih_menu)
{
  GMenu* menubar_model;
  GSimpleActionGroup* action_group;
  GtkWidget* menubar_widget;
  GtkWidget* inner_parent;


  if (!ih_dialog || !ih_menu)
    return;

  /* Create action group for this dialog's menu */
  action_group = g_simple_action_group_new();

  /* Store action group on dialog for later access by VALUE attribute setter */
  iupAttribSet(ih_dialog, "_IUPGTK4_MENU_ACTION_GROUP", (char*)action_group);

  /* Build menu model from IUP hierarchy (is_root=TRUE for menu bar) */
  menubar_model = gtk4BuildMenuModel(ih_menu, action_group, TRUE);
  if (!menubar_model)
  {
    g_object_unref(action_group);
    return;
  }

  /* Insert action group into window with "menu" prefix */
  gtk_widget_insert_action_group(ih_dialog->handle, "menu", G_ACTION_GROUP(action_group));
  g_object_unref(action_group);  /* window takes ownership */

  /* Apply pending recent files updates now that the action group is available */
  gtk4ApplyPendingRecentMenus(ih_menu);

  /* Create GtkPopoverMenuBar widget from model */
  menubar_widget = gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(menubar_model));
  g_object_unref(menubar_model);  /* menubar_widget takes ownership */

  /* Get menu VBox container */
  GtkWidget* menu_box = (GtkWidget*)iupAttribGet(ih_dialog, "_IUPGTK4_MENU_BOX");
  if (!menu_box)
  {
    gtk_widget_unparent(menubar_widget);
    return;
  }

  /* Configure expansion properties before adding to box. */
  inner_parent = (GtkWidget*)iupAttribGet(ih_dialog, "_IUPGTK4_INNER_PARENT");

  /* Menu bar should not expand - it wants its natural height only */
  gtk_widget_set_vexpand(menubar_widget, FALSE);
  gtk_widget_set_hexpand(menubar_widget, TRUE);  /* But expand horizontally */

  /* inner_parent must expand to fill remaining vertical space */
  gtk_widget_set_vexpand(inner_parent, TRUE);
  gtk_widget_set_hexpand(inner_parent, TRUE);

  /* Add menu bar to the top of the menu box (before inner_parent) */
  gtk_box_prepend(GTK_BOX(menu_box), menubar_widget);

  /* Store the menubar widget handle in the menu Ihandle */
  ih_menu->handle = menubar_widget;
}

IUP_SDK_API int iupdrvMenuGetMenuBarSize(Ihandle* ih_menu)
{
  GtkWidget* menubar_widget;

  if (!ih_menu || !ih_menu->handle)
    return 0;

  menubar_widget = (GtkWidget*)ih_menu->handle;

  int height = gtk_widget_get_height(menubar_widget);

  /* If not yet allocated (during initial layout), measure natural size */
  if (height == 0)
  {
    int min_height, nat_height;
    gtk_widget_measure(menubar_widget, GTK_ORIENTATION_VERTICAL, -1, &min_height, &nat_height, NULL, NULL);
    height = nat_height;
  }

  return height;
}

static void gtk4PopoverClosedCb(GtkPopover *popover, gpointer user_data)
{
  GMainLoop* loop = (GMainLoop*)user_data;
  if (loop && g_main_loop_is_running(loop))
    g_main_loop_quit(loop);
}

static void gtk4MenuParentDestroyCb(GtkWidget* parent, gpointer user_data)
{
  Ihandle* ih = (Ihandle*)user_data;
  GtkWidget* popover = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER");

  if (popover && GTK_IS_WIDGET(popover))
  {
    gtk_widget_unparent(popover);
    iupAttribSet(ih, "_IUPGTK4_POPOVER", NULL);
  }

  iupAttribSet(ih, "_IUPGTK4_POPOVER_PARENT", NULL);
}

static void gtk4AnchorPopoverClosedCb(GtkPopover *popover, gpointer user_data)
{
  Ihandle *ih = (Ihandle*)user_data;
  GtkWidget *anchor_window;

  /* Hide anchor window when popover closes */
  anchor_window = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_ANCHOR_WINDOW");
  if (anchor_window)
    gtk_widget_set_visible(anchor_window, FALSE);

  (void)popover;
}

static void gtk4PopoverMenuSetVHomogeneous(GtkWidget* popover)
{
  GtkWidget* sw = gtk_popover_get_child(GTK_POPOVER(popover));
  if (sw && GTK_IS_SCROLLED_WINDOW(sw))
  {
    GtkWidget* vp = gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw));
    if (vp && GTK_IS_VIEWPORT(vp))
    {
      GtkWidget* stack = gtk_viewport_get_child(GTK_VIEWPORT(vp));
      if (stack && GTK_IS_STACK(stack))
        gtk_stack_set_vhomogeneous(GTK_STACK(stack), TRUE);
    }
  }
}

IUP_SDK_API int iupdrvMenuPopup(Ihandle* ih, int x, int y)
{
  GtkWidget* popover;
  GtkWidget* parent_widget = NULL;
  GMenu* menu_model;
  GSimpleActionGroup* action_group;
  GMainLoop* loop;
  int local_x = 0, local_y = 0;
  int use_anchor_window = 0;

  /* Get stored menu model and action group from MapMethod */
  menu_model = (GMenu*)iupAttribGet(ih, "_IUPGTK4_MENU_MODEL");
  action_group = (GSimpleActionGroup*)iupAttribGet(ih, "_IUPGTK4_ACTION_GROUP");

  if (!menu_model || !action_group)
    return IUP_ERROR;

  /* Try to find an active visible window */
  {
    GList* toplevels = gtk_window_list_toplevels();
    GList* l;
    for (l = toplevels; l != NULL; l = l->next)
    {
      GtkWindow* win = GTK_WINDOW(l->data);
      if (gtk_window_is_active(win) && gtk_widget_get_visible(GTK_WIDGET(win)))
      {
        /* Found an active visible window, get its inner_parent if it's an IUP dialog */
        Ihandle* dlg = (Ihandle*)g_object_get_data(G_OBJECT(win), "IUP");
        if (dlg)
        {
          parent_widget = (GtkWidget*)iupAttribGet(dlg, "_IUPGTK4_INNER_PARENT");
          if (!parent_widget)
            parent_widget = GTK_WIDGET(win);
        }
        else
          parent_widget = GTK_WIDGET(win);
        break;
      }
    }
    g_list_free(toplevels);
  }

  /* If no parent_widget (no visible window), use anchor window */
  if (!parent_widget)
  {
    use_anchor_window = 1;
  }

  if (use_anchor_window)
  {
    /* Use positioned anchor window for free positioning at (x,y) */
    GtkWidget* anchor_window = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_ANCHOR_WINDOW");
    GtkWidget* old_popover = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER");

    /* Clean up previous popover if exists */
    if (old_popover && GTK_IS_WIDGET(old_popover))
    {
      GtkWidget* parent = gtk_widget_get_parent(old_popover);
      if (parent)
        gtk_widget_unparent(old_popover);
      iupAttribSet(ih, "_IUPGTK4_POPOVER", NULL);
    }

    if (!anchor_window)
    {
      /* Create a new invisible anchor window for the popover */
      anchor_window = gtk_window_new();
      gtk_window_set_decorated(GTK_WINDOW(anchor_window), FALSE);
      gtk_window_set_default_size(GTK_WINDOW(anchor_window), 1, 1);
      gtk_widget_set_opacity(anchor_window, 0.0);
      gtk_window_set_deletable(GTK_WINDOW(anchor_window), FALSE);

      iupAttribSet(ih, "_IUPGTK4_ANCHOR_WINDOW", (char*)anchor_window);

      gtk_window_present(GTK_WINDOW(anchor_window));

      /* Wait for window to be fully mapped */
      while (!gtk_widget_get_mapped(anchor_window))
        g_main_context_iteration(NULL, FALSE);

      /* Hide anchor window from taskbar on all platforms */
      {
        GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(anchor_window));
        if (surface)
        {
#ifdef GDK_WINDOWING_X11
          if (iupgtk4X11IsBackend())
            iupgtk4X11HideFromTaskbar(surface);
#endif
#ifdef GDK_WINDOWING_WIN32
          if (gtk4IsWin32Backend() && GDK_IS_WIN32_SURFACE(surface))
          {
            HWND hwnd = gdk_win32_surface_get_handle(surface);
            if (hwnd)
              iupgtk4Win32HideFromTaskbar(hwnd);
          }
#endif
#ifdef GDK_WINDOWING_MACOS
          if (gtk4IsMacosBackend() && GDK_IS_MACOS_SURFACE(surface))
          {
            gpointer nswindow = gdk_macos_surface_get_native_window(GDK_MACOS_SURFACE(surface));
            if (nswindow)
              iupgtk4MacosHideFromTaskbar(nswindow);
          }
#endif
        }
      }
    }
    else
    {
      gtk_widget_set_visible(anchor_window, TRUE);
      gtk_window_present(GTK_WINDOW(anchor_window));
    }

    /* Position the anchor window at (x,y) on platforms that support it */
    {
      GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(anchor_window));

      if (surface)
      {
#ifdef GDK_WINDOWING_X11
        if (iupgtk4X11IsBackend())
          iupgtk4X11MoveWindow(surface, x, y);
#endif
#ifdef GDK_WINDOWING_WIN32
        if (gtk4IsWin32Backend() && GDK_IS_WIN32_SURFACE(surface))
        {
          HWND hwnd = gdk_win32_surface_get_handle(surface);
          if (hwnd)
            iupgtk4Win32MoveWindow(hwnd, x, y);
        }
#endif
#ifdef GDK_WINDOWING_MACOS
        if (gtk4IsMacosBackend() && GDK_IS_MACOS_SURFACE(surface))
        {
          gpointer nswindow = gdk_macos_surface_get_native_window(GDK_MACOS_SURFACE(surface));
          if (nswindow)
            iupgtk4MacosMoveWindow(nswindow, x, y);
        }
#endif
      }
    }

    /* Create popover for anchor window approach */
    popover = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu_model));
    if (!popover)
      return IUP_ERROR;

    gtk4PopoverMenuSetVHomogeneous(popover);

    /* Insert action group into anchor window */
    gtk_widget_insert_action_group(anchor_window, "menu", G_ACTION_GROUP(action_group));

    gtk_widget_set_parent(popover, anchor_window);
    gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);
    gtk_widget_set_halign(popover, GTK_ALIGN_START);

    GdkRectangle pointing_rect = {0, 0, 1, 1};
    gtk_popover_set_pointing_to(GTK_POPOVER(popover), &pointing_rect);
    gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);

    /* Store popover for cleanup */
    iupAttribSet(ih, "_IUPGTK4_POPOVER", (char*)popover);
    ih->handle = popover;

    /* Connect closed signal to hide anchor window */
    g_signal_connect(popover, "closed", G_CALLBACK(gtk4AnchorPopoverClosedCb), (gpointer)ih);

    /* Flush pending idles so GTK's separator sync runs before sizing */
    while (g_main_context_pending(NULL))
      g_main_context_iteration(NULL, FALSE);

    gtk_popover_popup(GTK_POPOVER(popover));

    return IUP_NOERROR;
  }

  /* Dialog popup path (tree, etc.): Convert screen coordinates to parent_widget-local coordinates */
  {
    GtkNative *native = gtk_widget_get_native(parent_widget);
    if (native)
    {
      double native_x, native_y;
      int win_x = 0, win_y = 0;
      gtk_native_get_surface_transform(native, &native_x, &native_y);

#ifdef GDK_WINDOWING_X11
      if (iupgtk4X11IsBackend())
      {
        GdkSurface* surface = gtk_native_get_surface(native);
        if (surface)
          iupgtk4X11GetWindowPosition(surface, &win_x, &win_y);
      }
#endif

      graphene_point_t point_in = {x - win_x - native_x, y - win_y - native_y};
      graphene_point_t point_out;

      if (gtk_widget_compute_point(GTK_WIDGET(native), parent_widget, &point_in, &point_out))
      {
        local_x = (int)point_out.x;
        local_y = (int)point_out.y;
      }
      else
      {
        local_x = x;
        local_y = y;
      }
    }
  }

  /* Clean up previous popover if exists */
  {
    GtkWidget* old_popover = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER");
    if (old_popover && GTK_IS_WIDGET(old_popover))
    {
      GtkWidget* old_parent = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER_PARENT");
      if (old_parent && GTK_IS_WIDGET(old_parent))
        g_signal_handlers_disconnect_by_func(old_parent, gtk4MenuParentDestroyCb, ih);

      GtkWidget* parent = gtk_widget_get_parent(old_popover);
      if (parent)
        gtk_widget_unparent(old_popover);
      iupAttribSet(ih, "_IUPGTK4_POPOVER", NULL);
    }
  }

  /* Create a nested main loop, this will block until the popover is closed.
     This is needed because IUP expects IupPopup() to be synchronous. */
  loop = g_main_loop_new(NULL, FALSE);

  /* Create a fresh popover each time */
  popover = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu_model));
  if (!popover)
  {
    g_main_loop_unref(loop);
    return IUP_ERROR;
  }

  gtk4PopoverMenuSetVHomogeneous(popover);

  /* Connect closed signal to quit the nested main loop */
  g_signal_connect(popover, "closed", G_CALLBACK(gtk4PopoverClosedCb), (gpointer)loop);

  /* Store popover for cleanup in UnMapMethod */
  iupAttribSet(ih, "_IUPGTK4_POPOVER", (char*)popover);
  iupAttribSet(ih, "_IUPGTK4_POPOVER_PARENT", (char*)parent_widget);
  ih->handle = popover;

  /* Insert action group into the parent widget */
  gtk_widget_insert_action_group(parent_widget, "menu", G_ACTION_GROUP(action_group));

  /* Parent popover to the dialog's inner container */
  gtk_widget_set_parent(popover, parent_widget);

  /* Connect to parent's destroy signal to unparent popover before window is finalized */
  g_signal_connect(parent_widget, "destroy", G_CALLBACK(gtk4MenuParentDestroyCb), ih);

  gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);

  gtk_widget_set_halign(popover, GTK_ALIGN_START);

  {
    GdkRectangle pointing_rect = {local_x, local_y, 1, 1};
    gtk_popover_set_pointing_to(GTK_POPOVER(popover), &pointing_rect);
  }
  gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);

  /* Flush pending idles so GTK's separator sync runs before sizing */
  while (g_main_context_pending(NULL))
    g_main_context_iteration(NULL, FALSE);

  /* Show the popover */
  gtk_popover_popup(GTK_POPOVER(popover));

  /* Block here until popover is closed, the closed signal handler will quit the loop */
  g_main_loop_run(loop);
  g_main_loop_unref(loop);

  return IUP_NOERROR;
}

static int gtk4MenuMapMethod(Ihandle* ih)
{
  /* Menubars are created in iupgtk4DialogSetMenuBar(), but popup menus need GtkPopoverMenu */

  if (iupMenuIsMenuBar(ih))
  {
    /* Handled in iupgtk4DialogSetMenuBar() when dialog is mapped */
    return IUP_NOERROR;
  }

  /* This is a popup menu - store data for deferred creation in iupdrvMenuPopup.
   * We can't create the popover here because it needs a parent widget to resolve actions.
   * Store the action group and menu model, create popover when popup is called. */
  {
    GMenu* menu_model;
    GSimpleActionGroup* action_group;

    /* Create action group for this popup menu */
    action_group = g_simple_action_group_new();

    /* Build menu model from IUP hierarchy (is_root=FALSE for popup menus) */
    menu_model = gtk4BuildMenuModel(ih, action_group, FALSE);
    if (!menu_model)
    {
      g_object_unref(action_group);
      return IUP_ERROR;
    }

    /* Store for deferred popover creation */
    iupAttribSet(ih, "_IUPGTK4_MENU_MODEL", (char*)menu_model);
    iupAttribSet(ih, "_IUPGTK4_ACTION_GROUP", (char*)action_group);

    /* Set a placeholder handle so IupMap considers this mapped */
    ih->handle = (GtkWidget*)ih;  /* Placeholder, real handle set in iupdrvMenuPopup */
  }

  return IUP_NOERROR;
}

static void gtk4MenuUnMapMethod(Ihandle* ih)
{
  GMenu* menu_model = (GMenu*)iupAttribGet(ih, "_IUPGTK4_MENU_MODEL");
  GSimpleActionGroup* action_group = (GSimpleActionGroup*)iupAttribGet(ih, "_IUPGTK4_ACTION_GROUP");
  GtkWidget* popover = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER");
  GtkWidget* popover_parent = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_POPOVER_PARENT");
  GtkWidget* anchor_window = (GtkWidget*)iupAttribGet(ih, "_IUPGTK4_ANCHOR_WINDOW");

  /* Remove action group from parent widget first */
  if (popover_parent && GTK_IS_WIDGET(popover_parent))
  {
    /* Disconnect destroy signal before removing action group */
    g_signal_handlers_disconnect_by_func(popover_parent, gtk4MenuParentDestroyCb, ih);
    gtk_widget_insert_action_group(popover_parent, "menu", NULL);
    iupAttribSet(ih, "_IUPGTK4_POPOVER_PARENT", NULL);
  }

  /* Remove action group from anchor window if used */
  if (anchor_window && GTK_IS_WIDGET(anchor_window))
  {
    gtk_widget_insert_action_group(anchor_window, "menu", NULL);
  }

  if (popover && GTK_IS_WIDGET(popover))
  {
    GtkWidget* parent = gtk_widget_get_parent(popover);
    if (parent)
      gtk_widget_unparent(popover);

    iupAttribSet(ih, "_IUPGTK4_POPOVER", NULL);
  }

  /* Clean up anchor window if it was created */
  if (anchor_window && GTK_IS_WIDGET(anchor_window))
  {
    gtk_window_destroy(GTK_WINDOW(anchor_window));
    iupAttribSet(ih, "_IUPGTK4_ANCHOR_WINDOW", NULL);
  }

  if (menu_model)
  {
    g_object_unref(menu_model);
    iupAttribSet(ih, "_IUPGTK4_MENU_MODEL", NULL);
  }

  if (action_group)
  {
    g_object_unref(action_group);
    iupAttribSet(ih, "_IUPGTK4_ACTION_GROUP", NULL);
  }

  if (iupMenuIsMenuBar(ih))
    ih->parent = NULL;

  /* For popup menus, handle is the popover which we just cleaned up. */
  ih->handle = NULL;
}

IUP_SDK_API void iupdrvMenuInitClass(Iclass* ic)
{
  ic->Map = gtk4MenuMapMethod;
  ic->UnMap = gtk4MenuUnMapMethod;

  iupClassRegisterAttribute(ic, "FONT", NULL, NULL, IUPAF_SAMEASSYSTEM, "DEFAULTFONT", IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "BGCOLOR", NULL, iupdrvBaseSetBgColorAttrib, NULL, NULL, IUPAF_DEFAULT);
}

static int gtk4MenuItemSetValueAttrib(Ihandle* ih, const char* value)
{
  char* radio_action_name = (char*)iupAttribGet(ih, "_IUPGTK4_RADIO_ACTION_NAME");
  if (radio_action_name)
  {
    if (iupStrBoolean(value))
    {
      const char* target = iupAttribGet(ih, "_IUPGTK4_RADIO_TARGET");
      Ihandle* dialog = IupGetDialog(ih);
      GSimpleActionGroup* action_group = (GSimpleActionGroup*)iupAttribGet(dialog, "_IUPGTK4_MENU_ACTION_GROUP");
      if (target && action_group)
      {
        GAction* action = g_action_map_lookup_action(G_ACTION_MAP(action_group), radio_action_name);
        if (action)
          g_simple_action_set_state(G_SIMPLE_ACTION(action), g_variant_new_string(target));
      }
    }
    return 1;
  }

  /* GMenu-based system: check state is stored in GAction, not widget */
  char* action_name = (char*)iupAttribGet(ih, "_IUPGTK4_ACTION_NAME");

  if (action_name && iupAttribGet(ih, "_IUPGTK4_CHECKABLE"))
  {
    Ihandle* dialog = IupGetDialog(ih);
    GSimpleActionGroup* action_group = (GSimpleActionGroup*)iupAttribGet(dialog, "_IUPGTK4_MENU_ACTION_GROUP");


    if (action_group)
    {
      GAction* action = g_action_map_lookup_action(G_ACTION_MAP(action_group), action_name);
      if (action)
      {
        gboolean active = iupStrBoolean(value);
        g_simple_action_set_state(G_SIMPLE_ACTION(action), g_variant_new_boolean(active));
        /* Return 1 to store in hash table - needed for GetValue to work */
        return 1;
      }
    }
    /* Menu not built yet - return 1 to store in hash table for later use during menu building */
    return 1;
  }

  /* Always return 1 to store VALUE in hash table - needed for menu building to read initial state */
  return 1;
}

static char* gtk4MenuItemGetValueAttrib(Ihandle* ih)
{
  char* radio_action_name = (char*)iupAttribGet(ih, "_IUPGTK4_RADIO_ACTION_NAME");
  if (radio_action_name)
  {
    const char* target = iupAttribGet(ih, "_IUPGTK4_RADIO_TARGET");
    Ihandle* dialog = IupGetDialog(ih);
    GSimpleActionGroup* action_group = (GSimpleActionGroup*)iupAttribGet(dialog, "_IUPGTK4_MENU_ACTION_GROUP");
    if (target && action_group)
    {
      GAction* action = g_action_map_lookup_action(G_ACTION_MAP(action_group), radio_action_name);
      if (action)
      {
        GVariant* state = g_action_get_state(action);
        if (state)
        {
          const char* current = g_variant_get_string(state, NULL);
          int match = (strcmp(current, target) == 0);
          g_variant_unref(state);
          return iupStrReturnChecked(match);
        }
      }
    }
    return NULL;
  }

  /* GMenu-based system: check state is stored in GAction, not widget */
  char* action_name = (char*)iupAttribGet(ih, "_IUPGTK4_ACTION_NAME");

  if (action_name && iupAttribGet(ih, "_IUPGTK4_CHECKABLE"))
  {
    Ihandle* dialog = IupGetDialog(ih);
    GSimpleActionGroup* action_group = (GSimpleActionGroup*)iupAttribGet(dialog, "_IUPGTK4_MENU_ACTION_GROUP");

    if (action_group)
    {
      GAction* action = g_action_map_lookup_action(G_ACTION_MAP(action_group), action_name);
      if (action)
      {
        GVariant* state = g_action_get_state(action);
        if (state)
        {
          gboolean active = g_variant_get_boolean(state);
          g_variant_unref(state);
          return iupStrReturnChecked(active);
        }
      }
    }
  }

  return NULL;
}

static int gtk4MenuItemMapMethod(Ihandle* ih)
{
  /* Native menu system handles items in gtk4BuildMenuModel() */
  /* Items don't need native widgets - they're represented in GMenuModel */
  (void)ih;
  return IUP_NOERROR;
}

static int gtk4SubmenuMapMethod(Ihandle* ih)
{
  /* Native menu system handles submenus in gtk4BuildMenuModel() */
  (void)ih;
  return IUP_NOERROR;
}

static int gtk4MenuSeparatorMapMethod(Ihandle* ih)
{
  /* Native menu system handles separators in gtk4BuildMenuModel() */
  (void)ih;
  return IUP_NOERROR;
}

/*******************************************************************************************/

static void gtk4RecentActionActivated(GSimpleAction* action, GVariant* parameter, gpointer user_data)
{
  Ihandle* menu = (Ihandle*)user_data;
  Icallback recent_cb;
  Ihandle* config;
  const char* action_name;
  int index;

  if (!menu)
    return;

  recent_cb = (Icallback)iupAttribGet(menu, "_IUP_RECENT_CB");
  config = (Ihandle*)iupAttribGet(menu, "_IUP_CONFIG");

  if (!recent_cb || !config)
    return;

  action_name = g_action_get_name(G_ACTION(action));
  if (sscanf(action_name, "recent-%d", &index) == 1)
  {
    char attr_name[32];
    const char* filename;

    snprintf(attr_name, sizeof(attr_name), "_IUP_RECENT_FILE%d", index);
    filename = iupAttribGet(menu, attr_name);

    if (filename)
    {
      IupSetStrAttribute(config, "RECENTFILENAME", filename);
      IupSetStrAttribute(config, "TITLE", filename);
      config->parent = menu;

      recent_cb(config);

      config->parent = NULL;
      IupSetAttribute(config, "RECENTFILENAME", NULL);
      IupSetAttribute(config, "TITLE", NULL);
    }
  }

  (void)parameter;
}

IUP_SDK_API int iupdrvRecentMenuInit(Ihandle* menu, int max_recent, Icallback recent_cb)
{
  iupAttribSetInt(menu, "_IUP_RECENT_MAX", max_recent);
  iupAttribSet(menu, "_IUP_RECENT_CB", (char*)recent_cb);
  iupAttribSetInt(menu, "_IUP_RECENT_COUNT", 0);
  return 0;
}

IUP_SDK_API int iupdrvRecentMenuUpdate(Ihandle* menu, const char** filenames, int count, Icallback recent_cb)
{
  GSimpleActionGroup* action_group;
  GMenu* recent_menu;
  Ihandle* dialog;
  int max_recent, i, existing;

  if (!menu)
    return -1;

  max_recent = iupAttribGetInt(menu, "_IUP_RECENT_MAX");
  existing = iupAttribGetInt(menu, "_IUP_RECENT_COUNT");

  if (count > max_recent)
    count = max_recent;

  iupAttribSet(menu, "_IUP_RECENT_CB", (char*)recent_cb);

  for (i = 0; i < count; i++)
  {
    char attr_name[32];
    snprintf(attr_name, sizeof(attr_name), "_IUP_RECENT_FILE%d", i);
    iupAttribSetStr(menu, attr_name, filenames[i]);
  }

  for (; i < existing; i++)
  {
    char attr_name[32];
    snprintf(attr_name, sizeof(attr_name), "_IUP_RECENT_FILE%d", i);
    iupAttribSet(menu, attr_name, NULL);
  }

  iupAttribSetInt(menu, "_IUP_RECENT_COUNT", count);

  recent_menu = (GMenu*)iupAttribGet(menu, "_IUP_RECENT_GMENU");
  if (!recent_menu)
    return 0;

  /* Find the root menu by traversing up the parent chain */
  {
    Ihandle* root_menu = menu;
    while (root_menu->parent)
      root_menu = root_menu->parent;
    dialog = IupGetDialog(root_menu);
  }

  if (!dialog)
    return -1;

  action_group = (GSimpleActionGroup*)iupAttribGet(dialog, "_IUPGTK4_MENU_ACTION_GROUP");
  if (!action_group)
    return -1;

  for (i = 0; i < count; i++)
  {
    char action_name[32];
    GAction* existing_action;

    snprintf(action_name, sizeof(action_name), "recent-%d", i);

    existing_action = g_action_map_lookup_action(G_ACTION_MAP(action_group), action_name);
    if (!existing_action)
    {
      GSimpleAction* action = g_simple_action_new(action_name, NULL);
      g_signal_connect(action, "activate", G_CALLBACK(gtk4RecentActionActivated), menu);
      g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action));
      g_object_unref(action);
    }
  }

  g_menu_remove_all(recent_menu);

  for (i = 0; i < count; i++)
  {
    char action_name[64];
    snprintf(action_name, sizeof(action_name), "menu.recent-%d", i);
    g_menu_append(recent_menu, filenames[i], action_name);
  }

  return 0;
}

/*******************************************************************************************/

IUP_SDK_API void iupdrvMenuItemInitClass(Iclass* ic)
{
  ic->Map = gtk4MenuItemMapMethod;
  ic->UnMap = iupdrvBaseUnMapMethod;

  iupClassRegisterAttribute(ic, "FONT", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "ACTIVE", NULL, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "BGCOLOR", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "VALUE", gtk4MenuItemGetValueAttrib, gtk4MenuItemSetValueAttrib, NULL, NULL, IUPAF_NO_DEFAULTVALUE|IUPAF_NO_INHERIT|IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "TITLE", NULL, NULL, NULL, NULL, IUPAF_NO_DEFAULTVALUE|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TITLEIMAGE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMAGE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_IHANDLENAME|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMPRESS", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_IHANDLENAME|IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "HIDEMARK", NULL, NULL, NULL, NULL, IUPAF_DEFAULT);
}

IUP_SDK_API void iupdrvSubmenuInitClass(Iclass* ic)
{
  ic->Map = gtk4SubmenuMapMethod;
  ic->UnMap = iupdrvBaseUnMapMethod;

  iupClassRegisterAttribute(ic, "FONT", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "ACTIVE", NULL, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "BGCOLOR", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "TITLE", NULL, NULL, NULL, NULL, IUPAF_NO_DEFAULTVALUE|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMAGE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_IHANDLENAME|IUPAF_NO_INHERIT);
}

IUP_SDK_API void iupdrvMenuSeparatorInitClass(Iclass* ic)
{
  ic->Map = gtk4MenuSeparatorMapMethod;
  ic->UnMap = iupdrvBaseUnMapMethod;
}
