#!/bin/sh
# CrossEdge installer for macOS and Linux — https://crossedge.link
#
#   curl -fsSL https://crossedge.link/install.sh | sh
#
# Downloads the prebuilt crossedge binary for this OS/arch, verifies its
# checksum, installs it to ~/.local/bin, and makes sure it's on your PATH.
set -eu

BASE="https://crossedge.link"
BIN_DIR="${CROSSEDGE_INSTALL_DIR:-$HOME/.local/bin}"

say()  { printf '\033[1;36mcrossedge\033[0m %s\n' "$1"; }
fail() { printf '\033[1;31mcrossedge\033[0m %s\n' "$1" >&2; exit 1; }

command -v curl >/dev/null 2>&1 || fail "curl is required"
command -v tar  >/dev/null 2>&1 || fail "tar is required"

os=$(uname -s)
arch=$(uname -m)
case "$os" in
  Darwin) target="macos-universal" ;;
  Linux)
    case "$arch" in
      x86_64|amd64) target="linux-x86_64" ;;
      *) fail "no prebuilt binary for Linux/$arch yet — see $BASE" ;;
    esac ;;
  *) fail "unsupported OS: $os — see $BASE" ;;
esac

version=${CROSSEDGE_VERSION:-}
if [ -z "$version" ]; then
  version=$(curl -fsSL "$BASE/version.json" | tr -d ' \n' | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
fi
[ -n "$version" ] || fail "could not read the latest version from $BASE/version.json"
case "$version" in
  *[!0-9A-Za-z._-]*) fail "invalid CrossEdge version: $version" ;;
esac

pkg="crossedge-${target}.tar.gz"
url="$BASE/dl/v${version}/${pkg}"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

say "downloading crossedge v${version} (${target})…"
curl -fsSL "$url" -o "$tmp/$pkg" || fail "download failed: $url"

# Every published package has a mandatory SHA-256 entry. A missing checksum
# or hashing tool is a hard failure: update and rollback must never silently
# install an unverified archive.
sums=$(curl -fsSL "$BASE/dl/v${version}/SHA256SUMS") || \
  fail "could not download checksums for v${version}"
want=$(printf '%s\n' "$sums" | grep " ${pkg}\$" | cut -d' ' -f1 || true)
[ -n "$want" ] || fail "no checksum was published for $pkg"
if command -v sha256sum >/dev/null 2>&1; then
  got=$(sha256sum "$tmp/$pkg" | cut -d' ' -f1)
elif command -v shasum >/dev/null 2>&1; then
  got=$(shasum -a 256 "$tmp/$pkg" | cut -d' ' -f1)
else
  fail "sha256sum or shasum is required to verify $pkg"
fi
[ "$got" = "$want" ] || fail "checksum mismatch for $pkg — refusing to install"

# Stop any already-running CrossEdge first (updates replace it cleanly).
# The launchd label keeps its pre-0.11 spelling: macOS ties the TCC
# permission grants to it, and renaming would revoke them.
if [ "$os" = Darwin ]; then
  launchctl bootout "gui/$(id -u)/link.crossedge.crossedged" 2>/dev/null || true
else
  systemctl --user stop crossedge 2>/dev/null || true
  systemctl --user stop crossedged 2>/dev/null || true   # pre-0.11 unit
fi
pkill -x crossedge 2>/dev/null || true
pkill -x crossedged 2>/dev/null || true

mkdir -p "$BIN_DIR"
tar -xzf "$tmp/$pkg" -C "$tmp"
rm -f "$BIN_DIR/crossedge" "$BIN_DIR/crossedged"
install -m 755 "$tmp/crossedge" "$BIN_DIR/crossedge"
if [ "$os" = Linux ] && [ -f "$tmp/crossedge-viewer" ]; then
  install -m 755 "$tmp/crossedge-viewer" "$BIN_DIR/crossedge-viewer"
fi
if [ "$os" = Linux ] && [ -f "$tmp/crossedge-display-host-linux" ]; then
  install -m 755 "$tmp/crossedge-display-host-linux" "$BIN_DIR/crossedge-display-host-linux"
fi
if [ "$os" = Darwin ] && [ -f "$tmp/crossedge-display-host-macos" ]; then
  install -m 755 "$tmp/crossedge-display-host-macos" "$BIN_DIR/crossedge-display-host-macos"
fi
if [ "$os" = Darwin ] && [ -f "$tmp/crossedge-display-viewer-macos" ]; then
  install -m 755 "$tmp/crossedge-display-viewer-macos" "$BIN_DIR/crossedge-display-viewer-macos"
fi
# Muscle-memory shim for the old command name.
ln -sf crossedge "$BIN_DIR/crossedged"
say "installed $BIN_DIR/crossedge (v${version})"

# Ubuntu ships gtk4paintablesink separately from the core GStreamer runtime.
# When it is the only missing viewer component, install the distro-built plugin
# into GStreamer's supported per-user plugin directory without requiring sudo.
# `apt-get download` verifies the package through the host's signed APT index;
# CrossEdge does not redistribute or link the plugin.
provision_linux_gtk4_sink() {
  [ "$os" = Linux ] || return 0
  [ -x "$BIN_DIR/crossedge-viewer" ] || return 0
  command -v gst-inspect-1.0 >/dev/null 2>&1 || return 0
  gst-inspect-1.0 gtk4paintablesink >/dev/null 2>&1 && return 0
  command -v apt-get >/dev/null 2>&1 || return 0
  command -v dpkg-deb >/dev/null 2>&1 || return 0

  runtime_dir="$tmp/linux-viewer-runtime"
  extract_dir="$runtime_dir/extracted"
  mkdir -p "$extract_dir"
  say "adding the Ubuntu/Debian GTK4 display plugin for this user…"
  if ! (cd "$runtime_dir" && apt-get download gstreamer1.0-gtk4 >/dev/null 2>&1); then
    say "could not download gstreamer1.0-gtk4; the viewer probe will show what is missing"
    return 0
  fi
  deb=$(find "$runtime_dir" -maxdepth 1 -type f \
    -name 'gstreamer1.0-gtk4_*.deb' -print | sed -n '1p')
  if [ -z "$deb" ] || ! dpkg-deb -x "$deb" "$extract_dir"; then
    say "could not unpack gstreamer1.0-gtk4; the viewer probe will show what is missing"
    return 0
  fi
  plugin=$(find "$extract_dir/usr/lib" -type f \
    -path '*/gstreamer-1.0/libgstgtk4.so' -print | sed -n '1p')
  if [ -z "$plugin" ]; then
    say "gstreamer1.0-gtk4 did not contain libgstgtk4.so"
    return 0
  fi

  plugin_dir="${XDG_DATA_HOME:-$HOME/.local/share}/gstreamer-1.0/plugins"
  mkdir -p "$plugin_dir"
  install -m 644 "$plugin" "$plugin_dir/libgstgtk4.so"
  if GST_REGISTRY_1_0="$tmp/gstreamer-registry.bin" \
      gst-inspect-1.0 gtk4paintablesink >/dev/null 2>&1; then
    say "installed the GTK4 display plugin in $plugin_dir"
  else
    rm -f "$plugin_dir/libgstgtk4.so"
    say "the GTK4 display plugin could not load; the viewer probe will show why"
  fi
}

provision_linux_gtk4_sink

# Prefer VA-API H.264 decoding when the machine advertises it. Some current
# Ubuntu releases keep the VA decoder in a separately versioned package even
# when the rest of GStreamer is already installed. Put only the distro-built
# plugin in the supported per-user plugin directory, validate that it loads,
# and leave the software decoder untouched as a safe fallback.
provision_linux_va_decoder() {
  [ "$os" = Linux ] || return 0
  [ -x "$BIN_DIR/crossedge-viewer" ] || return 0
  command -v gst-inspect-1.0 >/dev/null 2>&1 || return 0
  if gst-inspect-1.0 vah264dec >/dev/null 2>&1 || \
      gst-inspect-1.0 vaapih264dec >/dev/null 2>&1; then
    return 0
  fi
  command -v vainfo >/dev/null 2>&1 || return 0
  vainfo 2>/dev/null | grep -q 'VAProfileH264.*VAEntrypointVLD' || return 0
  command -v apt-get >/dev/null 2>&1 || return 0
  command -v dpkg-deb >/dev/null 2>&1 || return 0

  runtime_dir="$tmp/linux-va-runtime"
  extract_dir="$runtime_dir/extracted"
  mkdir -p "$extract_dir"
  say "adding the Ubuntu/Debian VA-API H.264 decoder for this user…"
  if ! (cd "$runtime_dir" && apt-get download gstreamer1.0-vaapi >/dev/null 2>&1); then
    say "could not download gstreamer1.0-vaapi; software decoding remains available"
    return 0
  fi
  deb=$(find "$runtime_dir" -maxdepth 1 -type f \
    -name 'gstreamer1.0-vaapi_*.deb' -print | sed -n '1p')
  if [ -z "$deb" ] || ! dpkg-deb -x "$deb" "$extract_dir"; then
    say "could not unpack gstreamer1.0-vaapi; software decoding remains available"
    return 0
  fi
  plugin=$(find "$extract_dir/usr/lib" -type f \
    -path '*/gstreamer-1.0/libgstvaapi.so' -print | sed -n '1p')
  if [ -z "$plugin" ]; then
    say "gstreamer1.0-vaapi did not contain libgstvaapi.so"
    return 0
  fi

  plugin_dir="${XDG_DATA_HOME:-$HOME/.local/share}/gstreamer-1.0/plugins"
  mkdir -p "$plugin_dir"
  install -m 644 "$plugin" "$plugin_dir/libgstvaapi.so"
  if GST_REGISTRY_1_0="$tmp/gstreamer-va-registry.bin" \
      gst-inspect-1.0 vaapih264dec >/dev/null 2>&1 && \
      GST_REGISTRY_1_0="$tmp/crossedge-viewer-va-registry.bin" \
      "$BIN_DIR/crossedge-viewer" --probe 2>/dev/null | grep -q 'hardware=true'; then
    say "enabled VA-API hardware H.264 decoding"
  else
    rm -f "$plugin_dir/libgstvaapi.so"
    say "the VA-API decoder could not load; software decoding remains available"
  fi
}

provision_linux_va_decoder

# macOS: sign with a LOCAL stable identity so Accessibility/Input
# Monitoring grants survive updates (ad-hoc identities change per build,
# which silently kills TCC grants). Created once, reused forever.
if [ "$os" = Darwin ] && command -v codesign >/dev/null 2>&1; then
  if ! security find-identity -p codesigning 2>/dev/null | grep -q "CrossEdge Local Signing"; then
    certdir=$(mktemp -d)
    (
      cd "$certdir" || exit 1
      openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days 3650 -nodes \
        -subj "/CN=CrossEdge Local Signing" \
        -addext "keyUsage=digitalSignature" -addext "extendedKeyUsage=codeSigning" 2>/dev/null &&
      openssl pkcs12 -export -out ce.p12 -inkey k.pem -in c.pem -passout pass:crossedge-local 2>/dev/null &&
      security import ce.p12 -k "$HOME/Library/Keychains/login.keychain-db" \
        -P crossedge-local -T /usr/bin/codesign >/dev/null 2>&1
    ) || true
    rm -rf "$certdir"
  fi
  if security find-identity -p codesigning 2>/dev/null | grep -q "CrossEdge Local Signing"; then
    # The identifier keeps its pre-0.11 spelling on purpose — TCC grants
    # are keyed to it, and a rename would ask for every permission again.
    if codesign -f -s "CrossEdge Local Signing" --identifier link.crossedge.crossedged \
        "$BIN_DIR/crossedge" 2>/dev/null; then
      say "signed with your local identity — permissions now survive updates"
    fi
    if [ -x "$BIN_DIR/crossedge-display-host-macos" ]; then
      codesign -f -s "CrossEdge Local Signing" --identifier link.crossedge.display-host \
        "$BIN_DIR/crossedge-display-host-macos" 2>/dev/null || true
    fi
    if [ -x "$BIN_DIR/crossedge-display-viewer-macos" ]; then
      codesign -f -s "CrossEdge Local Signing" --identifier link.crossedge.display-viewer \
        "$BIN_DIR/crossedge-display-viewer-macos" 2>/dev/null || true
    fi
  fi
fi

# Make sure the shell can find it next command.
case ":$PATH:" in
  *":$BIN_DIR:"*) on_path=yes ;;
  *) on_path=no ;;
esac
if [ "$on_path" = no ]; then
  line="export PATH=\"$BIN_DIR:\$PATH\""
  shell_name=$(basename "${SHELL:-sh}")
  case "$shell_name" in
    zsh)  rc="$HOME/.zshrc" ;;
    bash) rc="$HOME/.bashrc" ;;
    *)    rc="$HOME/.profile" ;;
  esac
  if ! grep -qsF "$BIN_DIR" "$rc" 2>/dev/null; then
    printf '\n# CrossEdge\n%s\n' "$line" >> "$rc"
    say "added $BIN_DIR to PATH in $rc"
  fi
  say "open a NEW terminal (or run: $line)"
fi

# Run in the background, starting at login.
wait_for_panel() {
  attempt=0
  while [ "$attempt" -lt 40 ]; do
    if curl -fsS --max-time 2 http://127.0.0.1:4260/api/state >/dev/null 2>&1; then
      return 0
    fi
    attempt=$((attempt + 1))
    sleep 0.5
  done
  return 1
}

if "$BIN_DIR/crossedge" service install; then
  if ! wait_for_panel && [ "$os" = Darwin ]; then
    # launchd can still be settling immediately after replacing a loaded
    # executable. The idempotent restart re-bootstraps a missing agent.
    "$BIN_DIR/crossedge" restart >/dev/null 2>&1 || true
    wait_for_panel || fail "background service was registered but did not become ready"
  elif ! curl -fsS --max-time 2 http://127.0.0.1:4260/api/state >/dev/null 2>&1; then
    fail "background service was registered but did not become ready"
  fi
  say ""
  say "CrossEdge is running. Open the panel any time:   crossedge panel"
  if [ -z "${CROSSEDGE_NO_OPEN:-}" ]; then
    "$BIN_DIR/crossedge" panel >/dev/null 2>&1 || true
  fi
else
  say "could not set up background start — run it manually:   crossedge run"
fi
if [ "$os" = Darwin ]; then
  say ""
  say "macOS will ask for the Accessibility permission — grant it to"
  say "\"crossedge\" in System Settings → Privacy & Security → Accessibility,"
  say "then it can type and click on this Mac."
  say "To share this Mac's screen, also enable crossedge-display-host-macos"
  say "under Privacy & Security → Screen Recording when macOS asks."
elif [ "$os" = Linux ] && ! id -nG 2>/dev/null | tr ' ' '\n' | grep -qx input; then
  say ""
  say "to share THIS machine's keyboard & mouse with the desk, allow reading"
  say "input devices, then log out and back in:"
  say "  sudo usermod -aG input \$USER"
  say "(without it, this machine can still be controlled by the others)"
fi
if [ "$os" = Linux ] && [ -x "$BIN_DIR/crossedge-viewer" ]; then
  if ! "$BIN_DIR/crossedge-viewer" --probe >/dev/null 2>&1; then
    say ""
    say "remote display needs GTK 4, GStreamer H.264 plugins, and gtk4paintablesink."
    say "Run: crossedge-viewer --probe"
    say "to see which runtime component your distribution still needs."
    if command -v apt-get >/dev/null 2>&1; then
      say "Ubuntu/Debian: sudo apt-get install gstreamer1.0-gtk4"
    fi
  fi
fi
if [ "$os" = Linux ] && [ -x "$BIN_DIR/crossedge-display-host-linux" ]; then
  if ! "$BIN_DIR/crossedge-display-host-linux" --probe >/dev/null 2>&1; then
    say ""
    say "hosting this Linux screen needs a Wayland ScreenCast portal, PipeWire,"
    say "cursor metadata, and an H.264 encoder."
    say "Run: crossedge-display-host-linux --probe"
    say "to see which runtime component your distribution still needs."
  fi
fi
