#!/bin/sh
#
# Backplanes Spotlight CLI installer
# Copyright (c) 2026 Backplanes Inc.
#
# By installing and using this software you agree to:
#   - End User License Agreement: https://backplanes.com/legal/license
#   - Terms of Service:           https://backplanes.com/legal/terms
#   - Privacy Policy:             https://backplanes.com/legal/privacy
#
# Spotlight pre-processes your AI coding sessions locally, scrubbing PII
# deterministically before sending your data to our servers. Once on our
# servers, we do a second, more robust heuristic-based PII scrub and then
# store your reports in a database with row-based customer encryption keys.
#
set -eu

REPO_OWNER="Backplanes"
REPO_NAME="backplanes-cli"
RELEASE_BASE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases"
BINARY_NAME="backplanes"
DEFAULT_INSTALL_DIR="$HOME/.backplanes/bin"
INSTALL_DIR="${BACKPLANES_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"

blue=""
bold=""
reset=""
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  blue="$(printf '\033[34m')"
  bold="$(printf '\033[1m')"
  reset="$(printf '\033[0m')"
fi

die() {
  printf 'backplanes installer: %s\n' "$*" >&2
  exit 1
}

info() {
  printf '%s\n' "$*"
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

require_commands() {
  require_cmd uname
  require_cmd mktemp
  require_cmd curl
  require_cmd tar
  require_cmd grep
  require_cmd sed
  require_cmd chmod
  require_cmd mkdir
  require_cmd mv
  require_cmd cp
  require_cmd rm
  require_cmd touch
  if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
    die "Required command not found: sha256sum or shasum"
  fi
}

print_banner() {
  printf '%s' "$blue"
  cat <<'BANNER'
       ...
       -+++=:.
       -+++++++-:.
       -++++++++++=-.
       -++++++++++++++=:.
        .=++++++++++++++++-
           .:=+++++++++++=:
               .-=+++=-.          BACKPLANES
                .:=+-.            Spotlight CLI Installer
             .-++++++++=:.
         .-++++++++++++++++.
       -++++++++++++++++=:
       =++++++++++++=:.
       =++++++++=-.
       =++++=-..
       -+-..
BANNER
  printf '%s\n' "$reset"
}

print_legal_notice() {
  cat <<'NOTICE'
---------------------------------------------------------------
  Backplanes Spotlight CLI - installation
---------------------------------------------------------------

  Spotlight pre-processes your AI coding sessions LOCALLY,
  scrubbing PII deterministically before sending your data
  to our servers. Once on our servers, we do a second, more
  robust heuristic-based PII scrub and then store your
  reports in a database with row-based customer encryption
  keys.

  By installing, you agree to:
    EULA:    https://backplanes.com/legal/license
    Terms:   https://backplanes.com/legal/terms
    Privacy: https://backplanes.com/legal/privacy

---------------------------------------------------------------
NOTICE
}

detect_platform() {
  os="${BACKPLANES_TEST_UNAME_S:-$(uname -s)}"
  arch="${BACKPLANES_TEST_UNAME_M:-$(uname -m)}"

  case "$os:$arch" in
    Darwin:arm64|Darwin:aarch64)
      printf '%s\n' "darwin-arm64"
      ;;
    Linux:x86_64|Linux:amd64)
      printf '%s\n' "linux-x86_64"
      ;;
    Linux:arm64|Linux:aarch64)
      printf '%s\n' "linux-arm64"
      ;;
    *)
      die "Unsupported platform: $os $arch"
      ;;
  esac
}

normalize_version() {
  version="$1"
  version="${version#backplanes-cli-v}"
  version="${version#v}"
  [ -n "$version" ] || die "BACKPLANES_VERSION cannot be empty"
  printf '%s\n' "$version"
}

release_tag_for_version() {
  printf 'backplanes-cli-v%s\n' "$(normalize_version "$1")"
}

archive_name() {
  version="$1"
  platform="$2"
  printf 'backplanes-cli-v%s-%s.tar.gz\n' "$(normalize_version "$version")" "$platform"
}

release_asset_url() {
  tag="$1"
  asset="$2"
  printf '%s/download/%s/%s\n' "$RELEASE_BASE_URL" "$tag" "$asset"
}

resolve_latest_version() {
  if [ -n "${BACKPLANES_TEST_LATEST_VERSION:-}" ]; then
    normalize_version "$BACKPLANES_TEST_LATEST_VERSION"
    return
  fi

  latest_url="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "$RELEASE_BASE_URL/latest")" || die "Failed to resolve latest release"
  tag="${latest_url##*/}"
  case "$tag" in
    backplanes-cli-v*)
      normalize_version "$tag"
      ;;
    *)
      die "Latest release tag has unexpected format: $tag"
      ;;
  esac
}

resolve_version() {
  if [ -n "${BACKPLANES_VERSION:-}" ]; then
    normalize_version "$BACKPLANES_VERSION"
  else
    resolve_latest_version
  fi
}

version_core() {
  version="$(normalize_version "$1")"
  printf '%s\n' "${version%%-*}"
}

version_prerelease() {
  version="$(normalize_version "$1")"
  case "$version" in
    *-*) printf '%s\n' "${version#*-}" ;;
    *) printf '\n' ;;
  esac
}

is_numeric_identifier() {
  case "$1" in
    ''|*[!0123456789]*) return 1 ;;
    *) return 0 ;;
  esac
}

compare_prerelease_identifier() {
  a_id="$1"
  b_id="$2"
  if [ "$a_id" = "$b_id" ]; then
    printf '%s\n' "0"
    return
  fi

  a_numeric=0
  b_numeric=0
  if is_numeric_identifier "$a_id"; then
    a_numeric=1
  fi
  if is_numeric_identifier "$b_id"; then
    b_numeric=1
  fi

  if [ "$a_numeric" -eq 1 ] && [ "$b_numeric" -eq 1 ]; then
    if [ "$a_id" -lt "$b_id" ]; then
      printf '%s\n' "-1"
      return
    fi
    printf '%s\n' "1"
    return
  fi
  if [ "$a_numeric" -eq 1 ]; then
    printf '%s\n' "-1"
    return
  fi
  if [ "$b_numeric" -eq 1 ]; then
    printf '%s\n' "1"
    return
  fi
  if (LC_ALL=C; [ "$a_id" \< "$b_id" ]); then
    printf '%s\n' "-1"
    return
  fi
  printf '%s\n' "1"
}

compare_prerelease_versions() {
  a_pre="$1"
  b_pre="$2"
  if [ -z "$a_pre" ] && [ -z "$b_pre" ]; then
    printf '%s\n' "0"
    return
  fi
  if [ -z "$a_pre" ]; then
    printf '%s\n' "1"
    return
  fi
  if [ -z "$b_pre" ]; then
    printf '%s\n' "-1"
    return
  fi

  while :; do
    a_id="${a_pre%%.*}"
    b_id="${b_pre%%.*}"
    comparison="$(compare_prerelease_identifier "$a_id" "$b_id")"
    if [ "$comparison" != "0" ]; then
      printf '%s\n' "$comparison"
      return
    fi

    case "$a_pre" in
      *.*) a_pre="${a_pre#*.}" ;;
      *) a_pre="" ;;
    esac
    case "$b_pre" in
      *.*) b_pre="${b_pre#*.}" ;;
      *) b_pre="" ;;
    esac

    if [ -z "$a_pre" ] && [ -z "$b_pre" ]; then
      printf '%s\n' "0"
      return
    fi
    if [ -z "$a_pre" ]; then
      printf '%s\n' "-1"
      return
    fi
    if [ -z "$b_pre" ]; then
      printf '%s\n' "1"
      return
    fi
  done
}

compare_versions() {
  a_core="$(version_core "$1")"
  b_core="$(version_core "$2")"

  a_major="${a_core%%.*}"
  a_tail="${a_core#*.}"
  if [ "$a_tail" = "$a_core" ]; then
    a_tail="0.0"
  fi
  a_minor="${a_tail%%.*}"
  a_patch="${a_tail#*.}"
  if [ "$a_patch" = "$a_tail" ]; then
    a_patch="0"
  fi

  b_major="${b_core%%.*}"
  b_tail="${b_core#*.}"
  if [ "$b_tail" = "$b_core" ]; then
    b_tail="0.0"
  fi
  b_minor="${b_tail%%.*}"
  b_patch="${b_tail#*.}"
  if [ "$b_patch" = "$b_tail" ]; then
    b_patch="0"
  fi

  if [ "$a_major" -lt "$b_major" ]; then
    printf '%s\n' "-1"
    return
  fi
  if [ "$a_major" -gt "$b_major" ]; then
    printf '%s\n' "1"
    return
  fi
  if [ "$a_minor" -lt "$b_minor" ]; then
    printf '%s\n' "-1"
    return
  fi
  if [ "$a_minor" -gt "$b_minor" ]; then
    printf '%s\n' "1"
    return
  fi
  if [ "$a_patch" -lt "$b_patch" ]; then
    printf '%s\n' "-1"
    return
  fi
  if [ "$a_patch" -gt "$b_patch" ]; then
    printf '%s\n' "1"
    return
  fi
  compare_prerelease_versions "$(version_prerelease "$1")" "$(version_prerelease "$2")"
}

installed_version() {
  install_dir="$1"
  installed_bin="$install_dir/$BINARY_NAME"
  [ -x "$installed_bin" ] || return 0

  output="$("$installed_bin" --version 2>/dev/null || true)"
  set -- $output
  if [ "${1:-}" = "$BINARY_NAME" ] && [ -n "${2:-}" ]; then
    normalize_version "$2"
  fi
}

install_decision() {
  current="$1"
  target="$2"
  if [ -z "$current" ]; then
    printf '%s\n' "install"
    return
  fi

  comparison="$(compare_versions "$current" "$target")"
  case "$comparison" in
    -1) printf '%s\n' "upgrade" ;;
    0) printf '%s\n' "current" ;;
    1) printf '%s\n' "newer-installed" ;;
    *) die "Unexpected version comparison result: $comparison" ;;
  esac
}

confirm_install() {
  message="$1"
  case "${BACKPLANES_INSTALL_YES:-}" in
    1|y|Y|yes|YES|true|TRUE)
      return 0
      ;;
  esac

  if [ -n "${BACKPLANES_TEST_CONFIRM_RESPONSE+x}" ]; then
    response="$BACKPLANES_TEST_CONFIRM_RESPONSE"
  else
    if [ ! -r /dev/tty ]; then
      die "Confirmation required. Re-run with BACKPLANES_INSTALL_YES=1 to proceed noninteractively."
    fi
    printf '%s [y/N] ' "$message" > /dev/tty
    IFS= read -r response < /dev/tty || response=""
  fi

  case "$response" in
    y|Y|yes|YES|Yes)
      return 0
      ;;
    *)
      die "Installation cancelled"
      ;;
  esac
}

compute_sha256() {
  file="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$file" | sed 's/[[:space:]].*$//'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$file" | sed 's/[[:space:]].*$//'
  else
    die "Required command not found: sha256sum or shasum"
  fi
}

expected_checksum() {
  sums_file="$1"
  archive="$2"
  while read -r checksum filename extra; do
    [ -z "${checksum:-}" ] && continue
    [ -n "${extra:-}" ] && continue
    if [ "$filename" = "$archive" ]; then
      printf '%s\n' "$checksum"
      return 0
    fi
  done < "$sums_file"
  return 1
}

verify_checksum() {
  archive_path="$1"
  sums_file="$2"
  archive="$3"
  expected="$(expected_checksum "$sums_file" "$archive" || true)"
  [ -n "$expected" ] || die "Checksum entry not found for $archive"
  actual="$(compute_sha256 "$archive_path")"
  [ "$actual" = "$expected" ] || die "Checksum mismatch for $archive"
}

install_binary() {
  source_bin="$1"
  install_dir="$2"
  [ -f "$source_bin" ] || die "Archive did not contain $BINARY_NAME"
  mkdir -p "$install_dir"
  tmp_bin="$install_dir/.$BINARY_NAME.tmp.$$"
  cp "$source_bin" "$tmp_bin"
  chmod 0755 "$tmp_bin"
  mv "$tmp_bin" "$install_dir/$BINARY_NAME"
}

path_contains() {
  dir="$1"
  case ":$PATH:" in
    *":$dir:"*) return 0 ;;
    *) return 1 ;;
  esac
}

profile_for_shell() {
  shell_path="${SHELL:-}"
  shell_name="${shell_path##*/}"
  case "$shell_name" in
    zsh) printf '%s\n' "$HOME/.zshrc" ;;
    bash) printf '%s\n' "$HOME/.bashrc" ;;
    *) printf '%s\n' "$HOME/.profile" ;;
  esac
}

path_export_line() {
  dir="$1"
  if [ "$dir" = "$HOME/.backplanes/bin" ]; then
    printf '%s\n' 'export PATH="$HOME/.backplanes/bin:$PATH"'
  else
    printf 'export PATH="%s:$PATH"\n' "$dir"
  fi
}

ensure_path() {
  dir="$1"
  if path_contains "$dir"; then
    info "$dir is already on PATH"
    return
  fi

  profile="$(profile_for_shell)"
  line="$(path_export_line "$dir")"
  touch "$profile"
  if grep -F "$line" "$profile" >/dev/null 2>&1; then
    info "PATH already configured in $profile"
    return
  fi

  {
    printf '\n# Backplanes Spotlight CLI\n'
    printf '%s\n' "$line"
  } >> "$profile"
  info "Updated PATH in $profile"
}

download() {
  url="$1"
  dest="$2"
  curl -fsSL "$url" -o "$dest"
}

is_truthy_env_value() {
  case "$1" in
    1|[Tt][Rr][Uu][Ee]|[Oo][Nn]) return 0 ;;
    *) return 1 ;;
  esac
}

is_falsey_env_value() {
  case "$1" in
    0|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]) return 0 ;;
    *) return 1 ;;
  esac
}

prompt_telemetry_consent() {
  [ -t 0 ] || return 0
  [ -t 1 ] || return 0
  is_truthy_env_value "${DO_NOT_TRACK:-}" && return 0
  is_truthy_env_value "${BACKPLANES_TELEMETRY:-}" && return 0
  is_falsey_env_value "${BACKPLANES_TELEMETRY:-}" && return 0

  printf '%s\n' "Help improve Backplanes CLI by sending anonymous aggregate usage telemetry? (You"
  printf '%s\n' "can always see or disable what is sent via backplanes telemetry).  [Y]es / [N]o / [D]etails"
  printf '> '
  IFS= read -r reply || return 0
  case "$reply" in
    y|Y|yes|YES|Yes)
      "$INSTALL_DIR/$BINARY_NAME" telemetry enable --source installer >/dev/null 2>&1 || true
      ;;
    d|D|\?)
      printf '%s\n' "We send aggregate usage events such as installs, runs, reports created,"
      printf '%s\n' "session size buckets, harness/provider, and findings count buckets."
      printf '\n'
      printf '%s\n' "We never send prompts, transcripts, findings text, file paths, repo names,"
      printf '%s\n' "session IDs, usernames, hostnames, API keys, commands, or domains."
      printf '\n'
      printf '%s\n' "You can always see the data sent using backplanes telemetry status and can"
      printf '%s\n' "always enable or disable telemetry in the future with backplanes telemetry"
      printf '%s\n' "enable/disable."
      printf '\n'
      printf '%s' "Help improve Backplanes CLI by sending anonymous aggregate usage telemetry?  [Y]es / [N]o "
      IFS= read -r detail_reply || return 0
      case "$detail_reply" in
        y|Y|yes|YES|Yes)
          "$INSTALL_DIR/$BINARY_NAME" telemetry enable --source installer >/dev/null 2>&1 || true
          ;;
        *)
          "$INSTALL_DIR/$BINARY_NAME" telemetry disable --source installer >/dev/null 2>&1 || true
          ;;
      esac
      ;;
    *)
      "$INSTALL_DIR/$BINARY_NAME" telemetry disable --source installer >/dev/null 2>&1 || true
      ;;
  esac
}

launch_backplanes_if_tty() {
  # Require an interactive terminal end-to-end. /dev/tty alone is not enough:
  # `curl ... | sh >install.log` leaves stdout redirected to the log file, so
  # an exec'd interactive CLI would appear hung and write prompts into the log.
  # Matches the gating pattern used by prompt_telemetry_consent above.
  [ -r /dev/tty ] || return 0
  [ -t 1 ] || return 0
  [ -t 2 ] || return 0
  info ""
  info "${bold}Launching backplanes...${reset}"
  # Redirect all three streams to /dev/tty so the launched binary gets a
  # clean, consistent handle on the controlling terminal. crossterm-based
  # TUIs (e.g. Spotlight onboarding, auto-invoked after login) fail to
  # initialize their input reader if any of stdin/stdout/stderr inherit
  # piped or non-tty fds from the shell.
  exec "$INSTALL_DIR/$BINARY_NAME" < /dev/tty > /dev/tty 2> /dev/tty
}

main() {
  print_banner
  print_legal_notice
  require_commands

  platform="$(detect_platform)"
  version="$(resolve_version)"
  tag="$(release_tag_for_version "$version")"
  archive="$(archive_name "$version" "$platform")"
  archive_url="$(release_asset_url "$tag" "$archive")"
  sums_url="$(release_asset_url "$tag" "SHA256SUMS")"
  current_version="$(installed_version "$INSTALL_DIR")"
  decision="$(install_decision "$current_version" "$version")"

  case "$decision" in
    current)
      info "backplanes $current_version is already installed at $INSTALL_DIR/$BINARY_NAME"
      ensure_path "$INSTALL_DIR"
      export PATH="$INSTALL_DIR:$PATH"
      launch_backplanes_if_tty
      info "Open a new terminal or run: exec \"\$SHELL\" -l"
      return 0
      ;;
    newer-installed)
      info "backplanes $current_version is already installed at $INSTALL_DIR/$BINARY_NAME"
      info "Installed version is newer than requested version $version; skipping install."
      ensure_path "$INSTALL_DIR"
      export PATH="$INSTALL_DIR:$PATH"
      launch_backplanes_if_tty
      info "Open a new terminal or run: exec \"\$SHELL\" -l"
      return 0
      ;;
    upgrade)
      confirm_install "Upgrade backplanes from $current_version to $version at $INSTALL_DIR?"
      ;;
    install)
      confirm_install "Install backplanes $version to $INSTALL_DIR?"
      ;;
    *)
      die "Unexpected install decision: $decision"
      ;;
  esac

  tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/backplanes-installer.XXXXXX")"
  trap 'rm -rf "$tmp_dir"' EXIT INT TERM

  if [ "$decision" = "upgrade" ]; then
    info "Upgrading backplanes from $current_version to $version for $platform"
  else
    info "Installing backplanes $version for $platform"
  fi
  info "Downloading release archive..."
  download "$archive_url" "$tmp_dir/$archive" || die "Failed to download $archive_url"
  download "$sums_url" "$tmp_dir/SHA256SUMS" || die "Failed to download $sums_url"

  info "Verifying checksum..."
  verify_checksum "$tmp_dir/$archive" "$tmp_dir/SHA256SUMS" "$archive"

  tar -xzf "$tmp_dir/$archive" -C "$tmp_dir" "$BINARY_NAME" || die "Failed to extract $BINARY_NAME"
  install_binary "$tmp_dir/$BINARY_NAME" "$INSTALL_DIR"
  ensure_path "$INSTALL_DIR"
  export PATH="$INSTALL_DIR:$PATH"

  info "Installed to $INSTALL_DIR/$BINARY_NAME"
  if "$INSTALL_DIR/$BINARY_NAME" --version >/dev/null 2>&1; then
    info "$("$INSTALL_DIR/$BINARY_NAME" --version)"
  fi
  prompt_telemetry_consent

  # exec in launch_backplanes_if_tty replaces the shell, so the EXIT trap
  # registered after mktemp -d above won't run. Clean up the install temp
  # dir up front to avoid leaving the downloaded archive/extracted binary
  # behind in /tmp on every successful interactive install.
  rm -rf "$tmp_dir"
  trap - EXIT INT TERM

  launch_backplanes_if_tty
  info "${bold}Done.${reset} Open a new terminal or run: exec \"\$SHELL\" -l"
}

if [ "${BACKPLANES_INSTALLER_TESTING:-0}" != "1" ]; then
  main "$@"
fi
