"""Windows 터치 키보드(TabTip) 표시/숨김 — ITipInvocation COM API 사용."""
import ctypes
import os
import sys
from ctypes import wintypes

_TABTIP_PATHS = (
    r"C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe",
    r"C:\Program Files (x86)\Common Files\microsoft shared\ink\TabTip.exe",
)

CLSID_UIHostNoLaunch = (
    0x4CE576FA,
    0x83DC,
    0x4F88,
    (0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76),
)
IID_ITipInvocation = (
    0x37C994E7,
    0x432B,
    0x4834,
    (0xA2, 0xF7, 0xDC, 0xE1, 0xF1, 0x3B, 0x83, 0x4B),
)

REGDB_E_CLASSNOTREG = -2147221164  # 0x80040154
SW_SHOW = 5
CLSCTX_LOCAL_SERVER = 4


class GUID(ctypes.Structure):
    _fields_ = [
        ("Data1", wintypes.DWORD),
        ("Data2", wintypes.WORD),
        ("Data3", wintypes.WORD),
        ("Data4", wintypes.BYTE * 8),
    ]

    def __init__(self, guid_tuple):
        super().__init__()
        self.Data1 = guid_tuple[0]
        self.Data2 = guid_tuple[1]
        self.Data3 = guid_tuple[2]
        self.Data4[:] = guid_tuple[3]


def _tabtip_path() -> str | None:
    for path in _TABTIP_PATHS:
        if os.path.exists(path):
            return path
    return None


def _launch_tabtip() -> bool:
    path = _tabtip_path()
    if not path:
        return False
    ret = ctypes.windll.shell32.ShellExecuteW(
        None, "open", path, None, None, SW_SHOW,
    )
    return ret > 32


def _create_tip_invocation(ole32):
    clsid = GUID(CLSID_UIHostNoLaunch)
    iid = GUID(IID_ITipInvocation)
    obj = ctypes.c_void_p()
    hr = ole32.CoCreateInstance(
        ctypes.byref(clsid),
        None,
        CLSCTX_LOCAL_SERVER,
        ctypes.byref(iid),
        ctypes.byref(obj),
    )
    if hr == REGDB_E_CLASSNOTREG:
        if not _launch_tabtip():
            return None
        hr = ole32.CoCreateInstance(
            ctypes.byref(clsid),
            None,
            CLSCTX_LOCAL_SERVER,
            ctypes.byref(iid),
            ctypes.byref(obj),
        )
    if hr != 0:
        return None
    return obj


def _toggle_touch_keyboard() -> bool:
    ole32 = ctypes.windll.ole32
    user32 = ctypes.windll.user32

    obj = _create_tip_invocation(ole32)
    if not obj:
        return False

    vtable_ptr = ctypes.cast(obj, ctypes.POINTER(ctypes.c_void_p)).contents.value
    vtable = ctypes.cast(vtable_ptr, ctypes.POINTER(ctypes.c_void_p))
    toggle_fn = ctypes.WINFUNCTYPE(
        ctypes.c_long, ctypes.c_void_p, wintypes.HWND,
    )(vtable[3])

    hr = toggle_fn(obj, user32.GetDesktopWindow())
    release_fn = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)(vtable[2])
    release_fn(obj)
    return hr == 0


def show_touch_keyboard() -> bool:
    """터치 키보드를 화면에 표시한다."""
    if sys.platform != "win32":
        return False
    ole32 = ctypes.windll.ole32
    ole32.CoInitialize(None)
    try:
        return _toggle_touch_keyboard()
    finally:
        ole32.CoUninitialize()


def hide_touch_keyboard() -> bool:
    """표시 중인 터치 키보드를 숨긴다 (Toggle)."""
    return show_touch_keyboard()
