#!/bin/sh
# AXE CLI installer (macOS / Linux) — POSIX mirror of cli/axe.ps1.
# ----------------------------------------------------------------------------
# Two editions, two auth postures — mirroring GET $BASE/api/cli/download:
#
#   agent     the cross-platform end-user CLI. Served OPEN (no auth), so no
#             token is needed to install it.
#   operator  the Mac-host operator CLI (vault/ship/deploy). Still Bearer-gated,
#             so AXE_TOKEN stays REQUIRED for it.
#
# Public one-liner — agent edition, no token:
#
#   curl -fsSL https://axelabs.ai/cli.sh | AXE_CLI_EDITION=agent sh
#
# Operator edition (default) — copy the ready-made command from the dashboard
# "⤓ CLI 설치" card, which embeds a short-lived token:
#
#   curl -fsSL https://axelabs.ai/cli.sh | AXE_BASE='https://axelabs.ai' AXE_TOKEN='<token>' sh
#
# AXE_TOKEN is still honored for the agent edition when you have one — the
# server then attributes the download to you instead of serving it anonymously.
#
# It downloads the CLI to ~/axe-cli/axe (NOT the current directory — robust
# against CWD/symlink collisions, e.g. a ~/axe that points elsewhere), verifies
# it is the requested edition, makes it executable, and puts it on your PATH.
# The Windows equivalent is `irm $BASE/cli.ps1 | iex`.
#
# $BASE is only the host this installer DOWNLOADS from; it is never baked into
# the installed CLI (the binary's own service/OIDC host is stamped server-side).
#
# Per-tenant: each tenant serves its own installer (this is AXE's; a sovereign
# tenant's is realchoice.axelabs.ai/cli.sh, etc.) — override $BASE if needed.
# ----------------------------------------------------------------------------
set -eu

BASE="${AXE_BASE:-https://axelabs.ai}"
DIR="${AXE_CLI_DIR:-$HOME/axe-cli}"
BIN="$DIR/axe"
EDITION="${AXE_CLI_EDITION:-operator}"

case "$EDITION" in
  operator) DOWNLOAD_PATH="/api/cli/download" ;;
  agent) DOWNLOAD_PATH="/api/cli/download?edition=agent" ;;
  *)
    echo "axe install: AXE_CLI_EDITION must be operator or agent." >&2
    exit 1 ;;
esac

# The agent edition's download is OPEN, so its token is optional. Only the
# operator edition is still Bearer-gated — fail early there rather than let
# curl come back with a bare 401.
if [ "$EDITION" != "agent" ] && [ -z "${AXE_TOKEN:-}" ]; then
  echo "axe install: AXE_TOKEN not set — the operator-edition download is auth-gated." >&2
  echo "  Copy the install command (with token) from the dashboard CLI 설치 card," >&2
  echo "  or:  curl -fsSL $BASE/cli.sh | AXE_TOKEN='<token>' sh" >&2
  echo "  The end-user CLI needs no token:" >&2
  echo "       curl -fsSL $BASE/cli.sh | AXE_CLI_EDITION=agent sh" >&2
  exit 1
fi

# Git Bash / MSYS / Cygwin are Windows, not Unix: the PATH block at the bottom
# only reaches this shell's rc files there, so those shells get a second half
# (axe.cmd shim + native-PATH advice). Detect once, up front — the interpreter
# check below already differs.
case "$(uname -s 2>/dev/null || echo unknown)" in
  MINGW*|MSYS*|CYGWIN*) WINDOWS=1 ;;
  *) WINDOWS="" ;;
esac

if [ -n "$WINDOWS" ]; then
  # Pick the Python the axe.cmd shim will invoke — same order as axe.ps1 (py
  # launcher first). Probe by RUNNING each candidate: a Windows box with no
  # Python still ships 0-byte Microsoft Store aliases at python.exe /
  # python3.exe that `command -v` happily finds and that can never run the CLI.
  # `-c ""` also keeps such an alias from opening the Store (it only does that
  # when invoked with no arguments).
  # 이 프로브는 Git Bash 안에서 도는데, shim 이 도는 곳은 cmd.exe/PowerShell 이다.
  # 기준은 하나: 그 인터프리터의 디렉토리가 **영속(레지스트리) PATH** 에 있는가.
  # `where` 나 `command -v` 로는 부족하다 — 둘 다 이 프로세스가 물려받은 PATH 를
  # 보므로, Git Bash 가 얹은 임시 디렉토리(…\Git\usr\bin)의 python 을 통과시킨 뒤
  # 새 cmd.exe 에서 죽는 shim 을 만든다. 설치 위치로 거르지는 않는다 — MSYS2 의
  # mingw64 python 처럼 네이티브인데 트리 안에 있는 것은 정상 후보다.
  PERSIST="$(powershell.exe -NoProfile -Command \
    "[Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')" \
    2>/dev/null | tr -d '\r')"
  on_persistent_path() {   # $1 = 후보 실행파일의 POSIX 경로
    [ -n "$PERSIST" ] || return 0     # 조회 실패 시 막지 않는다 (정보 부족)
    _d="$(cygpath -w "$(dirname "$1")" 2>/dev/null)" || return 1
    _d="$(printf '%s' "$_d" | tr 'A-Z' 'a-z' | sed 's:\\*$::')"
    printf '%s' "$PERSIST" | tr ';' '\n' | tr 'A-Z' 'a-z' | sed 's:\\*$::' \
      | grep -qxF "$_d"
  }
  PY=""
  for CAND in "py -3" "python" "python3"; do
    CAND_EXE="${CAND%% *}"
    CAND_PATH="$(command -v "$CAND_EXE" 2>/dev/null || echo '')"
    [ -n "$CAND_PATH" ] && on_persistent_path "$CAND_PATH" || continue
    # shellcheck disable=SC2086  # $CAND is "py -3": the word split is the point.
    # 버전까지 물어본다: `-c ""` 는 Python 2 도 통과하므로, legacy `python` 이
    # PATH 에 있으면 뒤의 python3 를 못 보고 3 전용 CLI 에 2 를 물린 shim 이 깔린다.
    if ! $CAND -c "import sys;sys.exit(sys.version_info[0]!=3)" >/dev/null 2>&1; then
      continue
    fi
    # shim 은 cmd.exe/PowerShell 에서 돈다 — 이 셸의 PATH 가 아니라 **Windows 의**
    # PATH 에서 그 이름이 잡혀야 한다. `where` 는 Windows 뷰로 조회하므로, 여기서만
    # 보이는 인터프리터를 이름으로 실어 죽은 shim 을 만드는 일이 없다.
    PY="$CAND"; break
  done
  if [ -z "$PY" ]; then
    echo "axe install: no working Python 3 (tried py -3, python, python3)." >&2
    echo "  Install Python 3 from https://www.python.org/downloads/, then re-run." >&2
    exit 1
  fi
elif ! command -v python3 >/dev/null 2>&1; then
  echo "axe install: python3 not found. Install Python 3, then re-run." >&2
  exit 1
fi

# Safety: refuse to install INTO a git checkout. On the AXE operator host,
# ~/axe-cli is the customer-CLI SOURCE repo — overwriting axe there clobbers it
# (and that host already has the operator `axe` at ~/.axe/bin). A normal user's
# ~/axe-cli has no .git, so this never trips for them. (Mirrors the CLI's own
# auto-update git-checkout guard.) Override with AXE_CLI_DIR=<clean dir> if you
# really mean to install into a repo directory.
if [ -e "$DIR/.git" ]; then
  echo "axe install: $DIR is a git checkout — refusing to overwrite it." >&2
  echo "  You may already have 'axe' (e.g. operator host: ~/.axe/bin/axe)." >&2
  echo "  To install elsewhere: AXE_CLI_DIR=\"\$HOME/.axe-bin\" re-run the command." >&2
  exit 1
fi

echo "AXE CLI installer  ->  $BIN"
mkdir -p "$DIR"

# Download to a temp file first (atomic-ish swap), so a failed/wrong download
# never leaves a broken $BIN. -f → curl fails on HTTP errors (401 unauth, 500
# server-side integrity guard), so a bad response never gets installed.
#
# Send the Bearer header only when a token exists (the agent edition may have
# none). Positional params carry the optional flag because POSIX sh has no
# arrays; "$@" is exempt from `set -u` when empty.
if [ -n "${AXE_TOKEN:-}" ]; then
  set -- -H "Authorization: Bearer $AXE_TOKEN"
else
  set --
fi
TMP="$(mktemp "${TMPDIR:-/tmp}/axe-cli.XXXXXX")"
if ! curl -fsSL "$@" \
  -H "User-Agent: axe-cli/installer" \
  "$BASE$DOWNLOAD_PATH" -o "$TMP"; then
  rm -f "$TMP"
  echo "axe install: download failed ($BASE$DOWNLOAD_PATH) — check network / token." >&2
  exit 1
fi

# Integrity: confirm the selected edition, mirroring the server-side guard.
if [ "$EDITION" = "agent" ]; then
  if ! grep -q 'customer-facing' "$TMP" || grep -q 'def cmd_vault_unlock' "$TMP"; then
    rm -f "$TMP"
    echo "axe install: downloaded binary failed the agent-edition marker check." >&2
    exit 1
  fi
elif ! grep -q 'def cmd_vault_unlock' "$TMP"; then
    rm -f "$TMP"
    echo "axe install: downloaded binary failed the operator-edition marker check." >&2
    exit 1
fi

# Edition swap notice: both editions live at the SAME $BIN, so an agent install
# silently ate an operator CLI on a real machine — taking vault/ship/deploy/
# backlog with it, and with them every skill that assumes `axe vault`. Never
# block (unattended installs must not stall): say what is being replaced, and
# keep ONE backup slot per edition so re-installs don't pile up copies.
if [ -f "$BIN" ]; then
  if grep -q 'def cmd_vault_unlock' "$BIN" 2>/dev/null; then
    HAD=operator
  elif grep -q 'customer-facing' "$BIN" 2>/dev/null; then
    HAD=agent
  else
    HAD=""
  fi
  if [ -n "$HAD" ] && [ "$HAD" != "$EDITION" ]; then
    echo "  NOTE: replacing the $HAD edition with $EDITION at $BIN."
    if [ "$HAD" = operator ]; then
      echo "        operator-only commands (vault / ship / deploy / backlog) go away."
    else
      echo "        agent-only commands (tools / call / ctx / guidelines / ref) go away."
    fi
    # A failed backup must not block an unattended install — but it must not be
    # announced either. A full disk used to print both.
    if cp -p "$BIN" "$BIN.$HAD.bak" 2>/dev/null; then
      echo "        previous binary kept at $BIN.$HAD.bak"
    else
      echo "        WARNING: no backup written ($BIN.$HAD.bak) — the $HAD binary is gone."
    fi
  fi
fi

chmod +x "$TMP"
mv -f "$TMP" "$BIN"
echo "  installed ($EDITION): $BASE$DOWNLOAD_PATH  ->  $BIN  ($(wc -c < "$BIN" | tr -d ' ') bytes)"

# Put $DIR on PATH idempotently (mirrors the ps1's user-PATH update). Already
# reachable → nothing to do; else append a guarded block to the shell rc files.
case ":${PATH:-}:" in
  *":$DIR:"*)
    echo "  PATH: already includes $DIR" ;;
  *)
    ADDED=""
    for RC in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
      [ -e "$RC" ] || continue
      if ! grep -q 'axe-cli (AXE CLI)' "$RC" 2>/dev/null; then
        printf '\n# axe-cli (AXE CLI)\nexport PATH="%s:$PATH"\n' "$DIR" >> "$RC"
        ADDED="$ADDED $RC"
      fi
    done
    if [ -n "$ADDED" ]; then
      echo "  PATH: added $DIR to$ADDED (open a NEW shell to pick it up)"
    else
      echo "  PATH: add $DIR to your PATH, e.g.  export PATH=\"$DIR:\$PATH\""
    fi
    ;;
esac

# Windows shells get the other half the rc files can't give them: the same
# axe.cmd shim axe.ps1 writes (so cmd.exe / PowerShell can run `axe` at all),
# plus the one-liner that registers the user PATH natively.
#
# ponytail: advice instead of action for the Windows user PATH, because the only
# sh-reachable tool (setx) truncates a PATH longer than 1024 chars — it destroys
# the variable rather than extending it, and the .NET API the ps1 uses is not
# reachable from here. Upgrade trigger: a non-Git-Bash user reports "axe not
# found in a new terminal" — then shell out to powershell.exe -Command for it.
if [ -n "$WINDOWS" ]; then
  # %~dp0 = the shim's own directory (with trailing \), expanded by cmd.exe at
  # run time. Nothing is baked in: an absolute path would need cygpath AND an
  # encoding that survives a non-ASCII profile name (C:\Users\강수훈) in a file
  # cmd.exe reads in the OEM codepage. These three ASCII lines are byte-for-byte
  # the shim axe.ps1 writes, CRLF included.
  printf '@echo off\r\nset PYTHONUTF8=1\r\n%s "%%~dp0axe" %%*\r\n' "$PY" > "$DIR/axe.cmd"
  echo "  shim: $DIR/axe.cmd  ($PY)"
  if ! command -v python3 >/dev/null 2>&1; then
    echo "  note: python3 is not on PATH, so \"$BIN\" cannot be run directly here"
    echo "        (its shebang asks for python3). Run $DIR/axe.cmd instead."
  fi
  echo "  PATH: the block above only reaches this shell's rc files. For cmd.exe"
  echo "        / PowerShell / GUI apps, register it natively in PowerShell"
  echo "        (also refreshes the binary, safe to repeat):"
  # edition 을 항상 못박는다: ps1 은 $env:AXE_CLI_EDITION 을 존중하므로, 그 셸에
  # operator 가 남아 있으면 bare 한 줄이 운영자판을 깔아 방금 깐 agent 를 덮는다.
  if [ "$EDITION" = agent ]; then
    echo "        \$env:AXE_CLI_EDITION='agent'; irm $BASE/cli.ps1 | iex"
  else
    echo "        \$env:AXE_CLI_EDITION='$EDITION'; \$env:AXE_TOKEN='<token>'; irm $BASE/cli.ps1 | iex"
  fi
fi

echo ""
echo "Installed. In a NEW shell (or: export PATH=\"$DIR:\$PATH\"):"
echo "  axe login            # browser SSO (Microsoft once -> all services)"
echo "  axe whoami"
echo "  axe blueprint guide"
