"""Panel & Dock — schreibt panel.json + dock-pins, die eww konsumiert. UI-frei.

Vertrag: docs/eww-panel-dock-integration.md. TANINUX fasst ~/eww NIE direkt an;
es schreibt nur die Config-Dateien und feuert optional den eww-Apply-Hook
(scripts/panelcfg.sh apply) — das ist ein eww-BEREITGESTELLTES Skript, kein
Edit. eww zieht sonst per Watcher (≤2 s) nach.
"""
from __future__ import annotations

import json
import os
import subprocess
from dataclasses import dataclass
from pathlib import Path

from taninux.core.paths import data_dir

MODULES = ("music", "sys", "updates", "net", "bt", "vol")

_DEFAULTS = {
    "version": 1,
    "dock": {"autohide": True, "icon_size": 42, "hide_delay": 1.2},
    "bar": {
        "clock_format": "%H:%M %A, %d.%m.%Y",
        "modules": {m: True for m in MODULES},
    },
    # Update-Scope: gilt für Hub UND den eww-Update-Zähler. AUR ist Opt-in
    # (braucht paru/base-devel) — Default Repo + Flatpak.
    "updates": {"include_aur": False},
}


def _panel_path() -> Path:
    return data_dir() / "panel.json"


def _pins_path() -> Path:
    base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
    return Path(base) / "eww" / "dock-pins"


def _apply_script() -> Path:
    base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
    return Path(base) / "eww" / "scripts" / "panelcfg.sh"


# ----------------------------------------------------------- panel.json

def load() -> dict:
    """panel.json lesen, mit Defaults auffüllen (defensiv)."""
    cfg = json.loads(json.dumps(_DEFAULTS))  # deep copy
    try:
        data = json.loads(_panel_path().read_text())
    except (OSError, json.JSONDecodeError):
        return cfg
    if isinstance(data.get("dock"), dict):
        cfg["dock"].update({k: v for k, v in data["dock"].items()
                            if k in cfg["dock"]})
    bar = data.get("bar")
    if isinstance(bar, dict):
        if isinstance(bar.get("clock_format"), str):
            cfg["bar"]["clock_format"] = bar["clock_format"]
        if isinstance(bar.get("modules"), dict):
            for m in MODULES:
                if m in bar["modules"]:
                    cfg["bar"]["modules"][m] = bool(bar["modules"][m])
    upd = data.get("updates")
    if isinstance(upd, dict) and "include_aur" in upd:
        cfg["updates"]["include_aur"] = bool(upd["include_aur"])
    return cfg


def save(cfg: dict, *, apply: bool = True) -> None:
    """panel.json schreiben (mit Clamping) und optional eww anwenden lassen."""
    dock = cfg.get("dock", {})
    try:
        icon = int(dock.get("icon_size", 42))
    except (TypeError, ValueError):
        icon = 42
    try:
        delay = float(dock.get("hide_delay", 1.2))
    except (TypeError, ValueError):
        delay = 1.2
    out = {
        "version": 1,
        "dock": {
            "autohide": bool(dock.get("autohide", True)),
            "icon_size": max(24, min(64, icon)),
            "hide_delay": max(0.2, min(5.0, round(delay, 1))),
        },
        "bar": {
            "clock_format": str(cfg.get("bar", {}).get("clock_format")
                                or _DEFAULTS["bar"]["clock_format"]),
            "modules": {
                m: bool(cfg.get("bar", {}).get("modules", {}).get(m, True))
                for m in MODULES
            },
        },
        "updates": {
            "include_aur": bool(cfg.get("updates", {}).get("include_aur", False)),
        },
    }
    p = _panel_path()
    try:
        p.write_text(json.dumps(out, indent=2))
    except OSError:
        return
    if apply:
        apply_now()


def include_aur() -> bool:
    """Ob AUR im Update-/Such-Scope ist (Opt-in). Single source: panel.json."""
    return bool(load().get("updates", {}).get("include_aur", False))


def set_include_aur(value: bool) -> None:
    """AUR-Scope umschalten — panel.json aktualisieren + eww anwenden lassen."""
    cfg = load()
    cfg.setdefault("updates", {})["include_aur"] = bool(value)
    save(cfg, apply=True)


def apply_now() -> None:
    """eww-Apply-Hook feuern (best-effort). Skript gehört eww, wird nur ausgeführt."""
    script = _apply_script()
    if not script.exists():
        return
    try:
        subprocess.run([str(script), "apply"], check=False, timeout=5)
    except (FileNotFoundError, subprocess.TimeoutExpired, PermissionError):
        pass


# ----------------------------------------------------------- dock-pins

@dataclass
class Pin:
    exec: str
    wmclass: str
    icon: str


def load_pins() -> list[Pin]:
    """dock-pins lesen — Zeilen `exec|class|icon`."""
    pins: list[Pin] = []
    try:
        text = _pins_path().read_text()
    except OSError:
        return pins
    for line in text.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        parts = line.split("|")
        if len(parts) >= 3:
            pins.append(Pin(parts[0].strip(), parts[1].strip(), parts[2].strip()))
    return pins


def save_pins(pins: list[Pin]) -> None:
    """dock-pins komplett neu schreiben (idempotent; eww pollt alle 2 s)."""
    lines = [f"{p.exec}|{p.wmclass}|{p.icon}" for p in pins]
    path = _pins_path()
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text("\n".join(lines) + ("\n" if lines else ""))
    except OSError:
        pass
