"""Display — Monitor-/Output-Einstellungen, niri-only über native IPC.

Pro Output: An/Aus, Auflösung+Refresh, Scale, Rotation. Alles live über
core.display (`niri msg output`), unprivilegiert. Bewusst KEINE Persistenz in
die niri-Config (config.kdl).
"""
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 compositor, display
from taninux.gui.console import RunnerCard
from taninux.gui.process import ProcessRunner

# gängige Scale-Stufen
_SCALES = [1.0, 1.25, 1.5, 1.75, 2.0]
_ROTATIONS = ["normal", "90", "180", "270"]


def build() -> Gtk.Widget:
    page = Adw.PreferencesPage()

    if compositor.detect() == compositor.UNKNOWN:
        group = Adw.PreferencesGroup(title="Display")
        group.add(Adw.ActionRow(
            title="Display settings aren't available in this session",
        ))
        page.add(group)
        return page

    outs = display.outputs()
    if not outs:
        group = Adw.PreferencesGroup(title="Display")
        group.add(Adw.ActionRow(title="No monitors detected"))
        page.add(group)
        return page

    # Änderungen sind sofort am Bildschirm sichtbar (nicht persistent) — kein Log.
    runner = ProcessRunner()

    def apply(**kwargs) -> None:
        out = kwargs.pop("out")
        runner.run(display.apply_argv(out, **kwargs), privileged=False)

    for out in outs:
        page.add(_output_group(out, apply))

    page.add(_night_light_group())
    return page


def _output_group(out: display.Output, apply) -> Adw.PreferencesGroup:
    desc = f"{out.make} {out.model}".strip() or None
    group = Adw.PreferencesGroup(title=out.name, description=desc)

    # -- Enabled -----------------------------------------------------------
    enable_row = Adw.SwitchRow(title="Enabled")
    enable_row.set_active(out.enabled)
    enable_row.connect(
        "notify::active",
        lambda r, _p: apply(out=out, enabled=r.get_active()),
    )
    group.add(enable_row)

    # -- Resolution --------------------------------------------------------
    # eindeutige (w,h,hz) absteigend sortiert; aktueller Modus vorwählen.
    seen: set[tuple[int, int, float]] = set()
    modes: list[tuple[int, int, float]] = []
    for m in out.modes:
        key = (m[0], m[1], round(m[2], 2))
        if key not in seen:
            seen.add(key)
            modes.append(m)
    cur_mode = (out.width, out.height, out.refresh_hz)
    cur_key = (out.width, out.height, round(out.refresh_hz, 2))
    if cur_key not in seen and out.width:
        modes.insert(0, cur_mode)
    modes.sort(key=lambda m: (m[0], m[1], m[2]), reverse=True)

    if modes:
        labels = [f"{w}x{h} @ {hz:.2f}Hz" for (w, h, hz) in modes]
        res_row = Adw.ComboRow(title="Resolution")
        res_row.set_model(Gtk.StringList.new(labels))
        sel = next(
            (i for i, m in enumerate(modes)
             if (m[0], m[1], round(m[2], 2)) == cur_key),
            0,
        )
        res_row.set_selected(sel)
        # connect NACH set_selected, damit Initial nicht feuert
        res_row.connect(
            "notify::selected",
            lambda r, _p: apply(out=out, mode=modes[r.get_selected()]),
        )
        group.add(res_row)

    # -- Scale -------------------------------------------------------------
    scales = list(_SCALES)
    if not any(abs(s - out.scale) < 1e-3 for s in scales):
        scales.append(out.scale)
        scales.sort()
    scale_row = Adw.ComboRow(title="Scale")
    scale_row.set_model(Gtk.StringList.new([f"{s:g}" for s in scales]))
    sidx = next(
        (i for i, s in enumerate(scales) if abs(s - out.scale) < 1e-3), 0,
    )
    scale_row.set_selected(sidx)
    scale_row.connect(
        "notify::selected",
        lambda r, _p: apply(out=out, scale=scales[r.get_selected()]),
    )
    group.add(scale_row)

    # -- Rotation ----------------------------------------------------------
    rot_row = Adw.ComboRow(title="Rotation")
    rot_row.set_model(Gtk.StringList.new(_ROTATIONS))
    ridx = _ROTATIONS.index(out.transform) if out.transform in _ROTATIONS else 0
    rot_row.set_selected(ridx)
    rot_row.connect(
        "notify::selected",
        lambda r, _p: apply(out=out, transform=_ROTATIONS[r.get_selected()]),
    )
    group.add(rot_row)

    return group


def _night_light_group() -> Adw.PreferencesGroup:
    group = Adw.PreferencesGroup(
        title="Night light",
        description="Warm the screen colours in the evening.",
    )

    if not display.wlsunset_installed():
        group.add(Adw.ActionRow(title="Install to enable night light"))
        group.add(RunnerCard(
            "Install",
            ["pacman", "-S", "--needed", "--noconfirm", "wlsunset"],
            privileged=True,
        ))
        return group

    nl = display.night_light()

    enable_row = Adw.SwitchRow(title="Night light")
    enable_row.set_active(nl.enabled)
    group.add(enable_row)

    night_row = Adw.SpinRow.new_with_range(2500, 6500, 100)
    night_row.set_title("Night temperature")
    night_row.set_subtitle("Kelvin")
    night_row.set_value(nl.night_temp)
    group.add(night_row)

    day_row = Adw.SpinRow.new_with_range(3000, 6500, 100)
    day_row.set_title("Day temperature")
    day_row.set_subtitle("Kelvin")
    day_row.set_value(nl.day_temp)
    group.add(day_row)

    lat_row = Adw.EntryRow(title="Latitude")
    lat_row.set_text(f"{nl.lat:g}")
    group.add(lat_row)

    lon_row = Adw.EntryRow(title="Longitude")
    lon_row.set_text(f"{nl.lon:g}")
    group.add(lon_row)

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

    def _parse_float(text: str, fallback: float) -> float:
        try:
            return float(text.strip().replace(",", "."))
        except (ValueError, AttributeError):
            return fallback

    def _on_apply(_btn: Gtk.Button) -> None:
        lat = _parse_float(lat_row.get_text(), nl.lat)
        lon = _parse_float(lon_row.get_text(), nl.lon)
        path = display.write_night_light(
            day_temp=int(day_row.get_value()),
            night_temp=int(night_row.get_value()),
            lat=lat,
            lon=lon,
        )
        enabled = enable_row.get_active()
        ok = display.apply_night_light(enabled)
        state = "enabled" if enabled else "disabled"
        if ok:
            status.set_text(f"Night light {state}. Service unit: {path}")
        else:
            status.set_text(f"Wrote {path}, but systemctl --user failed.")

    apply_btn = Gtk.Button(label="Apply")
    apply_btn.add_css_class("suggested-action")
    apply_btn.add_css_class("pill")
    apply_btn.set_halign(Gtk.Align.START)
    apply_btn.set_margin_top(6)
    apply_btn.connect("clicked", _on_apply)

    box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
    box.set_margin_top(6)
    box.append(apply_btn)
    box.append(status)
    group.add(box)

    return group
