"""GTK 4 (libadwaita) implementation of the Edubuntu Menu Administration UI."""

from __future__ import annotations

import threading
from collections.abc import Callable

import gi

gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")

from gi.repository import Adw, GLib, Gtk  # noqa: E402

from .base import MenuAdminUI, ChecklistItem
from i18n import _

ICON_PATH = "/usr/share/icons/hicolor/scalable/apps/edubuntu-menu-admin.svg"


def _item_text(label: str, description: str) -> str:
    if description:
        return "{0}  \u2014  {1}".format(label, description)
    return label


def _build_checklist_scroll(
    items: list[ChecklistItem],
    width: int = 850,
    height: int = 500,
) -> tuple[Gtk.ScrolledWindow, list[tuple[str, Gtk.CheckButton]]]:
    scroll = Gtk.ScrolledWindow()
    scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
    scroll.set_min_content_width(width)
    scroll.set_min_content_height(height)

    box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
    checks: list[tuple[str, Gtk.CheckButton]] = []
    for item in items:
        cb = Gtk.CheckButton(label=_item_text(item.label, item.description))
        cb.set_active(item.checked)
        if item.disabled:
            cb.set_sensitive(False)
        box.append(cb)
        checks.append((item.key, cb))

    scroll.set_child(box)
    return scroll, checks


class GtkUI(MenuAdminUI):

    def __init__(self) -> None:
        self._app: Adw.Application | None = None

    def init(self) -> None:
        self._app = Adw.Application(application_id="org.edubuntu.MenuAdmin")
        self._app.register()

    def quit(self) -> None:
        pass

    def _run_dialog(self, dlg: Adw.MessageDialog) -> str:
        result: list[str] = []
        loop = GLib.MainLoop.new(None, False)

        def _on_response(_dlg: Adw.MessageDialog, resp: str) -> None:
            result.append(resp)
            loop.quit()

        dlg.connect("response", _on_response)
        if self._app:
            dlg.set_application(self._app)
        dlg.present()
        loop.run()
        return result[0] if result else "cancel"

    def show_info(self, title: str, text: str) -> None:
        dlg = Adw.MessageDialog.new(None, title, text)
        dlg.add_response("ok", _("OK"))
        dlg.set_default_response("ok")
        self._run_dialog(dlg)

    def show_error(self, title: str, text: str) -> None:
        dlg = Adw.MessageDialog.new(None, title, text)
        dlg.add_response("ok", _("OK"))
        dlg.set_default_response("ok")
        self._run_dialog(dlg)

    def show_question(
        self,
        title: str,
        text: str,
        ok_label: str = "Yes",
        cancel_label: str = "No",
    ) -> bool:
        dlg = Adw.MessageDialog.new(None, title, text)
        dlg.add_response("cancel", cancel_label)
        dlg.add_response("ok", ok_label)
        dlg.set_default_response("ok")
        dlg.set_close_response("cancel")
        return self._run_dialog(dlg) == "ok"

    def show_checklist(
        self,
        title: str,
        text: str,
        items: list[ChecklistItem],
        ok_label: str = "OK",
        cancel_label: str = "Cancel",
        width: int = 0,
        height: int = 0,
    ) -> list[str] | None:
        dlg = Adw.MessageDialog.new(None, title, text)
        dlg.add_response("cancel", cancel_label)
        dlg.add_response("ok", ok_label)
        dlg.set_default_response("ok")
        dlg.set_close_response("cancel")

        scroll, checks = _build_checklist_scroll(items, 850, max(height, 500))
        dlg.set_extra_child(scroll)

        resp = self._run_dialog(dlg)
        if resp == "ok":
            return [key for key, cb in checks if cb.get_active()]
        return None

    def show_tabbed_checklist(
        self,
        title: str,
        global_text: str,
        global_items: list[ChecklistItem],
        usernames: list[str],
        per_user_text: str,
        per_user_items_fn: Callable[[str, set[str]], list[ChecklistItem]],
        ok_label: str = "OK",
        cancel_label: str = "Cancel",
        width: int = 0,
        height: int = 0,
    ) -> tuple[list[str], str, list[str]] | None:
        dlg = Adw.MessageDialog.new(None, title, "")
        dlg.add_response("cancel", cancel_label)
        dlg.add_response("ok", ok_label)
        dlg.set_default_response("ok")
        dlg.set_close_response("cancel")
        dlg.set_response_enabled("ok", False)

        initial_global = {item.key for item in global_items if item.checked}
        initial_per_user_snapshot: list[set[str]] = [set()]

        notebook = Gtk.Notebook()

        global_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        global_label = Gtk.Label(label=global_text)
        global_label.set_wrap(True)
        global_label.set_xalign(0)
        global_label.set_margin_start(8)
        global_label.set_margin_end(8)
        global_label.set_margin_top(8)
        global_box.append(global_label)

        global_scroll, global_checks = _build_checklist_scroll(
            global_items, 850, max(height, 450),
        )
        global_box.append(global_scroll)
        notebook.append_page(global_box, Gtk.Label(label=_("Global")))

        user_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)

        user_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        user_header.set_margin_start(8)
        user_header.set_margin_end(8)
        user_header.set_margin_top(8)

        user_lbl = Gtk.Label(label=_("User:"))
        user_header.append(user_lbl)

        combo = Gtk.ComboBoxText()
        for u in usernames:
            combo.append_text(u)
        if usernames:
            combo.set_active(0)
        user_header.append(combo)
        user_box.append(user_header)

        per_user_label = Gtk.Label(label=per_user_text)
        per_user_label.set_wrap(True)
        per_user_label.set_xalign(0)
        per_user_label.set_margin_start(8)
        per_user_label.set_margin_end(8)
        user_box.append(per_user_label)

        user_scroll_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        user_box.append(user_scroll_box)

        user_checks: list[tuple[str, Gtk.CheckButton]] = []
        selected_user = [usernames[0] if usernames else ""]

        def _rebuild_user_list() -> None:
            nonlocal user_checks
            child = user_scroll_box.get_first_child()
            while child is not None:
                user_scroll_box.remove(child)
                child = user_scroll_box.get_first_child()

            uname = combo.get_active_text()
            if not uname:
                return
            selected_user[0] = uname
            current_global = {key for key, cb in global_checks if cb.get_active()}
            items = per_user_items_fn(uname, current_global)
            scroll, user_checks = _build_checklist_scroll(
                items, 850, max(height, 450),
            )
            user_scroll_box.append(scroll)

        def _check_changes() -> None:
            current_global = {key for key, cb in global_checks if cb.get_active()}
            changed = current_global != initial_global
            if not changed and user_checks:
                current_user = {key for key, cb in user_checks if cb.get_active()}
                changed = current_user != initial_per_user_snapshot[0]
            dlg.set_response_enabled("ok", changed)

        for _key, cb in global_checks:
            cb.connect("toggled", lambda _w: _check_changes())

        def _rebuild_and_track() -> None:
            _rebuild_user_list()
            initial_per_user_snapshot[0] = {
                key for key, cb in user_checks if cb.get_active()
            }
            for _key, cb in user_checks:
                cb.connect("toggled", lambda _w: _check_changes())
            _check_changes()

        combo.connect("changed", lambda _w: _rebuild_and_track())
        if usernames:
            _rebuild_and_track()

        notebook.append_page(user_box, Gtk.Label(label=_("Per-User")))

        def _on_tab_switch(_nb, _page, page_num):
            if page_num == 1 and usernames:
                _rebuild_and_track()

        notebook.connect("switch-page", _on_tab_switch)
        dlg.set_extra_child(notebook)

        resp = self._run_dialog(dlg)
        if resp == "ok":
            global_selected = [key for key, cb in global_checks if cb.get_active()]
            user_selected = [key for key, cb in user_checks if cb.get_active()]
            return (global_selected, selected_user[0], user_selected)
        return None

    def show_progress(
        self,
        title: str,
        text: str,
        callback: Callable[[], None],
    ) -> bool:
        dlg = Adw.MessageDialog.new(None, title, text)
        spinner = Gtk.Spinner()
        spinner.start()
        dlg.set_extra_child(spinner)
        if self._app:
            dlg.set_application(self._app)
        dlg.present()

        success = {"value": False}

        loop = GLib.MainLoop.new(None, False)

        def _finish() -> bool:
            spinner.stop()
            dlg.close()
            loop.quit()
            return False

        def worker():
            try:
                callback()
                success["value"] = True
            except Exception:
                success["value"] = False
            finally:
                GLib.idle_add(_finish)

        threading.Thread(target=worker, daemon=True).start()
        loop.run()
        return success["value"]
