"""Audio-Steuerung via pactl (PulseAudio/PipeWire). UI-frei.

Genug für eine pavucontrol-artige Geräte+Lautstärke-Steuerung: Sinks/Sources
auflisten, Default setzen, Volume/Mute lesen+setzen. Alles User-Level (kein
root). `kind` ist "sink" (Ausgabe) oder "source" (Eingabe).
"""
from __future__ import annotations

import re
import subprocess
from dataclasses import dataclass

_PCT = re.compile(r"(\d+)%")


@dataclass
class Device:
    name: str          # interner pactl-Name (für set-default etc.)
    description: str    # menschlicher Name (fürs UI)


@dataclass
class Stream:
    index: str          # sink-input-Index (für set-sink-input-* etc.)
    app: str            # App-Name fürs UI
    volume: int
    muted: bool


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


def available() -> bool:
    return bool(_pactl(["info"]))


def list_devices(kind: str, include_monitors: bool = False) -> list[Device]:
    """Parst `pactl list sinks|sources` für Name + Description je Block."""
    out = _pactl(["list", f"{kind}s"])
    devices: list[Device] = []
    name: str | None = None
    for line in out.splitlines():
        if line and not line[0].isspace():     # Block-Header "Sink #N"
            name = None
            continue
        s = line.strip()
        if s.startswith("Name:") and name is None:
            name = s.split(":", 1)[1].strip()
        elif s.startswith("Description:") and name is not None:
            desc = s.split(":", 1)[1].strip()
            if include_monitors or not name.endswith(".monitor"):
                devices.append(Device(name, desc))
            name = None
    return devices


def get_default(kind: str) -> str:
    return _pactl([f"get-default-{kind}"]).strip()


def get_volume(kind: str, name: str) -> int:
    m = _PCT.search(_pactl([f"get-{kind}-volume", name]))
    return int(m.group(1)) if m else 0


def get_muted(kind: str, name: str) -> bool:
    return "yes" in _pactl([f"get-{kind}-mute", name]).lower()


def set_default(kind: str, name: str) -> None:
    _pactl([f"set-default-{kind}", name])


def set_volume(kind: str, name: str, pct: int) -> None:
    _pactl([f"set-{kind}-volume", name, f"{int(pct)}%"])


def set_mute(kind: str, name: str, muted: bool) -> None:
    _pactl([f"set-{kind}-mute", name, "1" if muted else "0"])


_APP_NAME = re.compile(r'application\.name\s*=\s*"([^"]*)"')
_MEDIA_NAME = re.compile(r'media\.name\s*=\s*"([^"]*)"')


def playback_streams() -> list[Stream]:
    """Parst `pactl list sink-inputs` zu Stream je App. Robust/leer bei Fehler."""
    out = _pactl(["list", "sink-inputs"])
    streams: list[Stream] = []
    index: str | None = None
    muted = False
    volume = 0
    app = ""
    have_vol = False

    def flush() -> None:
        if index is not None:
            streams.append(Stream(index, app or f"Stream #{index}", volume, muted))

    for line in out.splitlines():
        if line and not line[0].isspace():       # Block-Header "Sink Input #N"
            flush()
            m = re.search(r"#(\d+)", line)
            index = m.group(1) if m else None
            muted, volume, app, have_vol = False, 0, "", False
            continue
        s = line.strip()
        if s.startswith("Mute:"):
            muted = "yes" in s.lower()
        elif s.startswith("Volume:") and not have_vol:
            m = _PCT.search(s)
            if m:
                volume = int(m.group(1))
                have_vol = True
        else:
            m = _APP_NAME.search(s)
            if m and not app:
                app = m.group(1)
            elif not app:
                m = _MEDIA_NAME.search(s)
                if m:
                    app = m.group(1)
    flush()
    return streams


def set_stream_volume(index: str, pct: int) -> None:
    _pactl(["set-sink-input-volume", str(index), f"{int(pct)}%"])


def set_stream_mute(index: str, muted: bool) -> None:
    _pactl(["set-sink-input-mute", str(index), "1" if muted else "0"])
