#!/usr/bin/env bash
#
# install-remotive-desktop.sh — install or upgrade RemotiveStudio Desktop.
#
# Prefers the native package channel and otherwise falls back to a direct
# artifact download (same structure as remotivelabs-cli's install.sh):
#
#   macOS            -> Homebrew cask       (fallback: signed + notarized DMG)
#                       When Homebrew is present it asks which to use; use
#                       --method brew|dmg to skip the prompt.
#   Debian/Ubuntu    -> apt  + RemotiveLabs apt repo   (fallback: tar bundle)
#   RHEL/Fedora/SUSE -> dnf/yum + RemotiveLabs yum repo (fallback: tar bundle)
#   Other Linux      -> tar bundle into ~/.local/share
#
# Once installed via brew/apt/yum, updates arrive through the package manager
# (`brew upgrade` / `apt upgrade` / `dnf upgrade`) — re-running this script is
# only needed for the DMG and tar paths.
#
# Usage:
#   curl -fsSL https://releases.beamylabs.com/remotive-studio-desktop/install.sh | bash
#   ./install-remotive-desktop.sh              # install/upgrade to the latest version
#   ./install-remotive-desktop.sh 0.0.41       # install/pin a specific version
#   ./install-remotive-desktop.sh -f           # force reinstall even if current
#   ./install-remotive-desktop.sh -y           # non-interactive (no prompts)
#   ./install-remotive-desktop.sh --method tar # force a backend (brew|apt|yum|dmg|tar)
#   ./install-remotive-desktop.sh --dry-run    # print what would happen, change nothing
#
# Environment overrides:
#   REMOTIVE_STUDIO_INSTALL_METHOD=brew|apt|yum|dmg|tar   same as --method
#
set -euo pipefail

# ---------------------------------------------------------------------------
# Constants — the RemotiveLabs distribution channels
# ---------------------------------------------------------------------------
RELEASE_BASE="https://releases.beamylabs.com/remotive-studio-desktop"
LATEST_URL="$RELEASE_BASE/latest/latest-version.txt"

PKG_NAME="remotive-studio-desktop"            # deb/rpm package name
CASK="remotivelabs/tap/remotive-studio-desktop"       # macOS Homebrew cask
CASK_TOKEN="remotive-studio-desktop"
MACOS_APP="/Applications/RemotiveStudio.app"

# Same repos that serve remotivelabs-cli — a machine that installed the CLI
# from packages.remotivelabs.com needs no new repo configuration.
APT_REPO_URL="https://packages.remotivelabs.com"
APT_DIST="remotivelabs-apt"
APT_COMPONENT="main"
APT_KEY_URL="https://packages.remotivelabs.com/apt-repo-signing-key.gpg"
APT_KEYRING="/usr/share/keyrings/remotivelabs-apt.gpg"       # dearmored (binary)
APT_KEYRING_ASC="/usr/share/keyrings/remotivelabs-apt.asc"   # armored (no gpg tool)
APT_LIST="/etc/apt/sources.list.d/remotivelabs.list"

YUM_BASEURL="https://packages.remotivelabs.com/yum/remotivelabs-yum"
YUM_REPO_FILE="/etc/yum.repos.d/remotivelabs.repo"

# Pinned RemotiveLabs release public key (Ed25519). Embedded so the script is
# self-contained: swapping the published .sha256/.sha256.sig alone cannot
# redirect trust. Fingerprint (SHA-256 of the raw 32-byte key, uppercase) is
# 946AE04FEF967D7E3AF65193800092E52BFD4D1CC4B84CD3C3E72B2194EC22A1 — matches
# the value pinned in remotivelabs-cli (studio/app_install.py).
RELEASE_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAbTKyWdHF6PdIntAVaABKEURD2jZM8Vgscuhk+HN15Ws=
-----END PUBLIC KEY-----"

# Tar-bundle destinations. These MUST stay aligned with the paths in
# remotivelabs-cli (studio/app_install.py), which detects the installed
# version via the marker file below.
TAR_APP_DIR_NAME="remotive-studio-desktop"
TAR_VERSION_MARKER=".installed-version"

# ---------------------------------------------------------------------------
# Args
# ---------------------------------------------------------------------------
VERSION=""
FORCE=""
YES=""
DRY_RUN=""
METHOD="${REMOTIVE_STUDIO_INSTALL_METHOD:-auto}"

usage() {
  cat >&2 <<'USAGE'
install-remotive-desktop.sh — install or upgrade RemotiveStudio Desktop.

Prefers the native package channel and otherwise falls back to a direct
artifact download:

  macOS            -> Homebrew cask       (fallback: signed + notarized DMG)
                      When Homebrew is present it asks which to use; use
                      --method brew|dmg to skip the prompt.
  Debian/Ubuntu    -> apt  + RemotiveLabs apt repo   (fallback: tar bundle)
  RHEL/Fedora/SUSE -> dnf/yum + RemotiveLabs yum repo (fallback: tar bundle)
  Other Linux      -> tar bundle into ~/.local/share

Usage:
  curl -fsSL https://releases.beamylabs.com/remotive-studio-desktop/install.sh | bash
  ./install-remotive-desktop.sh              # install/upgrade to the latest version
  ./install-remotive-desktop.sh 0.0.41       # install/pin a specific version
  ./install-remotive-desktop.sh -f           # force reinstall even if current
  ./install-remotive-desktop.sh -y           # non-interactive (no prompts)
  ./install-remotive-desktop.sh --method tar # force a backend (brew|apt|yum|dmg|tar)
  ./install-remotive-desktop.sh --dry-run    # print what would happen, change nothing

Environment overrides:
  REMOTIVE_STUDIO_INSTALL_METHOD=brew|apt|yum|dmg|tar   same as --method
USAGE
  exit "${1:-0}"
}

while [ $# -gt 0 ]; do
  case "$1" in
    -f|--force)   FORCE="1" ;;
    -y|--yes)     YES="1" ;;
    --dry-run)    DRY_RUN="1" ;;
    --method)     shift; METHOD="${1:-}" ;;
    --method=*)   METHOD="${1#*=}" ;;
    -h|--help)    usage 0 ;;
    -*)           echo "Unknown option: $1" >&2; usage 1 ;;
    *)            VERSION="$1" ;;
  esac
  shift
done

OS="$(uname -s)"
ARCH="$(uname -m)"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log()  { printf '%s\n' "$*" >&2; }
warn() { printf 'warning: %s\n' "$*" >&2; }
die()  { printf 'error: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

# run <cmd...> — execute, or print under --dry-run.
run() {
  if [ -n "$DRY_RUN" ]; then printf '+ %s\n' "$*" >&2; else "$@"; fi
}

# root_do / root_write — run as root (directly if root, else sudo).
SUDO=""
init_sudo() {
  if [ "$(id -u)" -eq 0 ]; then SUDO=""
  elif have sudo; then SUDO="sudo"
  else die "root privileges are required (install 'sudo' or run as root)."; fi
}
root_do() { if [ -n "$SUDO" ]; then run "$SUDO" "$@"; else run "$@"; fi; }
root_write() {
  local dest="$1"
  if [ -n "$DRY_RUN" ]; then
    printf '+ write %s:\n' "$dest" >&2; sed 's/^/  | /' >&2; return 0
  fi
  if [ -n "$SUDO" ]; then $SUDO tee "$dest" >/dev/null; else tee "$dest" >/dev/null; fi
}

require_curl() { have curl || die "curl is required."; }

# confirm_system_changes <lines> — explicit consent before modifying system
# state (package sources, keyrings, root installs); sudo only authenticates,
# it never says what is about to happen. Skipped with -y and under --dry-run.
# Reads /dev/tty so it works when piped (curl | bash); with no terminal it
# proceeds like -y so CI and scripted installs keep working.
confirm_system_changes() {
  if [ -n "$YES" ] || [ -n "$DRY_RUN" ]; then return 0; fi
  if ! { exec 3</dev/tty; } 2>/dev/null; then
    log "No terminal available for a confirmation prompt — proceeding (use -y to silence this notice)."
    return 0
  fi
  log ""
  log "This will:"
  printf '%s\n' "$1" | sed 's/^/  - /' >&2
  printf 'Continue? [Y/n] ' >&2
  local ans
  read -r ans <&3 || ans=""
  exec 3<&-
  case "$(printf '%s' "$ans" | tr '[:upper:]' '[:lower:]')" in
    n|no) die "aborted by user." ;;
    *) : ;;
  esac
}

# fetch <url> <dest> — download to $dest.
fetch() {
  log "Downloading $(basename "$1") ..."
  curl -fL --progress-bar -o "$2" "$1" || die "download failed: $1"
}

# resolve_version — echo the target version: the explicit arg, or the latest
# read from the release pointer file (a bare version string).
RESOLVED_VERSION=""
resolve_version() {
  if [ -n "$RESOLVED_VERSION" ]; then echo "$RESOLVED_VERSION"; return; fi
  if [ -n "$VERSION" ]; then RESOLVED_VERSION="$VERSION"; echo "$RESOLVED_VERSION"; return; fi
  require_curl
  local v
  v="$(curl -fsSL --max-time 15 "$LATEST_URL" | tr -d '[:space:]')"
  [ -n "$v" ] || die "could not resolve the latest version from $LATEST_URL"
  RESOLVED_VERSION="$v"; echo "$v"
}

# asset_url <asset> <version> — versioned release URL for a downloadable asset.
asset_url() { echo "$RELEASE_BASE/remotive-studio-desktop-$2/$1"; }

# verify <file> — fail-closed Ed25519 integrity check (mirrors remotivelabs-cli
# app_install.py). Downloads <file>.sha256 and <file>.sha256.sig from the same
# versioned release dir, verifies the signature was made by the pinned release
# key, asserts the sidecar names this artifact (guards against replaying a
# signature from another file), then compares the SHA-256. Any failure aborts.
verify() {
  local file="$1" version="$2"
  local asset; asset="$(basename "$file")"
  local sha="$TMP/$asset.sha256"
  local sig="$TMP/$asset.sha256.sig"

  if ! curl -fsSL -o "$sha" "$(asset_url "$asset.sha256" "$version")" \
    || ! curl -fsSL -o "$sig" "$(asset_url "$asset.sha256.sig" "$version")"; then
    die "missing integrity files for $asset — refusing to install."
  fi

  have openssl || die "openssl is required to verify the release signature — refusing to install."

  local keyfile="$TMP/release-public-key.pem"
  printf '%s\n' "$RELEASE_PUBLIC_KEY" > "$keyfile"

  # Ed25519 signs the message bytes directly (-rawin), matching how the release
  # workflow produces the detached .sha256.sig.
  if ! openssl pkeyutl -verify -pubin -inkey "$keyfile" -rawin -in "$sha" -sigfile "$sig" >/dev/null 2>&1; then
    die "signature on $asset.sha256 was not made by the RemotiveLabs release key — refusing to install."
  fi

  local expected actual
  expected=$(awk -v n="$asset" '{ name=$2; sub(/^\*/,"",name); if (name==n) { print $1; exit } }' "$sha")
  [ -n "$expected" ] || die "$asset.sha256 does not list $asset — refusing to install (signed hash does not belong to this artifact)."
  if have sha256sum; then actual=$(sha256sum "$file" | awk '{print $1}')
  else actual=$(shasum -a 256 "$file" | awk '{print $1}'); fi
  [ "$expected" = "$actual" ] || die "SHA-256 mismatch for $asset — refusing to install."
  log "Signature + checksum OK for $asset"
}

# assert_tar_safe <file> — refuse archives whose entries could write outside
# the extraction directory: absolute names, '..' components, or sym/hard links
# with absolute or '..' targets. verify() already proved the tarball is ours;
# this is defense-in-depth against a compromised pipeline shipping a
# traversal archive.
assert_tar_safe() {
  local file="$1" names links bad
  names=$(tar -tzf "$file") || die "cannot read the tarball — refusing to extract."
  links=$(tar -tvzf "$file" | sed -n -e 's/.* -> \(.*\)/\1/p' -e 's/.* link to \(.*\)/\1/p') \
    || die "cannot read the tarball — refusing to extract."
  bad=$(printf '%s\n' "$names" | grep -E '^/|(^|/)\.\.(/|$)' | head -3 || true)
  [ -z "$bad" ] || die "tarball contains unsafe entry paths ($bad) — refusing to extract."
  bad=$(printf '%s\n' "$links" | grep -E '^/|(^|/)\.\.(/|$)' | head -3 || true)
  [ -z "$bad" ] || die "tarball contains links escaping the install directory ($bad) — refusing to extract."
}

# validate_home — sanitize $HOME before it is used to derive the tar install
# paths, and echo the symlink-resolved result. The tar path runs `rm -rf` on
# paths derived from this value, so a malicious or misconfigured $HOME (/,
# /etc, another user's home, a symlink into a system tree, a ".." traversal)
# must abort the install — fail closed. (Same checks as remotivelabs-cli's
# install.sh.)
validate_home() {
  local home="${HOME:-}" resolved pw_home
  [ -n "$home" ] || die "\$HOME is unset or empty — cannot determine the install location."
  case "$home" in
    /*) : ;;
    *)  die "\$HOME ('$home') is not an absolute path — refusing to install." ;;
  esac
  case "/$home/" in
    */../*) die "\$HOME ('$home') contains a '..' path traversal component — refusing to install." ;;
  esac
  resolved="$(readlink -f -- "$home" 2>/dev/null)" && [ -n "$resolved" ] \
    || die "cannot resolve \$HOME ('$home') to a real path — refusing to install."

  pw_home="$(getent passwd "$(id -u)" 2>/dev/null | cut -d: -f6)"
  if [ -n "$pw_home" ]; then
    pw_home="$(readlink -f -- "$pw_home" 2>/dev/null || echo "$pw_home")"
    case "$resolved" in
      "$pw_home"|"$pw_home"/*) : ;;
      *) die "\$HOME ('$home' -> '$resolved') is not your home directory ('$pw_home') — refusing to install." ;;
    esac
  else
    warn "no passwd entry for uid $(id -u) — cannot verify \$HOME against the user database."
  fi

  case "$resolved" in
    /|/bin|/boot|/dev|/etc|/lib|/lib32|/lib64|/opt|/proc|/run|/sbin|/srv|/sys|/tmp|/usr|/var| \
    /bin/*|/boot/*|/dev/*|/etc/*|/lib/*|/lib32/*|/lib64/*|/proc/*|/run/*|/sbin/*|/sys/*|/usr/*)
      die "\$HOME ('$home' -> '$resolved') is a system directory — refusing to install." ;;
  esac
  echo "$resolved"
}

# ---------------------------------------------------------------------------
# macOS — Homebrew cask (primary)
# ---------------------------------------------------------------------------
install_brew() {
  # detect_method routes pinned installs to dmg, but --method brew with an
  # explicit version can still land here.
  if [ -n "$VERSION" ]; then
    warn "Homebrew installs the latest cask version only; ignoring requested version $VERSION."
    warn "To pin a version use '--method dmg $VERSION'."
  fi
  # Only touch our own cask — no `brew update`: refreshing all tap metadata
  # is a system-wide side effect that belongs to the user, and brew's own
  # auto-update policy already keeps install/upgrade fresh per their config.
  if brew list --cask "$CASK_TOKEN" >/dev/null 2>&1; then
    if [ -n "$FORCE" ]; then run brew reinstall --cask "$CASK"
    else run brew upgrade --cask "$CASK" || true; fi   # upgrade is a no-op when current
  else
    run brew install --cask "$CASK"
  fi
}

# ---------------------------------------------------------------------------
# macOS — signed + notarized DMG (fallback, no Homebrew needed)
# ---------------------------------------------------------------------------
install_dmg() {
  require_curl
  [ "$ARCH" = "arm64" ] || die "macOS builds are Apple Silicon (arm64) only; no Intel/x86_64 build is published."

  if brew list --cask "$CASK_TOKEN" >/dev/null 2>&1; then
    warn "RemotiveStudio is Homebrew-managed on this machine; installing the DMG directly"
    warn "will leave brew's records stale. Prefer 'brew upgrade --cask $CASK'."
  fi

  local ver; ver="$(resolve_version)"

  # Skip if already on the requested version (unless forced)
  if [ -d "$MACOS_APP" ] && [ -z "$FORCE" ]; then
    local current
    current=$(defaults read "$MACOS_APP/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "")
    if [ "$current" = "$ver" ]; then
      log "RemotiveStudio $ver is already installed. Use -f to force reinstall."
      return 0
    fi
    [ -n "$current" ] && log "Updating RemotiveStudio: $current -> $ver"
  fi

  local asset="RemotiveStudio-${ver}-arm64.dmg" file="$TMP/RemotiveStudio-${ver}-arm64.dmg"
  if [ -n "$DRY_RUN" ]; then
    log "+ fetch $(asset_url "$asset" "$ver"), verify codesign + Gatekeeper, replace $MACOS_APP"; return 0
  fi
  fetch "$(asset_url "$asset" "$ver")" "$file"

  local mount app
  mount=$(hdiutil attach "$file" -nobrowse -noautoopen | grep -o '/Volumes/.*' | head -1)
  app=$(find "$mount" -maxdepth 1 -name "*.app" | head -1)
  if [ -z "$app" ]; then
    hdiutil detach "$mount" -quiet || true
    die "no .app bundle found inside the DMG — aborting."
  fi

  # The DMG is Developer ID signed and notarized by Apple. Verify both before
  # installing; a failed check aborts so a tampered or improperly-signed
  # download never reaches /Applications. codesign --verify confirms the
  # signature is intact; spctl --assess runs the full Gatekeeper assessment
  # (signature + stapled notarization ticket).
  if ! codesign --verify --deep --strict "$app"; then
    hdiutil detach "$mount" -quiet || true
    die "code signature check failed for $(basename "$app") — aborting."
  fi
  if ! spctl --assess --type execute "$app"; then
    hdiutil detach "$mount" -quiet || true
    die "notarization / Gatekeeper check failed for $(basename "$app") — aborting."
  fi

  # Quit a running instance for a clean update
  if pgrep -f "$MACOS_APP" >/dev/null 2>&1; then
    log "Quitting running instance ..."
    osascript -e 'quit app "RemotiveStudio"' 2>/dev/null || true
    sleep 2
  fi

  # Clean replace rather than merge. No quarantine strip and no ad-hoc
  # re-sign: curl/hdiutil/cp don't set the com.apple.quarantine xattr, and
  # re-signing would invalidate the notarized Developer ID signature.
  [ -d "$MACOS_APP" ] && rm -rf "$MACOS_APP"
  cp -R "$app" /Applications/
  hdiutil detach "$mount" -quiet

  log "Installed $MACOS_APP"
}

# ---------------------------------------------------------------------------
# Linux — apt (Debian/Ubuntu)
# ---------------------------------------------------------------------------
configure_apt_repo() {
  require_curl
  root_do install -d -m 0755 /usr/share/keyrings /etc/apt/sources.list.d
  local keyref
  if have gpg; then
    if [ -n "$DRY_RUN" ]; then
      log "+ curl $APT_KEY_URL | ${SUDO:+$SUDO }gpg --dearmor -o $APT_KEYRING"
    elif [ -n "$SUDO" ]; then
      curl -fsSL "$APT_KEY_URL" | $SUDO gpg --dearmor --yes -o "$APT_KEYRING"
    else
      curl -fsSL "$APT_KEY_URL" | gpg --dearmor --yes -o "$APT_KEYRING"
    fi
    keyref="$APT_KEYRING"
  else
    if [ -n "$DRY_RUN" ]; then log "+ curl $APT_KEY_URL > $APT_KEYRING_ASC"
    else curl -fsSL "$APT_KEY_URL" | root_write "$APT_KEYRING_ASC"; fi
    keyref="$APT_KEYRING_ASC"
  fi
  printf 'deb [signed-by=%s] %s %s %s\n' "$keyref" "$APT_REPO_URL" "$APT_DIST" "$APT_COMPONENT" \
    | root_write "$APT_LIST"
  root_do apt-get update
}

install_apt() {
  init_sudo
  confirm_system_changes "add the RemotiveLabs apt repo ($APT_LIST)
install its signing key under /usr/share/keyrings
install/upgrade $PKG_NAME with apt-get (as root)"
  configure_apt_repo
  local spec="$PKG_NAME"
  local extra=""
  # A pinned version may be older than what's installed; let apt honor it.
  [ -n "$VERSION" ] && { spec="$PKG_NAME=$VERSION"; extra="--allow-downgrades"; }
  [ -n "$FORCE" ] && extra="$extra --reinstall"
  # `apt-get install` upgrades to the newest available when already installed.
  # shellcheck disable=SC2086
  root_do env DEBIAN_FRONTEND=noninteractive apt-get install -y $extra "$spec"
}

# ---------------------------------------------------------------------------
# Linux — yum/dnf (RHEL/Fedora/SUSE)
# ---------------------------------------------------------------------------
configure_yum_repo() {
  # gpgcheck=0 / repo_gpgcheck=0: same known-good configuration as the
  # remotivelabs-cli repo setup; integrity rests on HTTPS TLS to
  # packages.remotivelabs.com.
  root_write "$YUM_REPO_FILE" <<EOF
[remotivelabs]
name=RemotiveLabs
baseurl=$YUM_BASEURL
enabled=1
repo_gpgcheck=0
gpgcheck=0
EOF
}

install_yum() {
  init_sudo
  confirm_system_changes "add the RemotiveLabs yum repo ($YUM_REPO_FILE)
install/upgrade $PKG_NAME with dnf/yum (as root)"
  configure_yum_repo
  local mgr
  if have dnf; then mgr="dnf"; elif have yum; then mgr="yum"; else die "neither dnf nor yum found."; fi
  if [ -n "$FORCE" ]; then
    local spec="$PKG_NAME"; [ -n "$VERSION" ] && spec="$PKG_NAME-$VERSION"
    root_do "$mgr" -y reinstall "$spec"
  elif [ -n "$VERSION" ]; then
    root_do "$mgr" -y install "$PKG_NAME-$VERSION"
  elif rpm -q "$PKG_NAME" >/dev/null 2>&1; then
    # Already installed: `dnf install` is a no-op on an installed package, so
    # upgrade explicitly to pull the newest available version.
    root_do "$mgr" -y upgrade "$PKG_NAME"
  else
    root_do "$mgr" -y install "$PKG_NAME"
  fi
}

# ---------------------------------------------------------------------------
# Linux — tar bundle (fallback, no package manager needed)
# ---------------------------------------------------------------------------
install_linux_tar() {
  [ "$OS" = "Linux" ] || die "the tar bundle is Linux-only (on macOS use --method brew or dmg)."
  require_curl
  # Validate $HOME before anything else: all install paths derive from the
  # validated, symlink-resolved value — never from the raw environment.
  local real_home; real_home="$(validate_home)"
  local dest="$real_home/.local/share/$TAR_APP_DIR_NAME"
  local bindir="$real_home/.local/bin"

  local ver tar_arch
  ver="$(resolve_version)"
  case "$ARCH" in
    x86_64|amd64)   tar_arch=x86_64 ;;
    aarch64|arm64)  tar_arch=arm64 ;;
    *) die "unsupported Linux arch '$ARCH'." ;;
  esac

  # Skip if already on the requested version (unless forced)
  if [ -z "$FORCE" ] && [ -f "$dest/$TAR_VERSION_MARKER" ]; then
    local current; current="$(cat "$dest/$TAR_VERSION_MARKER" 2>/dev/null || echo "")"
    if [ "$current" = "$ver" ]; then
      log "remotive-studio-desktop $ver is already installed. Use -f to force reinstall."
      return 0
    fi
    [ -n "$current" ] && log "Updating remotive-studio-desktop: $current -> $ver"
  fi

  local asset="remotive-studio-desktop-${ver}-${tar_arch}.tar.gz"
  local file="$TMP/$asset"
  if [ -n "$DRY_RUN" ]; then
    log "+ fetch $(asset_url "$asset" "$ver") (+ sidecars), verify, extract to $dest, desktop integration"; return 0
  fi
  fetch "$(asset_url "$asset" "$ver")" "$file"
  verify "$file" "$ver"
  assert_tar_safe "$file"

  mkdir -p "$dest" "$bindir"
  # Clean replace on upgrade. A symlink here is not ours (we always create a
  # real directory), and --one-file-system keeps the recursive delete from
  # crossing a mount planted underneath.
  if [ -L "$dest" ]; then
    die "$dest is a symlink, not a directory created by this installer — refusing to replace it."
  fi
  rm -rf --one-file-system -- "$dest"
  mkdir -p "$dest"

  # The tarball may have a single top-level directory (electron-builder
  # layout) or be flat; unwrap so $dest holds the binary directly.
  tar -xzf "$file" -C "$dest" --no-same-owner --no-same-permissions
  local entries
  entries=$(find "$dest" -mindepth 1 -maxdepth 1 | wc -l)
  if [ "$entries" -eq 1 ]; then
    local top; top=$(find "$dest" -mindepth 1 -maxdepth 1 -type d | head -1)
    if [ -n "$top" ]; then
      # shellcheck disable=SC2086
      mv "$top"/* "$top"/.[!.]* "$dest"/ 2>/dev/null || true
      rmdir "$top" 2>/dev/null || true
    fi
  fi
  [ -x "$dest/RemotiveStudio" ] || die "extracted tarball does not contain a RemotiveStudio binary."
  printf '%s\n' "$ver" > "$dest/$TAR_VERSION_MARKER"

  linux_desktop_integration "$real_home" "$dest" "$bindir"

  log "Installed to $dest"
}

# linux_desktop_integration <home> <dest> <bindir> — .desktop entry, icon,
# and launcher symlink for the tar install (packages ship their own).
linux_desktop_integration() {
  local home="$1" dest="$2" bindir="$3"
  local icon_dir="$home/.local/share/icons/hicolor/512x512/apps"
  local desktop_dir="$home/.local/share/applications"
  local symlink="$bindir/remotive-studio-desktop"

  # Icon ships inside the bundle (resources/icon.png, an electron-builder
  # extraResource) so this script needs no side-channel asset.
  if [ -f "$dest/resources/icon.png" ]; then
    mkdir -p "$icon_dir"
    cp "$dest/resources/icon.png" "$icon_dir/remotive-studio-desktop.png"
  fi

  mkdir -p "$desktop_dir"
  cat > "$desktop_dir/remotive-studio-desktop.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=RemotiveStudio Desktop
GenericName=Recording Workspace
Comment=Visualize and analyze RemotiveLabs recordings
Exec=$dest/RemotiveStudio %U
Icon=remotive-studio-desktop
Terminal=false
Categories=Development;Engineering;
StartupWMClass=RemotiveStudio
EOF
  chmod 0644 "$desktop_dir/remotive-studio-desktop.desktop"

  # Launcher symlink: only create/replace when the path is unset or already
  # points into our managed install dir — never clobber a user's own file.
  if [ -L "$symlink" ]; then
    local target; target="$(readlink -f -- "$symlink" 2>/dev/null || echo "")"
    case "$target" in
      "$dest"|"$dest"/*) rm -f "$symlink"; ln -s "$dest/RemotiveStudio" "$symlink" ;;
      *) warn "$symlink points elsewhere — leaving it in place." ;;
    esac
  elif [ -e "$symlink" ]; then
    warn "$symlink exists and is not ours — leaving it in place."
  else
    ln -s "$dest/RemotiveStudio" "$symlink"
  fi

  # Best-effort cache refresh; absence of the tools is fine.
  update-desktop-database "$desktop_dir" >/dev/null 2>&1 || true
  gtk-update-icon-cache -f -t "$home/.local/share/icons/hicolor" >/dev/null 2>&1 || true

  case ":$PATH:" in
    *":$bindir:"*) : ;;
    *) log "Hint: add $bindir to your PATH to launch remotive-studio-desktop from a terminal." ;;
  esac
}

# ---------------------------------------------------------------------------
# Method selection
# ---------------------------------------------------------------------------

# choose_macos_method — when Homebrew is present, ask whether to install via
# the cask or the DMG. Reads from the controlling terminal so it still works
# when the script is piped (curl ... | bash). Defaults to brew when there is
# no terminal to prompt on or when -y was given.
choose_macos_method() {
  [ -n "$YES" ] && { echo brew; return; }
  if ! { exec 3</dev/tty; } 2>/dev/null; then echo brew; return; fi
  local ans
  log ""
  log "Homebrew detected. How would you like to install RemotiveStudio Desktop?"
  log "  [1] Homebrew  (brew install --cask $CASK) — recommended"
  log "  [2] Direct DMG install (no Homebrew)"
  printf 'Choose [1/2] (default 1): ' >&2
  read -r ans <&3 || ans=""
  exec 3<&-
  case "$ans" in
    2|dmg|d|D) echo dmg ;;
    *)         echo brew ;;
  esac
}

detect_method() {
  case "$OS" in
    Darwin)
      # An explicit version can only be honored by the DMG path (the cask
      # installs the tap's latest), so skip the prompt and pick dmg. An
      # existing cask-managed install always stays on brew.
      if brew list --cask "$CASK_TOKEN" >/dev/null 2>&1 && [ -z "$VERSION" ]; then echo brew
      elif [ -n "$VERSION" ]; then echo dmg
      elif have brew; then choose_macos_method
      else echo dmg; fi ;;
    Linux)
      if have apt-get; then echo apt
      elif have dnf || have yum; then echo yum
      else echo tar; fi ;;
    *) die "unsupported OS '$OS'." ;;
  esac
}

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------
case "$ARCH" in
  x86_64|amd64|arm64|aarch64) : ;;
  *) warn "unrecognized architecture '$ARCH' — install may fail." ;;
esac

[ "$METHOD" = "auto" ] && METHOD="$(detect_method)"
log "==> Installing $PKG_NAME via '$METHOD'${VERSION:+ (version $VERSION)}${DRY_RUN:+ [dry-run]}"

case "$METHOD" in
  brew) install_brew ;;
  apt)  install_apt ;;
  yum)  install_yum ;;
  dmg)  install_dmg ;;
  tar)  install_linux_tar ;;
  *)    die "unknown method '$METHOD' (expected: brew|apt|yum|dmg|tar)" ;;
esac

if [ -n "$DRY_RUN" ]; then log "Dry run complete."; exit 0; fi
log "Done."
