#!/usr/bin/env python3
"""
nv_mp4 · SMART cross-plat H.264 encode + HTML5 play SPV host path

  Playback  = HTML5 SPV · never needs ffmpeg
  Encode    = SMART pick among vendors (loose ffmpeg)
      NVIDIA  h264_nvenc
      Intel   h264_qsv   (Arc / Quick Sync)
      AMD     h264_amf (Win) · h264_vaapi (Linux)
      VAAPI   h264_vaapi (Intel/AMD Linux shared)
      Soft    libx264 · libopenh264
  Capture
      Linux   v4l2 + ALSA
      Windows dshow
      Apple   avfoundation + VideoToolbox encode

  You bought NVIDIA · also Arc · also AMD · other guys get h264 too.
"""
from __future__ import annotations

import json
import os
import platform
import re
import shutil
import subprocess
from functools import lru_cache
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

ROOT = Path(__file__).resolve().parents[1]
SETTINGS = ROOT / "settings"
NV_JSON = SETTINGS / "nv_mp4.json"

DEFAULTS: Dict[str, Any] = {
    "version": 2,
    "name": "nv_mp4",
    "cross_plat": True,
    "loose_ffmpeg": True,
    "smart": True,
    # prefer: auto | nvidia | intel | amd | vaapi | soft
    "prefer": "auto",
    "prefer_nvenc": True,  # compat: maps into prefer=nvidia when auto ties
    "codec": "auto",
    "video_size": "1280x720",
    "framerate": 30,
    "video_bitrate": "6000k",
    "audio_bitrate": "160k",
    "audio_rate": 44100,
    "nvenc_preset": "p4",
    "nvenc_tune": "ll",
    "qsv_preset": "veryfast",
    "vaapi_device": "/dev/dri/renderD128",
    "pix_fmt": "yuv420p",
    "note": "HTML5 play · SMART H.264 encode NV/Arc/AMD/soft · loose ffmpeg",
}

# encode candidates: (codec, vendor_tag, needs_any_of_gpus_tags or empty=always if encoder exists)
# score boost applied in pick
VENDOR_CODECS = (
    ("h264_nvenc", "nvidia"),
    ("hevc_nvenc", "nvidia"),
    ("h264_qsv", "intel"),
    ("hevc_qsv", "intel"),
    ("h264_amf", "amd"),
    ("hevc_amf", "amd"),
    ("h264_vaapi", "vaapi"),
    ("hevc_vaapi", "vaapi"),
    ("h264_videotoolbox", "apple"),
    ("hevc_videotoolbox", "apple"),
    ("libx264", "soft"),
    ("libopenh264", "soft"),
)


def plat() -> str:
    s = platform.system().lower()
    if s.startswith("linux"):
        return "linux"
    if s.startswith("darwin"):
        return "darwin"
    if s.startswith("win"):
        return "windows"
    return s or "unknown"


def which_ffmpeg() -> Optional[str]:
    return shutil.which("ffmpeg")


def which_nvidia_smi() -> Optional[str]:
    return shutil.which("nvidia-smi")


@lru_cache(maxsize=1)
def _ffmpeg_encoders_blob() -> str:
    ff = which_ffmpeg()
    if not ff:
        return ""
    try:
        p = subprocess.run(
            [ff, "-hide_banner", "-encoders"],
            capture_output=True,
            text=True,
            timeout=15,
        )
        return (p.stdout or "") + (p.stderr or "")
    except Exception:
        return ""


def ffmpeg_has_encoder(name: str) -> bool:
    blob = _ffmpeg_encoders_blob()
    if not blob:
        return False
    # word boundary-ish: encoder lines look like " V....D h264_nvenc "
    return re.search(rf"\b{re.escape(name)}\b", blob) is not None


def ffmpeg_list_h264_encoders() -> List[str]:
    names = [
        "h264_nvenc",
        "h264_qsv",
        "h264_amf",
        "h264_vaapi",
        "h264_videotoolbox",
        "libx264",
        "libopenh264",
    ]
    return [n for n in names if ffmpeg_has_encoder(n)]


def ffmpeg_hwaccels() -> List[str]:
    ff = which_ffmpeg()
    if not ff:
        return []
    try:
        p = subprocess.run(
            [ff, "-hide_banner", "-hwaccels"],
            capture_output=True,
            text=True,
            timeout=8,
        )
        out = []
        for ln in (p.stdout or "").splitlines():
            t = ln.strip().lower()
            if t and t != "hardware acceleration methods:":
                out.append(t)
        return out
    except Exception:
        return []


def nvidia_gpus() -> List[str]:
    smi = which_nvidia_smi()
    if not smi:
        return []
    try:
        p = subprocess.run([smi, "-L"], capture_output=True, text=True, timeout=8)
        return [ln.strip() for ln in (p.stdout or "").splitlines() if ln.strip()]
    except Exception:
        return []


def _lspci_lines() -> List[str]:
    if not shutil.which("lspci"):
        return []
    try:
        p = subprocess.run(
            ["lspci"],
            capture_output=True,
            text=True,
            timeout=8,
        )
        return (p.stdout or "").splitlines()
    except Exception:
        return []


def detect_gpus() -> Dict[str, Any]:
    """Multi-vendor GPU inventory · nvidia · intel/arc · amd · apple."""
    p = plat()
    nvidia = nvidia_gpus()
    intel: List[str] = []
    amd: List[str] = []
    apple: List[str] = []
    other: List[str] = []

    if p == "linux":
        for ln in _lspci_lines():
            low = ln.lower()
            if "vga" not in low and "3d" not in low and "display" not in low:
                continue
            if "nvidia" in low:
                if ln.strip() not in nvidia:
                    nvidia.append(ln.strip())
            elif "intel" in low or "arc" in low or "alchemist" in low or "xe " in low:
                intel.append(ln.strip())
            elif "amd" in low or "ati" in low or "radeon" in low or "raphael" in low:
                amd.append(ln.strip())
            else:
                other.append(ln.strip())
        # dri nodes
        dri = []
        dri_path = Path("/dev/dri")
        if dri_path.is_dir():
            dri = sorted(str(x) for x in dri_path.glob("renderD*"))
    elif p == "darwin":
        apple = ["VideoToolbox / Apple GPU (system)"]
        dri = []
    elif p == "windows":
        # best-effort: wmic or powershell optional
        dri = []
        try:
            p1 = subprocess.run(
                ["wmic", "path", "win32_VideoController", "get", "name"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            for ln in (p1.stdout or "").splitlines():
                t = ln.strip()
                if not t or t.lower() == "name":
                    continue
                low = t.lower()
                if "nvidia" in low:
                    nvidia.append(t)
                elif "intel" in low or "arc" in low:
                    intel.append(t)
                elif "amd" in low or "radeon" in low:
                    amd.append(t)
                else:
                    other.append(t)
        except Exception:
            pass
    else:
        dri = []

    vendors = []
    if nvidia:
        vendors.append("nvidia")
    if intel:
        vendors.append("intel")
    if amd:
        vendors.append("amd")
    if apple:
        vendors.append("apple")

    return {
        "nvidia": nvidia,
        "intel": intel,
        "amd": amd,
        "apple": apple,
        "other": other,
        "vendors": vendors,
        "dri_render": dri if p == "linux" else [],
        "primary": (
            "nvidia"
            if nvidia
            else "intel"
            if intel
            else "amd"
            if amd
            else "apple"
            if apple
            else "soft"
        ),
    }


def default_av_devices(p: Optional[str] = None) -> Dict[str, Any]:
    p = p or plat()
    if p == "linux":
        v = "/dev/video0" if Path("/dev/video0").exists() else None
        videos = sorted(str(x) for x in Path("/dev").glob("video*")) if Path("/dev").exists() else []
        return {
            "video_backend": "v4l2",
            "audio_backend": "alsa",
            "video_device": v or "/dev/video0",
            "audio_device": "plughw:CARD=CAMERA,DEV=0",
            "video_ok": bool(v),
            "video_nodes": videos,
            "note": "Linux · v4l2 + ALSA (you asked · yes)",
        }
    if p == "windows":
        return {
            "video_backend": "dshow",
            "audio_backend": "dshow",
            "video_device": "video=Integrated Camera",
            "audio_device": "audio=Microphone",
            "video_ok": None,
            "note": "Windows · dshow · set device names in settings",
        }
    if p == "darwin":
        return {
            "video_backend": "avfoundation",
            "audio_backend": "avfoundation",
            "video_device": "0",
            "audio_device": "0",
            "video_ok": None,
            "note": "Apple · avfoundation indices · VideoToolbox encode",
        }
    return {
        "video_backend": "unknown",
        "audio_backend": "unknown",
        "video_device": None,
        "audio_device": None,
        "video_ok": False,
    }


def _prefer_list(prefer: str, gpus: Dict[str, Any]) -> List[str]:
    """Ordered vendor preference for H.264 pick."""
    prefer = (prefer or "auto").lower()
    if prefer in ("nvidia", "nv", "nvenc"):
        return ["nvidia", "intel", "amd", "vaapi", "apple", "soft"]
    if prefer in ("intel", "arc", "qsv"):
        return ["intel", "vaapi", "nvidia", "amd", "apple", "soft"]
    if prefer in ("amd", "amf", "radeon"):
        return ["amd", "vaapi", "nvidia", "intel", "apple", "soft"]
    if prefer in ("vaapi",):
        return ["vaapi", "intel", "amd", "nvidia", "soft"]
    if prefer in ("apple", "vt", "videotoolbox"):
        return ["apple", "soft"]
    if prefer in ("soft", "cpu", "x264"):
        return ["soft"]
    # auto: primary discrete first
    primary = gpus.get("primary") or "soft"
    if primary == "nvidia":
        return ["nvidia", "intel", "amd", "vaapi", "apple", "soft"]
    if primary == "intel":
        return ["intel", "vaapi", "nvidia", "amd", "apple", "soft"]
    if primary == "amd":
        # Linux AMD → vaapi often; Win → amf
        if plat() == "windows":
            return ["amd", "vaapi", "nvidia", "intel", "soft"]
        return ["vaapi", "amd", "nvidia", "intel", "soft"]
    if primary == "apple":
        return ["apple", "soft"]
    return ["nvidia", "intel", "amd", "vaapi", "apple", "soft"]


def pick_encoder(
    *,
    prefer: str = "auto",
    prefer_nvenc: bool = True,
    want_hevc: bool = False,
) -> Tuple[str, str, Dict[str, Any]]:
    """
    SMART H.264 (default) pick for all guys.
    Returns (codec, reason, meta).
    """
    if not which_ffmpeg():
        return (
            "none",
            "ffmpeg absent · loose · HTML5 SPV playback only",
            {"vendor": "none", "family": "none"},
        )

    gpus = detect_gpus()
    if prefer == "auto" and prefer_nvenc and gpus.get("nvidia"):
        # keep historical "you bought NVIDIA" bias when auto + nv present
        order = _prefer_list("nvidia", gpus)
    else:
        order = _prefer_list(prefer, gpus)

    # map vendor → codec candidates (H.264 first unless hevc)
    def codecs_for(vendor: str) -> List[str]:
        if vendor == "nvidia":
            return ["hevc_nvenc", "h264_nvenc"] if want_hevc else ["h264_nvenc"]
        if vendor == "intel":
            return ["hevc_qsv", "h264_qsv"] if want_hevc else ["h264_qsv"]
        if vendor == "amd":
            if plat() == "windows":
                return ["hevc_amf", "h264_amf"] if want_hevc else ["h264_amf"]
            # Linux AMD usually VAAPI not AMF
            return ["hevc_vaapi", "h264_vaapi"] if want_hevc else ["h264_vaapi"]
        if vendor == "vaapi":
            return ["hevc_vaapi", "h264_vaapi"] if want_hevc else ["h264_vaapi"]
        if vendor == "apple":
            return (
                ["hevc_videotoolbox", "h264_videotoolbox"]
                if want_hevc
                else ["h264_videotoolbox"]
            )
        if vendor == "soft":
            return ["libx264", "libopenh264"]
        return []

    available = ffmpeg_list_h264_encoders()
    # also allow hevc list when wanted
    if want_hevc:
        for n in (
            "hevc_nvenc",
            "hevc_qsv",
            "hevc_amf",
            "hevc_vaapi",
            "hevc_videotoolbox",
        ):
            if ffmpeg_has_encoder(n) and n not in available:
                available.append(n)

    tried: List[str] = []
    for vendor in order:
        # skip vendor with no hardware evidence except soft/vaapi/apple
        if vendor == "nvidia" and not gpus.get("nvidia") and not ffmpeg_has_encoder(
            "h264_nvenc"
        ):
            continue
        if vendor == "intel" and not gpus.get("intel") and not ffmpeg_has_encoder(
            "h264_qsv"
        ):
            # still allow qsv if encoder exists (driver-only systems)
            if not ffmpeg_has_encoder("h264_qsv"):
                continue
        if vendor == "amd" and not gpus.get("amd") and not (
            ffmpeg_has_encoder("h264_amf") or ffmpeg_has_encoder("h264_vaapi")
        ):
            continue
        if vendor == "apple" and plat() != "darwin":
            continue
        if vendor == "vaapi" and plat() != "linux":
            if not ffmpeg_has_encoder("h264_vaapi"):
                continue

        for codec in codecs_for(vendor):
            tried.append(codec)
            if not ffmpeg_has_encoder(codec):
                continue
            # vaapi needs a render node ideally
            if codec.endswith("_vaapi") and plat() == "linux":
                dri = gpus.get("dri_render") or []
                if not dri and not Path("/dev/dri").exists():
                    continue
            reason = {
                "h264_nvenc": "NVIDIA NVENC H.264 · you bought NVIDIA",
                "hevc_nvenc": "NVIDIA NVENC HEVC",
                "h264_qsv": "Intel Quick Sync / Arc H.264",
                "hevc_qsv": "Intel Quick Sync / Arc HEVC",
                "h264_amf": "AMD AMF H.264 (Windows)",
                "hevc_amf": "AMD AMF HEVC (Windows)",
                "h264_vaapi": "VAAPI H.264 · AMD/Intel Linux",
                "hevc_vaapi": "VAAPI HEVC · AMD/Intel Linux",
                "h264_videotoolbox": "Apple VideoToolbox H.264",
                "hevc_videotoolbox": "Apple VideoToolbox HEVC",
                "libx264": "software libx264 · other guys / fallback",
                "libopenh264": "software OpenH264 · fallback",
            }.get(codec, codec)
            meta = {
                "vendor": vendor,
                "family": "hevc" if "hevc" in codec or "h265" in codec else "h264",
                "available_h264": ffmpeg_list_h264_encoders(),
                "order": order,
                "tried": tried,
                "gpus": gpus,
            }
            return codec, reason, meta

    return (
        "none",
        "no H.264 encoder in ffmpeg · loose · play still ok",
        {"vendor": "none", "family": "none", "tried": tried, "gpus": gpus},
    )


def load_settings() -> Dict[str, Any]:
    SETTINGS.mkdir(parents=True, exist_ok=True)
    s = dict(DEFAULTS)
    if NV_JSON.is_file():
        try:
            s.update(json.loads(NV_JSON.read_text(encoding="utf-8")))
        except Exception:
            pass
    for k, v in DEFAULTS.items():
        s.setdefault(k, v)
    return s


def save_settings(s: Dict[str, Any]) -> Path:
    SETTINGS.mkdir(parents=True, exist_ok=True)
    out = dict(s)
    out.pop("stream_key", None)
    out.pop("key", None)
    NV_JSON.write_text(json.dumps(out, indent=2) + "\n", encoding="utf-8")
    return NV_JSON


def probe() -> Dict[str, Any]:
    p = plat()
    gpus = detect_gpus()
    s = load_settings()
    prefer = s.get("prefer") or "auto"
    codec, reason, meta = pick_encoder(
        prefer=prefer,
        prefer_nvenc=bool(s.get("prefer_nvenc", True)),
    )
    av = default_av_devices(p)
    hw = ffmpeg_hwaccels()
    ff = which_ffmpeg()
    return {
        "ok": True,
        "name": "nv_mp4",
        "smart": True,
        "platform": p,
        "cross_plat": True,
        "loose_ffmpeg": True,
        "ffmpeg": bool(ff),
        "ffmpeg_path": ff,
        "nvidia_smi": bool(which_nvidia_smi()),
        "gpus": gpus,
        "gpu0": (gpus.get("nvidia") or gpus.get("intel") or gpus.get("amd") or [None])[0],
        "vendors": gpus.get("vendors") or [],
        "primary_vendor": gpus.get("primary"),
        "prefer": prefer,
        "h264_encoders": ffmpeg_list_h264_encoders(),
        "nvenc_h264": ffmpeg_has_encoder("h264_nvenc"),
        "qsv_h264": ffmpeg_has_encoder("h264_qsv"),
        "amf_h264": ffmpeg_has_encoder("h264_amf"),
        "vaapi_h264": ffmpeg_has_encoder("h264_vaapi"),
        "vt_h264": ffmpeg_has_encoder("h264_videotoolbox"),
        "libx264": ffmpeg_has_encoder("libx264"),
        "hwaccels": hw,
        "cuda": "cuda" in hw,
        "qsv": "qsv" in hw,
        "vaapi": "vaapi" in hw,
        "codec": codec,
        "codec_reason": reason,
        "codec_vendor": meta.get("vendor"),
        "codec_meta": {k: meta[k] for k in ("vendor", "family", "order") if k in meta},
        "av": av,
        "capture": {
            "linux": "v4l2 + ALSA",
            "windows": "dshow",
            "darwin": "avfoundation",
        }.get(p, p),
        "playback": "html5_spv · no ffmpeg required",
        "encode": "SMART H.264 · NV / Arc / AMD / VT / soft · loose",
        "ready_encode": codec != "none" and bool(ff),
        "ready_playback": True,
        "grin": 1,
    }


def _video_encode_args(codec: str, s: Dict[str, Any], fps: str, vbr: str, gop: str) -> List[str]:
    pix = s.get("pix_fmt") or "yuv420p"
    if codec == "h264_nvenc" or codec == "hevc_nvenc":
        args = [
            "-c:v",
            codec,
            "-preset",
            str(s.get("nvenc_preset") or "p4"),
            "-b:v",
            vbr,
            "-maxrate",
            vbr,
            "-bufsize",
            "12000k",
            "-pix_fmt",
            pix,
            "-g",
            gop,
            "-rc",
            "cbr",
        ]
        if s.get("nvenc_tune") and codec == "h264_nvenc":
            args += ["-tune", str(s.get("nvenc_tune"))]
        return args
    if codec in ("h264_qsv", "hevc_qsv"):
        return [
            "-c:v",
            codec,
            "-preset",
            str(s.get("qsv_preset") or "veryfast"),
            "-b:v",
            vbr,
            "-maxrate",
            vbr,
            "-g",
            gop,
            "-look_ahead",
            "0",
        ]
    if codec in ("h264_amf", "hevc_amf"):
        return [
            "-c:v",
            codec,
            "-quality",
            "speed",
            "-b:v",
            vbr,
            "-maxrate",
            vbr,
            "-g",
            gop,
        ]
    if codec in ("h264_vaapi", "hevc_vaapi"):
        # caller must put hwupload pipeline in input section — we use simple -c:v with scale_vaapi optional
        return [
            "-c:v",
            codec,
            "-b:v",
            vbr,
            "-maxrate",
            vbr,
            "-g",
            gop,
        ]
    if codec in ("h264_videotoolbox", "hevc_videotoolbox"):
        return [
            "-c:v",
            codec,
            "-b:v",
            vbr,
            "-realtime",
            "1",
            "-g",
            gop,
        ]
    # soft
    return [
        "-c:v",
        codec if codec in ("libx264", "libopenh264") else "libx264",
        "-preset",
        str(s.get("preset") or "veryfast"),
        "-b:v",
        vbr,
        "-maxrate",
        vbr,
        "-bufsize",
        "5000k",
        "-pix_fmt",
        pix,
        "-g",
        gop,
    ]


def build_encode_cmd(
    *,
    rtmp_url: Optional[str] = None,
    out_mp4: Optional[str] = None,
    dry: bool = False,
    settings: Optional[Dict[str, Any]] = None,
    video_device: Optional[str] = None,
    audio_device: Optional[str] = None,
) -> Dict[str, Any]:
    s = settings or load_settings()
    pr = probe()
    if not pr.get("ffmpeg"):
        return {
            "ok": False,
            "loose": True,
            "error": "ffmpeg optional-missing · HTML5 SPV still plays · install ffmpeg for HW/soft encode",
            "probe": pr,
        }

    prefer = s.get("prefer") or "auto"
    if s.get("codec") and s.get("codec") not in ("auto", "none", ""):
        codec = str(s.get("codec"))
        reason = f"forced settings.codec={codec}"
        vendor = "forced"
    else:
        codec, reason, meta = pick_encoder(
            prefer=prefer,
            prefer_nvenc=bool(s.get("prefer_nvenc", True)),
        )
        vendor = meta.get("vendor")
    if codec == "none":
        return {"ok": False, "loose": True, "error": "no encoder", "probe": pr}

    p = pr["platform"]
    av = pr["av"]
    vdev = video_device or s.get("video_device") or av.get("video_device")
    adev = audio_device or s.get("audio_device") or av.get("audio_device")
    size = s.get("video_size") or "1280x720"
    fps = str(int(s.get("framerate") or 30))
    vbr = s.get("video_bitrate") or "6000k"
    abr = s.get("audio_bitrate") or "160k"
    ar = str(int(s.get("audio_rate") or 44100))
    gop = str(int(fps) * 2)

    cmd: List[str] = [
        pr["ffmpeg_path"] or "ffmpeg",
        "-hide_banner",
        "-loglevel",
        "warning",
    ]

    # VAAPI wants device early
    if codec.endswith("_vaapi"):
        rend = s.get("vaapi_device") or (
            (pr.get("gpus") or {}).get("dri_render") or ["/dev/dri/renderD128"]
        )[0]
        cmd += ["-vaapi_device", str(rend)]

    # inputs cross-plat
    if p == "linux":
        cmd += [
            "-f",
            "v4l2",
            "-framerate",
            fps,
            "-video_size",
            size,
            "-i",
            str(vdev),
            "-f",
            "alsa",
            "-i",
            str(adev),
        ]
        if codec.endswith("_vaapi"):
            # convert to vaapi surface
            cmd += [
                "-vf",
                "format=nv12,hwupload",
            ]
    elif p == "windows":
        vname = str(vdev or "video=Integrated Camera")
        aname = str(adev or "audio=Microphone")
        if not vname.startswith("video="):
            vname = f"video={vname}"
        if not aname.startswith("audio="):
            aname = f"audio={aname}"
        cmd += ["-f", "dshow", "-framerate", fps, "-i", f"{vname}:{aname}"]
    elif p == "darwin":
        vi = str(vdev if vdev is not None else "0")
        ai = str(adev if adev is not None else "0")
        cmd += [
            "-f",
            "avfoundation",
            "-framerate",
            fps,
            "-video_size",
            size,
            "-i",
            f"{vi}:{ai}",
        ]
    else:
        return {"ok": False, "error": f"unsupported platform {p}", "probe": pr}

    cmd += _video_encode_args(codec, s, fps, vbr, gop)
    cmd += ["-c:a", "aac", "-b:a", abr, "-ar", ar, "-ac", "2"]

    if rtmp_url:
        url = "rtmp://live.twitch.tv/app/REDACTED" if dry else rtmp_url
        cmd += ["-f", "flv", url]
        kind = "rtmp"
    elif out_mp4:
        dest = "/tmp/nv_mp4_dry.mp4" if dry else str(out_mp4)
        cmd += ["-movflags", "+faststart", dest]
        kind = "mp4"
    else:
        return {"ok": False, "error": "need rtmp_url or out_mp4"}

    return {
        "ok": True,
        "cmd": cmd,
        "cmd_str": " ".join(cmd),
        "codec": codec,
        "vendor": vendor,
        "reason": reason,
        "platform": p,
        "capture": pr.get("capture"),
        "kind": kind,
        "loose_ffmpeg": True,
        "smart": True,
        "nv": codec.endswith("nvenc"),
        "intel": "qsv" in codec,
        "amd": "amf" in codec or (codec.endswith("vaapi") and "amd" in (pr.get("vendors") or [])),
        "probe": pr,
        "grin": 1,
    }


def export_for_pack() -> Dict[str, Any]:
    pr = probe()
    s = load_settings()
    return {
        "engine": "nv_mp4",
        "smart": True,
        "cross_plat": True,
        "loose_ffmpeg": True,
        "platform": pr.get("platform"),
        "capture": pr.get("capture"),
        "vendors": pr.get("vendors"),
        "primary_vendor": pr.get("primary_gpu") or pr.get("primary_vendor"),
        "gpu0": pr.get("gpu0"),
        "codec": pr.get("codec"),
        "codec_reason": pr.get("codec_reason"),
        "codec_vendor": pr.get("codec_vendor"),
        "h264_encoders": pr.get("h264_encoders"),
        "nvenc_h264": pr.get("nvenc_h264"),
        "qsv_h264": pr.get("qsv_h264"),
        "amf_h264": pr.get("amf_h264"),
        "vaapi_h264": pr.get("vaapi_h264"),
        "libx264": pr.get("libx264"),
        "ffmpeg": pr.get("ffmpeg"),
        "ready_encode": pr.get("ready_encode"),
        "ready_playback": True,
        "playback": "html5_spv",
        "video_size": s.get("video_size"),
        "prefer": s.get("prefer") or "auto",
        "secure": True,
        "note": "Key never in pack · SMART H.264 NV/Arc/AMD/soft · HTML5 without ffmpeg",
        "host_cmds": {
            "probe": "wb nv probe",
            "prefer": "wb nv prefer auto|nvidia|intel|amd|soft",
            "stream": "wb stream start",
        },
        "grin": 1,
    }


def init_settings() -> Dict[str, Any]:
    pr = probe()
    s = load_settings()
    s["platform"] = pr["platform"]
    s["codec"] = "auto"
    s["smart"] = True
    s["prefer"] = s.get("prefer") or "auto"
    av = pr.get("av") or {}
    s.setdefault("video_device", av.get("video_device"))
    s.setdefault("audio_device", av.get("audio_device"))
    # pick default vaapi render
    dri = (pr.get("gpus") or {}).get("dri_render") or []
    if dri:
        s.setdefault("vaapi_device", dri[0])
    s["prefer_nvenc"] = True
    s["loose_ffmpeg"] = True
    save_settings(s)
    return {"ok": True, "settings": s, "probe": pr}


def set_prefer(prefer: str) -> Dict[str, Any]:
    allowed = {
        "auto",
        "nvidia",
        "nv",
        "nvenc",
        "intel",
        "arc",
        "qsv",
        "amd",
        "amf",
        "vaapi",
        "apple",
        "soft",
        "cpu",
        "x264",
    }
    prefer = (prefer or "auto").lower()
    if prefer not in allowed:
        return {"ok": False, "error": f"prefer one of {sorted(allowed)}"}
    # normalize
    if prefer in ("nv", "nvenc"):
        prefer = "nvidia"
    if prefer in ("arc", "qsv"):
        prefer = "intel"
    if prefer in ("amf", "radeon"):
        prefer = "amd"
    if prefer in ("cpu", "x264"):
        prefer = "soft"
    s = load_settings()
    s["prefer"] = prefer
    s["codec"] = "auto"
    save_settings(s)
    pr = probe()
    return {"ok": True, "prefer": prefer, "codec": pr.get("codec"), "reason": pr.get("codec_reason")}


if __name__ == "__main__":
    print(json.dumps(probe(), indent=2))
