"""Power: Akku-Info (read) + Idle-Timeouts (niri-only über swayidle).

Akku/AC kommen aus /sys/class/power_supply (reiner Read). Idle-Timeouts
(Bildschirm aus / Suspend) verwaltet TANINUX über swayidle: niri implementiert
das ext-idle-notify-Protokoll, daher ist swayidle der passende Idle-Daemon.
Verwaltet wird ein eigener taninux-swayidle.service (swayidle hat KEINE
Config-Datei — alles steht in den ExecStart-Args); der Bildschirm wird per
`niri msg action power-off-monitors` ab- und wieder angeschaltet.

Die niri-Config (config.kdl) wird nicht angefasst; Autostart läuft über den
systemd-User-Service.
"""
from __future__ import annotations

import re
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path

_PS = Path("/sys/class/power_supply")
SWAYIDLE_UNIT = (
    Path.home() / ".config" / "systemd" / "user" / "taninux-swayidle.service"
)


# ----------------------------------------------------------- Akku / AC

@dataclass
class Battery:
    name: str
    capacity: int       # Prozent, -1 = unbekannt
    status: str         # Charging / Discharging / Full / ?


def _read(p: Path) -> str:
    try:
        return p.read_text().strip()
    except OSError:
        return ""


def batteries() -> list[Battery]:
    out: list[Battery] = []
    if not _PS.is_dir():
        return out
    for d in sorted(_PS.iterdir()):
        if _read(d / "type") == "Battery":
            cap = _read(d / "capacity")
            out.append(Battery(
                name=d.name,
                capacity=int(cap) if cap.isdigit() else -1,
                status=_read(d / "status") or "?",
            ))
    return out


def ac_online() -> bool | None:
    """True/False wenn ein AC-Adapter gefunden wird, sonst None."""
    if not _PS.is_dir():
        return None
    for d in sorted(_PS.iterdir()):
        if _read(d / "type") == "Mains" or d.name.startswith(("AC", "ADP")):
            v = _read(d / "online")
            if v in ("0", "1"):
                return v == "1"
    return None


# --------------------------------------------------- Energy-Mode (PPD)

def _out(args: list[str], timeout: int = 5) -> str:
    try:
        r = subprocess.run(args, capture_output=True, text=True,
                           check=False, timeout=timeout)
        return r.stdout if r.returncode == 0 else ""
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return ""


def ppd_installed() -> bool:
    return shutil.which("powerprofilesctl") is not None


def power_profiles() -> list[str]:
    """Verfügbare Profile aus `powerprofilesctl list` (z.B. performance,
    balanced, power-saver). Leer wenn Daemon nicht läuft."""
    profs: list[str] = []
    for line in _out(["powerprofilesctl", "list"]).splitlines():
        s = line.strip()
        if s.startswith("* "):           # aktiv markiert
            s = s[2:]
        if re.fullmatch(r"[a-z][a-z-]*:", s):   # reine "name:"-Zeile
            profs.append(s[:-1])
    return profs


def current_profile() -> str:
    return _out(["powerprofilesctl", "get"]).strip()


def set_profile(name: str) -> None:
    try:
        subprocess.run(["powerprofilesctl", "set", name],
                       check=False, timeout=5)
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass


# ------------------------------------------------- Bildschirmhelligkeit

def brightness_available() -> bool:
    """True, wenn brightnessctl da ist UND es ein Backlight-Gerät gibt.
    Desktops ohne Backlight → False (Gruppe wird dann weggelassen)."""
    if shutil.which("brightnessctl") is None:
        return False
    # `-c backlight info` gibt rc 0 nur bei vorhandenem Backlight.
    try:
        r = subprocess.run(
            ["brightnessctl", "-c", "backlight", "info"],
            capture_output=True, text=True, check=False, timeout=5,
        )
        if r.returncode == 0:
            return True
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False
    # Fallback: device-Liste nach class 'backlight' durchsuchen.
    return "class 'backlight'" in _out(["brightnessctl", "-l"])


def brightness_percent() -> int:
    """Aktuelle Helligkeit in Prozent. 0 bei Fehler."""
    # machine-readable: device,class,current,percent,max — Feld 4 ist "NN%".
    line = _out(["brightnessctl", "-m", "-c", "backlight", "info"]).strip()
    if line:
        fields = line.splitlines()[0].split(",")
        if len(fields) >= 4:
            pct = fields[3].rstrip("%")
            if pct.isdigit():
                return int(pct)
    # Fallback: aus get/max berechnen.
    cur = _out(["brightnessctl", "-c", "backlight", "get"]).strip()
    mx = _out(["brightnessctl", "-c", "backlight", "max"]).strip()
    if cur.isdigit() and mx.isdigit() and int(mx) > 0:
        return round(int(cur) * 100 / int(mx))
    return 0


def set_brightness_argv(pct: int) -> list[str]:
    """argv zum Setzen der Helligkeit (min 1 %, nie 0)."""
    return ["brightnessctl", "-c", "backlight", "set",
            f"{max(1, min(100, int(pct)))}%"]


# ----------------------------------------------------------- swayidle (niri)

def swayidle_installed() -> bool:
    return shutil.which("swayidle") is not None


def _read_idle_swayidle() -> dict[str, int]:
    """Liest screen_off/suspend (Sekunden) aus der ExecStart-Zeile des
    taninux-swayidle.service. swayidle hat keine Config-Datei — die Timeouts
    stehen als `timeout <sec> '<cmd>'`-Paare in den Args. 0 = aus."""
    res = {"screen_off": 0, "suspend": 0}
    if not SWAYIDLE_UNIT.is_file():
        return res
    text = SWAYIDLE_UNIT.read_text(errors="ignore")
    m = re.search(r"^\s*ExecStart\s*=\s*(.+)$", text, re.MULTILINE)
    if not m:
        return res
    exec_line = m.group(1)
    # Jedes timeout-Paar: timeout <sec> '<cmd>' — der cmd entscheidet das Ziel.
    for tm in re.finditer(r"timeout\s+(\d+)\s+(['\"])(.*?)\2", exec_line):
        sec = int(tm.group(1))
        cmd = tm.group(3).lower()
        if "power-off-monitors" in cmd or "dpms" in cmd:
            res["screen_off"] = sec
        elif "suspend" in cmd:
            res["suspend"] = sec
    return res


def _write_idle_swayidle(screen_off: int, suspend: int) -> Path:
    """Schreibt die TANINUX-verwaltete swayidle-User-Unit. Nur Listener mit
    Wert >0 kommen in die ExecStart-Zeile. Der Bildschirm wird per niri-Action
    (DPMS) ab-/angeschaltet; die niri-Config bleibt unberührt."""
    swayidle = shutil.which("swayidle") or "/usr/bin/swayidle"
    parts = [swayidle, "-w"]
    if screen_off > 0:
        parts += [
            "timeout", str(screen_off),
            "'niri msg action power-off-monitors'",
            "resume", "'niri msg action power-on-monitors'",
        ]
    if suspend > 0:
        parts += ["timeout", str(suspend), "'systemctl suspend'"]
    exec_start = " ".join(parts)

    lines = [
        "[Unit]",
        "Description=TANINUX-managed swayidle (idle timeouts)",
        "Documentation=man:swayidle(1)",
        "PartOf=graphical-session.target",
        "",
        "[Service]",
        "Type=simple",
        f"ExecStart={exec_start}",
        "Restart=on-failure",
        "",
        "[Install]",
        "WantedBy=default.target",
        "",
    ]
    SWAYIDLE_UNIT.parent.mkdir(parents=True, exist_ok=True)
    SWAYIDLE_UNIT.write_text("\n".join(lines))
    return SWAYIDLE_UNIT


def apply_swayidle() -> bool:
    """daemon-reload + User-Service enablen/(re)starten. True wenn ausgeführt."""
    if not swayidle_installed():
        return False
    try:
        subprocess.run(
            ["systemctl", "--user", "daemon-reload"],
            check=False, timeout=8,
        )
        subprocess.run(
            ["systemctl", "--user", "enable", "--now",
             "taninux-swayidle.service"],
            check=False, timeout=8,
        )
        subprocess.run(
            ["systemctl", "--user", "restart", "taninux-swayidle.service"],
            check=False, timeout=8,
        )
        return True
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False


# -------------------------------------------------- Idle (niri-only)

def idle_daemon_name() -> str:
    """Idle-Daemon für niri — swayidle (spricht ext-idle-notify)."""
    return "swayidle"


def idle_pkg() -> str:
    """Paketname für pacman — identisch zum Daemon-Namen."""
    return idle_daemon_name()


def idle_daemon_installed() -> bool:
    return swayidle_installed()


def read_idle_timeouts() -> dict[str, int]:
    """screen_off/suspend (Sekunden) aus der swayidle-Unit lesen. 0 = aus."""
    return _read_idle_swayidle()


def write_idle_config(screen_off: int, suspend: int) -> Path:
    """swayidle-Unit schreiben. 0 = Listener weglassen. Gibt den Pfad zurück."""
    return _write_idle_swayidle(screen_off, suspend)


def apply_idle() -> bool:
    """Idle-Daemon anwenden (best-effort, unprivilegiert)."""
    return apply_swayidle()
