import ctypes
from ctypes import wintypes
import json
import os
from pathlib import Path

import comtypes
import win32gui
from comtypes import COMMETHOD, GUID, HRESULT, IUnknown

from .lifecycle import hard_exit
from .resources import get_resource_path
from .window import set_close_guard, show_window, start_window_drag


def _pick_newest_version_dir(base_path: str) -> str:
    if not os.path.isdir(base_path):
        return ""
    version_dirs = [
        entry
        for entry in os.listdir(base_path)
        if os.path.isdir(os.path.join(base_path, entry))
    ]
    if not version_dirs:
        return ""
    version_dirs.sort(reverse=True)
    return os.path.join(base_path, version_dirs[0])


def _first_existing_path(paths) -> str:
    for path in paths:
        if path and os.path.exists(path):
            return path
    return ""


def _resolve_loader_path() -> str:
    # 1) Env override
    env_path = os.environ.get("WEBVIEW2_LOADER_PATH", "").strip()
    if env_path and os.path.exists(env_path):
        return env_path

    # 2) Current directory or package directory
    local_paths = [
        "WebView2Loader.dll",
        os.path.join(os.path.dirname(__file__), "WebView2Loader.dll"),
    ]
    found = _first_existing_path(local_paths)
    if found:
        return found

    # 3) Common install locations (Office/EdgeWebView/Adobe)
    candidates = []
    common_program_files = [
        os.environ.get("CommonProgramFiles", ""),
        os.environ.get("CommonProgramFiles(x86)", ""),
    ]
    program_files = [
        os.environ.get("ProgramFiles", ""),
        os.environ.get("ProgramFiles(x86)", ""),
        os.environ.get("ProgramW6432", ""),
    ]

    for base in common_program_files:
        candidates.extend(
            [
                os.path.join(base, "Microsoft", "EdgeWebView", "WebView2Loader.dll"),
                os.path.join(
                    base, "Adobe", "Microsoft", "EdgeWebView", "WebView2Loader.dll"
                ),
            ]
        )

    for base in program_files:
        candidates.extend(
            [
                os.path.join(
                    base, "Microsoft Office", "root", "Office16", "WebView2Loader.dll"
                ),
            ]
        )

    found = _first_existing_path(candidates)
    if found:
        return found

    # 4) Versioned EdgeWebView application directories
    for base in program_files:
        app_root = os.path.join(base, "Microsoft", "EdgeWebView", "Application")
        newest = _pick_newest_version_dir(app_root)
        if newest:
            candidate = os.path.join(newest, "WebView2Loader.dll")
            if os.path.exists(candidate):
                return candidate

    # 5) If not found, try system path as a last resort
    return "WebView2Loader.dll"


def _debug_log(message: str) -> None:
    if os.environ.get("WEBVIEW_FORM_DEBUG") == "1":
        print(message, flush=True)


def _load_webview2_loader():
    try:
        loader_path = _resolve_loader_path()
        _debug_log(f"WebView2Loader.dll resolved to: {loader_path}")
        return ctypes.WinDLL(loader_path)
    except Exception:
        _debug_log("Failed to load WebView2Loader.dll")
        hard_exit()


# Load WebView2Loader.dll
webview2_loader = _load_webview2_loader()

# Define IIDs
IID_ICoreWebView2Environment = GUID("{B96D6187-3505-40E9-A191-AE52151B2E3C}")
IID_ICoreWebView2Controller = GUID("{4D00C0D1-9435-4033-A696-03CF81F26D7A}")
IID_ICoreWebView2 = GUID("{76ECEACB-0462-4294-87D7-5CD0F86F3CC6}")
IID_ICoreWebView2Settings = GUID("{E562E4F0-D7FA-43AC-8D71-C05150499F00}")
IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler = GUID(
    "{4E8A3389-C9D8-4BD2-B6B5-124FEE6CC14D}"
)
IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler = GUID(
    "{6C4819F3-C9B7-4260-8127-C9F5BDE7F68C}"
)
IID_ICoreWebView2WebMessageReceivedEventHandler = GUID(
    "{57213F19-00E6-49FA-8E07-898EA01ECBD2}"
)
IID_ICoreWebView2WebMessageReceivedEventArgs = GUID(
    "{0F99A40C-E962-4207-9E92-E3D542EFF849}"
)
IID_ICoreWebView2ProcessFailedEventHandler = GUID(
    "{8155A9F4-1330-4D50-9146-903543DD638D}"
)
IID_ICoreWebView2ProcessFailedEventArgs = GUID(
    "{8155A9F4-1330-4D50-9146-903543DD638D}"
)  # Re-use if needed or generic

# Define COM Interfaces


class ICoreWebView2Environment(IUnknown):
    _iid_ = IID_ICoreWebView2Environment
    _methods_ = []


class ICoreWebView2Controller(IUnknown):
    _iid_ = IID_ICoreWebView2Controller
    _methods_ = []


class ICoreWebView2(IUnknown):
    _iid_ = IID_ICoreWebView2
    _methods_ = []


class ICoreWebView2Settings(IUnknown):
    _iid_ = IID_ICoreWebView2Settings
    # Methods must be in exact vtable order per WebView2 SDK
    _methods_ = [
        # 1. get_IsScriptEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_IsScriptEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "isScriptEnabled"),
        ),
        # 2. put_IsScriptEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_IsScriptEnabled",
            (["in"], wintypes.BOOL, "isScriptEnabled"),
        ),
        # 3. get_IsWebMessageEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_IsWebMessageEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "isWebMessageEnabled"),
        ),
        # 4. put_IsWebMessageEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_IsWebMessageEnabled",
            (["in"], wintypes.BOOL, "isWebMessageEnabled"),
        ),
        # 5. get_AreDefaultScriptDialogsEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_AreDefaultScriptDialogsEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "areDefaultScriptDialogsEnabled"),
        ),
        # 6. put_AreDefaultScriptDialogsEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_AreDefaultScriptDialogsEnabled",
            (["in"], wintypes.BOOL, "areDefaultScriptDialogsEnabled"),
        ),
        # 7. get_IsStatusBarEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_IsStatusBarEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "isStatusBarEnabled"),
        ),
        # 8. put_IsStatusBarEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_IsStatusBarEnabled",
            (["in"], wintypes.BOOL, "isStatusBarEnabled"),
        ),
        # 9. get_AreDevToolsEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_AreDevToolsEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "areDevToolsEnabled"),
        ),
        # 10. put_AreDevToolsEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_AreDevToolsEnabled",
            (["in"], wintypes.BOOL, "areDevToolsEnabled"),
        ),
        # 11. get_AreDefaultContextMenusEnabled
        COMMETHOD(
            [],
            HRESULT,
            "get_AreDefaultContextMenusEnabled",
            (["out"], ctypes.POINTER(wintypes.BOOL), "areDefaultContextMenusEnabled"),
        ),
        # 12. put_AreDefaultContextMenusEnabled
        COMMETHOD(
            [],
            HRESULT,
            "put_AreDefaultContextMenusEnabled",
            (["in"], wintypes.BOOL, "areDefaultContextMenusEnabled"),
        ),
    ]


class ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler(IUnknown):
    _iid_ = IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
    _methods_ = [
        COMMETHOD(
            [],
            HRESULT,
            "Invoke",
            (["in"], HRESULT, "errorCode"),
            (["in"], ctypes.POINTER(ICoreWebView2Environment), "createdEnvironment"),
        )
    ]


class ICoreWebView2CreateCoreWebView2ControllerCompletedHandler(IUnknown):
    _iid_ = IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler
    _methods_ = [
        COMMETHOD(
            [],
            HRESULT,
            "Invoke",
            (["in"], HRESULT, "errorCode"),
            (["in"], ctypes.POINTER(ICoreWebView2Controller), "createdController"),
        )
    ]


class ICoreWebView2WebMessageReceivedEventArgs(IUnknown):
    _iid_ = IID_ICoreWebView2WebMessageReceivedEventArgs
    _methods_ = [
        COMMETHOD(
            [],
            HRESULT,
            "get_Source",
            (["out"], ctypes.POINTER(wintypes.LPWSTR), "source"),
        ),
        COMMETHOD(
            [],
            HRESULT,
            "get_WebMessageAsJson",
            (["out"], ctypes.POINTER(wintypes.LPWSTR), "webMessageAsJson"),
        ),
        COMMETHOD(
            [],
            HRESULT,
            "TryGetWebMessageAsString",
            (["out"], ctypes.POINTER(wintypes.LPWSTR), "webMessageAsString"),
        ),
    ]


class ICoreWebView2WebMessageReceivedEventHandler(IUnknown):
    _iid_ = IID_ICoreWebView2WebMessageReceivedEventHandler
    _methods_ = [
        COMMETHOD(
            [],
            HRESULT,
            "Invoke",
            (["in"], ctypes.POINTER(IUnknown), "sender"),
            (["in"], ctypes.POINTER(ICoreWebView2WebMessageReceivedEventArgs), "args"),
        )
    ]


class ICoreWebView2ProcessFailedEventHandler(IUnknown):
    _iid_ = IID_ICoreWebView2ProcessFailedEventHandler
    _methods_ = [
        COMMETHOD(
            [],
            HRESULT,
            "Invoke",
            (["in"], ctypes.POINTER(IUnknown), "sender"),
            (["in"], ctypes.POINTER(IUnknown), "args"),
        )
    ]


ICoreWebView2Environment._methods_ = [
    COMMETHOD(
        [],
        HRESULT,
        "CreateCoreWebView2Controller",
        (["in"], wintypes.HWND, "ParentWindow"),
        (
            ["in"],
            ctypes.POINTER(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler),
            "handler",
        ),
    ),
    # Other methods omitted for brevity
]

ICoreWebView2Controller._methods_ = [
    COMMETHOD(
        [],
        HRESULT,
        "get_IsVisible",
        (["out"], ctypes.POINTER(wintypes.BOOL), "isVisible"),
    ),
    COMMETHOD([], HRESULT, "put_IsVisible", (["in"], wintypes.BOOL, "isVisible")),
    COMMETHOD(
        [], HRESULT, "get_Bounds", (["out"], ctypes.POINTER(wintypes.RECT), "bounds")
    ),
    COMMETHOD([], HRESULT, "put_Bounds", (["in"], wintypes.RECT, "bounds")),
    COMMETHOD(
        [],
        HRESULT,
        "get_ZoomFactor",
        (["out"], ctypes.POINTER(ctypes.c_double), "zoomFactor"),
    ),
    COMMETHOD([], HRESULT, "put_ZoomFactor", (["in"], ctypes.c_double, "zoomFactor")),
    COMMETHOD(
        [],
        HRESULT,
        "add_ZoomFactorChanged",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_ZoomFactorChanged", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "SetBoundsAndZoomFactor",
        (["in"], wintypes.RECT, "bounds"),
        (["in"], ctypes.c_double, "zoomFactor"),
    ),
    COMMETHOD([], HRESULT, "MoveFocus", (["in"], ctypes.c_int, "reason")),
    COMMETHOD(
        [],
        HRESULT,
        "add_MoveFocusRequested",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_MoveFocusRequested", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_GotFocus",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD([], HRESULT, "remove_GotFocus", (["in"], ctypes.c_longlong, "token")),
    COMMETHOD(
        [],
        HRESULT,
        "add_LostFocus",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD([], HRESULT, "remove_LostFocus", (["in"], ctypes.c_longlong, "token")),
    COMMETHOD(
        [],
        HRESULT,
        "add_AcceleratorKeyPressed",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "remove_AcceleratorKeyPressed",
        (["in"], ctypes.c_longlong, "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "get_ParentWindow",
        (["out"], ctypes.POINTER(wintypes.HWND), "parentWindow"),
    ),
    COMMETHOD([], HRESULT, "put_ParentWindow", (["in"], wintypes.HWND, "parentWindow")),
    COMMETHOD([], HRESULT, "NotifyParentWindowPositionChanged"),
    COMMETHOD([], HRESULT, "Close"),
    COMMETHOD(
        [],
        HRESULT,
        "get_CoreWebView2",
        (["out"], ctypes.POINTER(ctypes.POINTER(IUnknown)), "coreWebView2"),
    ),
]

ICoreWebView2._methods_ = [
    COMMETHOD(
        [],
        HRESULT,
        "get_Settings",
        (
            ["out"],
            ctypes.POINTER(ICoreWebView2Settings),
            "settings",
            ctypes.POINTER(ICoreWebView2Settings)(),
        ),
    ),
    COMMETHOD(
        [], HRESULT, "get_Source", (["out"], ctypes.POINTER(wintypes.LPWSTR), "uri")
    ),
    COMMETHOD([], HRESULT, "Navigate", (["in"], wintypes.LPWSTR, "uri")),
    COMMETHOD(
        [], HRESULT, "NavigateToString", (["in"], wintypes.LPWSTR, "htmlContent")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_NavigationStarting",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_NavigationStarting", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_ContentLoading",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_ContentLoading", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_SourceChanged",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_SourceChanged", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_HistoryChanged",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_HistoryChanged", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_NavigationCompleted",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_NavigationCompleted", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_FrameNavigationStarting",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "remove_FrameNavigationStarting",
        (["in"], ctypes.c_longlong, "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_FrameNavigationCompleted",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "remove_FrameNavigationCompleted",
        (["in"], ctypes.c_longlong, "token"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_ScriptDialogOpening",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_ScriptDialogOpening", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_PermissionRequested",
        (["in"], ctypes.POINTER(IUnknown), "eventHandler"),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_PermissionRequested", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_ProcessFailed",
        (
            ["in"],
            ctypes.POINTER(ICoreWebView2ProcessFailedEventHandler),
            "eventHandler",
        ),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_ProcessFailed", (["in"], ctypes.c_longlong, "token")
    ),
    COMMETHOD(
        [],
        HRESULT,
        "AddScriptToExecuteOnDocumentCreated",
        (["in"], wintypes.LPWSTR, "javaScript"),
        (["in"], ctypes.POINTER(IUnknown), "handler"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "RemoveScriptToExecuteOnDocumentCreated",
        (["in"], ctypes.c_longlong, "id"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "ExecuteScript",
        (["in"], wintypes.LPWSTR, "javaScript"),
        (["in"], ctypes.POINTER(IUnknown), "handler"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "CapturePreview",
        (["in"], ctypes.c_int, "capturePreviewImageFormat"),
        (["in"], ctypes.POINTER(IUnknown), "stream"),
        (["in"], ctypes.POINTER(IUnknown), "handler"),
    ),
    COMMETHOD([], HRESULT, "Reload"),
    COMMETHOD(
        [],
        HRESULT,
        "PostWebMessageAsJson",
        (["in"], wintypes.LPWSTR, "webMessageAsJson"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "PostWebMessageAsString",
        (["in"], wintypes.LPWSTR, "webMessageAsString"),
    ),
    COMMETHOD(
        [],
        HRESULT,
        "add_WebMessageReceived",
        (
            ["in"],
            ctypes.POINTER(ICoreWebView2WebMessageReceivedEventHandler),
            "handler",
        ),
        (["out"], ctypes.POINTER(ctypes.c_longlong), "token"),
    ),
    COMMETHOD(
        [], HRESULT, "remove_WebMessageReceived", (["in"], ctypes.c_longlong, "token")
    ),
]


# Handler Implementations
class EnvironmentCompletedHandler(comtypes.COMObject):
    _com_interfaces_ = [ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler]

    def __init__(self, webview):
        self.webview = webview
        super().__init__()

    def Invoke(self, errorCode, createdEnvironment):
        if errorCode != 0 or not createdEnvironment:
            _debug_log(f"EnvironmentCompletedHandler error: {errorCode}")
            hard_exit()
            return 0
        try:
            self.webview._on_env_created(createdEnvironment)
        except Exception:
            _debug_log("EnvironmentCompletedHandler failed while processing env")
            hard_exit()
        return 0


class ControllerCompletedHandler(comtypes.COMObject):
    _com_interfaces_ = [ICoreWebView2CreateCoreWebView2ControllerCompletedHandler]

    def __init__(self, webview):
        self.webview = webview
        super().__init__()

    def Invoke(self, errorCode, createdController):
        if errorCode != 0 or not createdController:
            _debug_log(f"ControllerCompletedHandler error: {errorCode}")
            hard_exit()
            return 0
        try:
            self.webview._on_controller_created(createdController)
        except Exception:
            _debug_log("ControllerCompletedHandler failed while processing controller")
            hard_exit()
        return 0


class WebMessageReceivedHandler(comtypes.COMObject):
    _com_interfaces_ = [ICoreWebView2WebMessageReceivedEventHandler]

    def __init__(self, webview):
        self.webview = webview
        super().__init__()

    def Invoke(self, sender, args):
        try:
            self.webview._on_web_message(args)
        except Exception:
            hard_exit()
        return 0


class ProcessFailedHandler(comtypes.COMObject):
    _com_interfaces_ = [ICoreWebView2ProcessFailedEventHandler]

    def __init__(self, webview):
        self.webview = webview
        super().__init__()

    def Invoke(self, sender, args):
        _debug_log("ProcessFailed event triggered")
        hard_exit()
        return 0


class WebView:
    def __init__(self, schema=None, debug: bool = False, theme: str = "light"):
        self.hwnd = None
        self.env = None
        self.controller = None
        self.core_webview = None
        self.result = None
        self.schema = schema
        self.debug = debug
        self.theme = theme
        self._schema_sent = False
        self._context_menu_blocker_added = False

    def create(self, hwnd: int):
        _debug_log("WebView.create() start")
        self.hwnd = hwnd
        # Create Environment
        try:
            CreateCoreWebView2EnvironmentWithOptions = (
                webview2_loader.CreateCoreWebView2EnvironmentWithOptions
            )
        except Exception:
            _debug_log("Missing CreateCoreWebView2EnvironmentWithOptions in loader DLL")
            hard_exit()

        CreateCoreWebView2EnvironmentWithOptions.argtypes = [
            wintypes.LPCWSTR,
            wintypes.LPCWSTR,
            ctypes.c_void_p,
            ctypes.POINTER(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler),
        ]
        CreateCoreWebView2EnvironmentWithOptions.restype = HRESULT

        handler = EnvironmentCompletedHandler(self)
        hr = CreateCoreWebView2EnvironmentWithOptions(None, None, None, handler)
        if hr != 0:
            _debug_log(f"CreateCoreWebView2EnvironmentWithOptions failed: {hr}")
            hard_exit()

    def _on_env_created(self, env_ptr):
        self.env = ctypes.cast(env_ptr, ctypes.POINTER(ICoreWebView2Environment))

        _debug_log("CoreWebView2 environment created")

        handler = ControllerCompletedHandler(self)
        self.env.CreateCoreWebView2Controller(self.hwnd, handler)

    def _on_controller_created(self, controller_ptr):
        self.controller = ctypes.cast(
            controller_ptr, ctypes.POINTER(ICoreWebView2Controller)
        )

        _debug_log("CoreWebView2 controller created")

        # Resize to fit window
        rect = wintypes.RECT()
        try:
            ctypes.windll.user32.GetClientRect(self.hwnd, ctypes.byref(rect))
        except Exception as exc:
            _debug_log(f"GetClientRect failed: {exc!r}")
            raise
        try:
            self.controller.put_Bounds(rect)
        except Exception as exc:
            _debug_log(f"controller.put_Bounds failed: {exc!r}")
            raise
        try:
            self.controller.put_IsVisible(True)
        except Exception as exc:
            _debug_log(f"controller.put_IsVisible failed: {exc!r}")
            raise

        # Get CoreWebView2
        try:
            core_ptr = self.controller.get_CoreWebView2()
            self.core_webview = ctypes.cast(core_ptr, ctypes.POINTER(ICoreWebView2))
        except Exception as exc:
            _debug_log(f"controller.get_CoreWebView2 failed: {exc!r}")
            raise

        self._configure_settings()

        # Register events
        self._register_events()

        # Load content
        self._load_content()

    def _register_events(self):
        # WebMessageReceived
        msg_handler = WebMessageReceivedHandler(self)
        try:
            self.core_webview.add_WebMessageReceived(msg_handler)
        except Exception as exc:
            _debug_log(f"add_WebMessageReceived failed: {exc!r}")
            raise

        # ProcessFailed
        fail_handler = ProcessFailedHandler(self)
        try:
            self.core_webview.add_ProcessFailed(fail_handler)
        except Exception as exc:
            _debug_log(f"add_ProcessFailed failed: {exc!r}")
            raise

    def _load_content(self):
        html_path = get_resource_path("index.html")
        if not os.path.exists(html_path):
            _debug_log(f"index.html not found at: {html_path}")
            hard_exit()
        # Convert to file URI
        uri = Path(html_path).resolve().as_uri()
        _debug_log(f"Navigating to: {uri}")
        try:
            self.core_webview.Navigate(uri)
        except Exception as exc:
            _debug_log(f"Navigate failed: {exc!r}")
            raise

    def _configure_settings(self):
        try:
            settings = self.core_webview.get_Settings()
            # Enable DevTools only in debug mode
            settings.put_AreDevToolsEnabled(self.debug)
            # Try to disable context menu via settings
            try:
                settings.put_AreDefaultContextMenusEnabled(self.debug)
            except Exception:
                pass
            _debug_log(f"WebView settings: debug={self.debug}")
        except Exception as exc:
            _debug_log(f"configure settings failed: {exc!r}")

        # Inject JavaScript to disable right-click context menu (more reliable)
        if not self.debug:
            self._inject_context_menu_blocker()

    def _inject_context_menu_blocker(self) -> None:
        if not self.core_webview or self._context_menu_blocker_added:
            return
        script = (
            "(function(){"
            "document.addEventListener('contextmenu', function(e){"
            "e.preventDefault();"
            "}, {capture:true});"
            "})();"
        )
        try:
            self.core_webview.AddScriptToExecuteOnDocumentCreated(script, None)
            self._context_menu_blocker_added = True
            _debug_log("Context menu blocker injected on document created")
        except Exception as exc:
            _debug_log(f"AddScriptToExecuteOnDocumentCreated failed: {exc!r}")
            # Fallback: try to inject immediately after load
            try:
                self.core_webview.ExecuteScript(script, None)
                self._context_menu_blocker_added = True
                _debug_log("Context menu blocker injected via ExecuteScript")
            except Exception as exc2:
                _debug_log(f"ExecuteScript failed: {exc2!r}")

    def _post_schema(self):
        if self._schema_sent:
            return
        if not self.core_webview:
            hard_exit()
        payload = self.schema if isinstance(self.schema, dict) else None
        # Include theme in the message
        message = {"type": "schema", "payload": payload, "theme": self.theme}
        try:
            self.core_webview.PostWebMessageAsJson(json.dumps(message))
            self._schema_sent = True
        except Exception as exc:
            _debug_log(f"PostWebMessageAsJson failed: {exc!r}")
            hard_exit()

    def _on_web_message(self, args):
        try:
            json_str = args.get_WebMessageAsJson()
        except Exception as exc:
            _debug_log(f"get_WebMessageAsJson failed: {exc!r}")
            return

        if isinstance(json_str, tuple):
            json_str = json_str[0]
        if hasattr(json_str, "value"):
            json_str = json_str.value
        if json_str is None:
            _debug_log("WebMessage JSON is None")
            return
        json_str = str(json_str)

        try:
            data = json.loads(json_str)
            # Some WebView2 versions return the string itself as a JSON string if PostWebMessageAsString was used.
            # But we expect JSON object.
            if isinstance(data, str):
                data = json.loads(data)
        except Exception as exc:
            _debug_log(f"WebMessage parse failed: {exc!r}; raw={json_str!r}")
            return

        if not isinstance(data, dict):
            _debug_log(f"WebMessage ignored (non-object): {data!r}")
            return

        msg_type = data.get("type")

        if msg_type == "ready":
            set_close_guard(False)
            self._post_schema()
            # Show window after content is ready (prevents white flash)
            if self.hwnd:
                show_window(self.hwnd)
        elif msg_type == "confirm":
            self.result = data.get("payload")
            if self.hwnd:
                try:
                    win32gui.DestroyWindow(self.hwnd)
                except Exception:
                    pass
            ctypes.windll.user32.PostQuitMessage(0)
        elif msg_type == "cancel":
            hard_exit()
        elif msg_type == "startDrag":
            # Start window drag for frameless windows
            if self.hwnd:
                start_window_drag(self.hwnd)

    def resize(self, width, height):
        if self.controller:
            rect = wintypes.RECT(0, 0, width, height)
            self.controller.put_Bounds(rect)
