"""Region & Input — System-Locale (Sprache) + Tastatur-Layout via localectl.

Lese-Werte kommen aus core.localization (unprivilegiert). Das Setzen läuft
privilegiert über den Helper (localectl steht in dessen Allowlist).

Ehrlicher Hinweis: niri nimmt sein aktives Tastaturlayout aus der EIGENEN
Config (config.kdl, input/keyboard). `localectl set-x11-keymap` setzt das
System-Default (Konsole, X11-Apps, XKB) — nicht zwingend das Live-Layout in
niri. Die Konsolen-Keymap (`set-keymap`) und das Locale gelten dagegen voll
system-weit.
"""
from __future__ import annotations

import gi

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

from taninux.core import localization
from taninux.gui import confirm
from taninux.gui.process import ProcessRunner


def build() -> Gtk.Widget:
    page = Adw.PreferencesPage()
    page.add(_language_group())
    page.add(_datetime_group())
    page.add(_keyboard_group())
    return page


# -- gemeinsamer Button+Konsole-Block (argv erst zur Klickzeit bekannt) -----

def _action_block(button_label: str):
    """Liefert (box, button, runner, status). Caller verdrahtet button 'clicked'."""
    runner = ProcessRunner()

    button = Gtk.Button(label=button_label)
    button.add_css_class("suggested-action")
    button.add_css_class("pill")
    button.set_halign(Gtk.Align.START)

    status = Gtk.Label(xalign=0.0, wrap=True)
    status.add_css_class("dim-label")

    def on_done(_r: ProcessRunner, code: int) -> None:
        status.set_label("✔ Applied" if code == 0 else f"✘ failed (exit {code})")
        button.set_sensitive(True)

    runner.connect("done", on_done)

    box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
    box.set_margin_top(6)
    box.append(button)
    box.append(status)
    return box, button, runner, status


# -- Sprache --------------------------------------------------------------

def _language_group() -> Adw.PreferencesGroup:
    st = localization.status()
    locales = localization.list_locales()

    group = Adw.PreferencesGroup(
        title="Language",
        description="Applies on next login.",
    )

    combo = Adw.ComboRow(title="System language")
    combo.set_model(Gtk.StringList.new(locales or ["(none generated)"]))
    combo.set_enable_search(True)
    if st.locale in locales:
        combo.set_selected(locales.index(st.locale))
    group.add(combo)

    box, button, runner, _status = _action_block("Apply language")

    def on_click(_b: Gtk.Button) -> None:
        idx = combo.get_selected()
        if not locales or idx < 0 or idx >= len(locales):
            return
        locale = locales[idx]

        def do_run() -> None:
            button.set_sensitive(False)
            runner.run(localization.set_locale_argv(locale), privileged=True)

        confirm.confirm(
            button,
            "Change system language?",
            f"Sets LANG={locale}. Applies on next login.",
            do_run,
        )

    button.connect("clicked", on_click)
    group.add(box)
    return group


# -- Datum & Zeit ---------------------------------------------------------

def _datetime_group() -> Adw.PreferencesGroup:
    timezones = localization.list_timezones()
    cur_tz = localization.current_timezone()

    group = Adw.PreferencesGroup(title="Date and Time")

    # Aktuelle lokale Zeit (read-only).
    cur = Adw.ActionRow(title="Current")
    cur_label = Gtk.Label(label=localization.local_time() or "—")
    cur_label.add_css_class("dim-label")
    cur_label.set_selectable(True)
    cur.add_suffix(cur_label)
    group.add(cur)

    # Zeitzone wählen + privilegiert anwenden.
    tz_combo = Adw.ComboRow(title="Time zone")
    tz_combo.set_model(Gtk.StringList.new(timezones or ["(none)"]))
    tz_combo.set_enable_search(True)
    if cur_tz in timezones:
        tz_combo.set_selected(timezones.index(cur_tz))
    group.add(tz_combo)

    box, button, runner, status = _action_block("Apply time zone")

    def on_click(_b: Gtk.Button) -> None:
        idx = tz_combo.get_selected()
        if not timezones or idx < 0 or idx >= len(timezones):
            return
        tz = timezones[idx]

        def do_run() -> None:
            button.set_sensitive(False)
            runner.run(localization.set_timezone_argv(tz), privileged=True)

        confirm.confirm(
            button,
            "Change system time zone?",
            f"Sets the system time zone to {tz}.",
            do_run,
        )

    button.connect("clicked", on_click)

    # Automatische Zeit (NTP) — meldet in dieselbe Status-Zeile.
    ntp_runner = ProcessRunner()
    ntp_runner.connect(
        "done",
        lambda _r, code: status.set_label("✔ Applied" if code == 0 else f"✘ exit {code}"),
    )

    ntp_row = Adw.SwitchRow(
        title="Automatic time (NTP)",
        subtitle="Synchronize the clock over the network.",
    )
    ntp_row.set_active(localization.ntp_enabled())

    def on_ntp(_row: Adw.SwitchRow, _pspec) -> None:
        ntp_runner.run(localization.set_ntp_argv(ntp_row.get_active()), privileged=True)

    ntp_row.connect("notify::active", on_ntp)
    group.add(ntp_row)

    group.add(box)
    return group


# -- Tastatur -------------------------------------------------------------

def _keyboard_group() -> Adw.PreferencesGroup:
    st = localization.status()
    layouts = localization.list_x11_layouts()

    group = Adw.PreferencesGroup(title="Keyboard")

    # Ist-Zustand sichtbar machen (gerade weil niri davon abweichen kann).
    cur = Adw.ActionRow(title="Current (system)")
    cur_val = f"{st.x11_layout or '—'}"
    if st.x11_variant:
        cur_val += f" · {st.x11_variant}"
    cur_val += f"   ·   console: {st.vc_keymap or '—'}"
    cur_label = Gtk.Label(label=cur_val)
    cur_label.add_css_class("dim-label")
    cur_label.set_selectable(True)
    cur.add_suffix(cur_label)
    group.add(cur)

    layout_combo = Adw.ComboRow(title="Layout")
    layout_combo.set_model(Gtk.StringList.new(layouts or ["(none)"]))
    layout_combo.set_enable_search(True)
    if st.x11_layout in layouts:
        layout_combo.set_selected(layouts.index(st.x11_layout))
    group.add(layout_combo)

    variant_combo = Adw.ComboRow(title="Variant")
    group.add(variant_combo)

    def _selected_layout() -> str:
        idx = layout_combo.get_selected()
        return layouts[idx] if layouts and 0 <= idx < len(layouts) else ""

    variants_state: dict[str, list[str]] = {"list": [""]}

    def _reload_variants(*_a) -> None:
        layout = _selected_layout()
        variants = localization.list_x11_variants(layout) if layout else [""]
        variants_state["list"] = variants
        # "" als "default" anzeigen
        labels = ["default" if v == "" else v for v in variants]
        variant_combo.set_model(Gtk.StringList.new(labels))
        # aktuelle Variante vorwählen, falls sie zum Layout passt
        if st.x11_variant in variants:
            variant_combo.set_selected(variants.index(st.x11_variant))
        else:
            variant_combo.set_selected(0)

    layout_combo.connect("notify::selected", _reload_variants)
    _reload_variants()

    box, button, runner, _status = _action_block("Apply layout")

    def on_click(_b: Gtk.Button) -> None:
        layout = _selected_layout()
        if not layout:
            return
        vidx = variant_combo.get_selected()
        variants = variants_state["list"]
        variant = variants[vidx] if 0 <= vidx < len(variants) else ""

        def do_run() -> None:
            button.set_sensitive(False)
            runner.run(
                localization.set_x11_keymap_argv(layout, variant), privileged=True
            )

        confirm.confirm(
            button,
            "Change keyboard layout?",
            f"Sets the system XKB layout to {layout}.",
            do_run,
        )

    button.connect("clicked", on_click)
    group.add(box)
    return group
