#!/bin/sh
# Soft-Bot CLI installer.
#
#   curl -fsSL https://soft-machine.io/install.sh | sh
#
# Installs `softbot` — Soft-Machine from your terminal: sign in, chat with your
# workspaces, mirror terminal-run agent sessions, and manage this machine's
# daemon (`softbot host …`). No root needed; nothing is written outside your
# home unless you are root or ask for it.
#
# What it does: detects the OS + CPU, reads the release manifest, downloads the
# matching tarball, VERIFIES its sha256 against the manifest, unpacks it under
# $SOFTBOT_HOME/versions/<version>/ (the binary and the daemon package it
# carries stay together there), points $SOFTBOT_HOME/current at it, and links
# `softbot` onto your PATH. Re-running upgrades in place; the previous version
# stays on disk until you remove it.
#
# Strict POSIX sh (dash, ash, busybox): no bashisms. Fail-closed: any download,
# checksum or unpack error aborts non-zero and leaves the previous install alone.
#
# Tunables (environment):
#   SOFTBOT_BASE_URL     release channel        (default https://dl.soft-machine.io/softbot)
#   SOFTBOT_VERSION      pin a version          (default: the manifest's latest)
#   SOFTBOT_HOME         install root           (default ~/.softbot; /usr/local/lib/softbot as root)
#   SOFTBOT_BIN_DIR      where the link goes    (default ~/.local/bin; /usr/local/bin as root)
#   SOFTBOT_NO_MODIFY_PATH  set 1 to skip the PATH hint for your shell profile
#   NO_COLOR             set to disable styling

set -eu

BASE_URL="${SOFTBOT_BASE_URL:-https://dl.soft-machine.io/softbot}"
DOWNLOAD_PAGE="https://soft-machine.io/download"

if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  BOLD="$(printf '\033[1m')"; DIM="$(printf '\033[2m')"; RESET="$(printf '\033[0m')"
else
  BOLD=""; DIM=""; RESET=""
fi

say() { printf '%s\n' "$*"; }
step() { printf '%s==>%s %s\n' "$BOLD" "$RESET" "$*"; }
fail() { printf 'install.sh: error: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

# --- prerequisites ---------------------------------------------------------------
have curl || have wget || fail "curl or wget is required"
have tar || fail "tar is required"

fetch() { # fetch URL DEST
  if have curl; then curl -fsSL --retry 3 "$1" -o "$2"
  else wget -q "$1" -O "$2"; fi
}
fetch_text() { # fetch URL → stdout
  if have curl; then curl -fsSL --retry 3 "$1"
  else wget -q "$1" -O -; fi
}
sha256_of() {
  if have sha256sum; then sha256sum "$1" | awk '{print $1}'
  elif have shasum; then shasum -a 256 "$1" | awk '{print $1}'
  elif have openssl; then openssl dgst -sha256 "$1" | awk '{print $NF}'
  else fail "no sha256sum/shasum/openssl available to verify the download"
  fi
}

# --- detect the platform -----------------------------------------------------------
os="$(uname -s)"
case "$os" in
  Darwin) platform="darwin" ;;
  Linux) platform="linux" ;;
  MINGW* | MSYS* | CYGWIN*)
    fail "on Windows, download softbot-win32-x64.tar.gz from $DOWNLOAD_PAGE and unpack it into a folder on your PATH" ;;
  *) fail "unsupported OS '$os' — see $DOWNLOAD_PAGE" ;;
esac
machine="$(uname -m)"
case "$machine" in
  x86_64 | amd64) arch="x64" ;;
  aarch64 | arm64) arch="arm64" ;;
  *) fail "unsupported CPU '$machine' — see $DOWNLOAD_PAGE" ;;
esac
target="$platform-$arch"

# --- where it goes ---------------------------------------------------------------
if [ "$(id -u)" -eq 0 ]; then
  HOME_DIR="${SOFTBOT_HOME:-/usr/local/lib/softbot}"
  BIN_DIR="${SOFTBOT_BIN_DIR:-/usr/local/bin}"
else
  HOME_DIR="${SOFTBOT_HOME:-$HOME/.softbot}"
  BIN_DIR="${SOFTBOT_BIN_DIR:-$HOME/.local/bin}"
fi

# --- resolve the release -------------------------------------------------------------
step "Reading the release manifest"
manifest="$(fetch_text "$BASE_URL/manifest.json")" || fail "could not fetch $BASE_URL/manifest.json"
# Tiny JSON reads without jq: the manifest is machine-written on one schema, so a
# targeted extraction is exact. `latest` is a bare semver; the target block holds
# url / sha256 / size on their own keys.
json_str() { # json_str JSON KEY → the string value of "KEY" (first occurrence)
  printf '%s' "$1" | tr -d '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n 1
}
latest="$(json_str "$manifest" latest)"
[ -n "$latest" ] || fail "manifest has no latest version"
version="${SOFTBOT_VERSION:-$latest}"
block="$(printf '%s' "$manifest" | tr -d '\n' | sed -n "s/.*\"$target\"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p")"
[ -n "$block" ] || fail "no softbot build for $target in this release — see $DOWNLOAD_PAGE"
expected="$(json_str "{$block}" sha256)"
file="$(json_str "{$block}" file)"
[ -n "$file" ] || file="softbot-$target.tar.gz"
if [ "$version" != "$latest" ]; then
  # A pinned version: trust its own sidecar rather than the rolling manifest.
  expected=""
fi
url="$BASE_URL/v/$version/$file"

# --- download + verify ------------------------------------------------------------------
tmp="$(mktemp -d 2>/dev/null || mktemp -d -t softbot)"
trap 'rm -rf "$tmp"' EXIT INT TERM

step "Downloading softbot $version for $target"
fetch "$url" "$tmp/$file" || fail "download failed: $url"
if [ -z "$expected" ]; then
  fetch "$url.sha256" "$tmp/$file.sha256" || fail "download failed: $url.sha256"
  expected="$(awk '{print $1}' "$tmp/$file.sha256")"
fi
[ -n "$expected" ] || fail "no checksum published for $file"
actual="$(sha256_of "$tmp/$file")"
[ "$expected" = "$actual" ] || fail "CHECKSUM MISMATCH for $file (expected $expected, got $actual) — refusing to install"
say "    checksum ok ${DIM}$actual${RESET}"

# --- unpack + link -----------------------------------------------------------------------
dest="$HOME_DIR/versions/$version"
staging="$HOME_DIR/versions/.staging-$$"
mkdir -p "$HOME_DIR/versions" "$BIN_DIR" || fail "cannot create $HOME_DIR or $BIN_DIR"
rm -rf "$staging"
mkdir -p "$staging"
tar -xzf "$tmp/$file" -C "$staging" || fail "could not unpack $file"
[ -x "$staging/softbot" ] || fail "the tarball has no softbot binary"
rm -rf "$dest"
mv "$staging" "$dest"
# `current` is a link to a DIRECTORY: `ln -sf` onto it would create the new link
# inside the old version, and `mv` onto it would move into it. Remove, then link.
rm -f "$HOME_DIR/current"
ln -s "$dest" "$HOME_DIR/current"
rm -f "$BIN_DIR/softbot"
ln -s "$HOME_DIR/current/softbot" "$BIN_DIR/softbot"

installed="$("$BIN_DIR/softbot" version 2>/dev/null || true)"
step "Installed ${installed:-softbot $version} → $BIN_DIR/softbot"
if [ -f "$dest/daemon/manifest.json" ]; then
  say "    carries the compute daemon package ${DIM}(softbot host status)${RESET}"
fi

# --- PATH -------------------------------------------------------------------------------
case ":$PATH:" in
  *":$BIN_DIR:"*) on_path=1 ;;
  *) on_path=0 ;;
esac
if [ "$on_path" -eq 0 ] && [ "${SOFTBOT_NO_MODIFY_PATH:-0}" != "1" ]; then
  say ""
  say "$BIN_DIR is not on your PATH. Add it with:"
  shell_name="$(basename "${SHELL:-sh}")"
  case "$shell_name" in
    fish) say "    fish_add_path $BIN_DIR" ;;
    zsh) say "    echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.zshrc && exec zsh" ;;
    *) say "    echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.profile && . ~/.profile" ;;
  esac
fi

say ""
say "Next:"
say "    softbot login          sign in with your Soft-Machine account"
say "    softbot chat           open a workspace conversation in the terminal"
say "    softbot host status    this machine's compute daemon"
say ""
say "${DIM}Docs: https://soft-machine.io/download${RESET}"
