#!/usr/bin/env bash
# scripts/install/client.sh - install and setup all client related apps and environments
set -euo pipefail

ORIG_USER=${SUDO_USER:-$(id -un)}
REF=${LESAVKA_REF:-master}
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
SCRIPT_REPO_ROOT=$(cd -- "$SCRIPT_DIR/../.." && pwd)
DEFAULT_REPO_URL=ssh://git@scm.bstein.dev:2242/bstein/lesavka.git
REPO_URL=${LESAVKA_REPO_URL:-}
SRC=/var/src/lesavka
INSTALL_SOURCE=${LESAVKA_INSTALL_SOURCE:-auto}
export TMPDIR=${TMPDIR:-/var/tmp}
USER_HOME=$(getent passwd "$ORIG_USER" | cut -d: -f6)
CLIENT_PKI_DIR=${LESAVKA_CLIENT_PKI_DIR:-$USER_HOME/.config/lesavka/pki}
CLIENT_PASTE_KEY_FILE=${LESAVKA_PASTE_KEY_FILE:-$USER_HOME/.config/lesavka/paste-key}
CLIENT_PKI_AUTO_FETCH=${LESAVKA_CLIENT_PKI_AUTO_FETCH:-1}
CLIENT_PKI_SSH_SOURCE=${LESAVKA_CLIENT_PKI_SSH_SOURCE:-theia:/etc/lesavka/lesavka-client-pki.tar.gz}
CLIENT_CAPTURE_DIR=${LESAVKA_CLIENT_CAPTURE_DIR:-$USER_HOME/Pictures/lesavka}

log() {
  printf '==> %s\n' "$*"
}

manifest_package_version() {
  local manifest=$1
  [[ -f $manifest ]] || return 1
  awk -F'"' '
    $0 ~ /^\[package\]/ { in_package=1; next }
    in_package && $0 ~ /^\[/ { exit }
    in_package && $0 ~ /^[[:space:]]*version[[:space:]]*=/ { print $2; exit }
  ' "$manifest"
}

source_revision() {
  local repo=$1
  local sha=""
  sha=$(run_as_user git -C "$repo" rev-parse --short HEAD 2>/dev/null || true)
  if [[ -n $sha ]] && ! run_as_user git -C "$repo" diff --quiet --ignore-submodules -- 2>/dev/null; then
    sha="${sha}+dirty"
  fi
  printf '%s\n' "$sha"
}

resolve_source_checkout() {
  case "$INSTALL_SOURCE" in
    auto)
      if [[ -d $SCRIPT_REPO_ROOT/.git ]]; then
        SRC=$SCRIPT_REPO_ROOT
        log "3. Using local source checkout at $SRC"
        echo "   ↪ set LESAVKA_INSTALL_SOURCE=ref to install from ${REF} via Git"
        return 0
      fi
      ;;
    local)
      if [[ ! -d $SCRIPT_REPO_ROOT/.git ]]; then
        echo "❌ LESAVKA_INSTALL_SOURCE=local requested, but $SCRIPT_REPO_ROOT is not a Git checkout." >&2
        exit 1
      fi
      SRC=$SCRIPT_REPO_ROOT
      log "3. Using local source checkout at $SRC"
      return 0
      ;;
    ref|git)
      ;;
    *)
      echo "❌ unsupported LESAVKA_INSTALL_SOURCE=$INSTALL_SOURCE (expected auto, local, or ref)" >&2
      exit 1
      ;;
  esac

  log "3. Syncing source checkout for ref ${REF}"
  if [[ ! -d /var/src ]]; then
    sudo mkdir -p /var/src
  fi
  sudo chown "$ORIG_USER":"$ORIG_USER" /var/src
  if [[ -d $SRC/.git ]]; then
    run_as_user git -C "$SRC" fetch --all --tags --prune
  else
    run_as_user git clone "$REPO_URL" "$SRC"
  fi
  if run_as_user git -C "$SRC" rev-parse --verify --quiet "origin/$REF" >/dev/null; then
    run_as_user git -C "$SRC" checkout -B "$REF" "origin/$REF"
  else
    run_as_user git -C "$SRC" checkout --force "$REF"
  fi
}

installed_kernel_module_trees() {
  local roots=(/usr/lib/modules /lib/modules)
  local seen=()
  local root entry
  for root in "${roots[@]}"; do
    [[ -d $root ]] || continue
    for entry in "$root"/*; do
      [[ -d $entry ]] || continue
      seen+=("$(basename "$entry")")
    done
  done
  if [[ ${#seen[@]} -eq 0 ]]; then
    return 0
  fi
  printf '%s\n' "${seen[@]}" | awk '!seen[$0]++'
}

require_command() {
  local cmd=$1
  local pkg_hint=$2
  if command -v "$cmd" >/dev/null 2>&1; then
    return 0
  fi
  echo "❌ required command '$cmd' is unavailable after install (expected via $pkg_hint)" >&2
  exit 1
}

require_linkable() {
  local path=$1
  local label=$2
  if ldd "$path" 2>/dev/null | grep -q 'not found'; then
    echo "❌ $label is present but has unresolved shared-library dependencies:" >&2
    ldd "$path" 2>/dev/null | grep 'not found' >&2 || true
    exit 1
  fi
}

require_gst_element() {
  local element=$1
  if run_as_user gst-inspect-1.0 "$element" >/dev/null 2>&1; then
    return 0
  fi
  echo "❌ required GStreamer element '$element' is unavailable after install." >&2
  exit 1
}

gst_element_available() {
  run_as_user gst-inspect-1.0 "$1" >/dev/null 2>&1
}

first_available_gst_element() {
  local element
  for element in "$@"; do
    if gst_element_available "$element"; then
      printf '%s\n' "$element"
      return 0
    fi
  done
  return 1
}

ffmpeg_hevc_nvenc_usable() {
  [[ -e /dev/nvidiactl ]] || return 1
  run_as_user ffmpeg -hide_banner -loglevel error \
    -f lavfi -i color=c=black:s=128x128:r=1 \
    -frames:v 1 -an -pix_fmt nv12 -c:v hevc_nvenc -f null - \
    >/dev/null 2>&1
}

report_client_media_acceleration() {
  log "1f. Inspecting client media acceleration routes"

  local hevc_encoder=""
  local ffmpeg_hevc_encoder=""
  local h264_encoder=""
  local h264_decoder=""
  local opus_encoder=""
  local opus_decoder=""
  local webrtc_dsp=""
  local proprietary_bits=()
  local opensource_bits=()

  hevc_encoder=$(first_available_gst_element \
    nvh265enc \
    nvautogpuh265enc \
    vah265enc \
    vaapih265enc \
    v4l2h265enc || true)
  if command -v ffmpeg >/dev/null 2>&1 && ffmpeg_hevc_nvenc_usable; then
    ffmpeg_hevc_encoder="hevc_nvenc"
  fi
  h264_encoder=$(first_available_gst_element \
    nvh264enc \
    nvautogpuh264enc \
    vah264enc \
    vaapih264enc \
    v4l2h264enc || true)
  h264_decoder=$(first_available_gst_element \
    nvh264dec \
    nvh264sldec \
    vah264dec \
    vaapih264dec \
    v4l2h264dec \
    v4l2slh264dec || true)
  opus_encoder=$(first_available_gst_element opusenc || true)
  opus_decoder=$(first_available_gst_element opusdec || true)
  webrtc_dsp=$(first_available_gst_element webrtcdsp || true)

  for element in nvh265enc nvautogpuh265enc nvh265dec nvh264enc nvautogpuh264enc nvh264dec nvh264sldec; do
    if gst_element_available "$element"; then
      proprietary_bits+=("$element")
    fi
  done
  for element in vulkanh264enc vulkanh264dec vulkanh265dec; do
    if gst_element_available "$element"; then
      opensource_bits+=("$element")
    fi
  done
  for element in vah265enc vaapih265enc v4l2h265enc vah264dec vaapih264dec v4l2h264dec v4l2slh264dec; do
    if gst_element_available "$element"; then
      opensource_bits+=("$element")
    fi
  done

  if command -v nvidia-smi >/dev/null 2>&1; then
    echo "   ↪ nvidia-smi is available; proprietary NVIDIA driver tooling is present"
    if [[ -z $hevc_encoder ]]; then
      if gst-inspect-1.0 nvcodec 2>&1 | grep -q 'Unable to initialize CUDA library'; then
        echo "⚠️  NVIDIA nvcodec is installed but CUDA initialization failed; NVENC HEVC is unavailable to GStreamer."
        if [[ -n $ffmpeg_hevc_encoder ]]; then
          echo "    FFmpeg hevc_nvenc encoded a probe frame and will be used for HEVC upstream hardware encode."
        else
          echo "    Install a working NVENC/VAAPI/V4L2 HEVC route before enabling HEVC upstream."
        fi
      fi
    fi
  else
    echo "   ↪ nvidia-smi is not available; NVIDIA proprietary tooling was not detected"
  fi

  if [[ ${#proprietary_bits[@]} -gt 0 ]]; then
    echo "   ↪ proprietary NVIDIA GStreamer route: ${proprietary_bits[*]}"
  else
    echo "   ↪ proprietary NVIDIA GStreamer route: not exposed"
  fi
  if [[ ${#opensource_bits[@]} -gt 0 ]]; then
    echo "   ↪ Vulkan/VAAPI/V4L2 GStreamer route: ${opensource_bits[*]}"
  else
    echo "   ↪ Vulkan/VAAPI/V4L2 GStreamer route: not exposed"
  fi

  if [[ -n $hevc_encoder || -n $ffmpeg_hevc_encoder ]]; then
    echo "   ↪ upstream HEVC hardware encoder candidate: ${hevc_encoder:-ffmpeg:$ffmpeg_hevc_encoder}"
  else
    echo "⚠️  no hardware HEVC encoder was detected; upstream HEVC will fail instead of using x265enc"
  fi
  if [[ -n $h264_encoder ]]; then
    echo "   ↪ upstream H.264 hardware encoder candidate: $h264_encoder"
  else
    echo "⚠️  no hardware H.264 encoder was detected; H.264 uplink will fail instead of using x264enc"
  fi
  if [[ -n $h264_decoder ]]; then
    echo "   ↪ downstream H.264 hardware decoder candidate: $h264_decoder"
  else
    echo "⚠️  no hardware H.264 decoder was detected; downstream eye video will fail instead of using decodebin"
  fi
  if [[ -n $opus_encoder && -n $opus_decoder ]]; then
    echo "✅  Opus upstream audio transport route: encoder=$opus_encoder decoder=$opus_decoder"
  else
    echo "⚠️  Opus upstream audio route is not fully exposed; Lesavka will fall back to PCM"
  fi
  if [[ -n $webrtc_dsp ]]; then
    echo "✅  microphone noise suppression route: $webrtc_dsp"
  else
    echo "   ↪ microphone noise suppression route: unavailable; raw microphone path still works"
  fi
  echo "   ↪ override decoder route with LESAVKA_H264_DECODER=<hardware-element>; software routes require LESAVKA_ALLOW_SOFTWARE_VIDEO=1"
}

require_kernel_module() {
  local module=$1
  local why=$2
  if [[ -d "/sys/module/${module}" ]] || modinfo "$module" >/dev/null 2>&1; then
    return 0
  fi
  local running_kernel
  running_kernel=$(uname -r)
  mapfile -t module_trees < <(installed_kernel_module_trees)
  echo "❌ required kernel module '$module' is unavailable for the running kernel $(uname -r)." >&2
  echo "   Lesavka needs it for $why." >&2
  if [[ ${#module_trees[@]} -eq 0 ]]; then
    echo "   No kernel module trees are currently installed under /usr/lib/modules or /lib/modules." >&2
  else
    echo "   Installed kernel module trees: ${module_trees[*]}" >&2
    if [[ ! " ${module_trees[*]} " =~ [[:space:]]${running_kernel}[[:space:]] ]]; then
      echo "   The machine is booted into an older kernel than the modules that are currently installed." >&2
      echo "   Reboot into one of the installed kernels above, then rerun the installer." >&2
    else
      echo "   The current kernel tree exists, but modinfo still cannot resolve '$module'." >&2
      echo "   Verify the kernel package and headers are healthy, then rerun the installer." >&2
    fi
  fi
  exit 1
}

run_as_user() {
  sudo -u "$ORIG_USER" env HOME="$USER_HOME" SSH_AUTH_SOCK="${SSH_AUTH_SOCK:-}" "$@"
}

refresh_gstreamer_registry_cache() {
  log "1e. Refreshing GStreamer plugin registry"
  run_as_user mkdir -p "$USER_HOME/.cache/gstreamer-1.0"
  run_as_user rm -f "$USER_HOME/.cache/gstreamer-1.0"/registry.* 2>/dev/null || true
  sudo rm -f /root/.cache/gstreamer-1.0/registry.* 2>/dev/null || true

  # Touch the GPU codec plugins after cache removal so stale pre-driver caches
  # cannot hide NVIDIA/VAAPI decoder factories from the live desktop app.
  run_as_user gst-inspect-1.0 nvcodec >/dev/null 2>&1 || true
  run_as_user gst-inspect-1.0 va >/dev/null 2>&1 || true
}

install_verified_executable() {
  local src=$1
  local dest=$2
  local label=${3:-$dest}
  local dest_dir dest_base tmp

  if [[ ! -s "$src" ]]; then
    echo "❌ refusing to install $label: source '$src' is missing or empty." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi
  if [[ ! -x "$src" ]]; then
    echo "❌ refusing to install $label: source '$src' is not executable." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi

  dest_dir=$(dirname "$dest")
  dest_base=$(basename "$dest")
  sudo install -d -m 0755 "$dest_dir"
  tmp=$(sudo mktemp "$dest_dir/.${dest_base}.install.XXXXXX")
  sudo rm -f "$tmp"
  sudo install -Dm755 "$src" "$tmp"
  if ! sudo test -s "$tmp" || ! sudo test -x "$tmp"; then
    sudo rm -f "$tmp"
    echo "❌ refusing to replace $label: staged install output was not a non-empty executable." >&2
    echo "   Preserving the existing installed executable at '$dest'." >&2
    exit 1
  fi
  sudo mv -f "$tmp" "$dest"
  sudo chmod 0755 "$dest"
}

warn_running_stale_client_processes() {
  local installed=/usr/local/bin/lesavka-client
  local installed_inode=""
  installed_inode=$(stat -Lc '%i' "$installed" 2>/dev/null || true)
  local stale=()
  local pid exe exe_inode cmd

  while read -r pid; do
    [[ -n "$pid" ]] || continue
    exe=$(readlink "/proc/$pid/exe" 2>/dev/null || true)
    [[ -n "$exe" ]] || continue
    exe_inode=$(stat -Lc '%i' "/proc/$pid/exe" 2>/dev/null || true)
    if [[ "$exe" == *" (deleted)"* || ( -n "$installed_inode" && "$exe_inode" != "$installed_inode" ) ]]; then
      cmd=$(tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null || true)
      stale+=("pid=$pid exe=$exe cmd=${cmd:-unknown}")
    fi
  done < <(pgrep -u "$ORIG_USER" -f '(^|/)(lesavka|lesavka-client)( |$|--)' 2>/dev/null || true)

  if ((${#stale[@]} == 0)); then
    return
  fi

  echo "⚠️  Lesavka is still running from an older/deleted binary."
  echo "    Close and relaunch Lesavka before judging video/audio performance from this install."
  printf '    %s\n' "${stale[@]}"
}

pacman_install() {
  local log_file
  log_file=$(mktemp --tmpdir="$TMPDIR" lesavka-pacman.XXXXXX.log)
  if sudo pacman -Sq --needed --noconfirm "$@" 2>&1 | tee "$log_file"; then
    rm -f "$log_file"
    return 0
  fi
  if grep -Eq "breaks dependency '.*pipewire" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch stopped the package transaction because PipeWire packages are at mixed exact versions.
   Lesavka now installs PipeWire as one coherent set, but this host still needs a sync transaction.
   Run:
     sudo pacman -Syu
   Then rerun the Lesavka client installer.
MSG
  elif grep -Eq "failed retrieving file|failed to retrieve some files|failed to commit transaction \\(failed to retrieve some files\\)" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch failed while downloading packages from the configured mirrors.
   No Lesavka files were replaced. Refresh or choose healthier mirrors, then rerun the installer.
   Good first retry:
     sudo pacman -Syu --disable-download-timeout
   If mirrors keep timing out, refresh /etc/pacman.d/mirrorlist before retrying.
MSG
  elif grep -Eq "pipewire-jack.*jack2|jack2.*pipewire-jack|are in conflict \\(jack\\)" "$log_file"; then
    cat >&2 <<'MSG'
❌ Arch refused to install pipewire-jack because JACK is already provided by jack2.
   Lesavka does not require JACK. The installer only keeps pipewire-jack coherent
   on hosts that already use it; do not remove jack2 just for Lesavka.
   Pull the latest installer and rerun the Lesavka client install.
MSG
  fi
  echo "   pacman log: $log_file" >&2
  exit 1
}

pipewire_package_set() {
  printf '%s\n' libpipewire pipewire pipewire-audio pipewire-pulse
  for package in pipewire-alsa pipewire-jack; do
    if pacman -Q "$package" >/dev/null 2>&1; then
      printf '%s\n' "$package"
    fi
  done
}

fetch_client_pki_bundle() {
  [[ $CLIENT_PKI_AUTO_FETCH != 0 && $CLIENT_PKI_AUTO_FETCH != false && $CLIENT_PKI_AUTO_FETCH != no ]] || return 1
  [[ $CLIENT_PKI_SSH_SOURCE == *:* ]] || return 1

  local host=${CLIENT_PKI_SSH_SOURCE%%:*}
  local remote_path=${CLIENT_PKI_SSH_SOURCE#*:}
  local tmp_bundle
  tmp_bundle=$(run_as_user mktemp --tmpdir="$TMPDIR" lesavka-client-pki.XXXXXX.tar.gz)
  if run_as_user scp -q -o BatchMode=yes -o ConnectTimeout=5 \
    "$host:$remote_path" "$tmp_bundle" >/dev/null 2>&1; then
    printf '%s\n' "$tmp_bundle"
    return 0
  fi
  rm -f "$tmp_bundle"
  return 1
}

install_client_pki_bundle() {
  local bundle=${LESAVKA_CLIENT_PKI_BUNDLE:-}
  local fetched_bundle=0
  if [[ -z $bundle ]]; then
    if [[ -s "$CLIENT_PKI_DIR/ca.crt" && -s "$CLIENT_PKI_DIR/client.crt" && -s "$CLIENT_PKI_DIR/client.key" && -s "$CLIENT_PASTE_KEY_FILE" ]]; then
      echo "   ↪ TLS client identity already present: $CLIENT_PKI_DIR"
      echo "   ↪ paste shared key already present: $CLIENT_PASTE_KEY_FILE"
      return 0
    fi

    if bundle=$(fetch_client_pki_bundle); then
      fetched_bundle=1
      echo "   ↪ fetched TLS client enrollment bundle from $CLIENT_PKI_SSH_SOURCE"
    else
      if [[ -s "$CLIENT_PKI_DIR/ca.crt" && -s "$CLIENT_PKI_DIR/client.crt" && -s "$CLIENT_PKI_DIR/client.key" ]]; then
        echo "⚠️  TLS client identity is present but encrypted paste key is missing: $CLIENT_PASTE_KEY_FILE"
        echo "   Rerun after the server installer refreshes $CLIENT_PKI_SSH_SOURCE, or provide LESAVKA_CLIENT_PKI_BUNDLE."
        return 0
      fi
      echo "⚠️  no TLS client identity installed."
      echo "   Rerun with LESAVKA_CLIENT_PKI_BUNDLE=/path/to/lesavka-client-pki.tar.gz,"
      echo "   or make $CLIENT_PKI_SSH_SOURCE readable over SSH and rerun the installer."
      echo "   HTTPS/mTLS relay connections will not work until this bundle is installed."
      return 0
    fi
  fi

  log "5b. Installing TLS client identity"
  local tmp
  tmp=$(mktemp -d)
  sudo tar -xzf "$bundle" -C "$tmp"
  for item in ca.crt client.crt client.key; do
    if [[ ! -s "$tmp/$item" ]]; then
      echo "❌ TLS client bundle $bundle is missing $item" >&2
      sudo rm -rf "$tmp"
      exit 1
    fi
  done
  sudo install -d -m 0700 -o "$ORIG_USER" -g "$ORIG_USER" "$CLIENT_PKI_DIR"
  sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$tmp/ca.crt" "$CLIENT_PKI_DIR/ca.crt"
  sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$tmp/client.crt" "$CLIENT_PKI_DIR/client.crt"
  sudo install -m 0600 -o "$ORIG_USER" -g "$ORIG_USER" "$tmp/client.key" "$CLIENT_PKI_DIR/client.key"
  if [[ -s "$tmp/paste-key" ]]; then
    sudo install -d -m 0700 -o "$ORIG_USER" -g "$ORIG_USER" "$(dirname "$CLIENT_PASTE_KEY_FILE")"
    sudo install -m 0600 -o "$ORIG_USER" -g "$ORIG_USER" "$tmp/paste-key" "$CLIENT_PASTE_KEY_FILE"
  else
    echo "⚠️  TLS client bundle $bundle does not include paste-key; encrypted paste RPC will use HID fallback."
  fi
  sudo rm -rf "$tmp"
  if [[ $fetched_bundle == 1 ]]; then
    rm -f "$bundle"
  fi
  echo "   ↪ installed TLS client identity: $CLIENT_PKI_DIR"
  if [[ -s "$CLIENT_PASTE_KEY_FILE" ]]; then
    echo "   ↪ installed encrypted paste key: $CLIENT_PASTE_KEY_FILE"
  fi
}

mkdir -p "$TMPDIR"

if [[ -z $REPO_URL ]] && [[ -d $SCRIPT_REPO_ROOT/.git ]]; then
  REPO_URL=$(git -C "$SCRIPT_REPO_ROOT" config --get remote.origin.url || true)
fi
REPO_URL=${REPO_URL:-$DEFAULT_REPO_URL}

log "1. Installing base packages"
mapfile -t PIPEWIRE_PACKAGES < <(pipewire_package_set)
pacman_install \
  git rustup protobuf abseil-cpp gcc clang llvm-libs compiler-rt evtest base-devel libpulse \
  "${PIPEWIRE_PACKAGES[@]}" wireplumber \
  alsa-utils gst-plugin-pipewire \
  gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav gst-plugin-va intel-media-driver \
  ffmpeg wmctrl qt6-tools wl-clipboard xclip xsel desktop-file-utils openssl

ensure_yay() {
  if command -v yay >/dev/null 2>&1; then
    if run_as_user yay --version >/dev/null 2>&1; then
      return
    fi
  fi
  run_as_user env TMPDIR="$TMPDIR" bash -c 'rm -rf "$TMPDIR/yay" &&
    cd "$TMPDIR" && git clone --depth 1 https://aur.archlinux.org/yay.git &&
    cd yay && makepkg -si --noconfirm'
}

log "1b. Installing grpcurl"
if sudo pacman -Si grpcurl >/dev/null 2>&1; then
  pacman_install grpcurl
else
  ensure_yay
  if ! run_as_user yay -S --needed --noconfirm grpcurl-bin; then
    log "grpcurl AUR install failed once, rebuilding yay and retrying"
    ensure_yay
    run_as_user yay -S --needed --noconfirm grpcurl-bin
  fi
fi

# 1c. input access
log "1c. Ensuring input group access for $ORIG_USER"
sudo usermod -aG input "$ORIG_USER"

log "1d. Verifying runtime tools"
require_command pactl "libpulse"
require_command gst-inspect-1.0 "gstreamer"
require_command ffmpeg "ffmpeg"
require_command arecord "alsa-utils"
require_command speaker-test "alsa-utils"
require_command wmctrl "wmctrl"
require_command qdbus6 "qt6-tools"
require_command protoc "protobuf"
if ! command -v wl-paste >/dev/null 2>&1 \
  && ! command -v xclip >/dev/null 2>&1 \
  && ! command -v xsel >/dev/null 2>&1; then
  echo "❌ no clipboard reader found after install (expected one of wl-clipboard/xclip/xsel)" >&2
  exit 1
fi
require_linkable "$(command -v protoc)" "protoc"
if [[ -e /usr/lib/libclang.so ]]; then
  require_linkable /usr/lib/libclang.so "libclang"
fi
require_kernel_module snd_usb_audio "USB microphones and USB headsets"
refresh_gstreamer_registry_cache
require_gst_element pulsesrc
require_gst_element pulsesink
require_gst_element pipewiresrc
report_client_media_acceleration
protoc --version >/dev/null
if ! run_as_user pactl info >/dev/null 2>&1; then
  echo "⚠️  pactl is installed, but no PulseAudio/PipeWire Pulse server is reachable right now."
  echo "    Lesavka will still install, but local speaker/mic staging may stay empty until the host audio session is up."
fi

# 2. Rust tool-chain for both root & user
log "2. Ensuring Rust toolchain"
sudo rustup default stable
run_as_user rustup default stable

# 3. resolve the build source. Local checkouts are preferred so development
# installs do not silently rebuild an older /var/src clone.
resolve_source_checkout

# 4. build
log "4. Building client release binary"
run_as_user env TMPDIR="$TMPDIR" bash -c "cd '$SRC/client' && cargo clean && cargo build --release"

# 5. install binary
log "5. Installing launchable client binaries"
install_verified_executable "$SRC/target/release/lesavka-client" /usr/local/bin/lesavka-client "lesavka-client"
sudo ln -sf /usr/local/bin/lesavka-client /usr/local/bin/lesavka
sudo install -d -m 0755 -o "$ORIG_USER" -g "$ORIG_USER" "$USER_HOME/.local/bin"
sudo ln -sf /usr/local/bin/lesavka-client "$USER_HOME/.local/bin/lesavka-client"
sudo chown -h "$ORIG_USER":"$ORIG_USER" "$USER_HOME/.local/bin/lesavka-client"
install_client_pki_bundle
sudo install -d -m 0755 -o "$ORIG_USER" -g "$ORIG_USER" "$CLIENT_CAPTURE_DIR"
echo "   ↪ capture folder: $CLIENT_CAPTURE_DIR"

log "6. Registering desktop application"
sudo install -Dm644 "$SRC/client/assets/icons/hicolor/1024x1024/apps/lesavka.png" \
  /usr/share/icons/hicolor/1024x1024/apps/lesavka.png
sudo install -Dm644 "$SRC/client/assets/icons/hicolor/1024x1024/apps/lesavka.png" \
  /usr/share/pixmaps/lesavka.png
sudo install -Dm644 "$SRC/client/assets/linux/lesavka.desktop" \
  /usr/share/applications/lesavka.desktop
if command -v update-desktop-database >/dev/null 2>&1; then
  sudo update-desktop-database /usr/share/applications
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
  sudo gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true
fi

log "7. Removing legacy auto-start service"
sudo systemctl disable --now lesavka-client.service >/dev/null 2>&1 || true
sudo rm -f /etc/systemd/system/lesavka-client.service
sudo systemctl daemon-reload

echo
echo "✅ lesavka-client install complete"
INSTALLED_VERSION=$(manifest_package_version "$SRC/client/Cargo.toml" 2>/dev/null || true)
INSTALLED_SHA=$(source_revision "$SRC")
if [[ -n ${INSTALLED_VERSION:-} ]]; then
  echo "➡️  Installed: lesavka-client ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
fi
echo "   Binary: /usr/local/bin/lesavka-client"
echo "   Launch alias: /usr/local/bin/lesavka"
echo "   User PATH alias: $USER_HOME/.local/bin/lesavka-client"
echo "   Desktop entry: /usr/share/applications/lesavka.desktop"
echo "   Build source: $SRC/target/release/lesavka-client"
echo "   TLS identity: $CLIENT_PKI_DIR"
echo "   Paste key: $CLIENT_PASTE_KEY_FILE"
echo "   Captures: $CLIENT_CAPTURE_DIR"
warn_running_stale_client_processes
echo "✅  Installed version: lesavka-client ${INSTALLED_VERSION:-unknown}${INSTALLED_SHA:+ ($INSTALLED_SHA)}"
echo
echo "Quick start:"
echo "  KDE menu: search for Lesavka"
echo "  Terminal: /usr/local/bin/lesavka"
