#!/usr/bin/env bash
# Static device + OBS config snapshot for the OBS Landing overlay.
# Output JSON also carries the `rig` field consumed by the Project Loading
# overlay's "detected rig :: …" line.
#
# Modes:
#   ./telemetry.sh                 — wrap telemetry.json → telemetry.js
#                                    (idempotent; preserves hand edits to JSON)
#   ./telemetry.sh --collect       — re-read hardware + OBS + hostname,
#                                    interactively review every field, then wrap
#   ./telemetry.sh --collect --no-review
#                                  — collect, skip prompts (unattended)
#
# Use --collect after a hardware change, kernel update, or OBS settings change.
# For cosmetic tweaks just edit telemetry.json by hand and re-run with no args.
set -euo pipefail

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OBS_DIR="$(cd "$DIR/.." && pwd)"
JSON="$OBS_DIR/landing/telemetry.json"
JS="$OBS_DIR/landing/telemetry.js"

sanitize() {
  printf '%s' "${1:-}" \
    | tr -d '"\\' \
    | tr '\n\r\t' '   ' \
    | sed 's/  */ /g; s/^ //; s/ $//'
}
emit_str() { [[ -z "${1:-}" ]] && printf 'null' || printf '"%s"' "$1"; }
emit_num() { [[ -z "${1:-}" ]] && printf 'null' || printf '%s'   "$1"; }

# ── INI reader (section-scoped) ─────────────────────
ini_get() {
  # Usage: ini_get FILE SECTION KEY → prints value (empty if missing)
  local file="$1" section="$2" key="$3"
  [[ -f "$file" ]] || return 0
  awk -v section="[$section]" -v key="$key" '
    $0 == section { in_section = 1; next }
    /^\[/         { in_section = 0; next }
    in_section && index($0, key"=") == 1 {
      sub("^[^=]*=", "")
      print
      exit
    }
  ' "$file"
}

# Friendlier names for the encoder ids OBS stores in basic.ini
prettify_encoder() {
  case "${1:-}" in
    obs_x264|x264)                                 echo "x264";;
    jim_nvenc|ffmpeg_nvenc|obs_nvenc_h264_tex|obs_nvenc_h264_soft) echo "NVENC H.264";;
    obs_nvenc_hevc_tex|obs_nvenc_hevc_soft)        echo "NVENC HEVC";;
    obs_nvenc_av1_tex|obs_nvenc_av1_soft)          echo "NVENC AV1";;
    obs_qsv11)                                     echo "QSV";;
    *)                                             echo "${1:-}";;
  esac
}

# Pull a top-level field out of a flat JSON object via python3.
json_field() {
  local file="$1" field="$2"
  [[ -f "$file" ]] || return 0
  python3 - "$file" "$field" <<'PY' 2>/dev/null || true
import json, sys
try:
    with open(sys.argv[1]) as f:
        d = json.load(f)
    v = d.get(sys.argv[2])
    print('' if v is None else v)
except Exception:
    pass
PY
}

# ── OBS config block ────────────────────────────────
collect_obs() {
  local user_ini="$OBS_DIR/user.ini"
  [[ -f "$user_ini" ]] || { echo "null"; return; }

  local profile renderer
  profile=$(ini_get  "$user_ini" "Basic" "Profile")
  renderer=$(ini_get "$user_ini" "Video" "Renderer")
  [[ -z "$profile" ]] && profile="Untitled"

  local profile_dir="$OBS_DIR/basic/profiles/$profile"
  local ini="$profile_dir/basic.ini"
  [[ -f "$ini" ]] || { echo "null"; return; }

  local mode base_w base_h out_w out_h fps
  mode=$(ini_get   "$ini" "Output" "Mode")
  base_w=$(ini_get "$ini" "Video"  "BaseCX")
  base_h=$(ini_get "$ini" "Video"  "BaseCY")
  out_w=$(ini_get  "$ini" "Video"  "OutputCX")
  out_h=$(ini_get  "$ini" "Video"  "OutputCY")
  fps=$(ini_get    "$ini" "Video"  "FPSInt")

  local color_fmt color_space color_range
  color_fmt=$(ini_get   "$ini" "Video" "ColorFormat")
  color_space=$(ini_get "$ini" "Video" "ColorSpace")
  color_range=$(ini_get "$ini" "Video" "ColorRange")

  local sample_rate ch_setup
  sample_rate=$(ini_get "$ini" "Audio" "SampleRate")
  ch_setup=$(ini_get    "$ini" "Audio" "ChannelSetup")

  # Stream encoder + bitrate. Path differs Simple vs Advanced.
  local enc_raw="" bitrate="" rc="" keyint="" h264_profile=""
  if [[ "$mode" == "Advanced" ]]; then
    enc_raw=$(ini_get "$ini" "AdvOut" "Encoder")
    local sej="$profile_dir/streamEncoder.json"
    rc=$(json_field           "$sej" "rate_control")
    bitrate=$(json_field      "$sej" "bitrate")
    keyint=$(json_field       "$sej" "keyint_sec")
    h264_profile=$(json_field "$sej" "profile")
  else
    enc_raw=$(ini_get "$ini" "SimpleOutput" "StreamEncoder")
    bitrate=$(ini_get "$ini" "SimpleOutput" "VBitrate")
  fi
  local enc_pretty
  enc_pretty=$(prettify_encoder "$enc_raw")

  profile=$(sanitize "$profile")
  renderer=$(sanitize "$renderer")
  mode=$(sanitize "$mode")
  color_fmt=$(sanitize "$color_fmt")
  color_space=$(sanitize "$color_space")
  color_range=$(sanitize "$color_range")
  ch_setup=$(sanitize "$ch_setup")
  enc_pretty=$(sanitize "$enc_pretty")
  rc=$(sanitize "$rc")
  h264_profile=$(sanitize "$h264_profile")

  cat <<JSON
{
    "profile":    $(emit_str "$profile"),
    "renderer":   $(emit_str "$renderer"),
    "outputMode": $(emit_str "$mode"),
    "canvas":     { "w": $(emit_num "$base_w"), "h": $(emit_num "$base_h") },
    "output":     { "w": $(emit_num "$out_w"),  "h": $(emit_num "$out_h")  },
    "fps":        $(emit_num "$fps"),
    "color": {
      "format": $(emit_str "$color_fmt"),
      "space":  $(emit_str "$color_space"),
      "range":  $(emit_str "$color_range")
    },
    "stream": {
      "encoder":     $(emit_str "$enc_pretty"),
      "rateControl": $(emit_str "$rc"),
      "bitrateKbps": $(emit_num "$bitrate"),
      "keyintSec":   $(emit_num "$keyint"),
      "profile":     $(emit_str "$h264_profile")
    },
    "audio": {
      "sampleRateHz": $(emit_num "$sample_rate"),
      "channels":     $(emit_str "$ch_setup")
    }
  }
JSON
}

# ── Hardware specs ──────────────────────────────────
collect() {
  local cpu_model cpu_threads
  cpu_model=$(awk -F': ' '/^model name/ {print $2; exit}' /proc/cpuinfo)
  cpu_threads=$(nproc)

  local mem_total_kb mem_total_g
  mem_total_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
  mem_total_g=$(awk -v k="$mem_total_kb" 'BEGIN {printf "%.1f", k/1024/1024}')

  local kernel; kernel=$(uname -r)

  # Rig identity — title-cased hostname (ignia → Ignia, midgolem → Midgolem).
  # Consumed by the Project Loading overlay's "detected rig :: …" line.
  local rig; rig=$(hostname)
  rig="${rig^}"

  local gpu_name="" gpu_vram_total_mb=""
  if command -v nvidia-smi >/dev/null 2>&1; then
    local q
    q=$(nvidia-smi --query-gpu=name,memory.total \
                   --format=csv,noheader,nounits 2>/dev/null | head -n1 || true)
    if [[ -n "$q" ]]; then
      IFS=',' read -r gpu_name gpu_vram_total_mb <<<"$q"
      gpu_name=$(sanitize "$gpu_name")
      gpu_vram_total_mb=$(printf '%s' "$gpu_vram_total_mb" | tr -d ' ')
    fi
  fi

  cpu_model=$(sanitize "$cpu_model")
  kernel=$(sanitize "$kernel")
  rig=$(sanitize "$rig")
  local now_iso; now_iso=$(date -u +%FT%TZ)

  local obs_block; obs_block=$(collect_obs)

  local tmp="$JSON.tmp.$$"
  cat > "$tmp" <<JSON
{
  "collectedAt": "$now_iso",
  "rig":         $(emit_str "$rig"),
  "cpu": {
    "model":   $(emit_str "$cpu_model"),
    "threads": $(emit_num "$cpu_threads")
  },
  "mem": {
    "totalGB": $(emit_num "$mem_total_g")
  },
  "host": {
    "kernel": $(emit_str "$kernel")
  },
  "gpu": {
    "name":        $(emit_str "$gpu_name"),
    "vramTotalMB": $(emit_num "$gpu_vram_total_mb")
  },
  "obs": $obs_block
}
JSON

  # Validate before promoting — catches any malformed substitution.
  if ! python3 -m json.tool "$tmp" >/dev/null 2>&1; then
    echo "collect: produced invalid JSON, leaving $tmp for inspection" >&2
    exit 1
  fi
  mv -f "$tmp" "$JSON"
  echo "wrote $JSON"
}

# Walk every leaf in telemetry.json and prompt for a per-field override.
# Preserves JSON types (numbers stay numbers, strings stay strings, the
# literal word `null` becomes JSON null). Empty input keeps the current value.
# Skips `collectedAt` — it's auto-generated.
review() {
  [[ -f "$JSON" ]] || { echo "review: $JSON not found" >&2; exit 1; }
  echo
  echo "── Review telemetry fields ─────────────────────────────"
  echo "  Press Enter to keep the value shown."
  echo "  Type a new value to override (use 'null' to clear)."
  echo "  Ctrl-C to abort without writing."
  echo
  local tmp="$JSON.review.$$"
  if ! python3 - "$JSON" "$tmp" <<'PY'
import json, sys

src, dst = sys.argv[1], sys.argv[2]
with open(src) as f:
    doc = json.load(f)

SKIP = {'collectedAt'}

def coerce(raw, original):
    if raw == '':
        return original
    if raw.strip().lower() == 'null':
        return None
    if isinstance(original, bool):
        return raw.strip().lower() in ('1', 'true', 'yes', 'y', 'on')
    if isinstance(original, int) and not isinstance(original, bool):
        try:    return int(raw)
        except: return raw
    if isinstance(original, float):
        try:    return float(raw)
        except: return raw
    return raw

def walk(node, prefix=''):
    if isinstance(node, dict):
        for k in list(node.keys()):
            path = f'{prefix}.{k}' if prefix else k
            if k in SKIP:
                continue
            v = node[k]
            if isinstance(v, dict):
                walk(v, path)
            else:
                shown = 'null' if v is None else json.dumps(v, ensure_ascii=False)
                try:
                    raw = input(f'  {path:28s} = {shown:30s}  override: ')
                except EOFError:
                    raw = ''
                node[k] = coerce(raw, v)

walk(doc)
with open(dst, 'w') as f:
    json.dump(doc, f, indent=2, ensure_ascii=False)
    f.write('\n')
PY
  then
    echo "review: aborted (or python error) — leaving $JSON unchanged" >&2
    rm -f "$tmp"
    exit 1
  fi
  if ! python3 -m json.tool "$tmp" >/dev/null 2>&1; then
    echo "review: produced invalid JSON, leaving $tmp for inspection" >&2
    exit 1
  fi
  mv -f "$tmp" "$JSON"
  echo
  echo "  ✓ updated $JSON"
}

# Wraps telemetry.json as `window.__TEL = {...};` so index.html can load it
# via a <script> tag. fetch() on file:// is blocked in CEF; script-tag isn't.
wrap_js() {
  if [[ ! -f "$JSON" ]]; then
    echo "telemetry.json not found — run with --collect first" >&2
    exit 1
  fi
  if ! python3 -m json.tool "$JSON" >/dev/null 2>&1; then
    echo "telemetry.json is not valid JSON — fix it before re-wrapping" >&2
    exit 1
  fi
  local tmp="$JS.tmp.$$"
  { printf 'window.__TEL = '; cat "$JSON"; printf ';\n'; } > "$tmp"
  mv -f "$tmp" "$JS"
  echo "wrote $JS"
}

# ── Argument parsing ────────────────────────────────
DO_COLLECT=0
DO_REVIEW=1
for arg in "$@"; do
  case "$arg" in
    --collect)    DO_COLLECT=1 ;;
    --no-review)  DO_REVIEW=0  ;;
    -h|--help)    sed -n 's/^# \?//p' "$0" | head -16; exit 0 ;;
    *)            echo "unknown flag: $arg" >&2; exit 2 ;;
  esac
done

if (( DO_COLLECT )); then
  collect
  (( DO_REVIEW )) && review
fi
wrap_js
