/** \file
 * \brief WinUI Driver
 *
 * See Copyright Notice in "iup.h"
 */

extern "C" {
#include "iup.h"
#include "iupcbs.h"
#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drv.h"
#include "iup_class.h"
#include "iup_childtree.h"
#include "iup_canvas.h"
#include "iup_image.h"
}

#include "iupwinui_drv.h"

#include <winrt/Microsoft.UI.Xaml.Automation.h>

using namespace winrt;
using namespace Microsoft::UI::Xaml;
using namespace Microsoft::UI::Xaml::Controls;
using namespace Microsoft::UI::Xaml::Media;
using namespace Windows::Foundation;

/****************************************************************************
 * Color Management
 ****************************************************************************/

IUP_DRV_API void iupwinuiSetBgColor(InativeHandle* handle, unsigned char r, unsigned char g, unsigned char b)
{
  if (!handle)
    return;

  IInspectable obj{nullptr};
  winrt::copy_from_abi(obj, handle);
  Control ctrl = obj.try_as<Control>();
  if (ctrl)
  {
    Windows::UI::Color color;
    color.A = 255;
    color.R = r;
    color.G = g;
    color.B = b;
    ctrl.Background(SolidColorBrush(color));
  }
}

IUP_DRV_API void iupwinuiSetFgColor(InativeHandle* handle, unsigned char r, unsigned char g, unsigned char b)
{
  if (!handle)
    return;

  IInspectable obj{nullptr};
  winrt::copy_from_abi(obj, handle);
  Control ctrl = obj.try_as<Control>();
  if (ctrl)
  {
    Windows::UI::Color color;
    color.A = 255;
    color.R = r;
    color.G = g;
    color.B = b;
    ctrl.Foreground(SolidColorBrush(color));
  }
}

/****************************************************************************
 * Widget Management
 ****************************************************************************/

IUP_DRV_API void iupwinuiAddToParent(Ihandle* ih)
{
  if (!ih || !ih->handle || winuiHandleIsHWND(ih))
    return;

  Canvas parentCanvas = iupwinuiGetParentCanvas(ih);
  if (!parentCanvas)
    return;

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    parentCanvas.Children().Append(elem);
}

/****************************************************************************
 * Base Driver Functions
 ****************************************************************************/

static void winuiSubtractHwndChildren(Ihandle* ih, HRGN rgn)
{
  while (ih)
  {
    if (ih->handle && winuiHandleIsHWND(ih) && ih->iclass->nativetype != IUP_TYPEDIALOG)
    {
      if (ih->currentwidth > 0 && ih->currentheight > 0)
      {
        HRGN childRgn = CreateRectRgn(ih->x, ih->y, ih->x + ih->currentwidth, ih->y + ih->currentheight);
        CombineRgn(rgn, rgn, childRgn, RGN_DIFF);
        DeleteObject(childRgn);
      }
    }

    if (ih->firstchild)
      winuiSubtractHwndChildren(ih->firstchild, rgn);

    ih = ih->brother;
  }
}

static void winuiUpdateIslandClipRegion(Ihandle* ih)
{
  Ihandle* dialog = IupGetDialog(ih);
  if (!dialog || !dialog->handle)
    return;

  IupWinUIDialogAux* aux = winuiGetAux<IupWinUIDialogAux>(dialog, IUPWINUI_DIALOG_AUX);
  if (!aux || !aux->islandHwnd)
    return;

  RECT rect;
  GetClientRect((HWND)dialog->handle, &rect);

  HRGN rgn = CreateRectRgn(0, 0, rect.right, rect.bottom);
  winuiSubtractHwndChildren(dialog->firstchild, rgn);
  SetWindowRgn(aux->islandHwnd, rgn, TRUE);
}

extern "C" IUP_SDK_API void iupdrvBaseLayoutUpdateMethod(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (ih->currentwidth > 0 && ih->currentheight > 0)
      SetWindowPos(hwnd, NULL, ih->x, ih->y, ih->currentwidth, ih->currentheight, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
    else
      SetWindowPos(hwnd, NULL, ih->x, ih->y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);

    if (ih->iclass->nativetype != IUP_TYPEDIALOG)
      winuiUpdateIslandClipRegion(ih);

    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    Canvas::SetLeft(elem, ih->x);
    Canvas::SetTop(elem, ih->y);

    FrameworkElement fe = elem.try_as<FrameworkElement>();
    if (fe)
    {
      if (ih->currentwidth > 0)
        fe.Width(ih->currentwidth);
      if (ih->currentheight > 0)
        fe.Height(ih->currentheight);
    }
  }
}

extern "C" IUP_SDK_API void iupdrvBaseUnMapMethod(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return;

  iupwinuiTipsDestroy(ih);

  if (winuiHandleIsHWND(ih))
    return;

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    FrameworkElement fe = elem.try_as<FrameworkElement>();
    if (fe)
    {
      DependencyObject parent = fe.Parent();
      if (parent)
      {
        Panel panel = parent.try_as<Panel>();
        if (panel)
        {
          uint32_t index;
          if (panel.Children().IndexOf(elem, index))
            panel.Children().RemoveAt(index);
        }
      }
    }
  }

  winuiReleaseHandle<UIElement>(ih);
}

static int winuiGetDialogMenuHeight(Ihandle* dialog)
{
  IupWinUIDialogAux* aux = winuiGetAux<IupWinUIDialogAux>(dialog, IUPWINUI_DIALOG_AUX);
  if (aux && aux->menuBar)
  {
    double h = aux->menuBar.ActualHeight();
    return (h > 0) ? (int)h : 40;
  }
  return 0;
}

extern "C" IUP_SDK_API void iupdrvScreenToClient(Ihandle* ih, int *x, int *y)
{
  if (!ih || !ih->handle)
    return;

  Ihandle* dialog = IupGetDialog(ih);
  if (!dialog || !dialog->handle)
    return;

  HWND hwnd = (HWND)dialog->handle;
  POINT p;
  p.x = *x;
  p.y = *y;
  ScreenToClient(hwnd, &p);

  if (ih != dialog)
  {
    p.x -= ih->x;
    p.y -= ih->y + winuiGetDialogMenuHeight(dialog);
  }

  *x = p.x;
  *y = p.y;
}

extern "C" IUP_SDK_API void iupdrvClientToScreen(Ihandle* ih, int *x, int *y)
{
  if (!ih || !ih->handle)
    return;

  Ihandle* dialog = IupGetDialog(ih);
  if (!dialog || !dialog->handle)
    return;

  HWND hwnd = (HWND)dialog->handle;
  POINT p;

  if (ih != dialog)
  {
    p.x = ih->x + *x;
    p.y = ih->y + *y + winuiGetDialogMenuHeight(dialog);
  }
  else
  {
    p.x = *x;
    p.y = *y;
  }

  ClientToScreen(hwnd, &p);
  *x = p.x;
  *y = p.y;
}

extern "C" IUP_SDK_API int iupdrvBaseSetZorderAttrib(Ihandle* ih, const char* value)
{
  if (!ih || !ih->handle || winuiHandleIsHWND(ih))
    return 0;

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    if (iupStrEqualNoCase(value, "TOP"))
    {
      int z = Canvas::GetZIndex(elem);
      Canvas::SetZIndex(elem, z + 1);
    }
    else if (iupStrEqualNoCase(value, "BOTTOM"))
    {
      int z = Canvas::GetZIndex(elem);
      Canvas::SetZIndex(elem, z - 1);
    }
    return 1;
  }

  return 0;
}

extern "C" IUP_SDK_API void iupdrvSetVisible(Ihandle* ih, int visible)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE);
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    elem.Visibility(visible ? Visibility::Visible : Visibility::Collapsed);
}

extern "C" IUP_SDK_API int iupdrvIsVisible(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return 0;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      return IsWindowVisible(hwnd);
    return 0;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    return elem.Visibility() == Visibility::Visible ? 1 : 0;

  return 0;
}

extern "C" IUP_SDK_API void iupdrvSetActive(Ihandle* ih, int active)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    if (IupClassMatch(ih, "dialog"))
    {
      IupWinUIDialogAux* dlgaux = winuiGetAux<IupWinUIDialogAux>(ih, IUPWINUI_DIALOG_AUX);
      if (dlgaux && dlgaux->rootPanel)
        dlgaux->rootPanel.IsHitTestVisible(active ? true : false);
      return;
    }

    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      EnableWindow(hwnd, active);
    return;
  }

  Control ctrl = winuiGetHandle<Control>(ih);
  if (ctrl)
  {
    ctrl.IsEnabled(active ? true : false);
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    elem.IsHitTestVisible(active ? true : false);
}

extern "C" IUP_SDK_API int iupdrvIsActive(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return 0;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      return IsWindowEnabled(hwnd);
    return 0;
  }

  Control ctrl = winuiGetHandle<Control>(ih);
  if (ctrl)
    return ctrl.IsEnabled() ? 1 : 0;

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    return elem.IsHitTestVisible() ? 1 : 0;

  return 1;
}

IUP_DRV_API void iupwinuiCanvasCallAction(Ihandle* ih)
{
  IFn cb = (IFn)IupGetCallback(ih, "ACTION");
  if (cb && !(ih->data->inside_resize) && ih->currentwidth > 0 && ih->currentheight > 0)
  {
    iupAttribSetStrf(ih, "CLIPRECT", "%d %d %d %d", 0, 0, ih->currentwidth, ih->currentheight);
    cb(ih);
    iupAttribSet(ih, "CLIPRECT", NULL);
  }
}

extern "C" IUP_SDK_API void iupdrvPostRedraw(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      InvalidateRect(hwnd, NULL, FALSE);
    return;
  }

  if (IupClassMatch(ih, "canvas"))
  {
    if (iupAttribGet(ih, "_IUPWINUI_REDRAW_PENDING"))
      return;

    iupAttribSet(ih, "_IUPWINUI_REDRAW_PENDING", "1");

    void* dq_ptr = iupwinuiGetDispatcherQueue();
    if (dq_ptr)
    {
      IupWinUICanvasAux* aux = winuiGetAux<IupWinUICanvasAux>(ih, IUPWINUI_CANVAS_AUX);
      if (!aux)
        return;
      auto alive = aux->alive;

      Windows::Foundation::IInspectable dq_obj{nullptr};
      winrt::copy_from_abi(dq_obj, dq_ptr);
      Microsoft::UI::Dispatching::DispatcherQueue dq = dq_obj.as<Microsoft::UI::Dispatching::DispatcherQueue>();

      dq.TryEnqueue([ih, alive]() {
        if (!*alive)
          return;
        iupAttribSet(ih, "_IUPWINUI_REDRAW_PENDING", NULL);
        iupwinuiCanvasCallAction(ih);
      });
    }
    else
    {
      iupAttribSet(ih, "_IUPWINUI_REDRAW_PENDING", NULL);
      iupwinuiCanvasCallAction(ih);
    }
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
    elem.InvalidateArrange();
}

extern "C" IUP_SDK_API void iupdrvRedrawNow(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
      RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_INTERNALPAINT | RDW_UPDATENOW);
    return;
  }

  if (IupClassMatch(ih, "canvas"))
  {
    iupwinuiCanvasCallAction(ih);
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    elem.InvalidateArrange();
    elem.UpdateLayout();
  }
}

extern "C" IUP_SDK_API void iupdrvReparent(Ihandle* ih)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    Ihandle* dialog = IupGetDialog(ih);
    if (dialog && dialog->handle)
      SetParent((HWND)ih->handle, (HWND)dialog->handle);
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (!elem)
    return;

  FrameworkElement fe = elem.try_as<FrameworkElement>();
  if (fe)
  {
    DependencyObject oldParent = fe.Parent();
    if (oldParent)
    {
      Panel oldPanel = oldParent.try_as<Panel>();
      if (oldPanel)
      {
        uint32_t index;
        if (oldPanel.Children().IndexOf(elem, index))
          oldPanel.Children().RemoveAt(index);
      }
    }
  }

  Canvas newParent = iupwinuiGetParentCanvas(ih);
  if (newParent)
    newParent.Children().Append(elem);
}

extern "C" IUP_SDK_API void iupdrvSendKey(int key, int press)
{
  unsigned int keyval, state;
  INPUT input[2];
  WORD state_scan = 0, key_scan;
  ZeroMemory(input, 2 * sizeof(INPUT));

  iupdrvKeyEncode(key, &keyval, &state);
  if (!keyval)
    return;

  LPARAM extra_info = GetMessageExtraInfo();
  if (state)
    state_scan = (WORD)MapVirtualKey(state, MAPVK_VK_TO_VSC);
  key_scan = (WORD)MapVirtualKey(keyval, MAPVK_VK_TO_VSC);

  if (press & 0x01)
  {
    if (state)
    {
      input[0].type = INPUT_KEYBOARD;
      input[0].ki.wVk = (WORD)state;
      input[0].ki.wScan = state_scan;
      input[0].ki.dwExtraInfo = extra_info;

      input[1].type = INPUT_KEYBOARD;
      input[1].ki.wVk = (WORD)keyval;
      input[1].ki.wScan = key_scan;
      input[1].ki.dwExtraInfo = extra_info;

      SendInput(2, input, sizeof(INPUT));
    }
    else
    {
      input[0].type = INPUT_KEYBOARD;
      input[0].ki.wVk = (WORD)keyval;
      input[0].ki.wScan = key_scan;
      input[0].ki.dwExtraInfo = extra_info;

      SendInput(1, input, sizeof(INPUT));
    }
  }

  if (press & 0x02)
  {
    ZeroMemory(input, 2 * sizeof(INPUT));

    if (state)
    {
      input[0].type = INPUT_KEYBOARD;
      input[0].ki.dwFlags = KEYEVENTF_KEYUP;
      input[0].ki.wVk = (WORD)keyval;
      input[0].ki.wScan = key_scan;
      input[0].ki.dwExtraInfo = extra_info;

      input[1].type = INPUT_KEYBOARD;
      input[1].ki.dwFlags = KEYEVENTF_KEYUP;
      input[1].ki.wVk = (WORD)state;
      input[1].ki.wScan = state_scan;
      input[1].ki.dwExtraInfo = extra_info;

      SendInput(2, input, sizeof(INPUT));
    }
    else
    {
      input[0].type = INPUT_KEYBOARD;
      input[0].ki.dwFlags = KEYEVENTF_KEYUP;
      input[0].ki.wVk = (WORD)keyval;
      input[0].ki.wScan = key_scan;
      input[0].ki.dwExtraInfo = extra_info;

      SendInput(1, input, sizeof(INPUT));
    }
  }
}

extern "C" IUP_SDK_API void iupdrvWarpPointer(int x, int y)
{
  SetCursorPos(x, y);
}

static DWORD winuiGetButtonFlags(int bt, int pressed)
{
  if (pressed)
  {
    switch (bt)
    {
    case IUP_BUTTON1: return MOUSEEVENTF_LEFTDOWN;
    case IUP_BUTTON2: return MOUSEEVENTF_MIDDLEDOWN;
    case IUP_BUTTON3: return MOUSEEVENTF_RIGHTDOWN;
    case IUP_BUTTON4: return MOUSEEVENTF_XDOWN;
    case IUP_BUTTON5: return MOUSEEVENTF_XDOWN;
    }
  }
  else
  {
    switch (bt)
    {
    case IUP_BUTTON1: return MOUSEEVENTF_LEFTUP;
    case IUP_BUTTON2: return MOUSEEVENTF_MIDDLEUP;
    case IUP_BUTTON3: return MOUSEEVENTF_RIGHTUP;
    case IUP_BUTTON4: return MOUSEEVENTF_XUP;
    case IUP_BUTTON5: return MOUSEEVENTF_XUP;
    }
  }
  return 0;
}

extern "C" IUP_SDK_API void iupdrvSendMouse(int x, int y, int bt, int status)
{
  INPUT input;
  int vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
  int vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
  int vw = GetSystemMetrics(SM_CXVIRTUALSCREEN);
  int vh = GetSystemMetrics(SM_CYVIRTUALSCREEN);
  if (vw <= 0) vw = 1;
  if (vh <= 0) vh = 1;

  SetCursorPos(x, y);

  ZeroMemory(&input, sizeof(INPUT));
  input.type = INPUT_MOUSE;
  input.mi.dx = (LONG)(((LONGLONG)(x - vx) * 65535 + vw / 2) / vw);
  input.mi.dy = (LONG)(((LONGLONG)(y - vy) * 65535 + vh / 2) / vh);
  input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
  input.mi.dwExtraInfo = GetMessageExtraInfo();

  if (bt != 'W' && status == -1)
  {
    input.mi.dwFlags |= MOUSEEVENTF_MOVE;
  }
  else
  {
    if (bt != 'W')
      input.mi.dwFlags |= winuiGetButtonFlags(bt, status);

    switch (bt)
    {
    case 'W':
      input.mi.mouseData = status * 120;
      input.mi.dwFlags |= MOUSEEVENTF_WHEEL;
      break;
    case IUP_BUTTON4:
      input.mi.mouseData = XBUTTON1;
      break;
    case IUP_BUTTON5:
      input.mi.mouseData = XBUTTON2;
      break;
    }
  }

  if (status == 2)
  {
    SendInput(1, &input, sizeof(INPUT));

    input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
    input.mi.dwFlags |= winuiGetButtonFlags(bt, 0);
    SendInput(1, &input, sizeof(INPUT));

    input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
    input.mi.dwFlags |= winuiGetButtonFlags(bt, 1);
    SendInput(1, &input, sizeof(INPUT));

    input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
    input.mi.dwFlags |= winuiGetButtonFlags(bt, 0);
    SendInput(1, &input, sizeof(INPUT));
  }
  else
    SendInput(1, &input, sizeof(INPUT));
}

extern "C" IUP_SDK_API int iupdrvBaseSetBgColorAttrib(Ihandle* ih, const char* value)
{
  unsigned char r, g, b;
  if (!iupStrToRGB(value, &r, &g, &b))
    return 0;
  if (winuiHandleIsHWND(ih))
    return 1;
  iupwinuiSetBgColor(ih->handle, r, g, b);
  return 1;
}

extern "C" IUP_SDK_API int iupdrvBaseSetFgColorAttrib(Ihandle* ih, const char* value)
{
  unsigned char r, g, b;
  if (!iupStrToRGB(value, &r, &g, &b))
    return 0;
  if (winuiHandleIsHWND(ih))
    return 1;
  iupwinuiSetFgColor(ih->handle, r, g, b);
  return 1;
}

static struct {
  const char* iupname;
  LPCTSTR sysid;
  int xamlShape;  /* Microsoft::UI::Input::InputSystemCursorShape or -1 */
} winuiCursorTable[] = {
  { "ARROW",          IDC_ARROW,       0 },   /* Arrow */
  { "BUSY",           IDC_WAIT,        13 },  /* Wait */
  { "CROSS",          IDC_CROSS,       1 },   /* Cross */
  { "HAND",           IDC_HAND,        3 },   /* Hand */
  { "HELP",           IDC_HELP,        4 },   /* Help */
  { "IUP",            IDC_HELP,        4 },   /* Help */
  { "MOVE",           IDC_SIZEALL,     6 },   /* SizeAll */
  { "PEN",            IDC_CROSS,       1 },   /* Cross */
  { "RESIZE_N",       IDC_SIZENS,      8 },   /* SizeNorthSouth */
  { "RESIZE_S",       IDC_SIZENS,      8 },   /* SizeNorthSouth */
  { "RESIZE_NS",      IDC_SIZENS,      8 },   /* SizeNorthSouth */
  { "SPLITTER_HORIZ", IDC_SIZENS,      8 },   /* SizeNorthSouth */
  { "RESIZE_W",       IDC_SIZEWE,      10 },  /* SizeWestEast */
  { "RESIZE_E",       IDC_SIZEWE,      10 },  /* SizeWestEast */
  { "RESIZE_WE",      IDC_SIZEWE,      10 },  /* SizeWestEast */
  { "SPLITTER_VERT",  IDC_SIZEWE,      10 },  /* SizeWestEast */
  { "RESIZE_NE",      IDC_SIZENESW,    7 },   /* SizeNortheastSouthwest */
  { "RESIZE_SW",      IDC_SIZENESW,    7 },   /* SizeNortheastSouthwest */
  { "RESIZE_SE",      IDC_SIZENWSE,    9 },   /* SizeNorthwestSoutheast */
  { "RESIZE_NW",      IDC_SIZENWSE,    9 },   /* SizeNorthwestSoutheast */
  { "TEXT",           IDC_IBEAM,       5 },   /* IBeam */
  { "UPARROW",        IDC_UPARROW,     12 },  /* UpArrow */
  { "APPSTARTING",    IDC_APPSTARTING,  16 },  /* AppStarting */
  { "NO",             IDC_NO,          11 },   /* UniversalNo */
  { "NONE",           NULL,            -1 },
  { "NULL",           NULL,            -1 }
};

static HCURSOR winuiGetCursor(Ihandle* ih, const char* name)
{
  int i, count = sizeof(winuiCursorTable) / sizeof(winuiCursorTable[0]);

  for (i = 0; i < count; i++)
  {
    if (iupStrEqualNoCase(name, winuiCursorTable[i].iupname))
    {
      if (!winuiCursorTable[i].sysid)
        return NULL;
      return LoadCursor(NULL, winuiCursorTable[i].sysid);
    }
  }

  HCURSOR cur = (HCURSOR)iupImageGetCursor(name);
  if (cur)
    return cur;

  (void)ih;
  return LoadCursor(NULL, IDC_ARROW);
}

static void winuiSetXamlCursor(Ihandle* ih, const char* name)
{
  using namespace Microsoft::UI::Input;

  int shape = -1;
  int i, count = sizeof(winuiCursorTable) / sizeof(winuiCursorTable[0]);

  for (i = 0; i < count; i++)
  {
    if (iupStrEqualNoCase(name, winuiCursorTable[i].iupname))
    {
      shape = winuiCursorTable[i].xamlShape;
      break;
    }
  }

  if (shape < 0)
    shape = 0;  /* Arrow */

  UIElement element{nullptr};
  Windows::Foundation::IInspectable obj{nullptr};
  winrt::copy_from_abi(obj, ih->handle);
  element = obj.try_as<UIElement>();

  if (element)
  {
    auto protectedUI = element.try_as<Microsoft::UI::Xaml::IUIElementProtected>();
    if (protectedUI)
      protectedUI.ProtectedCursor(InputSystemCursor::Create(static_cast<InputSystemCursorShape>(shape)));
  }
}

extern "C" IUP_SDK_API int iupdrvBaseSetCursorAttrib(Ihandle* ih, const char* value)
{
  if (!ih || !ih->handle || !value)
    return 0;

  if (winuiHandleIsHWND(ih))
  {
    HCURSOR hCur = winuiGetCursor(ih, value);
    iupAttribSet(ih, "_IUPWIN_HCURSOR", (char*)hCur);
    if (hCur)
      SetCursor(hCur);
  }
  else
    winuiSetXamlCursor(ih, value);

  return 1;
}

extern "C" IUP_SDK_API int iupdrvGetScrollbarSize(void)
{
  int xv = GetSystemMetrics(SM_CXVSCROLL);
  int yh = GetSystemMetrics(SM_CYHSCROLL);
  return xv > yh ? xv : yh;
}

extern "C" IUP_SDK_API void iupdrvSetAccessibleTitle(Ihandle* ih, const char* title)
{
  if (!ih || !ih->handle)
    return;

  if (winuiHandleIsHWND(ih))
  {
    HWND hwnd = (HWND)ih->handle;
    if (hwnd)
    {
      if (!title)
        SetWindowTextA(hwnd, "");
      else
        SetWindowTextA(hwnd, title);
    }
    return;
  }

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    DependencyObject dep = elem.try_as<DependencyObject>();
    if (dep)
    {
      hstring name = title ? iupwinuiStringToHString(title) : hstring();
      Automation::AutomationProperties::SetName(dep, name);
    }
  }
}

extern "C" IUP_SDK_API void iupdrvSetAccessibleDescription(Ihandle* ih, const char* description)
{
  if (!ih || !ih->handle || winuiHandleIsHWND(ih))
    return;

  UIElement elem = winuiGetHandle<UIElement>(ih);
  if (elem)
  {
    DependencyObject dep = elem.try_as<DependencyObject>();
    if (dep)
    {
      hstring help = description ? iupwinuiStringToHString(description) : hstring();
      Automation::AutomationProperties::SetHelpText(dep, help);
    }
  }
}

extern "C" IUP_SDK_API void iupdrvBaseRegisterCommonAttrib(Iclass* ic)
{
  (void)ic;
}

extern "C" IUP_SDK_API void iupdrvBaseRegisterVisualAttrib(Iclass* ic)
{
  iupClassRegisterAttribute(ic, "TIPMARKUP", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPICON", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPDELAY", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "TIPRECT", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED|IUPAF_DEFAULT);
}

IUP_DRV_API Canvas iupwinuiGetParentCanvas(Ihandle* ih)
{
  Ihandle* parent = iupChildTreeGetNativeParent(ih);
  if (!parent || !parent->handle)
    return nullptr;

  if (IupClassMatch(parent, "dialog"))
  {
    IupWinUIDialogAux* aux = winuiGetAux<IupWinUIDialogAux>(parent, IUPWINUI_DIALOG_AUX);
    if (aux)
      return aux->contentCanvas;
  }
  else if (IupClassMatch(parent, "frame"))
  {
    IupWinUIFrameAux* aux = winuiGetAux<IupWinUIFrameAux>(parent, IUPWINUI_FRAME_AUX);
    if (aux)
      return aux->innerCanvas;
  }
  else if (IupClassMatch(parent, "popover"))
  {
    IupWinUIPopoverAux* aux = winuiGetAux<IupWinUIPopoverAux>(parent, IUPWINUI_POPOVER_AUX);
    if (aux)
      return aux->innerCanvas;
  }
  else if (IupClassMatch(parent, "tabs"))
  {
    Ihandle* tabChild = ih;
    while (tabChild && tabChild->parent != parent)
      tabChild = tabChild->parent;

    if (tabChild)
    {
      char* containerPtr = iupAttribGet(tabChild, "_IUPTAB_CONTAINER");
      if (containerPtr)
      {
        IInspectable obj{nullptr};
        winrt::copy_from_abi(obj, containerPtr);
        return obj.try_as<Canvas>();
      }
    }
  }

  return winuiGetHandle<Canvas>(parent);
}

/****************************************************************************
 * String Conversion (UTF-8/UTF-16)
 ****************************************************************************/

IUP_DRV_API hstring iupwinuiStringToHString(const char* str)
{
  if (!str || !str[0])
    return hstring();

  int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
  if (wlen <= 0)
    return hstring();

  std::wstring wstr(wlen - 1, L'\0');
  MultiByteToWideChar(CP_UTF8, 0, str, -1, &wstr[0], wlen);

  return hstring(wstr);
}

IUP_DRV_API char* iupwinuiHStringToString(const hstring& str)
{
  if (str.empty())
    return NULL;

  int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
  if (len <= 0)
    return NULL;

  char* buf = iupStrGetMemory(len);
  WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, buf, len, NULL, NULL);

  return buf;
}

IUP_DRV_API std::wstring iupwinuiStringToWString(const char* str)
{
  if (!str || !str[0])
    return std::wstring();

  int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
  if (wlen <= 0)
    return std::wstring();

  std::wstring wstr(wlen - 1, L'\0');
  MultiByteToWideChar(CP_UTF8, 0, str, -1, &wstr[0], wlen);

  return wstr;
}

IUP_DRV_API hstring iupwinuiProcessMnemonic(const char* str, char* c)
{
  if (c)
    *c = 0;

  if (!str || !str[0])
    return hstring();

  char mnemonic = 0;
  char* processed = iupStrProcessMnemonic(str, &mnemonic, -1);
  if (!processed)
    return hstring();

  hstring result = iupwinuiStringToHString(processed);

  if (processed != str)
    free(processed);

  if (c)
    *c = mnemonic;

  return result;
}
