"""Full-width taskbar strip: jump to any open window.

Reads open windows from `hyprctl clients -j`, groups them by app (class). A group
with a single window focuses it directly; a group with several windows opens a
pop-out list so you can pick a specific instance. The strip scrolls sideways when
many apps are open. Focusing a window closes the menu.
"""

from __future__ import annotations

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("AstalApps", "0.1")
from gi.repository import AstalApps, Gtk  # noqa: E402

from lib.proc import run_json, run_text


class Taskbar(Gtk.Box):
    def __init__(self, on_activate):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.add_css_class("taskbar")
        self._on_activate = on_activate
        self._apps = AstalApps.Apps()
        self._wm_index = self._build_wm_index()

        header = Gtk.Label(label="Open windows", xalign=0.0)
        header.add_css_class("section-title")
        self.append(header)

        self._row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self._row.add_css_class("taskbar-row")
        scroller = Gtk.ScrolledWindow(
            vscrollbar_policy=Gtk.PolicyType.NEVER,
            hscrollbar_policy=Gtk.PolicyType.AUTOMATIC)
        scroller.set_child(self._row)
        self.append(scroller)

    def _build_wm_index(self) -> dict:
        idx = {}
        for app in self._apps.get_list():
            for key in (app.get_wm_class(), app.get_executable(), app.get_name()):
                if key:
                    idx.setdefault(key.lower(), app)
        return idx

    def _icon_for(self, cls: str) -> str:
        app = self._wm_index.get((cls or "").lower())
        if app and app.get_icon_name():
            return app.get_icon_name()
        return (cls or "application-x-executable").lower()

    # -- populate ----------------------------------------------------------
    def refresh(self) -> None:
        run_json(["hyprctl", "clients", "-j"], self._on_clients)

    def _on_clients(self, ok: bool, data) -> None:
        child = self._row.get_first_child()
        while child:
            self._row.remove(child)
            child = self._row.get_first_child()
        if not ok or not isinstance(data, list):
            self._row.append(Gtk.Label(label="no window data"))
            return
        groups: dict[str, list] = {}
        for w in data:
            if not w.get("mapped", True) or not w.get("class"):
                continue
            groups.setdefault(w["class"], []).append(w)
        if not groups:
            self._row.append(Gtk.Label(label="No open windows"))
            return
        for cls, wins in sorted(groups.items()):
            self._row.append(self._group_button(cls, wins))

    def _group_button(self, cls: str, wins: list) -> Gtk.Widget:
        icon = Gtk.Image.new_from_icon_name(self._icon_for(cls))
        icon.set_pixel_size(32)
        if len(wins) == 1:
            btn = Gtk.Button()
            btn.add_css_class("task-tile")
            btn.set_child(icon)
            btn.set_tooltip_text(wins[0].get("title") or cls)
            btn.connect("clicked", lambda *_a, w=wins[0]: self._focus(w))
            return btn
        # multiple windows -> pop-out list of specific instances
        btn = Gtk.MenuButton()
        btn.add_css_class("task-tile")
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        box.append(icon)
        badge = Gtk.Label(label=str(len(wins)))
        badge.add_css_class("task-badge")
        box.append(badge)
        btn.set_child(box)
        btn.set_tooltip_text(f"{cls} ({len(wins)})")
        pop = Gtk.Popover()
        pop.add_css_class("task-popover")
        plist = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
        for w in wins:
            item = Gtk.Button(label=w.get("title") or cls)
            item.add_css_class("task-window")
            item.connect("clicked", lambda *_a, ww=w: (pop.popdown(), self._focus(ww)))
            plist.append(item)
        pop.set_child(plist)
        btn.set_popover(pop)
        return btn

    def _focus(self, w) -> None:
        addr = w.get("address")
        if addr:
            # This is a hyprlua (Lua-configured Hyprland) setup: `hyprctl dispatch`
            # evaluates its argument as Lua, so the native `focuswindow address:…`
            # syntax is a Lua error. Use the hyprlua focus dispatcher, which also
            # switches to the window's workspace.
            run_text(["hyprctl", "dispatch",
                      f'hl.dsp.focus({{ window = "address:{addr}" }})'],
                     lambda *_a: None)
        self._on_activate()
