"""Section 5: the full-width application drawer that replaces nwg-drawer.

Top to bottom: a favourites row (full-width module), then search, then a FlowBox of
all apps. Collapsed it is a bottom strip; expanded it fills down to the bottom.
Right-click / long-press an app tile to pin or unpin it from favourites.
"""

from __future__ import annotations

import gi

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

from ui.favorites import Favorites, add_pin_gestures

_STRIP_HEIGHT = 150  # collapsed grid height (logical px)


class AppDrawer(Gtk.Box):
    def __init__(self, settings, on_launch, on_toggle_expand):
        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        self.add_css_class("appdrawer")
        self.settings = settings
        self._on_launch = on_launch
        self._on_toggle_expand = on_toggle_expand
        self._apps = AstalApps.Apps()
        self._expanded = False

        self.favorites = Favorites(settings, on_launch)
        self.append(self.favorites)
        self.append(Gtk.Separator())

        self.append(self._build_header())

        self._search = Gtk.SearchEntry(placeholder_text="Search applications")
        self._search.add_css_class("appdrawer-search")
        self._search.connect("search-changed", self._on_search)
        self.append(self._search)

        self._flow = Gtk.FlowBox(
            selection_mode=Gtk.SelectionMode.NONE, homogeneous=True,
            min_children_per_line=4, max_children_per_line=12,
            row_spacing=8, column_spacing=8,
            valign=Gtk.Align.START)  # keep rows their natural height (no stretch)
        self._flow.add_css_class("appdrawer-flow")
        self._flow.connect("child-activated", self._on_child_activated)

        self._scroll = Gtk.ScrolledWindow(
            hscrollbar_policy=Gtk.PolicyType.NEVER, vexpand=True)
        self._scroll.set_propagate_natural_height(False)
        self._scroll.set_child(self._flow)
        self.append(self._scroll)

        self._apply_mode()
        self._populate(self._sorted_all())
        # re-render tiles when favourites change so the pinned star stays in sync
        settings.subscribe(self._on_settings_changed)

    def _on_settings_changed(self) -> None:
        # Deferred so we never rebuild the FlowBox from inside a tile's own gesture.
        GLib.idle_add(lambda: (self._on_search(self._search), False)[1])

    def _build_header(self) -> Gtk.Widget:
        header = Gtk.CenterBox()
        header.add_css_class("appdrawer-header")
        title = Gtk.Label(label="Applications", xalign=0.0)
        title.add_css_class("section-title")
        header.set_start_widget(title)
        self._expand_btn = Gtk.Button(label="")  # nf-fa-expand (uniform with quad expand)
        self._expand_btn.add_css_class("quad-action")
        self._expand_btn.set_tooltip_text("Expand")
        self._expand_btn.connect("clicked", lambda *_: self._toggle_expand())
        header.set_end_widget(self._expand_btn)
        return header

    # -- expansion ---------------------------------------------------------
    def _apply_mode(self) -> None:
        if self._expanded:
            self.set_vexpand(True)
            self._scroll.set_min_content_height(_STRIP_HEIGHT)
            self._scroll.set_max_content_height(100000)
            self._expand_btn.set_label("")   # nf-fa-compress
        else:
            self.set_vexpand(False)
            self._scroll.set_min_content_height(_STRIP_HEIGHT)
            self._scroll.set_max_content_height(_STRIP_HEIGHT)
            self._expand_btn.set_label("")   # nf-fa-expand

    def _toggle_expand(self) -> None:
        self._expanded = not self._expanded
        self._apply_mode()
        self._on_toggle_expand(self._expanded)

    def set_expanded(self, value: bool) -> None:
        if value != self._expanded:
            self._toggle_expand()

    # -- data --------------------------------------------------------------
    def _sorted_all(self) -> list:
        apps = list(self._apps.get_list())
        apps.sort(key=lambda a: (-a.get_frequency(), a.get_name().lower()))
        return apps

    def _on_search(self, entry: Gtk.SearchEntry) -> None:
        text = entry.get_text().strip()
        results = self._apps.fuzzy_query(text) if text else self._sorted_all()
        self._populate(results)

    def _populate(self, apps: list) -> None:
        child = self._flow.get_first_child()
        while child:
            self._flow.remove(child)
            child = self._flow.get_first_child()
        for app in apps:
            self._flow.append(self._app_button(app))

    def _app_button(self, app) -> Gtk.Widget:
        btn = Gtk.Button(valign=Gtk.Align.START)
        btn.add_css_class("app-tile")
        btn.app = app
        overlay = Gtk.Overlay()
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        icon = Gtk.Image.new_from_icon_name(app.get_icon_name() or "application-x-executable")
        icon.set_pixel_size(48)
        label = Gtk.Label(label=app.get_name(), ellipsize=3, max_width_chars=12,
                          justify=Gtk.Justification.CENTER)
        content.append(icon)
        content.append(label)
        overlay.set_child(content)
        # a star marks pinned apps (visible only when favourited)
        star = Gtk.Label(label="★")
        star.add_css_class("fav-star")
        star.set_halign(Gtk.Align.END)
        star.set_valign(Gtk.Align.START)
        star.set_visible(self.settings.is_favorite(app.get_entry()))
        overlay.add_overlay(star)
        btn.set_child(overlay)
        btn.connect("clicked", lambda *_: self._launch(app))
        add_pin_gestures(btn, lambda: self._toggle_fav(app))
        return btn

    def _toggle_fav(self, app) -> None:
        self.settings.toggle_favorite(app.get_entry())

    def _on_child_activated(self, _flow, child) -> None:
        btn = child.get_child()
        if btn and getattr(btn, "app", None):
            self._launch(btn.app)

    def _launch(self, app) -> None:
        try:
            app.launch()
        except Exception:
            pass
        self._on_launch()

    def on_show(self) -> None:
        self._search.set_text("")
        self._populate(self._sorted_all())
        self.favorites.refresh()
        self._search.grab_focus()
