from __future__ import annotations

from typing import Any, Callable


def _load_gtk_modules() -> tuple[Any, Any]:
    try:
        import gi

        gi.require_version("Gtk", "3.0")
        from gi.repository import Gtk, Gdk

        return Gtk, Gdk
    except Exception:
        return _FallbackGtk, _FallbackGdk


class _FallbackWindow:
    def __init__(self, title: str = "") -> None:
        self.title = title
        self.transient_for = None
        self.modal = False
        self.visible = False
        self.destroyed = False
        self.children: list[object] = []
        self.signal_handlers: dict[str, list[Callable[..., Any]]] = {}

    def set_title(self, title: str) -> None:
        self.title = title

    def set_transient_for(self, window: Any) -> None:
        self.transient_for = window

    def set_modal(self, modal: bool) -> None:
        self.modal = bool(modal)

    def set_type_hint(self, _hint: Any) -> None:
        return

    def set_keep_above(self, _value: bool) -> None:
        return

    def set_skip_taskbar_hint(self, _value: bool) -> None:
        return

    def set_focus_on_map(self, _value: bool) -> None:
        return

    def set_resizable(self, _value: bool) -> None:
        return

    def set_decorated(self, _value: bool) -> None:
        return

    def connect(self, signal_name: str, callback: Callable[..., Any], *args: Any) -> Any:
        self.signal_handlers.setdefault(signal_name, []).append((callback, args))
        return (signal_name, callback, args)

    def disconnect(self, _handler: Any) -> None:
        return

    def add(self, widget: object) -> None:
        self.children.append(widget)

    def show_all(self) -> None:
        self.visible = True

    def present(self) -> None:
        self.show_all()

    def hide(self) -> None:
        self.visible = False

    def destroy(self) -> None:
        self.visible = False
        self.destroyed = True

    def emit(self, signal_name: str, *args: Any) -> None:
        for callback, callback_args in self.signal_handlers.get(signal_name, []):
            callback(self, *args, *callback_args)


class _FallbackGtk:
    class WindowType:
        TOPLEVEL = "toplevel"
        POPUP = "popup"

    class WindowPosition:
        MOUSE = "mouse"

    class Window:
        def __init__(self, _type: Any = None):
            self._window = _FallbackWindow()

        def __getattr__(self, name: str):
            return getattr(self._window, name)

    class Box(_FallbackWindow):
        pass

    class Orientation:
        VERTICAL = 1

    class WindowTypeHint:
        TOOLTIP = 1

    @staticmethod
    def Window_new(*args: Any, **kwargs: Any) -> _FallbackWindow:
        return _FallbackWindow(*args, **kwargs)

    @staticmethod
    def Box_new(*_args: Any, **_kwargs: Any) -> _FallbackWindow:
        return _FallbackWindow()


class _FallbackGdk:
    class WindowTypeHint:
        TOOLTIP = 1


class PopupWindow:
    def __init__(
        self,
        title: str | None = None,
        *,
        transient_for: Any | None = None,
        auto_close: bool = True,
        gtk_module: Any | None = None,
        gdk_module: Any | None = None,
    ) -> None:
        if gtk_module is not None or gdk_module is not None:
            self.gtk = gtk_module if gtk_module is not None else _FallbackGtk
            self.gdk = gdk_module if gdk_module is not None else _FallbackGdk
        else:
            self.gtk, self.gdk = _load_gtk_modules()
        self.window = self._new_window()
        if title is not None:
            self.window.set_title(title)
        self.auto_close = auto_close
        self._auto_close_disabled = False
        self._closed = False
        self._parent_destroy_handler = None
        self._transient_parent = None
        self._content = self._new_box()
        self.window.add(self._content)
        self.transient_for = transient_for

        if transient_for is not None:
            self.set_transient_for(transient_for)

        if hasattr(self.window, "set_keep_above"):
            self.window.set_keep_above(True)
        if hasattr(self.window, "set_skip_taskbar_hint"):
            self.window.set_skip_taskbar_hint(True)
        if hasattr(self.window, "set_focus_on_map"):
            self.window.set_focus_on_map(True)
        if hasattr(self.window, "set_resizable"):
            self.window.set_resizable(True)
        if hasattr(self.window, "set_decorated"):
            self.window.set_decorated(True)

        self.window.connect("focus-out-event", self._handle_focus_out)
        self.window.connect("delete-event", self._handle_delete_event)

    def _new_window(self) -> Any:
        try:
            return self.gtk.Window(type=self.gtk.WindowType.TOPLEVEL)
        except (AttributeError, TypeError, Exception):
            try:
                return self.gtk.Window(type=self.gtk.WindowType.POPUP)
            except (AttributeError, TypeError, Exception):
                try:
                    return self.gtk.Window()
                except TypeError:
                    return self.gtk.Window_new()

    def _new_box(self) -> Any:
        try:
            return self.gtk.Box(orientation=self.gtk.Orientation.VERTICAL, spacing=0)
        except TypeError:
            return self.gtk.Box_new(orientation=self.gtk.Orientation.VERTICAL, spacing=0)

    def _link_parent_destroy(self, parent: Any) -> None:
        if hasattr(parent, "connect"):
            handler = parent.connect("destroy", lambda *_args: self.close())
            self._parent_destroy_handler = handler

    def set_transient_for(self, parent: Any | None) -> None:
        if self._parent_destroy_handler is not None and self._transient_parent is not None:
            disconnect = getattr(self._transient_parent, "disconnect", None)
            if callable(disconnect):
                disconnect(self._parent_destroy_handler)
            self._parent_destroy_handler = None

        self.transient_for = None
        self._transient_parent = None
        if parent is not None:
            try:
                self.window.set_transient_for(parent)
            except Exception:
                return
            self.transient_for = parent
            self._transient_parent = parent
            if hasattr(self.window, "set_modal"):
                self.window.set_modal(True)
            if hasattr(self.window, "set_destroy_with_parent"):
                self.window.set_destroy_with_parent(True)
            self._link_parent_destroy(parent)

    def _handle_focus_out(self, *_args: Any) -> bool:
        if (
            self.auto_close
            and not self._auto_close_disabled
            and self.transient_for is not None
        ):
            self.close()
        return False

    def simulate_parent_destroy(self) -> None:
        self.close()

    def _handle_delete_event(self, *_args: Any) -> bool:
        # Returning True stops GTK's default handler, which would destroy the
        # window and every child widget the dashboard still holds references to.
        self.close()
        return True

    def add(self, widget: object) -> None:
        self._content.add(widget)

    def get_content_area(self) -> Any:
        return self._content

    def set_auto_close(self, enabled: bool) -> None:
        self.auto_close = bool(enabled)

    def suspend_auto_close(self, suspend: bool = True) -> None:
        self._auto_close_disabled = bool(suspend)

    def show(self) -> None:
        self._closed = False
        self.window.show_all()

    def resize_to_content(self) -> None:
        get_preferred_size = getattr(self._content, "get_preferred_size", None)
        resize = getattr(self.window, "resize", None)
        if not callable(get_preferred_size) or not callable(resize):
            return
        try:
            _minimum, natural = get_preferred_size()
            width = int(natural.width)
            height = int(natural.height)
        except (AttributeError, TypeError, ValueError):
            return
        if width > 0 and height > 0:
            resize(width, height)

    def simulate_focus_out(self) -> None:
        self._handle_focus_out()

    def close(self) -> None:
        self._closed = True
        if hasattr(self.window, "hide"):
            self.window.hide()
        elif hasattr(self.window, "destroy"):
            self.window.destroy()
        elif hasattr(self.window, "visible"):
            self.window.visible = False

    @property
    def is_visible(self) -> bool:
        get_visible = getattr(self.window, "get_visible", None)
        if get_visible is not None:
            return bool(get_visible())
        return bool(getattr(self.window, "visible", False))

    @property
    def is_closed(self) -> bool:
        return self._closed

    def show_all(self) -> None:
        self.show()
