#!/bin/sh
# Matrix light-client installer (Phase 1 B5). ONE copy-paste command from the
# web page brings a machine online:
#
#   curl -fsSL <cloud>/install.sh | sh -s -- --token <t> --cloud <cloud>
#
# It installs a user-level Node runtime (only if the system Node is too old),
# unpacks the client bundle to ~/.matrix/versions/<ver>, points ~/.matrix/current
# at it, drops a `matrix` launcher on PATH, and hands the one-time token to
# `matrix onboard` (redeem → discover accounts → share compute → autostart →
# open the browser). NO sudo, NO silent fallback: every failure is loud.
set -eu

# ---- loud failure reporting -------------------------------------------------
STEP="starting"
on_exit() {
  code=$?
  if [ "$code" -ne 0 ]; then
    printf '\n[matrix-install] FAILED during: %s (exit %s)\n' "$STEP" "$code" >&2
    printf '[matrix-install] nothing was launched. Fix the above and re-run the install command.\n' >&2
  fi
}
trap on_exit EXIT INT TERM

log() { printf '[matrix-install] %s\n' "$*"; }
err() { printf '[matrix-install] ERROR: %s\n' "$*" >&2; exit 1; }

download() {
  # download <url> <out> — curl or wget, fail loud (no partial file left behind)
  _url="$1"; _out="$2"
  if command -v curl >/dev/null 2>&1; then
    curl -fSL --retry 2 "$_url" -o "$_out" || err "download failed: $_url"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO "$_out" "$_url" || err "download failed: $_url"
  else
    err "need curl or wget to download $_url — install one and re-run"
  fi
}

# ---- args -------------------------------------------------------------------
TOKEN=""
CLOUD=""
HEADLESS=0
SERVER=0
EMAIL=""
PASSWORD_STDIN=0
while [ $# -gt 0 ]; do
  case "$1" in
    --token) TOKEN="${2:-}"; shift 2 ;;
    --cloud) CLOUD="${2:-}"; shift 2 ;;
    --headless) HEADLESS=1; shift ;;
    --server) SERVER=1; shift ;;
    --email) EMAIL="${2:-}"; shift 2 ;;
    --password-stdin) PASSWORD_STDIN=1; shift ;;
    *) err "unknown flag: $1 (usage: [--token <安装码>] [--cloud <url>] [--headless --server])" ;;
  esac
done
# 零参数可跑 (装了再登录): cloud falls back to env then the public cloud; a
# missing token is fine INTERACTIVELY — `matrix onboard` opens the browser and
# asks for the 安装码. Headless still requires the token (onboard fails loud).
[ -n "$CLOUD" ] || CLOUD="${MATRIX_CLOUD:-https://matrix.visuallogic.ai}"
[ -n "$TOKEN" ] || [ "$HEADLESS" -eq 0 ] || err "--token <安装码> is required with --headless"
[ -n "$TOKEN" ] || log "no --token — after install, the browser will open for 注册/登录, then paste the 安装码 back here"
# strip a trailing slash from the cloud URL so "$CLOUD/path" never doubles it
CLOUD="${CLOUD%/}"

MATRIX_HOME="$HOME/.matrix"
# LTS >= 22.5 (node:sqlite). Override with MATRIX_NODE_VERSION if the pinned
# build is ever pulled. v22.11.0 is the first LTS ("Jod") of the 22 line.
NODE_VERSION="${MATRIX_NODE_VERSION:-v22.11.0}"

# ---- 1. Node runtime (>=22.5) ----------------------------------------------
STEP="checking Node runtime"
node_ok() {
  # exit 0 iff "$1" is a node whose version is >= 22.5
  "$1" -e 'var v=process.versions.node.split(".").map(Number);process.exit((v[0]>22||(v[0]===22&&v[1]>=5))?0:1)' 2>/dev/null
}
NODE=""
if command -v node >/dev/null 2>&1 && node_ok node; then
  NODE="$(command -v node)"
  log "using system Node: $NODE ($("$NODE" --version 2>/dev/null))"
else
  STEP="downloading Node runtime"
  os="$(uname -s)"; arch="$(uname -m)"
  case "$os" in
    Darwin) case "$arch" in
        arm64) plat="darwin-arm64" ;;
        x86_64) plat="darwin-x64" ;;
        *) err "unsupported macOS architecture: $arch" ;;
      esac ;;
    Linux) case "$arch" in
        x86_64) plat="linux-x64" ;;
        aarch64|arm64) plat="linux-arm64" ;;
        *) err "unsupported Linux architecture: $arch" ;;
      esac ;;
    *) err "unsupported OS: $os (need Darwin or Linux)" ;;
  esac
  tarball="node-${NODE_VERSION}-${plat}.tar.gz"
  url="https://nodejs.org/dist/${NODE_VERSION}/${tarball}"
  log "system Node missing or < 22.5 — installing Node ${NODE_VERSION} for ${plat} (user-level, no sudo)"
  rtdir="$MATRIX_HOME/runtime"
  mkdir -p "$rtdir"
  tmpn="$(mktemp -d "${TMPDIR:-/tmp}/matrix-node.XXXXXX")"
  download "$url" "$tmpn/$tarball"
  tar -xzf "$tmpn/$tarball" -C "$tmpn" || err "could not extract $tarball"
  rm -rf "$rtdir/node"
  mv "$tmpn/node-${NODE_VERSION}-${plat}" "$rtdir/node" || err "could not place node runtime"
  rm -rf "$tmpn"
  NODE="$rtdir/node/bin/node"
  [ -x "$NODE" ] || err "downloaded node binary not executable at $NODE"
  node_ok "$NODE" || err "downloaded node failed the >=22.5 version check"
  log "installed Node runtime at $NODE ($("$NODE" --version 2>/dev/null))"
fi

# ---- 2. client bundle -------------------------------------------------------
STEP="downloading client bundle"
tmpb="$(mktemp -d "${TMPDIR:-/tmp}/matrix-bundle.XXXXXX")"
download "$CLOUD/client-bundle.tar.gz" "$tmpb/client-bundle.tar.gz"
# latest.json is best-effort (used only to name the version dir)
if download "$CLOUD/latest.json" "$tmpb/latest.json" 2>/dev/null; then :; else rm -f "$tmpb/latest.json"; fi

STEP="extracting client bundle"
mkdir -p "$tmpb/x"
tar -xzf "$tmpb/client-bundle.tar.gz" -C "$tmpb/x" || err "could not extract client-bundle.tar.gz"

# read the version: latest.json (sidecar) → bundle latest.json → VERSION → package.json → timestamp
read_json_version() { "$NODE" -e 'try{process.stdout.write(String(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).version||""))}catch(e){}' "$1" 2>/dev/null || true; }
VERSION=""
[ -f "$tmpb/latest.json" ] && VERSION="$(read_json_version "$tmpb/latest.json")"
[ -z "$VERSION" ] && [ -f "$tmpb/x/latest.json" ] && VERSION="$(read_json_version "$tmpb/x/latest.json")"
[ -z "$VERSION" ] && [ -f "$tmpb/x/VERSION" ] && VERSION="$(tr -d ' \n\r\t' < "$tmpb/x/VERSION")"
[ -z "$VERSION" ] && [ -f "$tmpb/x/package.json" ] && VERSION="$(read_json_version "$tmpb/x/package.json")"
if [ -z "$VERSION" ]; then
  VERSION="$(date +%Y%m%d%H%M%S)"
  log "no version marker in bundle — using timestamp dir $VERSION"
fi

STEP="installing bundle to ~/.matrix/versions/$VERSION"
[ -f "$tmpb/x/services/control-plane/src/server.mjs" ] || err "bad bundle: missing services/control-plane/src/server.mjs"
mkdir -p "$MATRIX_HOME/versions"
VDIR="$MATRIX_HOME/versions/$VERSION"
rm -rf "$VDIR"
mv "$tmpb/x" "$VDIR" || err "could not install bundle to $VDIR"
rm -rf "$tmpb"
# ~/.matrix/current → this version (atomic-ish: ln -sfn replaces in place)
ln -sfn "$VDIR" "$MATRIX_HOME/current"
log "installed bundle → $VDIR (current -> $VERSION)"

# ---- 3. launcher on PATH ----------------------------------------------------
STEP="writing launcher"
mkdir -p "$MATRIX_HOME/bin"
LAUNCHER="$MATRIX_HOME/bin/matrix"
cat > "$LAUNCHER" <<EOF
#!/bin/sh
# Matrix CLI launcher (generated by install.sh). Runs the bundled worker CLI
# with MATRIX_BUNDLE_ROOT pinned to the current version.
export MATRIX_BUNDLE_ROOT="\$HOME/.matrix/current"
exec "$NODE" "\$HOME/.matrix/current/workers/matrix-worker/src/cli.mjs" "\$@"
EOF
chmod +x "$LAUNCHER"
log "wrote launcher $LAUNCHER"

STEP="updating PATH"
add_path_block() {
  rc="$1"
  marker="# >>> matrix cli >>>"
  if [ -f "$rc" ] && grep -qF "$marker" "$rc" 2>/dev/null; then return 0; fi
  {
    printf '\n%s\n' "$marker"
    printf 'case ":$PATH:" in *":$HOME/.matrix/bin:"*) ;; *) PATH="$HOME/.matrix/bin:$PATH" ;; esac\n'
    printf 'export PATH\n'
    printf '# <<< matrix cli <<<\n'
  } >> "$rc"
  log "added ~/.matrix/bin to PATH in $rc"
}
shellname="$(basename "${SHELL:-/bin/sh}")"
case "$shellname" in
  zsh) rcfile="$HOME/.zshrc" ;;
  bash) rcfile="$HOME/.bashrc" ;;
  *) rcfile="$HOME/.profile" ;;
esac
touch "$rcfile"
add_path_block "$rcfile"
PATH="$HOME/.matrix/bin:$PATH"; export PATH

# ---- 4. onboard (redeem token, share compute, autostart, open browser) ------
STEP="onboarding"
set -- onboard --cloud "$CLOUD"
[ -n "$TOKEN" ] && set -- "$@" --token "$TOKEN"
[ "$HEADLESS" -eq 1 ] && set -- "$@" --headless
[ "$SERVER" -eq 1 ] && set -- "$@" --server
[ -n "$EMAIL" ] && set -- "$@" --email "$EMAIL"
[ "$PASSWORD_STDIN" -eq 1 ] && set -- "$@" --password-stdin
log "launching: matrix $*"
exec "$LAUNCHER" "$@"
