#!/bin/sh
set -eu

# When it finishes, and when it fails, this script tells shell.online how it
# went: one request carrying a single word (ok, unsupported_arch,
# checksum_mismatch, ...) and the binary name, nothing else, so that a
# platform that keeps failing gets noticed and fixed. Set
# SHELL_ONLINE_INSTALL_REPORT=0 to skip it.
#
# A run that is only a check, a monitor or a CI job, should set
# SHELL_ONLINE_INSTALL_CHECK=1: every request then carries the user agent
# shell.online-install-check, which the site counts as a monitor and not as
# a person, and nothing is reported.
curl_agent=
wget_agent=
if [ "${SHELL_ONLINE_INSTALL_CHECK:-0}" = 1 ]; then
  curl_agent="-A shell.online-install-check"
  wget_agent="-U shell.online-install-check"
fi

fail() {
  printf 'shell.online: %s\n' "$1" >&2
  report "${2:-failed}"
  exit 1
}

report() {
  [ "${SHELL_ONLINE_INSTALL_REPORT:-1}" != 0 ] || return 0
  [ -z "$curl_agent" ] || return 0
  case "${base_url:-}" in
    http://*|https://*) ;;
    *) return 0 ;;
  esac
  report_url="$base_url/install/report?outcome=$1&platform=${binary_name:-unknown}"
  if command -v curl >/dev/null 2>&1; then
    curl -fsS -m 5 -o /dev/null "$report_url" >/dev/null 2>&1 || true
  elif command -v wget >/dev/null 2>&1; then
    wget -q -T 5 -O /dev/null "$report_url" >/dev/null 2>&1 || true
  fi
}

base_url=${SHELL_ONLINE_BASE_URL:-https://shell.online}
system_name=$(uname -s 2>/dev/null || true)
machine_name=$(uname -m 2>/dev/null || true)

case "$system_name" in
  Darwin) platform=darwin ;;
  Linux) platform=linux ;;
  FreeBSD) platform=freebsd ;;
  OpenBSD) platform=openbsd ;;
  NetBSD) platform=netbsd ;;
  DragonFly) platform=dragonfly ;;
  SunOS) platform=solaris ;;
  *) fail "unsupported operating system: ${system_name:-unknown} (supported: macOS, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly BSD, and Solaris)" unsupported_os ;;
esac

case "$machine_name" in
  arm64|aarch64) architecture=arm64 ;;
  x86_64|amd64)
    if [ "$platform" = darwin ] && command -v sysctl >/dev/null 2>&1 &&
      [ "$(sysctl -in sysctl.proc_translated 2>/dev/null || true)" = 1 ]; then
      architecture=arm64
    else
      architecture=amd64
    fi
    ;;
  i386|i486|i586|i686|x86) architecture=386 ;;
  armv5*|armv5l) architecture=armv5 ;;
  armv6*|armv6l) architecture=armv6 ;;
  armv7*|armv7l|armv8l|arm) architecture=armv7 ;;
  mips) architecture=mips ;;
  mipsel|mipsle) architecture=mipsle ;;
  mips64) architecture=mips64 ;;
  mips64el|mips64le) architecture=mips64le ;;
  ppc64|powerpc64) architecture=ppc64 ;;
  ppc64le|powerpc64le) architecture=ppc64le ;;
  riscv64) architecture=riscv64 ;;
  s390x) architecture=s390x ;;
  loongarch64|loong64) architecture=loong64 ;;
  *) fail "unsupported architecture: ${machine_name:-unknown}; see https://shell.online/docs/platforms/" unsupported_arch ;;
esac

if [ -n "${SHELL_ONLINE_INSTALL_DIR:-}" ]; then
  install_dir=$SHELL_ONLINE_INSTALL_DIR
elif [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then
  install_dir=/usr/local/bin
elif [ -n "${XDG_BIN_HOME:-}" ]; then
  install_dir=$XDG_BIN_HOME
elif [ -n "${HOME:-}" ]; then
  install_dir=$HOME/.local/bin
else
  fail 'HOME is not set; set SHELL_ONLINE_INSTALL_DIR to an absolute writable directory' no_home
fi

case "$install_dir" in
  /*) ;;
  *) fail "install directory must be absolute: $install_dir" install_dir_relative ;;
esac
case "$install_dir" in
  *:*) fail "install directory cannot contain a colon: $install_dir" install_dir_colon ;;
esac

temporary_root=${TMPDIR:-/tmp}
if [ ! -d "$temporary_root" ] || [ ! -w "$temporary_root" ]; then
  fail "temporary directory is not writable: $temporary_root" temp_dir_unwritable
fi
temporary_dir=$(mktemp -d "$temporary_root/shell-online.XXXXXX") ||
  fail "could not create a temporary directory under $temporary_root" temp_dir
trap 'rm -rf "$temporary_dir"' EXIT HUP INT TERM
binary_name="shell-$platform-$architecture"
binary_url="$base_url/downloads/$binary_name"

download() {
  source_url=$1
  destination=$2
  # The agent flags are two words or none, on purpose unquoted.
  if command -v curl >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    if ! curl -fsSL $curl_agent "$source_url" -o "$destination"; then
      fail "download failed: $source_url" download_failed
    fi
  elif command -v wget >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    if ! wget -q $wget_agent "$source_url" -O "$destination"; then
      fail "download failed: $source_url" download_failed
    fi
  else
    fail 'curl or wget is required; install either tool and run the installer again' no_downloader
  fi
}

download "$binary_url" "$temporary_dir/shell"
download "$base_url/downloads/SHA256SUMS" "$temporary_dir/SHA256SUMS"
manifest_first_line=$(sed -n '1p' "$temporary_dir/SHA256SUMS")
case "$manifest_first_line" in
  '<!doctype html'*|'<html'*)
    fail "expected a checksum manifest, received HTML from $base_url/downloads/SHA256SUMS" manifest_html
    ;;
esac
expected=$(awk -v filename="$binary_name" '$2 == filename { print $1; exit }' "$temporary_dir/SHA256SUMS")

case "$expected" in
  *[!0-9a-f]*|'')
    fail "release manifest has no valid checksum for $binary_name" manifest_missing
    ;;
esac
if [ "${#expected}" -ne 64 ]; then
  fail "release manifest has an invalid checksum for $binary_name" manifest_invalid
fi

if command -v sha256sum >/dev/null 2>&1; then
  actual=$(sha256sum "$temporary_dir/shell" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
  actual=$(shasum -a 256 "$temporary_dir/shell" | awk '{print $1}')
else
  fail 'sha256sum or shasum is required to verify the downloaded binary' no_sha_tool
fi

if [ "$actual" != "$expected" ]; then
  fail "downloaded binary failed checksum verification (expected $expected, received $actual)" checksum_mismatch
fi

if ! mkdir -p "$install_dir"; then
  fail "could not create install directory: $install_dir" install_dir_create
fi
if [ ! -d "$install_dir" ] || [ ! -w "$install_dir" ]; then
  fail "install directory is not writable: $install_dir (set SHELL_ONLINE_INSTALL_DIR to another directory)" install_dir_unwritable
fi

target=$install_dir/shell
if [ -x "$target" ]; then
  previous_version=$($target --version 2>/dev/null || true)
  if [ -n "$previous_version" ]; then
    printf 'Updating %s at %s\n' "$previous_version" "$target"
  else
    printf 'Replacing the existing executable at %s\n' "$target"
  fi
fi

if command -v install >/dev/null 2>&1; then
  if ! install -m 0755 "$temporary_dir/shell" "$target"; then
    fail "could not write executable: $target" write_failed
  fi
else
  if ! cp "$temporary_dir/shell" "$target" || ! chmod 0755 "$target"; then
    fail "could not write executable: $target" write_failed
  fi
fi

printf 'Installed shell to %s\n' "$target"
printf 'Verified SHA-256: %s\n' "$actual"
path_value=${PATH:-}
case ":$path_value:" in
  *":$install_dir:"*) ;;
  *)
    shell_name=${SHELL:-}
    shell_name=${shell_name##*/}
    printf '\n%s is not on your PATH yet.\n' "$install_dir"
    case "$shell_name" in
      fish)
        printf 'Run this once in fish:\n  fish_add_path "%s"\n' "$install_dir"
        ;;
      *)
        profile=
        case "$shell_name" in
          zsh)
            if [ -n "${ZDOTDIR:-}" ]; then
              profile=$ZDOTDIR/.zshrc
            elif [ -n "${HOME:-}" ]; then
              profile=$HOME/.zshrc
            fi
            ;;
          bash)
            if [ -n "${HOME:-}" ]; then
              if [ "$platform" = darwin ]; then
                profile=$HOME/.bash_profile
              else
                profile=$HOME/.bashrc
              fi
            fi
            ;;
          *)
            if [ -n "${HOME:-}" ]; then
              profile=$HOME/.profile
            fi
            ;;
        esac
        printf 'For this terminal, run:\n  export PATH="%s:$PATH"\n' "$install_dir"
        if [ -n "$profile" ]; then
          printf 'To keep it after restart, add this line to %s:\n  export PATH="%s:$PATH"\n' "$profile" "$install_dir"
        else
          printf 'To keep it after restart, add that export line to your shell startup file.\n'
        fi
        ;;
    esac
    ;;
esac

resolved_shell=$(command -v shell 2>/dev/null || true)
if [ -n "$resolved_shell" ] && [ "$resolved_shell" != "$target" ]; then
  printf '\nWarning: shell currently resolves to %s, not the new %s.\n' "$resolved_shell" "$target" >&2
  printf 'Put %s earlier on PATH, remove the older install, or run %s directly.\n' "$install_dir" "$target" >&2
fi

"$target" --version
report ok
printf '\nNext:\n'
printf '  shell <your-command>   Run it in the background and print its browser link\n'
printf '  shell help             See the guided start, share, and stop flow\n'
