#!/usr/bin/env bash
DW_THEME_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$DW_THEME_DIR/Theme.sh" ]]; then source "$DW_THEME_DIR/Theme.sh"
elif [[ -f "$DW_THEME_DIR/../share/dw-sysinfo/Theme.sh" ]]; then source "$DW_THEME_DIR/../share/dw-sysinfo/Theme.sh"
else C_RESET=""; C_BOLD=""; C_INFO=""; C_GOLD=""; C_OK=""; C_WARN=""; C_DANGER=""; C_MUTED=""; fi
# DW SysInfo fuer Linux 1.1.1
# Liest Systeminformationen lokal und nur lesend. Keine Netzwerkverbindung, keine Telemetrie.
# MIT-Lizenz, (c) 2026 Michael Bergfeld, DoMeZos-Ware
set -u
export LC_ALL=C.UTF-8 2>/dev/null || export LC_ALL=C
VERSION="1.1.1"
US=$'\x1f'
FULL=0; FORMAT="text"; OUT=""; ONLY=""
ROWS=()

usage() {
cat <<'EOF'
DW SysInfo 1.1.1 - Systeminformationen lokal anzeigen und exportieren

Aufruf:
  dw-sysinfo [Optionen]

Optionen:
  --format text|json|html   Ausgabeformat (Standard: text)
  --out DATEI               in Datei schreiben statt auf den Bildschirm
  --section LISTE           nur diese Bereiche, kommagetrennt (siehe --list-sections)
  --list-sections           verfügbare Bereiche anzeigen
  --full                    sensible Daten NICHT maskieren (Seriennummern, MAC/IP, Namen, WLAN)
  --version                 Version anzeigen
  -h, --help                diese Hilfe

Beispiele:
  dw-sysinfo
  dw-sysinfo --section cpu,ram,disks
  dw-sysinfo --format html --out ~/bericht.html
  dw-sysinfo --format json --full --out ~/sysinfo-voll.json

Ohne --full sind sensible Angaben maskiert; Berichte lassen sich so gefahrlos weitergeben.
Das Programm benötigt keine Root-Rechte, verändert nichts und nutzt kein Netzwerk.
Einige Werte (z. B. Seriennummern, RAM-Module) sind nur mit Root lesbar und erscheinen sonst als "nicht verfügbar".
EOF
}

SECTION_IDS=(system cpu ram board gpu display disks net power security software sensors)
section_name() {
  case "$1" in
    system) echo "System";; cpu) echo "Prozessor";; ram) echo "Arbeitsspeicher";; board) echo "Mainboard";;
    gpu) echo "Grafik";; display) echo "Bildschirme";; disks) echo "Laufwerke";; net) echo "Netzwerk";;
    power) echo "Akku und Energie";; security) echo "Sicherheit";; software) echo "Software und Prozesse";; sensors) echo "Sensoren";;
  esac
}

have() { command -v "$1" >/dev/null 2>&1; }
readf() { local v; v="$(head -n1 "$1" 2>/dev/null)" && printf '%s' "$v"; }
trim() { local v="$1"; v="${v#"${v%%[![:space:]]*}"}"; v="${v%"${v##*[![:space:]]}"}"; printf '%s' "$v"; }

is_placeholder() {
  local l="${1,,}"
  case "$l" in
    ""|"default string"|"to be filled by o.e.m."|"system serial number"|"none"|"not applicable"|"o.e.m."|"default"|"not specified"|"unknown"|"n/a") return 0;;
  esac
  return 1
}

# add ABSCHNITT SCHLUESSEL WERT [sensibel=0|1] [balken_prozent]
add() {
  local s="$1" k="$2" v="${3:-}" sens="${4:-0}" bar="${5:-}"
  v="${v//[[:cntrl:]]/ }"; v="$(trim "$v")"
  if is_placeholder "$v"; then v="nicht verfügbar"; fi
  ROWS+=("${s}${US}${k}${US}${v}${US}${sens}${US}${bar}")
}

human() { # Bytes -> lesbar, einheitlich eine Nachkommastelle
  awk -v b="$1" 'BEGIN{ split("B KB MB GB TB PB",u," "); i=1; while(b>=1024 && i<6){b/=1024;i++}; if(i==1) printf "%d %s",b,u[i]; else { x=sprintf("%.1f",b); sub(/\./,",",x); printf "%s %s",x,u[i] } }'
}

bar_class() {
  local p="${1:-0}"
  if [ "$p" -ge 90 ] 2>/dev/null; then printf 'crit'
  elif [ "$p" -ge 70 ] 2>/dev/null; then printf 'warn'
  else printf 'ok'; fi
}

color_pct() {
  local p="${1:-0}" t="$2"
  if [ ! -t 1 ]; then printf '%s' "$t"; return; fi
  case "$(bar_class "$p")" in
    crit) printf '%s%s%s' "$C_DANGER" "$t" "$C_RESET";;
    warn) printf '%s%s%s' "$C_WARN" "$t" "$C_RESET";;
    *)    printf '%s%s%s' "$C_OK" "$t" "$C_RESET";;
  esac
}

is_gold_key() {
  case "$1" in
    *"Seriennummer"*) return 1;;
    "Max. Takt"|"Gesamt (nutzbar)"|"Verfügbar"|"Swap gesamt") return 0;;
    *"Kapazität"*|*"Ladung"*|*"Geschwindigkeit"*) return 0;;
    Datenträger\ [0-9]|Datenträger\ [0-9][0-9]) return 0;;
    Dateisystem\ *) return 0;;
  esac
  return 1
}

lscpu_f() { have lscpu && lscpu 2>/dev/null | awk -F: -v k="$1" '$1==k{sub(/^[ \t]+/,"",$2); print $2; exit}'; }

sec_system() {
  local s=system pn up d h m
  if [ -r /etc/os-release ]; then pn="$(. /etc/os-release; echo "${PRETTY_NAME:-}")"; fi
  add $s "Betriebssystem" "${pn:-$(uname -s)}"
  add $s "Kernel" "$(uname -r)"
  add $s "Architektur" "$(uname -m)"
  add $s "Rechnername" "$(hostname 2>/dev/null || uname -n)" 1
  add $s "Benutzername" "$(id -un 2>/dev/null)" 1
  if [ -r /proc/uptime ]; then
    up="$(cut -d. -f1 /proc/uptime)"; d=$((up/86400)); h=$(((up%86400)/3600)); m=$(((up%3600)/60))
    add $s "Betriebszeit" "$d Tage, $h Std., $m Min."
    add $s "Letzter Start" "$(date -d "@$(( $(date +%s) - up ))" '+%d.%m.%Y %H:%M' 2>/dev/null)"
  fi
  if grep -qi microsoft /proc/version 2>/dev/null; then add $s "Startmodus" "nicht zutreffend (WSL)"
  else add $s "Startmodus" "$([ -d /sys/firmware/efi ] && echo UEFI || echo Legacy-BIOS)"; fi
  add $s "Hersteller" "$(readf /sys/class/dmi/id/sys_vendor)"
  add $s "Modell" "$(readf /sys/class/dmi/id/product_name)"
  add $s "Modellversion" "$(readf /sys/class/dmi/id/product_version)"
  add $s "BIOS/UEFI-Hersteller" "$(readf /sys/class/dmi/id/bios_vendor)"
  add $s "BIOS/UEFI-Version" "$(readf /sys/class/dmi/id/bios_version)"
  add $s "BIOS/UEFI-Datum" "$(readf /sys/class/dmi/id/bios_date)"
  add $s "Seriennummer" "$(readf /sys/class/dmi/id/product_serial)" 1
  add $s "Desktop" "${XDG_CURRENT_DESKTOP:-nicht erkannt}"
  add $s "Sitzungstyp" "${XDG_SESSION_TYPE:-nicht erkannt}"
  add $s "Shell" "$(basename "${SHELL:-}")"
  add $s "Sprache" "${LANG:-}"
  add $s "Zeitzone" "$( (have timedatectl && timedatectl show -p Timezone --value 2>/dev/null) || readf /etc/timezone)"
  if have systemd-detect-virt; then add $s "Virtualisierung" "$(systemd-detect-virt 2>/dev/null)"; fi
  if grep -qi microsoft /proc/version 2>/dev/null; then add $s "Hinweis" "Läuft unter WSL (Windows-Subsystem für Linux), Hardwaredaten sind teils virtualisiert"; fi
}

sec_cpu() {
  local s=cpu n
  n="$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)"
  local nm; nm="$(lscpu_f 'Model name')"
  [ -z "$nm" ] && nm="$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2)"
  add $s "Name" "$nm"
  add $s "Hersteller" "$(lscpu_f 'Vendor ID')"
  add $s "Logische Prozessoren" "$n"
  add $s "Kerne pro Sockel" "$(lscpu_f 'Core(s) per socket')"
  add $s "Sockel" "$(lscpu_f 'Socket(s)')"
  add $s "Threads pro Kern" "$(lscpu_f 'Thread(s) per core')"
  local mx; mx="$(lscpu_f 'CPU max MHz')"; [ -n "$mx" ] && add $s "Max. Takt" "$(awk -v v="$mx" 'BEGIN{printf "%.2f GHz", v/1000}' | sed 's/\./,/')"
  add $s "L2-Cache" "$(lscpu_f 'L2 cache')"
  add $s "L3-Cache" "$(lscpu_f 'L3 cache')"
  if grep -qE '\b(vmx|svm)\b' /proc/cpuinfo 2>/dev/null; then add $s "Virtualisierung (Hardware)" "unterstützt ($(grep -oE '\b(vmx|svm)\b' /proc/cpuinfo | head -n1))"; else add $s "Virtualisierung (Hardware)" "nicht erkannt"; fi
  add $s "Aktueller Governor" "$(readf /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)"
  local la; la="$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null)"; add $s "Last (1/5/15 Min.)" "$la"
}

sec_ram() {
  local s=ram t a st sf
  t="$(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null)"; a="$(awk '/^MemAvailable:/{print $2}' /proc/meminfo 2>/dev/null)"
  st="$(awk '/^SwapTotal:/{print $2}' /proc/meminfo 2>/dev/null)"; sf="$(awk '/^SwapFree:/{print $2}' /proc/meminfo 2>/dev/null)"
  if [ -n "$t" ]; then
    add $s "Gesamt (nutzbar)" "$(human $((t*1024)))"
    if [ -n "$a" ]; then
      add $s "Verfügbar" "$(human $((a*1024)))"
      add $s "Auslastung" "$(( (t-a)*100/t )) %" 0 "$(( (t-a)*100/t ))"
    fi
  fi
  if [ -n "$st" ]; then add $s "Swap gesamt" "$(human $((st*1024)))"; [ "$st" -gt 0 ] && add $s "Swap frei" "$(human $((sf*1024)))"; fi
  if [ -e /sys/block/zram0 ]; then add $s "zram" "aktiv"; fi
  add $s "Hinweis" "RAM-Module (Typ, Takt, Hersteller) sind nur mit Root über dmidecode lesbar"
}

sec_board() {
  local s=board
  add $s "Hersteller" "$(readf /sys/class/dmi/id/board_vendor)"
  add $s "Produkt" "$(readf /sys/class/dmi/id/board_name)"
  add $s "Version" "$(readf /sys/class/dmi/id/board_version)"
  add $s "Seriennummer" "$(readf /sys/class/dmi/id/board_serial)" 1
  add $s "Gehäusetyp" "$(readf /sys/class/dmi/id/chassis_type)"
}

sec_gpu() {
  local s=gpu line slot cnt=0 drv
  if have lspci; then
    while IFS= read -r line; do
      [ -z "$line" ] && continue
      cnt=$((cnt+1)); slot="${line%% *}"
      add $s "GPU $cnt" "$(lspci -mm -s "$slot" 2>/dev/null | awk -F'"' '{print $4 " " $6}')"
      drv="$(lspci -k -s "$slot" 2>/dev/null | awk -F': ' '/Kernel driver in use/{print $2}')"
      add $s "GPU $cnt Treiber" "$drv"
    done < <(lspci 2>/dev/null | grep -Ei 'vga|3d controller|display controller')
  fi
  if [ "$cnt" -eq 0 ]; then
    for c in /sys/class/drm/card[0-9]; do [ -e "$c/device/vendor" ] && { cnt=$((cnt+1)); add $s "GPU $cnt (PCI-ID)" "$(readf "$c/device/vendor"):$(readf "$c/device/device")"; add $s "GPU $cnt Treiber" "$(basename "$(readlink -f "$c/device/driver" 2>/dev/null)")"; }; done
  fi
  [ "$cnt" -eq 0 ] && add $s "Grafik" ""
  if have glxinfo; then add $s "OpenGL-Renderer" "$(glxinfo -B 2>/dev/null | awk -F': ' '/OpenGL renderer/{print $2; exit}')"; fi
}

sec_display() {
  local s=display c st mode n=0 line
  if [ -n "${DISPLAY:-}" ] && have xrandr; then
    while IFS= read -r line; do n=$((n+1)); add $s "Anzeige $n" "$line"; done < <(xrandr --query 2>/dev/null | grep ' connected' | sed -E 's/ \(.*//')
  fi
  if [ "$n" -eq 0 ]; then
    for c in /sys/class/drm/card*-*; do
      [ -r "$c/status" ] || continue
      st="$(readf "$c/status")"; [ "$st" = "connected" ] || continue
      mode="$(head -n1 "$c/modes" 2>/dev/null)"; n=$((n+1))
      add $s "Anzeige $n ($(basename "$c" | cut -d- -f2-))" "${mode:-verbunden}"
    done
  fi
  [ "$n" -eq 0 ] && add $s "Bildschirme" "nicht ermittelbar (keine grafische Sitzung)"
}

pair() { # pair SCHLUESSEL ZEILE : liest KEY="wert" ohne eval
  sed -n "s/.*\b$1=\"\([^\"]*\)\".*/\1/p" <<<"$2" | sed 's/\\x20/ /g'
}

sec_disks() {
  local s=disks line n=0 before=${#ROWS[@]} name model size tran rota ser mp dev fs kb pct
  if have lsblk; then
    while IFS= read -r line; do
      name="$(pair NAME "$line")"; model="$(pair MODEL "$line")"; size="$(pair SIZE "$line")"
      tran="$(pair TRAN "$line")"; rota="$(pair ROTA "$line")"; ser="$(pair SERIAL "$line")"
      n=$((n+1))
      add $s "Datenträger $n" "/dev/${name:-?}: ${model:-unbekannt}, ${size:-?}, ${tran:-intern}, $([ "$rota" = 1 ] && echo HDD || echo SSD/Flash)"
      [ -n "$ser" ] && add $s "Datenträger $n Seriennummer" "$ser" 1
    done < <(lsblk -dn -P -o NAME,MODEL,SIZE,TRAN,ROTA,SERIAL,TYPE 2>/dev/null | grep -v 'TYPE="loop"' | grep -v 'TYPE="rom"' | grep -v 'NAME="zram')
    lsblk -n -o TYPE 2>/dev/null | grep -q crypt && add $s "Verschlüsselung" "LUKS/dm-crypt-Datenträger erkannt"
  fi
  while IFS='|' read -r mp dev fs kb pct; do
    [ -n "$mp" ] && add $s "Dateisystem $mp" "$dev, $fs, $(human $((kb*1024))), $pct belegt" 0 "${pct%%%}"
  done < <(df -PTk -x tmpfs -x devtmpfs -x squashfs -x overlay -x efivarfs 2>/dev/null | awk 'NR>1 && $3>0 {printf "%s|%s|%s|%s|%s\n",$7,$1,$2,$3,$6}')
  [ ${#ROWS[@]} -eq "$before" ] && add $s "Laufwerke" ""
}

sec_net() {
  local s=net i n=0 st sp t addr gw before=${#ROWS[@]}
  for i in /sys/class/net/*; do
    n=$(basename "$i"); [ "$n" = "lo" ] && continue
    t="Ethernet/Sonstige"; [ -d "$i/wireless" ] && t="WLAN"; [ -d "$i/bridge" ] && t="Bridge"
    case "$n" in docker*|veth*|virbr*|br-*) t="virtuell";; esac
    st="$(readf "$i/operstate")"
    add $s "$n: Typ / Status" "$t / ${st:-unbekannt}"
    sp="$(readf "$i/speed")"; [ -n "$sp" ] && [ "$sp" -gt 0 ] 2>/dev/null && add $s "$n: Geschwindigkeit" "$sp Mbit/s"
    add $s "$n: MAC-Adresse" "$(readf "$i/address")" 1
    add $s "$n: Treiber" "$(basename "$(readlink -f "$i/device/driver" 2>/dev/null)" 2>/dev/null)"
    if have ip; then
      for addr in $(ip -o -4 addr show dev "$n" 2>/dev/null | awk '{print $4}'); do add $s "$n: IPv4" "$addr" 1; done
    fi
    if [ "$t" = "WLAN" ] && have iwgetid; then add $s "$n: WLAN-Name (SSID)" "$(iwgetid -r "$n" 2>/dev/null)" 1; fi
  done
  if have ip; then
    gw="$(ip -4 route show default 2>/dev/null | awk '{print $3; exit}')"; [ -n "$gw" ] && add $s "Standard-Gateway" "$gw" 1
  fi
  [ -r /etc/resolv.conf ] && for addr in $(awk '/^nameserver/{print $2}' /etc/resolv.conf); do add $s "DNS-Server" "$addr" 1; done
  [ ${#ROWS[@]} -eq "$before" ] && add $s "Netzwerk" ""
}

sec_power() {
  local s=power b n=0 cap st ef ed cyc
  for b in /sys/class/power_supply/*; do
    [ -r "$b/type" ] || continue
    case "$(readf "$b/type")" in
      Battery)
        n=$((n+1)); cap="$(readf "$b/capacity")"; st="$(readf "$b/status")"
        add $s "Akku $n Ladung / Status" "${cap:-?} % / ${st:-?}"
        ef="$(readf "$b/energy_full")"; [ -z "$ef" ] && ef="$(readf "$b/charge_full")"
        ed="$(readf "$b/energy_full_design")"; [ -z "$ed" ] && ed="$(readf "$b/charge_full_design")"
        [ -n "$ef" ] && [ -n "$ed" ] && [ "$ed" -gt 0 ] 2>/dev/null && add $s "Akku $n Gesundheit" "$((ef*100/ed)) % der Nennkapazität"
        cyc="$(readf "$b/cycle_count")"; [ -n "$cyc" ] && [ "$cyc" != "0" ] && add $s "Akku $n Ladezyklen" "$cyc"
        add $s "Akku $n Technologie" "$(readf "$b/technology")";;
      Mains) add $s "Netzteil" "$([ "$(readf "$b/online")" = 1 ] && echo angeschlossen || echo getrennt)";;
    esac
  done
  [ "$n" -eq 0 ] && add $s "Akku" "kein Akku erkannt (Desktop-PC oder virtuelle Maschine)"
}

sec_security() {
  local s=security sb f svc act
  f=/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
  if [ -r "$f" ]; then sb="$(od -An -t u1 -j4 -N1 "$f" 2>/dev/null | tr -d ' ')"; add $s "Secure Boot" "$([ "$sb" = 1 ] && echo aktiv || echo aus)"
  elif [ -d /sys/firmware/efi ]; then add $s "Secure Boot" "nicht ermittelbar"
  else add $s "Secure Boot" "nicht zutreffend (Legacy-BIOS)"; fi
  if [ -r /sys/module/apparmor/parameters/enabled ]; then add $s "AppArmor" "$([ "$(readf /sys/module/apparmor/parameters/enabled)" = Y ] && echo aktiv || echo aus)"; fi
  if [ -r /sys/fs/selinux/enforce ]; then add $s "SELinux" "$([ "$(readf /sys/fs/selinux/enforce)" = 1 ] && echo enforcing || echo permissive)"; fi
  if have systemctl; then
    for svc in ufw firewalld nftables; do act="$(systemctl is-active "$svc" 2>/dev/null)"; [ "$act" = active ] && add $s "Firewall-Dienst" "$svc aktiv"; done
  fi
  add $s "ASLR (Adressraum-Zufall)" "$([ "$(readf /proc/sys/kernel/randomize_va_space)" = 2 ] && echo voll || echo "eingeschränkt/aus")"
  add $s "Hinweis" "Firewall-Regeln und Festplattenverschlüsselung im Detail erfordern Root und werden nicht gelesen"
}

sec_software() {
  local s=software n t
  have dpkg-query && add $s "Pakete (dpkg)" "$(dpkg-query -f '.\n' -W 2>/dev/null | wc -l)"
  have rpm && add $s "Pakete (rpm)" "$(rpm -qa 2>/dev/null | wc -l)"
  have pacman && add $s "Pakete (pacman)" "$(pacman -Qq 2>/dev/null | wc -l)"
  have flatpak && add $s "Flatpak-Anwendungen" "$(flatpak list --app 2>/dev/null | wc -l)"
  have snap && add $s "Snap-Pakete" "$(snap list 2>/dev/null | awk 'NR>1' | wc -l)"
  add $s "Laufende Prozesse" "$(ls /proc 2>/dev/null | grep -c '^[0-9][0-9]*$')"
  have systemctl && add $s "Fehlgeschlagene systemd-Dienste" "$(systemctl --failed --no-legend 2>/dev/null | wc -l)"
  for t in python3 gcc git node java docker; do
    have "$t" && add $s "Werkzeug $t" "$("$t" --version 2>&1 | head -n1)"
  done
}

sec_sensors() {
  local s=sensors z t n=0 h nm
  for z in /sys/class/thermal/thermal_zone*; do
    [ -r "$z/temp" ] || continue
    t="$(readf "$z/temp")"; [ -n "$t" ] && [ "$t" -gt 0 ] 2>/dev/null || continue
    n=$((n+1)); add $s "Temperatur $(readf "$z/type")" "$(awk -v v="$t" 'BEGIN{printf "%.1f °C", v/1000}' | sed 's/\./,/')"
  done
  for h in /sys/class/hwmon/hwmon*; do
    nm="$(readf "$h/name")"
    for t in "$h"/temp*_input; do
      [ -r "$t" ] || continue; n=$((n+1))
      add $s "hwmon $nm $(basename "$t" _input)" "$(awk -v v="$(readf "$t")" 'BEGIN{printf "%.1f °C", v/1000}' | sed 's/\./,/')"
    done
  done
  [ "$n" -eq 0 ] && add $s "Sensoren" "keine Temperatursensoren lesbar"
}

mask() { printf '***'; }
html_esc() { local s="$1"; s="${s//&/&amp;}"; s="${s//</&lt;}"; s="${s//>/&gt;}"; s="${s//\"/&quot;}"; printf '%s' "$s"; }
json_esc() { local s="$1"; s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; printf '%s' "$s"; }
stamp() { date '+%Y-%m-%d %H:%M:%S'; }
val() { local v="$1" sens="$2"; if [ "$sens" = 1 ] && [ "$FULL" -eq 0 ] && [ "$v" != "nicht verfügbar" ]; then mask "$v"; else printf '%s' "$v"; fi; }

selected() { # Bereich ausgewaehlt?
  [ -z "$ONLY" ] && return 0
  case ",$ONLY," in *",$1,"*) return 0;; esac
  return 1
}

emit_text() {
  printf '%s%sDW SysInfo %s - %s%s\n' "$C_INFO" "$C_BOLD" "$VERSION" "$(stamp)" "$C_RESET"
  if [ "$FULL" -eq 1 ]; then
    echo "Sensible Daten: enthalten"
  else
    printf '%s%sSeriennummern und MAC-Adressen sind maskiert. Mit --full anzeigen.%s\n' "$C_WARN" "$C_BOLD" "$C_RESET"
  fi
  local cur="" sid key value sens bar shown name
  for r in "${ROWS[@]}"; do
    IFS="$US" read -r sid key value sens bar <<<"$r"
    if [ "$sid" != "$cur" ]; then
      cur="$sid"; name="$(section_name "$sid")"
      printf '\n%s%s╔══ %s ══╗%s\n' "$C_INFO" "$C_BOLD" "$name" "$C_RESET"
    fi
    shown="$(val "$value" "$sens")"
    if [ "$sens" = 1 ] && [ "$FULL" -eq 0 ] && [ "$value" != "nicht verfügbar" ]; then
      shown="${C_WARN}${shown}${C_RESET}"
    elif [ -n "$bar" ]; then shown="$(color_pct "$bar" "$shown")"
    elif is_gold_key "$key"; then shown="${C_GOLD}${shown}${C_RESET}"
    fi
    echo "$key: $shown"
  done
}

emit_json() {
  local cur="" first_sec=1 first_item=1 sid key value sens bar
  printf '{\n  "tool": "DW SysInfo %s",\n  "generated": "%s",\n  "sensitiveDataIncluded": %s,\n  "sections": [\n' "$VERSION" "$(stamp)" "$([ "$FULL" -eq 1 ] && echo true || echo false)"
  for r in "${ROWS[@]}"; do
    IFS="$US" read -r sid key value sens bar <<<"$r"
    if [ "$sid" != "$cur" ]; then
      [ -n "$cur" ] && printf '\n    ] }'
      [ "$first_sec" -eq 0 ] && printf ',\n'
      first_sec=0; first_item=1; cur="$sid"
      printf '    { "name": "%s", "items": [\n' "$(json_esc "$(section_name "$sid")")"
    fi
    [ "$first_item" -eq 0 ] && printf ',\n'
    first_item=0
    printf '      { "key": "%s", "value": "%s" }' "$(json_esc "$key")" "$(json_esc "$(val "$value" "$sens")")"
  done
  [ -n "$cur" ] && printf '\n    ] }'
  printf '\n  ]\n}\n'
}

emit_html() {
  local cur="" sid key value sens bar w cls
  cat <<EOF
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>DW SysInfo Bericht</title><style>
:root{--bg:#050d1f;--surface:#091428;--border:#12294f;--cyan:#00d4ff;--gold:#f0c040;--text:#f0f4ff;--muted:#8899bb;--danger:#ff5577;--warn:#f0c040;--ok:#00ffaa;--font-head:Syne,'Segoe UI',sans-serif;--font-mono:'JetBrains Mono','DM Mono',Consolas,monospace}
body{margin:24px auto;max-width:900px;padding:0 12px;background:radial-gradient(ellipse at top left,#0d1e3a,var(--bg) 70%);color:var(--text);font:15px/1.6 var(--font-mono)}
h1,.card h2{font-family:var(--font-head);color:var(--cyan);font-weight:800}h1{font-size:1.6rem}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,350px),1fr));gap:16px;align-items:start}
.card{background:rgba(9,20,40,.70);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgba(0,212,255,.18);border-radius:16px;box-shadow:0 4px 40px rgba(0,0,0,.35);padding:12px 16px;margin:0}
.card h2{font-size:1.1rem;margin:.2em 0 .6em}table{border-collapse:collapse;width:100%}td{border-bottom:1px solid var(--border);padding:6px 8px;vertical-align:top;color:var(--text)}td:first-child{width:34%;font-weight:600;color:var(--muted)}.n{color:var(--muted);font-size:.9rem}.gold{color:var(--gold)}.mask{color:var(--warn)}
.bar{height:8px;background:var(--border);border-radius:999px;overflow:hidden;margin-top:6px}.bar>i{display:block;height:100%;background:var(--ok)}.bar.warn>i{background:var(--warn)}.bar.crit>i{background:var(--danger)}
.copy{cursor:pointer}.copy:hover,.copy:focus{color:var(--cyan);outline:1px solid var(--cyan)}.notice{padding:12px 16px;border:1px solid var(--warn);border-radius:12px;color:var(--warn);font-weight:bold}
</style></head><body>
<h1>DW SysInfo Bericht</h1><p class="notice">$([ "$FULL" -eq 1 ] && echo "Sensible Daten: enthalten." || echo "Seriennummern und MAC-Adressen sind maskiert. Mit --full anzeigen.")</p><p class="n">Erstellt $(stamp) mit DW SysInfo $VERSION. Wert anklicken, um ihn zu kopieren.</p><main class="grid">
EOF
  for r in "${ROWS[@]}"; do
    IFS="$US" read -r sid key value sens bar <<<"$r"
    if [ "$sid" != "$cur" ]; then
      [ -n "$cur" ] && printf '</table></section>\n'
      cur="$sid"; printf '<section class="card"><h2>%s</h2><table>\n' "$(html_esc "$(section_name "$sid")")"
    fi
    printf '<tr><td>%s</td><td class="copy" tabindex="0" title="Wert kopieren">%s' "$(html_esc "$key")" "$(html_esc "$(val "$value" "$sens")")"
    if [ -n "$bar" ] && [ "$bar" -ge 0 ] 2>/dev/null; then
      w="$bar"; [ "$w" -gt 100 ] && w=100
      cls="$(bar_class "$bar")"
      printf '<div class="bar %s" role="meter" aria-valuenow="%s" aria-valuemin="0" aria-valuemax="100"><i style="width:%s%%"></i></div>' "$cls" "$bar" "$w"
    fi
    printf '</td></tr>\n'
  done
  [ -n "$cur" ] && printf '</table></section>\n'
  cat <<'EOF'
</main><script>
async function copyValue(cell){const value=cell.firstChild.textContent.trim();try{await navigator.clipboard.writeText(value)}catch(e){const input=document.createElement('textarea');input.value=value;document.body.appendChild(input);input.select();document.execCommand('copy');input.remove()}cell.title='Kopiert'}
document.querySelectorAll('.copy').forEach(cell=>{cell.addEventListener('click',()=>copyValue(cell));cell.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();copyValue(cell)}})})
</script></body></html>
EOF
}

# ---- Argumente
while [ $# -gt 0 ]; do
  case "$1" in
    -h|--help) usage; exit 0;;
    --version) echo "DW SysInfo $VERSION"; exit 0;;
    --list-sections) for i in "${SECTION_IDS[@]}"; do printf '%-10s %s\n' "$i" "$(section_name "$i")"; done; exit 0;;
    --full) FULL=1;;
    --html) FORMAT=html;;
    --format) shift; FORMAT="${1:-}";;
    --format=*) FORMAT="${1#*=}";;
    --out) shift; OUT="${1:-}";;
    --out=*) OUT="${1#*=}";;
    --section) shift; ONLY="${1:-}";;
    --section=*) ONLY="${1#*=}";;
    *) echo "Unbekannte Option: $1" >&2; echo "Hilfe: dw-sysinfo --help" >&2; exit 2;;
  esac
  shift
done
case "$FORMAT" in text|json|html) ;; *) echo "Ungültiges Format: '$FORMAT' (erlaubt: text, json, html)" >&2; exit 2;; esac
if [ -n "$ONLY" ]; then
  IFS=',' read -ra want <<<"$ONLY"
  for w in "${want[@]}"; do
    ok=0; for i in "${SECTION_IDS[@]}"; do [ "$i" = "$w" ] && ok=1; done
    [ "$ok" -eq 0 ] && { echo "Unbekannter Bereich: '$w' (siehe --list-sections)" >&2; exit 2; }
  done
fi

[ -t 2 ] && printf 'Erfasse Systemdaten…\n' >&2
for i in "${SECTION_IDS[@]}"; do selected "$i" && "sec_$i"; done
[ -t 2 ] && printf '\r\033[K' >&2

if [ -n "$OUT" ]; then
  outdir="$(dirname -- "$OUT")"
  [ -d "$outdir" ] || { echo "Fehler: Ausgabeordner existiert nicht: $outdir" >&2; exit 1; }
  "emit_$FORMAT" >"$OUT" || { echo "Fehler: Schreiben nach '$OUT' fehlgeschlagen" >&2; exit 1; }
  echo "Gespeichert: $OUT" >&2
else
  "emit_$FORMAT"
fi
