"""Generic chrome around any module: a header (icon, title, expand, settings) and
a body that is either the module's compact widget or an 'enable me' placeholder.

A disabled quad never calls the module's build(), so a disabled Bluetooth/Network
quad spawns no backend work at all.
"""

from __future__ import annotations

import gi

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

from module_base import ModuleContext, ModuleInstance, ModuleSpec


class QuadCard(Gtk.Box):
    def __init__(self, spec: ModuleSpec, settings, services,
                 request_expand, request_collapse):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.add_css_class("quad-card")
        self.spec = spec
        self.settings = settings
        self.services = services
        self.ctx = ModuleContext(spec, settings, services, request_expand, request_collapse)
        self.instance: ModuleInstance | None = None

        self._header = self._build_header()
        self.append(self._header)

        self._body_holder = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self._body_holder.add_css_class("quad-body")
        self._body_holder.set_vexpand(True)
        self.append(self._body_holder)

        self._features: dict[str, bool] = {}
        self._rebuild_body()

    # -- header ------------------------------------------------------------
    def _build_header(self) -> Gtk.Widget:
        header = Gtk.CenterBox()
        header.add_css_class("quad-header")

        title = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        icon = Gtk.Label(label=self.spec.icon)
        icon.add_css_class("quad-icon")
        name = Gtk.Label(label=self.spec.title, xalign=0.0)
        name.add_css_class("quad-title")
        title.append(icon)
        title.append(name)
        header.set_start_widget(title)

        actions = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
        self._expand_btn = Gtk.Button(label="")  # nf-fa-expand
        self._expand_btn.add_css_class("quad-action")
        self._expand_btn.set_tooltip_text("Expand")
        self._expand_btn.connect("clicked", lambda *_: self.ctx.expand())
        actions.append(self._build_settings_button())
        actions.append(self._expand_btn)
        header.set_end_widget(actions)
        return header

    def _build_settings_button(self) -> Gtk.Widget:
        btn = Gtk.MenuButton(label="")  # nf-fa-cog
        btn.add_css_class("quad-action")
        btn.set_tooltip_text("Settings")
        pop = Gtk.Popover()
        pop.add_css_class("quad-settings")
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

        box.append(self._switch_row("Enabled", self.settings.quad_enabled(
            self.spec.id, self.spec.default_enabled),
            lambda v: self.settings.set_quad_enabled(self.spec.id, v)))

        for feat in self.spec.features:
            box.append(Gtk.Separator())
            box.append(self._switch_row(
                feat.label, self.settings.feature(self.spec.id, feat.id, feat.default),
                lambda v, fid=feat.id: self.settings.set_feature(self.spec.id, fid, v)))

        pop.set_child(box)
        btn.set_popover(pop)
        return btn

    @staticmethod
    def _switch_row(label: str, value: bool, on_change) -> Gtk.Widget:
        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        row.add_css_class("switch-row")
        lbl = Gtk.Label(label=label, xalign=0.0, hexpand=True)
        sw = Gtk.Switch(active=value)
        sw.connect("state-set", lambda _sw, v: (on_change(v), False)[1])
        row.append(lbl)
        row.append(sw)
        return row

    # -- body --------------------------------------------------------------
    def _clear_body(self) -> None:
        if self.instance and self.instance.destroy:
            self.instance.destroy()
        self.instance = None
        child = self._body_holder.get_first_child()
        while child:
            self._body_holder.remove(child)
            child = self._body_holder.get_first_child()

    def _rebuild_body(self) -> None:
        self._clear_body()
        self._features = self._feature_snapshot()
        enabled = self.settings.quad_enabled(self.spec.id, self.spec.default_enabled)
        if enabled:
            inst = self.spec.build(self.ctx)
            self.instance = inst
            self._body_holder.append(inst.compact)
            self._expand_btn.set_sensitive(inst.expanded is not None)
        else:
            self._expand_btn.set_sensitive(False)
            self._body_holder.append(self._disabled_placeholder())

    def _feature_snapshot(self) -> dict[str, bool]:
        return {f.id: self.settings.feature(self.spec.id, f.id, f.default)
                for f in self.spec.features}

    def _disabled_placeholder(self) -> Gtk.Widget:
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10,
                      valign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER)
        box.add_css_class("quad-disabled")
        box.append(Gtk.Label(label=f"{self.spec.title} is off"))
        btn = Gtk.Button(label="Enable")
        btn.add_css_class("enable-btn")
        btn.connect("clicked",
                    lambda *_: self.settings.set_quad_enabled(self.spec.id, True))
        box.append(btn)
        return box

    # -- lifecycle ---------------------------------------------------------
    def on_settings_changed(self) -> None:
        """Called by the grid when settings.json changes; rebuild if this quad's
        enablement flipped or any of its feature toggles changed, so every switch in
        the settings popover takes effect live (the popover is only reachable while
        the grid is collapsed, so no expanded view is ever reparented mid-rebuild)."""
        enabled = self.settings.quad_enabled(self.spec.id, self.spec.default_enabled)
        has_module = self.instance is not None
        features_changed = enabled and self._feature_snapshot() != self._features
        if enabled != has_module or features_changed:
            self._rebuild_body()

    @property
    def expanded_widget(self) -> Gtk.Widget | None:
        return self.instance.expanded if self.instance else None

    def on_show(self) -> None:
        if self.instance and self.instance.on_show:
            self.instance.on_show()

    def on_hide(self) -> None:
        if self.instance and self.instance.on_hide:
            self.instance.on_hide()
