#!/bin/sh
# LoopCode command-line installer.
#
#   curl -fsSL https://loopcode.heksync.in/cli.sh | sh
#
# Installs the `loopcode` command for the current user — no sudo — then signs you in.
#   ~/.loopcode/app/<version>/   the CLI (one JavaScript file, run by your Node.js)
#   ~/.local/bin/loopcode        the command, a small launcher
# Your conversations and sign-in live in ~/.loopcode/cli and are never touched by this script.
#
# Options (environment variables):
#   LOOPCODE_BIN_DIR=/some/dir     where to put the `loopcode` command (default ~/.local/bin)
#   LOOPCODE_NO_MODIFY_PATH=1      don't add that directory to your shell profile
#   LOOPCODE_NO_LOGIN=1            don't start sign-in at the end
# Uninstall:
#   curl -fsSL https://loopcode.heksync.in/cli.sh | sh -s -- --uninstall
#
# The whole script is inside main(), called on the last line, so a download cut off halfway
# cannot run half an installer.

set -eu

REQUIRED_NODE="22.18.0"
SITE="${LOOPCODE_INSTALL_SITE:-https://loopcode.heksync.in}"
HOME_DIR="${HOME:?HOME is not set}"
APP_ROOT="$HOME_DIR/.loopcode/app"
BIN_DIR="${LOOPCODE_BIN_DIR:-$HOME_DIR/.local/bin}"
LAUNCHER="$BIN_DIR/loopcode"
MARKER="# loopcode-launcher"

if [ -t 1 ]; then BOLD="$(printf '\033[1m')"; DIM="$(printf '\033[2m')"; GREEN="$(printf '\033[32m')"; RED="$(printf '\033[31m')"; YELLOW="$(printf '\033[33m')"; RESET="$(printf '\033[0m')"; else BOLD=""; DIM=""; GREEN=""; RED=""; YELLOW=""; RESET=""; fi

say() { printf '%s\n' "$*"; }
step() { printf '  %s%s%s\n' "$DIM" "$*" "$RESET"; }
fail() { printf '\n  %s%s%s\n\n' "$RED" "$*" "$RESET" >&2; exit 1; }

# HTTPS only. Plain HTTP is allowed for localhost alone, so the installer can be tested end to end.
case "$SITE" in
  https://*) SECURE=1 ;;
  http://localhost*|http://127.0.0.1*) SECURE=0 ;;
  *) printf '  Refusing to download over an insecure connection: %s\n' "$SITE" >&2; exit 1 ;;
esac

download() { # url destination
  if command -v curl >/dev/null 2>&1; then
    if [ "$SECURE" = 1 ]; then curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --connect-timeout 15 "$1" -o "$2"; else curl -fsSL "$1" -o "$2"; fi
  elif command -v wget >/dev/null 2>&1; then
    if [ "$SECURE" = 1 ]; then wget -q --https-only --tries=3 -O "$2" "$1"; else wget -q -O "$2" "$1"; fi
  else fail "This installer needs curl or wget."; fi
}

sha256_of() {
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
  elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
  else fail "This installer needs sha256sum or shasum to check the download."; fi
}

# 1 when $1 >= $2, comparing dotted versions numerically.
version_at_least() {
  [ "$(printf '%s\n%s\n' "$2" "$1" | sort -t. -k1,1n -k2,2n -k3,3n | head -n1)" = "$2" ]
}

shell_profile() {
  case "$(basename "${SHELL:-sh}")" in
    zsh) printf '%s' "$HOME_DIR/.zshrc" ;;
    bash) if [ "$(uname -s)" = "Darwin" ]; then printf '%s' "$HOME_DIR/.bash_profile"; else printf '%s' "$HOME_DIR/.bashrc"; fi ;;
    fish) printf '%s' "$HOME_DIR/.config/fish/config.fish" ;;
    *) printf '%s' "$HOME_DIR/.profile" ;;
  esac
}

uninstall() {
  say ""
  if [ -f "$LAUNCHER" ] && grep -q "$MARKER" "$LAUNCHER" 2>/dev/null; then rm -f "$LAUNCHER"; step "removed $LAUNCHER"; fi
  if [ -d "$APP_ROOT" ]; then rm -rf "$APP_ROOT"; step "removed $APP_ROOT"; fi
  say "  ${GREEN}LoopCode's command-line tool is uninstalled.${RESET}"
  say "  ${DIM}Your conversations and sign-in are still in ~/.loopcode/cli; delete that folder to remove them too.${RESET}"
  say ""
}

main() {
  if [ "${1:-}" = "--uninstall" ]; then uninstall; return; fi

  say ""
  say "  ${BOLD}LoopCode${RESET} ${DIM}· command-line installer${RESET}"
  say ""

  case "$(uname -s)" in
    Darwin|Linux) ;;
    MINGW*|MSYS*|CYGWIN*) fail "On Windows, install from PowerShell instead:  irm https://loopcode.heksync.in/cli.ps1 | iex" ;;
    *) fail "This installer supports macOS and Linux. On Windows, use PowerShell:  irm https://loopcode.heksync.in/cli.ps1 | iex" ;;
  esac

  # Node.js runs the CLI. We don't install it for you — it's yours to manage — but we say how.
  if ! command -v node >/dev/null 2>&1; then
    say "  ${YELLOW}LoopCode needs Node.js ${REQUIRED_NODE} or newer, and Node.js isn't installed.${RESET}"
    say ""
    if [ "$(uname -s)" = "Darwin" ] && command -v brew >/dev/null 2>&1; then say "    brew install node"; else say "    https://nodejs.org  (or with nvm: nvm install 24)"; fi
    say ""
    fail "Install Node.js, then run this installer again."
  fi
  NODE_VERSION="$(node -p 'process.versions.node' 2>/dev/null || printf '0.0.0')"
  if ! version_at_least "$NODE_VERSION" "$REQUIRED_NODE"; then
    fail "LoopCode needs Node.js ${REQUIRED_NODE} or newer; you have ${NODE_VERSION}. Update it (nvm install 24, brew upgrade node, or nodejs.org) and run this again."
  fi
  step "Node.js $NODE_VERSION"

  command -v tar >/dev/null 2>&1 || fail "This installer needs tar."

  # Which build is current. The site answers with key=value lines.
  TMP="$(mktemp -d 2>/dev/null || mktemp -d -t loopcode)"
  trap 'rm -rf "$TMP"' EXIT INT TERM
  download "$SITE/api/loopcode/cli" "$TMP/latest" || fail "Could not reach $SITE. Check your connection and try again."
  VERSION="$(sed -n 's/^version=//p' "$TMP/latest" | head -n1)"
  URL="$(sed -n 's/^url=//p' "$TMP/latest" | head -n1)"
  SUM_URL="$(sed -n 's/^sha256_url=//p' "$TMP/latest" | head -n1)"
  if [ -z "$VERSION" ] || [ -z "$URL" ]; then
    REASON="$(sed -n 's/^error=//p' "$TMP/latest" | head -n1)"
    fail "No command-line build is available right now${REASON:+ ($REASON)}. Please try again later."
  fi
  case "$VERSION" in *[!0-9.]*) fail "The release server gave an unexpected version: $VERSION" ;; esac

  if [ -f "$APP_ROOT/current/VERSION" ] && [ "$(cat "$APP_ROOT/current/VERSION")" = "$VERSION" ] && [ -f "$LAUNCHER" ]; then
    step "LoopCode $VERSION is already installed; refreshing the command"
  else
    step "downloading LoopCode $VERSION"
    download "$URL" "$TMP/loopcode.tar.gz" || fail "The download failed. Please try again."
    if [ -n "$SUM_URL" ]; then
      download "$SUM_URL" "$TMP/loopcode.tar.gz.sha256" || fail "Could not fetch the checksum. Please try again."
      EXPECTED="$(cut -d' ' -f1 "$TMP/loopcode.tar.gz.sha256")"
      ACTUAL="$(sha256_of "$TMP/loopcode.tar.gz")"
      [ "$EXPECTED" = "$ACTUAL" ] || fail "The download did not match its checksum, so nothing was installed. Please try again."
      step "checksum verified"
    fi
    mkdir -p "$TMP/extract"
    tar -xzf "$TMP/loopcode.tar.gz" -C "$TMP/extract"
    [ -f "$TMP/extract/loopcode/loopcode.mjs" ] || fail "The download was not a LoopCode build."
    # Make sure it runs here before it replaces anything.
    RAN="$(node "$TMP/extract/loopcode/loopcode.mjs" --version 2>/dev/null || true)"
    [ "$RAN" = "$VERSION" ] || fail "The downloaded build did not start on this machine, so nothing was changed. Please write to team@heksync.in."
    step "verified it runs"
    mkdir -p "$APP_ROOT"
    rm -rf "$APP_ROOT/$VERSION"
    mv "$TMP/extract/loopcode" "$APP_ROOT/$VERSION"
    # Switch versions in one step, then tidy up the rest.
    ln -sfn "$APP_ROOT/$VERSION" "$APP_ROOT/current.new" && mv -f "$APP_ROOT/current.new" "$APP_ROOT/current" 2>/dev/null || { rm -f "$APP_ROOT/current"; ln -s "$APP_ROOT/$VERSION" "$APP_ROOT/current"; }
    for old in "$APP_ROOT"/*; do
      [ "$old" = "$APP_ROOT/$VERSION" ] || [ "$old" = "$APP_ROOT/current" ] || rm -rf "$old"
    done
  fi

  # The command itself. A file we didn't write is left alone.
  mkdir -p "$BIN_DIR"
  if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ] && ! grep -q "$MARKER" "$LAUNCHER" 2>/dev/null; then
    fail "$LAUNCHER already exists and wasn't installed by LoopCode. Move it aside, or set LOOPCODE_BIN_DIR to another folder, and run this again."
  fi
  rm -f "$LAUNCHER"
  cat > "$LAUNCHER" <<LAUNCHER
#!/bin/sh
$MARKER — installed by $SITE/cli.sh; run it again to update.
if ! command -v node >/dev/null 2>&1; then
  echo "loopcode needs Node.js $REQUIRED_NODE or newer: https://nodejs.org" >&2
  exit 127
fi
exec node "$APP_ROOT/current/loopcode.mjs" "\$@"
LAUNCHER
  chmod 755 "$LAUNCHER"
  step "installed $LAUNCHER"

  # Make sure the shell can find it.
  ON_PATH=0
  case ":${PATH:-}:" in *":$BIN_DIR:"*) ON_PATH=1 ;; esac
  if [ "$ON_PATH" = 0 ]; then
    PROFILE="$(shell_profile)"
    if [ "${LOOPCODE_NO_MODIFY_PATH:-}" = "1" ]; then
      say ""
      say "  ${YELLOW}$BIN_DIR is not on your PATH.${RESET} Add it to use the loopcode command:"
      say "    export PATH=\"$BIN_DIR:\$PATH\""
    elif ! grep -qs "# added by LoopCode" "$PROFILE"; then
      mkdir -p "$(dirname "$PROFILE")"
      if [ "$(basename "$PROFILE")" = "config.fish" ]; then
        printf '\n# added by LoopCode\nfish_add_path %s\n' "$BIN_DIR" >> "$PROFILE"
      else
        printf '\n# added by LoopCode\nexport PATH="%s:$PATH"\n' "$BIN_DIR" >> "$PROFILE"
      fi
      step "added $BIN_DIR to your PATH in $PROFILE"
    fi
  fi

  say ""
  say "  ${GREEN}${BOLD}LoopCode $VERSION is installed.${RESET}"

  # First install: sign in now, while you're here. Needs a terminal to talk to, which a
  # piped `curl | sh` still has through /dev/tty.
  if [ "${LOOPCODE_NO_LOGIN:-}" != "1" ] && [ ! -s "$HOME_DIR/.loopcode/cli/account.json" ] && [ -t 1 ] && (: </dev/tty) 2>/dev/null; then
    say ""
    say "  Let's sign you in — your browser will open to approve this terminal."
    "$LAUNCHER" login </dev/tty || say "  ${DIM}You can sign in any time with: loopcode login${RESET}"
  fi

  say ""
  if [ "$ON_PATH" = 1 ]; then
    say "  Open a project folder and run ${BOLD}loopcode${RESET} to start."
  else
    say "  Open a new terminal (or run: ${BOLD}export PATH=\"$BIN_DIR:\$PATH\"${RESET}), then run ${BOLD}loopcode${RESET} in a project folder."
  fi
  say "  ${DIM}Update any time by running this installer again. Help: loopcode --help · team@heksync.in${RESET}"
  say ""
}

main "$@"
