import asyncio
import functools
import threading
import weakref
from typing import Awaitable, Callable, Any, TypeVar, cast
from webview import Window

T = TypeVar("T")


class AsyncAwareWindowWrapper:
    """Smart window wrapper that automatically uses to_thread in async contexts."""

    GUI_METHODS = {
        "create_file_dialog",
        "create_folder_dialog",
        "create_message_dialog",
        "show_message",
        "confirm",
        "alert",
        "show_confirmation_dialog",
        "show_text_dialog",
        "show_open_dialog",
        "show_save_dialog",
    }

    def __init__(self, window: Window):
        self._window_ref = weakref.ref(window)
        window_obj = window

        # Wrap each GUI method
        for method_name in self.GUI_METHODS:
            if hasattr(window_obj, method_name):
                original_method = getattr(window_obj, method_name)
                if callable(original_method):
                    wrapped_method = self._create_wrapper(method_name)
                    setattr(self, method_name, wrapped_method)

    def _get_window(self) -> Window:
        """Get the original window object, raising an error if it has been destroyed."""
        window = self._window_ref()
        if window is None:
            raise RuntimeError("The original window has been destroyed")
        return window

    def __getattr__(self, name: str) -> Any:
        """Pass through non-wrapped attributes to the original window."""
        if name.startswith("_"):
            raise AttributeError(
                f"'{type(self).__name__}' object has no attribute '{name}'"
            )

        window = self._get_window()
        try:
            return getattr(window, name)
        except AttributeError:
            raise AttributeError(
                f"'{type(self).__name__}' object has no attribute '{name}'"
            ) from None

    def _create_wrapper(self, method_name: str) -> Callable[..., Any]:
        """Create a smart wrapper for a specific method."""

        @functools.wraps(getattr(Window, method_name))
        def wrapper(*args, **kwargs) -> Any:
            window = self._get_window()
            method = getattr(window, method_name)

            if self._is_in_async_context():
                return self._call_in_thread(method, *args, **kwargs)
            else:
                return method(*args, **kwargs)

        return wrapper

    def _is_in_async_context(self) -> bool:
        """Check if currently running in an async context."""
        try:
            asyncio.get_running_loop()
            return True
        except RuntimeError:
            return False

    def _call_in_thread(self, method: Callable[..., T], *args, **kwargs) -> T:
        """Run the method in a new thread and wait for the result."""
        result = None
        exception = None

        def run():
            nonlocal result, exception
            try:
                result = method(*args, **kwargs)
            except Exception as e:
                exception = e

        thread = threading.Thread(target=run)
        thread.start()
        thread.join()

        if exception:
            raise exception
        return cast(T, result)

    def create_async_method(self, method_name: str) -> Callable[..., Awaitable[Any]]:
        """Create an async version of the wrapper."""
        if method_name not in self.GUI_METHODS:
            raise ValueError(f"{method_name} is not a GUI method")

        window = self._get_window()
        original = getattr(window, method_name)

        @functools.wraps(original)
        async def async_wrapper(*args, **kwargs) -> Any:
            return await asyncio.to_thread(original, *args, **kwargs)

        return async_wrapper

    def get_original_window(self) -> Window:
        """Get the original window object."""
        return self._get_window()
