#!/bin/sh
# SigmaShake ssg installer — no Node.js or npm required.
# Downloads the native ssg binary directly from the SigmaShake download CDN
# (download.sigmashake.com/cli/* → the canonical release artifact).
#
# Usage:
#   curl -fsSL https://install.sigmashake.com | sh
#   curl -fsSL https://install.sigmashake.com/install.sh | sh
#   curl -fsSL https://install.sigmashake.com | sh -s -- --no-setup
#   curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=/usr/local/bin
#   curl -fsSL https://install.sigmashake.com | sh -s -- -y
#   curl -fsSL https://install.sigmashake.com | sh -s -- --uninstall
#   # Non-interactive install + account link in one shot (magic-link token):
#   curl -fsSL https://install.sigmashake.com | SSG_INSTALL_TOKEN=<token> sh -s -- -y
#
# After install (when -y/SSG_INSTALL_YES consent is present, or the script is
# run interactively), this installer also runs `ssg init`, starts the local
# governance daemon, links the account (SSG_INSTALL_TOKEN, or an interactive
# `ssg auth login` when a TTY is available and --no-auth was not passed), and
# runs `ssg status` — then prints one `ssg-install-timing: ...` line to stderr
# and writes the same fields to $SIGMASHAKE_HOME/install-timing.json.
#
# --uninstall reverses a prior curl-pipe install: it invokes
# `ssg uninstall --package-remove` on the currently-installed binary (strips
# hooks, disables autostart, stops any running local daemon) and then removes
# the ssg binary + adjacent public/ dashboard assets from the install
# directory. It does NOT touch ~/.sigmashake (license, rules, audit logs) —
# that is per-user state, preserved by design so a reinstall keeps config.
# Pass --install-dir=<dir> to target a non-default install; otherwise it
# checks $SSG_INSTALL_DIR, then ~/.local/bin, then /usr/local/bin, then PATH.
#
# Non-interactive installs (no tty on stdin) require -y/--yes as explicit
# Terms of Service consent; without it the script fails closed. -y also
# records the consent (version, URL, timestamp) to consent.json in the
# ssg home directory.
# Existing exact-target updates may instead pass --preserve-consent together
# with --install-path and --no-setup. That mode validates current recorded
# consent and leaves the consent file byte-for-byte untouched.
#
# Environment overrides:
#   SSG_INSTALL_DIR=<path>       Override install location (default: ~/.local/bin)
#   SSG_DRY_RUN=1                Print actions without writing files
#   SSG_INSTALL_BASE_URL=<url>   Override CDN base URL (default: https://download.sigmashake.com)
#                                Used by the hermetic install test suite; do not set in production.
#   SSG_INSTALL_YES=1            Exactly equivalent to -y/--yes (same ToS consent recording,
#                                distinguished in consent.json as method "env:SSG_INSTALL_YES").
#   SSG_INSTALL_TOKEN=<token>    Magic-link install token; after setup this installer runs
#                                `ssg auth login --no-browser`, which reads the token from this
#                                env var (never pass it as an argv flag — it would leak via ps/proc),
#                                then unsets it once that command returns. Single-use, ~15-minute
#                                TTL. Pasting it inline lands in shell history — prefer
#                                `read -s SSG_INSTALL_TOKEN && export SSG_INSTALL_TOKEN` first, or
#                                copy it fresh from the accounts page's copy button right before use.
#   --no-auth                    Skip the interactive `ssg auth login` prompt after setup.
#                                Does not affect a non-interactive SSG_INSTALL_TOKEN login.
#
# SHA256 allowlist is baked at release time.
# Regenerate with: bash sigmashake-dist/scripts/checksums.sh
set -eu

# ── Timing (SSG-INSTALL-TIMING) ───────────────────────────────────────────────
# Millisecond epoch. Prefers `date +%s%N` (GNU coreutils / most Linux); a
# ns-epoch is 19 digits, so requiring >=13 digits accepts it while rejecting
# a literal unexpanded "%N" (stock macOS BSD date has no %N) or any other
# non-numeric output, which falls back to whole-second resolution (×1000).
# Never fails the install: a timing hiccup must not take down the installer.
_now_ms() {
  _T="$(date +%s%N 2>/dev/null || true)"
  case "$_T" in
    ''|*[!0-9]*) _T='' ;;
  esac
  if [ -n "$_T" ] && [ "${#_T}" -ge 13 ]; then
    echo $((_T / 1000000))
  else
    echo $(($(date +%s) * 1000))
  fi
}
# Renders a timing value as a bare JSON number when purely numeric, else as a
# quoted JSON string (covers the "skipped" / "interactive" sentinel values).
_json_val() {
  case "$1" in
    ''|*[!0-9]*) printf '"%s"' "$1" ;;
    *) printf '%s' "$1" ;;
  esac
}
_T_SCRIPT_START="$(_now_ms)"
_AUTH_EXCLUDE_MS=0
_DOWNLOAD_MS=0
_EXTRACT_MS=0
_VERIFY_MS=0

VERSION=""
NO_SETUP=0
YES=0
NO_AUTH=0
DRY_RUN="${SSG_DRY_RUN:-0}"
INSTALL_DIR="${SSG_INSTALL_DIR:-}"
INSTALL_PATH=""
INSTALL_PATH_SET=0
PRESERVE_CONSENT=0
UNINSTALL=0
# Testability override: point the installer at a local fixture CDN in tests.
# Default is the production CDN. When set, ALL download.sigmashake.com URLs
# are replaced with this base (no trailing slash). Default behavior is
# byte-identical when the variable is absent.
BASE="${SSG_INSTALL_BASE_URL:-https://download.sigmashake.com}"
TERMS_VERSION="2026-06-13"
TERMS_URL="https://sigmashake.com/terms"

# Tracks HOW -y consent was given so consent.json can distinguish the two
# non-interactive paths (set alongside YES=1 below; never both at once — the
# explicit flag always wins when both happen to be present).
_YES_METHOD=""

for _arg in "$@"; do
  case "$_arg" in
    --no-setup)       NO_SETUP=1 ;;
    -y|--yes)        YES=1; _YES_METHOD="-y" ;;
    --no-auth)        NO_AUTH=1 ;;
    --dry-run)        DRY_RUN=1 ;;
    --version=*)      VERSION="${_arg#--version=}" ;;
    --install-dir=*)  INSTALL_DIR="${_arg#--install-dir=}" ;;
    --install-path=*) INSTALL_PATH="${_arg#--install-path=}"; INSTALL_PATH_SET=1 ;;
    --preserve-consent) PRESERVE_CONSENT=1 ;;
    --uninstall)      UNINSTALL=1 ;;
  esac
done

# SSG_INSTALL_YES=1 is exactly equivalent to -y/--yes (same consent recording
# below) — lets a piped `sh -s --` invocation carry consent via env alone.
# Recorded under a distinct method marker so consent.json can tell the two
# paths apart; only applies when -y/--yes wasn't already the explicit source.
if [ "${SSG_INSTALL_YES:-0}" = "1" ] && [ -z "$_YES_METHOD" ]; then
  YES=1
  _YES_METHOD="env:SSG_INSTALL_YES"
fi

# Per-platform default version. Platforms are released on independent cadences,
# so the right default is "highest version actually released for THIS platform"
# rather than a single global pin. checksums.sh updates these (and _sha256_for
# below) from the built binaries at release time.
_default_version_for() {
  case "$1" in
    linux-x64)    echo "1.1.8" ;;
    linux-arm64)  echo "1.1.8" ;;
    darwin-x64)   echo "1.1.8" ;;
    darwin-arm64) echo "1.1.8" ;;
    *)            echo "" ;;
  esac
}

# Color output only when stderr is a terminal
if [ -t 2 ] && command -v tput >/dev/null 2>&1; then
  _GREEN="$(tput setaf 2 2>/dev/null || true)"
  _YELLOW="$(tput setaf 3 2>/dev/null || true)"
  _RED="$(tput setaf 1 2>/dev/null || true)"
  _BOLD="$(tput bold 2>/dev/null || true)"
  _RESET="$(tput sgr0 2>/dev/null || true)"
else
  _GREEN=''; _YELLOW=''; _RED=''; _BOLD=''; _RESET=''
fi

_info() { printf '%s✓%s  %s\n' "$_GREEN" "$_RESET" "$1" >&2; }
_warn() { printf '%s!%s  %s\n' "$_YELLOW" "$_RESET" "$1" >&2; }
_err()  { printf '%s✗%s  %s\n' "$_RED" "$_RESET" "$1" >&2; }
_consent_path() {
  printf '%s/consent.json\n' "${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
}
_write_yes_consent() {
  _SSG_HOME="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
  _CONSENT_PATH="$(_consent_path)"
  _ACCEPTED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")"
  mkdir -p "$_SSG_HOME"
  (
    umask 077
    cat > "$_CONSENT_PATH" <<EOF
{
  "tosVersion": "$TERMS_VERSION",
  "tosUrl": "$TERMS_URL",
  "acceptedAt": "$_ACCEPTED_AT",
  "method": "$_YES_METHOD"
}
EOF
  )
  _info "accepted ToS $TERMS_VERSION ($TERMS_URL) via $_YES_METHOD"
}

# ── Uninstall ─────────────────────────────────────────────────────────────────
# Runs before the ToS-consent gate and platform detection below — uninstall
# needs neither. Resolution order mirrors the install-target resolution used
# by the default (no --install-dir/--install-path) install path.
if [ "$UNINSTALL" = "1" ]; then
  _UNINSTALL_DIR="$INSTALL_DIR"
  if [ -z "$_UNINSTALL_DIR" ]; then
    if [ -f "$HOME/.local/bin/ssg" ]; then
      _UNINSTALL_DIR="$HOME/.local/bin"
    elif [ -f "/usr/local/bin/ssg" ]; then
      _UNINSTALL_DIR="/usr/local/bin"
    else
      _PATH_SSG_UNINSTALL="$(command -v ssg 2>/dev/null || true)"
      if [ -n "$_PATH_SSG_UNINSTALL" ]; then
        _UNINSTALL_DIR="$(dirname "$_PATH_SSG_UNINSTALL")"
      fi
    fi
  fi

  if [ -z "$_UNINSTALL_DIR" ] || [ ! -f "$_UNINSTALL_DIR/ssg" ]; then
    _err "No ssg installation found (checked \$SSG_INSTALL_DIR / --install-dir, ~/.local/bin, /usr/local/bin, PATH)."
    _err "Nothing to uninstall."
    exit 1
  fi

  _UNINSTALL_TARGET="$_UNINSTALL_DIR/ssg"
  _info "Found ssg at $_UNINSTALL_TARGET"
  _info "Running package-remove cleanup (hooks, autostart, local daemon)..."
  if ! "$_UNINSTALL_TARGET" uninstall --package-remove; then
    _warn "'ssg uninstall --package-remove' exited non-zero — continuing with binary removal anyway."
  fi

  rm -f "$_UNINSTALL_TARGET"
  # Same removal style used for the public/ refresh in the install path below
  # (find -delete, not a recursive remove) — mirrors the existing convention
  # already used in this file for clearing the public/ dashboard assets dir.
  if [ -d "$_UNINSTALL_DIR/public" ]; then
    find "$_UNINSTALL_DIR/public" -depth -delete 2>/dev/null || true
  fi

  _info "Removed ssg from $_UNINSTALL_DIR."
  _info "Per-user state (license, rules, audit logs) is left untouched at:"
  _info "  ${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
  _info "Remove that directory manually for a full wipe."
  exit 0
fi

if [ "$PRESERVE_CONSENT" = "1" ]; then
  if [ "$YES" = "1" ]; then
    _err "--preserve-consent cannot be combined with -y/--yes"
    exit 1
  fi
  if [ "$INSTALL_PATH_SET" != "1" ] || [ "$NO_SETUP" != "1" ]; then
    _err "--preserve-consent requires --install-path=<existing-ssg> and --no-setup"
    exit 1
  fi
  _PRESERVED_CONSENT_PATH="$(_consent_path)"
  if [ ! -f "$_PRESERVED_CONSENT_PATH" ] || [ ! -r "$_PRESERVED_CONSENT_PATH" ]; then
    _err "--preserve-consent requires current recorded consent at $_PRESERVED_CONSENT_PATH"
    exit 1
  fi
  _PRESERVED_TERMS_VERSION="$(sed -n 's/.*"tosVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  _PRESERVED_TERMS_URL="$(sed -n 's/.*"tosUrl"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  _PRESERVED_ACCEPTED_AT="$(sed -n 's/.*"acceptedAt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$_PRESERVED_CONSENT_PATH")"
  case "$_PRESERVED_ACCEPTED_AT" in
    [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z) ;;
    *) _PRESERVED_ACCEPTED_AT="" ;;
  esac
  if [ "$_PRESERVED_TERMS_VERSION" != "$TERMS_VERSION" ] || \
     [ "$_PRESERVED_TERMS_URL" != "$TERMS_URL" ] || \
     [ -z "$_PRESERVED_ACCEPTED_AT" ]; then
    _err "--preserve-consent requires current recorded consent for $TERMS_VERSION at $TERMS_URL"
    exit 1
  fi
elif [ "$YES" != "1" ] && [ ! -t 0 ]; then
  _err "Terms of Service consent is required for non-interactive install."
  _err "Read: $TERMS_URL"
  _err "Then rerun: curl -fsSL https://install.sigmashake.com | sh -s -- -y"
  exit 1
fi

# !! SHA256 ALLOWLIST — regenerated by: bash sigmashake-dist/scripts/checksums.sh !!
# Last updated: 2026-09-05 (linux 1.1.6 restored from 32546889f — lost release
# bump; SHAs re-derived from the live 1.1.6 linux-x64/linux-arm64 CDN tarballs).
# SHAs match _default_version_for above; bumping either without the other will
# fail SHA verification at install time.
_sha256_for() {
  case "$1" in
    linux-x64)    echo "d41371769692a1f79a6faf2af91736339765f51e197a2ac4614523b94885641b" ;;
    linux-arm64)  echo "b3ddfcddee3abaf2985724e9db5eb713db9aed2f8ca0f3946a0d66990308ffa0" ;;
    darwin-x64)   echo "f7249e069d15d8e0d2a46505afe34353329e4b59185ee0dab4c71f835e6d8dfb" ;;
    darwin-arm64) echo "0a679c6d6846ff249584517a175d742f55ad7278d68a98dae97adf1e23e8fee1" ;;
    *)            echo "" ;;
  esac
}
# !! END SHA256 ALLOWLIST !!

# ── Platform detection ────────────────────────────────────────────────────────
_OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
_ARCH="$(uname -m)"

case "$_ARCH" in
  x86_64|amd64)  _ARCH="x64" ;;
  aarch64|arm64) _ARCH="arm64" ;;
  *)
    _err "Unsupported CPU architecture: $_ARCH"
    _err "Supported: x86_64 / amd64, aarch64 / arm64"
    exit 1 ;;
esac

case "$_OS" in
  linux)  _PLATFORM="linux-$_ARCH" ;;
  darwin) _PLATFORM="darwin-$_ARCH" ;;
  *)
    _err "Unsupported OS: $_OS"
    _err "Windows users: run install.ps1"
    _err "  iwr -useb https://install.sigmashake.com/install.ps1 | iex"
    exit 1 ;;
esac

# Resolve VERSION: explicit --version=X wins; otherwise pick the per-platform
# default so darwin doesn't get pinned to a linux version that was never
# published for it.
_DEFAULT_VERSION="$(_default_version_for "$_PLATFORM")"
if [ -z "$VERSION" ]; then
  VERSION="$_DEFAULT_VERSION"
  if [ -z "$_DEFAULT_VERSION" ]; then
    _err "No default version for platform: $_PLATFORM"
    _err "See https://sigmashake.com/pricing to subscribe, then re-run this installer."
    exit 1
  fi
elif [ -z "$_DEFAULT_VERSION" ] || [ "$VERSION" != "$_DEFAULT_VERSION" ]; then
  _err "no baked SHA256 for ${_PLATFORM}@${VERSION} (available: ${_DEFAULT_VERSION:-none})"
  _err "The requested release must be published and baked into this installer first."
  exit 1
fi

_EXPECTED_SHA="$(_sha256_for "$_PLATFORM")"
if [ -z "$_EXPECTED_SHA" ]; then
  _err "No SHA256 on record for platform: $_PLATFORM"
  _err "The platform release must be published and baked into this installer first."
  exit 1
fi

# ── Install target ────────────────────────────────────────────────────────────
# --install-path is the orchestrator-only exact-active-target mode. It replaces
# one already-probed `ssg` directory entry and never touches sibling assets.
if [ "$INSTALL_PATH_SET" = "1" ]; then
  case "$INSTALL_PATH" in
    */) _err "--install-path must not end with a directory separator"; exit 1 ;;
    /*) ;;
    *) _err "--install-path must be an absolute path"; exit 1 ;;
  esac
  if [ "$(basename "$INSTALL_PATH")" != "ssg" ] || [ ! -f "$INSTALL_PATH" ]; then
    _err "--install-path must name an existing ssg executable"
    exit 1
  fi
  INSTALL_DIR="$(dirname "$INSTALL_PATH")"
  if [ ! -w "$INSTALL_DIR" ]; then
    _err "active ssg directory is not writable: $INSTALL_DIR"
    exit 1
  fi
elif [ -z "$INSTALL_DIR" ]; then
  _LOCAL_BIN="$HOME/.local/bin"
  if mkdir -p "$_LOCAL_BIN" 2>/dev/null && [ -w "$_LOCAL_BIN" ]; then
    INSTALL_DIR="$_LOCAL_BIN"
  elif [ -w "/usr/local/bin" ]; then
    INSTALL_DIR="/usr/local/bin"
  else
    mkdir -p "$_LOCAL_BIN"
    INSTALL_DIR="$_LOCAL_BIN"
  fi
fi

# ── Prerequisites ─────────────────────────────────────────────────────────────
for _tool in curl tar; do
  if ! command -v "$_tool" >/dev/null 2>&1; then
    _err "Required tool not found: $_tool"
    exit 1
  fi
done

if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
  _err "SHA256 verification requires sha256sum (Linux) or shasum (macOS) — neither found"
  exit 1
fi

_checksum() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  else
    shasum -a 256 "$1" | awk '{print $1}'
  fi
}

_canonical_path() {
  _CANON_PATH="$1"
  if command -v realpath >/dev/null 2>&1; then
    realpath "$_CANON_PATH" 2>/dev/null && return
  fi
  if command -v readlink >/dev/null 2>&1; then
    readlink -f "$_CANON_PATH" 2>/dev/null && return
  fi
  _CANON_DIR="$(dirname "$_CANON_PATH")"
  _CANON_BASE="$(basename "$_CANON_PATH")"
  (cd -P "$_CANON_DIR" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$_CANON_BASE") \
    || printf '%s\n' "$_CANON_PATH"
}

_ssg_version_of() {
  # Bounded probe ONLY: without a timeout command, a broken/hostile shadowed
  # binary could hang the installer — skip the probe (path compare still runs).
  command -v timeout >/dev/null 2>&1 || return 0
  timeout 5 "$1" --version 2>/dev/null | awk 'NR == 1 { gsub(/\r/, ""); print; exit }'
}

# ── Header ────────────────────────────────────────────────────────────────────
printf '\n%sSigmaShake Installer%s  ssg %s · %s → %s\n\n' \
  "$_BOLD" "$_RESET" "$VERSION" "$_PLATFORM" "$INSTALL_DIR" >&2

# ── License notice ────────────────────────────────────────────────────────────
printf '%sIMPORTANT — Paid subscription required%s\n' "$_BOLD" "$_RESET" >&2
printf 'ssg requires an active paid SigmaShake subscription to operate.\n' >&2
printf 'Free-tier access has been discontinued.\n' >&2
printf '\n' >&2
printf '  Start a subscription: https://billing.sigmashake.com/start-checkout\n' >&2
printf '  Pricing details:      https://sigmashake.com/pricing\n' >&2
printf '\n' >&2
printf 'The binary will be installed now. You will be prompted to activate\n' >&2
printf 'your license on first use. Without an active subscription, governance\n' >&2
printf 'checks will not run.\n' >&2
printf '\n' >&2

# ── Download ──────────────────────────────────────────────────────────────────
_TMP="$(mktemp -d)"
_EXACT_STAGE=""
_cleanup() {
  if [ -n "$_EXACT_STAGE" ]; then rm -f "$_EXACT_STAGE"; fi
  rm -rf "$_TMP"
}
trap _cleanup EXIT INT TERM

_ARCHIVE="$_TMP/ssg.tgz"
_EXTRACT="$_TMP/extract"
# The CLI ships through the paid-funnel download CDN: /cli/<version>/<file>
# 302-redirects to the canonical GitHub Releases asset (the same artifact the
# Homebrew tap pulls). The tarball root holds the `ssg` binary + `public/`.
_CDN_URL="${BASE}/cli/$VERSION/ssg-$_PLATFORM.tar.gz"

_info "Downloading from the SigmaShake CDN..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" = "1" ]; then
  _warn "[dry-run] would fetch: $_CDN_URL"
else
  if ! curl -fsSL --connect-timeout 30 --max-time 120 -o "$_ARCHIVE" "$_CDN_URL"; then
    _err "Download failed."
    _err "URL: $_CDN_URL"
    _err "Check your connection, or see https://sigmashake.com/pricing"
    exit 1
  fi
  _DOWNLOAD_MS=$(($(_now_ms) - _T_PHASE))
fi

# ── Extract ───────────────────────────────────────────────────────────────────
_info "Extracting binaries..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" != "1" ]; then
  mkdir -p "$_EXTRACT"
  tar xzf "$_ARCHIVE" -C "$_EXTRACT"
  _SSG_BIN="$_EXTRACT/ssg"
  if [ ! -f "$_SSG_BIN" ]; then
    _err "Binary not found in archive (expected: ssg at the archive root)"
    exit 1
  fi
  _EXTRACT_MS=$(($(_now_ms) - _T_PHASE))
fi

# ── SHA256 integrity check ────────────────────────────────────────────────────
_info "Verifying integrity..."
_T_PHASE="$(_now_ms)"
if [ "$DRY_RUN" != "1" ]; then
  _ACTUAL="$(_checksum "$_SSG_BIN")"
  if [ "$_ACTUAL" != "$_EXPECTED_SHA" ]; then
    _err "SHA256 mismatch — refusing to install"
    _err "  expected: $_EXPECTED_SHA"
    _err "  got:      $_ACTUAL"
    _err "Report issues at: https://sigmashake.com/security"
    exit 1
  fi
  _VERIFY_MS=$(($(_now_ms) - _T_PHASE))
  _info "Integrity verified."
fi

# ── Install ───────────────────────────────────────────────────────────────────
if [ "$DRY_RUN" = "1" ]; then
  if [ "$INSTALL_PATH_SET" = "1" ]; then
    _warn "[dry-run] would atomically replace active binary: $INSTALL_PATH"
    _warn "[dry-run] sibling files and public/ would remain untouched"
  else
    _warn "[dry-run] would install to: $INSTALL_DIR"
    _warn "[dry-run] files: ssg  (+ any archive-root sidecars)"
    _warn "[dry-run] would also install: public/ (dashboard React SPA assets)"
  fi
  echo "" >&2
  _info "Dry-run complete — no files written."
  exit 0
fi

# Install every top-level file in the archive co-located so ssg auto-discovers
# its siblings. The CDN tarball ships `ssg` (+ `public/`, handled below); any
# future archive-root sidecars install the same way.
# Resolution order in ssg: SSG_HOOK_FAST_BIN env var → sibling of execPath → PATH.
_INSTALLED=0
if [ "$INSTALL_PATH_SET" = "1" ]; then
  # Stage on the target filesystem so the final rename is atomic. Exact-target
  # mode intentionally installs only the verified binary: a generic Bun/npm
  # bin directory may contain an unrelated `public/` tree or other sidecars.
  _EXACT_STAGE="$INSTALL_DIR/.ssg.new.$$"
  if command -v install >/dev/null 2>&1; then
    install -m 755 "$_SSG_BIN" "$_EXACT_STAGE" 2>/dev/null || {
      _err "Failed to stage active ssg replacement"
      exit 1
    }
  else
    cp "$_SSG_BIN" "$_EXACT_STAGE" && chmod 755 "$_EXACT_STAGE" || {
      _err "Failed to stage active ssg replacement"
      exit 1
    }
  fi
  if ! mv -f "$_EXACT_STAGE" "$INSTALL_PATH"; then
    _err "Failed to atomically replace active ssg binary"
    exit 1
  fi
  _EXACT_STAGE=""
  _INSTALLED=1
else
  mkdir -p "$INSTALL_DIR" 2>/dev/null || {
    _err "Cannot create install directory: $INSTALL_DIR"
    _err "Pick a writable location and rerun:"
    _err "  curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=<dir>"
    exit 1
  }
  for _f in "$_EXTRACT"/*; do
    [ -f "$_f" ] || continue   # skip public/ (a directory)
    _NAME="$(basename "$_f")"
    _COPY_OK=0
    if command -v install >/dev/null 2>&1; then
      if install -m 755 "$_f" "$INSTALL_DIR/$_NAME" 2>/dev/null; then
        _COPY_OK=1
      elif cp "$_f" "$INSTALL_DIR/$_NAME" && chmod 755 "$INSTALL_DIR/$_NAME"; then
        _COPY_OK=1
      fi
    else
      if cp "$_f" "$INSTALL_DIR/$_NAME" && chmod 755 "$INSTALL_DIR/$_NAME"; then
        _COPY_OK=1
      fi
    fi
    if [ "$_COPY_OK" != "1" ]; then
      _err "Failed to install $_NAME to $INSTALL_DIR (permissions? disk full?)"
      _err "Rerun with a writable location:"
      _err "  curl -fsSL https://install.sigmashake.com | sh -s -- --install-dir=<dir>"
      exit 1
    fi
    _INSTALLED=$((_INSTALLED + 1))
  done
fi

if [ "$_INSTALLED" -eq 0 ]; then
  _err "No binaries extracted from the archive — download may be corrupt"
  exit 1
fi

# Install the React SPA assets adjacent to the binary. `ssg serve` resolves
# its public dir via candidate #3 (`<execDir>/public/`) when run outside a
# checkout — without this step a stale public/ from a previous install would
# silently serve the old dashboard UI after a binary-only refresh.
if [ "$INSTALL_PATH_SET" != "1" ] && [ -d "$_EXTRACT/public" ]; then
  _PUBLIC_DST="$INSTALL_DIR/public"
  mkdir -p "$_PUBLIC_DST"
  find "$_PUBLIC_DST" -mindepth 1 -delete 2>/dev/null || true
  cp -R "$_EXTRACT/public/." "$_PUBLIC_DST/"
  _info "Installed dashboard assets to $_PUBLIC_DST"
fi

_info "Installed $_INSTALLED files to $INSTALL_DIR"

if [ "$YES" = "1" ]; then
  _write_yes_consent
fi

# ── PATH hint ─────────────────────────────────────────────────────────────────
hash -r 2>/dev/null || true
_PATH_SSG="$(command -v ssg 2>/dev/null || true)"
_SHADOW_WARN=0
_SHADOW_VERSION=""
_SHADOW_PATH_REAL=""
_INSTALLED_SSG_REAL="$(_canonical_path "$INSTALL_DIR/ssg")"
_SHADOW_SEEN=""

_shadow_mark_seen() {
  # "|"-delimited (not space-delimited): real paths can contain spaces.
  _SHADOW_SEEN="$_SHADOW_SEEN|$1|"
}

_shadow_is_seen() {
  case "$_SHADOW_SEEN" in
    *"|$1|"*) return 0 ;;
    *) return 1 ;;
  esac
}

# Case-matched next-step advice for a shadowing/stale `ssg` found at $1 —
# shared by the primary `command -v` hit below and the full-PATH walk
# (OI-044) so every stale copy gets the same actionable guidance, not just
# the first.
_shadow_advice() {
  case "$1" in
    *pnpm*|*/.local/share/pnpm/*)
      _warn "  Remove the old pnpm install:  pnpm remove -g @sigmashake/ssg" ;;
    *npm*|*/node_modules/.bin/ssg|*/.nvm/*|*/.npm-global/*)
      _warn "  Remove the old npm install:   npm uninstall -g @sigmashake/ssg" ;;
    *bun*|*/.bun/bin/ssg)
      _warn "  Remove the old Bun install:   bun remove -g @sigmashake/ssg" ;;
    /usr/local/bin/ssg)
      _warn "  Remove the old copy:          sudo rm -f \"$1\"" ;;
    */.sigmashake/bin/ssg)
      # This path is created + symlinked by SigmaShake Desktop
      # (PathRegistration.swift) and prepended to PATH via ~/.zprofile — it is
      # also the governance hook binary on a machine with Desktop installed.
      # Never tell the user to delete or overwrite it in place: that breaks
      # the running Desktop app and can wedge every governed tool call.
      _warn "  This is SigmaShake Desktop's managed ssg — do not delete it."
      _warn "  Desktop's bundled CLI is taking precedence over this fresh install."
      _warn "  Preferred:       update SigmaShake Desktop so its bundled ssg matches."
      _warn "  Or, for this shell only:  export PATH=\"$INSTALL_DIR:\$PATH\" && hash -r" ;;
    *)
      _warn "  Remove or upgrade the old install at the path above." ;;
  esac
}

if [ -z "$_PATH_SSG" ]; then
  printf '\n' >&2
  _warn "ssg is not yet on your PATH."
  _SHELL_RC=""
  case "$(basename "${SHELL:-sh}")" in
    zsh)  _SHELL_RC="~/.zshrc" ;;
    fish)
      _warn "  Run once:  fish_add_path $INSTALL_DIR"
      _SHELL_RC="" ;;
    *)    _SHELL_RC="~/.bashrc" ;;
  esac
  if [ -n "$_SHELL_RC" ]; then
    _PATH_EX='$PATH'
  _warn "  Add to $_SHELL_RC:  export PATH=\"$INSTALL_DIR:$_PATH_EX\""
  fi
  _warn "  Then open a new terminal or run:  export PATH=\"$INSTALL_DIR:\$PATH\""
  printf '\n' >&2
else
  _SHADOW_PATH_REAL="$(_canonical_path "$_PATH_SSG")"
  _SHADOW_VERSION="$(_ssg_version_of "$_PATH_SSG" || true)"
  # Warn on a different resolved path, or (when the bounded probe ran) a
  # different version at the same path. An empty probe result is "unknown",
  # not a mismatch — never false-positive on probe unavailability.
  if [ "$_SHADOW_PATH_REAL" != "$_INSTALLED_SSG_REAL" ]; then
    _SHADOW_WARN=1
  elif [ -n "$_SHADOW_VERSION" ] && [ "$_SHADOW_VERSION" != "$VERSION" ]; then
    _SHADOW_WARN=1
  fi
  _shadow_mark_seen "$_SHADOW_PATH_REAL"
fi

printf '\n' >&2
_info "ssg ${VERSION} installed to $INSTALL_DIR."
if [ "$_SHADOW_WARN" = "1" ]; then
  printf '\n' >&2
  _warn "PATH SHADOWING DETECTED: running 'ssg' still resolves to:"
  _warn "  $_PATH_SSG"
  _warn "  resolved path: $_SHADOW_PATH_REAL"
  _warn "  version there: ${_SHADOW_VERSION:-<unreadable>} (expected: $VERSION)"
  _warn "  installed binary: $INSTALL_DIR/ssg"
  _shadow_advice "$_PATH_SSG"
  _warn "  Or put this install first:    export PATH=\"$INSTALL_DIR:\$PATH\""
  _warn "  Then clear the shell cache:   hash -r"
  _warn "  If it still resolves stale, open a new shell and retry:  ssg --version"
fi

# Walk the REST of PATH beyond the first `command -v` hit — a stale copy
# further down PATH is invisible to the check above and can silently take
# over again after any future PATH reorder (OI-044, ledger: `which -a ssg`
# on a real host surfaced 4 distinct copies, only the first of which the old
# check ever saw). Warn once per unique resolved path; never re-report
# $_PATH_SSG (already handled above) or a copy that IS the binary we just
# installed (e.g. a duplicate PATH entry pointing at $INSTALL_DIR).
if [ -n "$_PATH_SSG" ]; then
  _SHADOW_OLD_IFS="$IFS"
  IFS=':'
  for _SHADOW_DIR in $PATH; do
    IFS="$_SHADOW_OLD_IFS"
    [ -z "$_SHADOW_DIR" ] && _SHADOW_DIR="."
    _SHADOW_CAND="$_SHADOW_DIR/ssg"
    if [ -f "$_SHADOW_CAND" ] && [ -x "$_SHADOW_CAND" ]; then
      _SHADOW_CAND_REAL="$(_canonical_path "$_SHADOW_CAND")"
      if ! _shadow_is_seen "$_SHADOW_CAND_REAL"; then
        _shadow_mark_seen "$_SHADOW_CAND_REAL"
        if [ "$_SHADOW_CAND_REAL" != "$_INSTALLED_SSG_REAL" ]; then
          _SHADOW_CAND_VERSION="$(_ssg_version_of "$_SHADOW_CAND" || true)"
          printf '\n' >&2
          _warn "ADDITIONAL STALE COPY ON PATH: $_SHADOW_CAND"
          _warn "  resolved path: $_SHADOW_CAND_REAL"
          _warn "  version there: ${_SHADOW_CAND_VERSION:-<unreadable>} (expected: $VERSION)"
          _shadow_advice "$_SHADOW_CAND"
        fi
      fi
    fi
    IFS=':'
  done
  IFS="$_SHADOW_OLD_IFS"
fi
printf '\n' >&2

# ── Subscription reminder ─────────────────────────────────────────────────────
printf '%sSubscription required to activate%s\n' "$_BOLD" "$_RESET" >&2
printf 'ssg is installed but will not govern tool calls without an active\n' >&2
printf 'paid subscription. Activate your license before running ssg init:\n' >&2
printf '\n' >&2
printf '  1. Start a subscription: https://billing.sigmashake.com/start-checkout\n' >&2
printf '  2. Activate your license: ssg auth login\n' >&2
printf '  3. Wire agent protection: ssg init\n' >&2
printf '  4. Verify governance:     ssg status\n' >&2
printf '\n' >&2

# ── First-time setup + daemon + account link + status (all timed) ────────────
_INIT_MS="skipped"
_DAEMON_MS="skipped"
_AUTH_MS="skipped"
_STATUS_MS="skipped"
_RESULT="ok"

_ssg_daemon_running() {
  "$INSTALL_DIR/ssg" daemon --status >/dev/null 2>&1
}

_RUN_SETUP=0
if [ "$NO_SETUP" = "0" ]; then
  if [ "$YES" = "1" ] || { [ -t 0 ] && [ -t 1 ]; }; then
    _RUN_SETUP=1
  fi
fi

if [ "$_RUN_SETUP" = "1" ]; then
  printf '%sRunning first-time setup...%s\n\n' "$_BOLD" "$_RESET" >&2
  _T_PHASE="$(_now_ms)"
  if [ "$YES" = "1" ]; then
    # Installer -y (flag or SSG_INSTALL_YES=1) is explicit ToS consent:
    # recorded above via _write_yes_consent, so --accept-terms keeps init
    # non-interactive.
    if "$INSTALL_DIR/ssg" init -y --accept-terms; then _INIT_RC=0; else _INIT_RC=$?; fi
  else
    # Interactive-TTY consent path (no -y): `ssg init` scaffolds .sigmashake/
    # and wires the governance hook for every detected AI client. The -y flag
    # here only accepts THIS plan prompt so we don't stall mid-script waiting
    # on a keypress — it is not ToS consent (no consent.json write above).
    if "$INSTALL_DIR/ssg" init -y; then _INIT_RC=0; else _INIT_RC=$?; fi
  fi
  _INIT_MS=$(($(_now_ms) - _T_PHASE))
  if [ "$_INIT_RC" != "0" ]; then
    _err "First-time setup failed (exit $_INIT_RC). Rerun: ssg init -y --accept-terms"
    exit "$_INIT_RC"
  fi

  # ── Daemon ───────────────────────────────────────────────────────────────
  _T_PHASE="$(_now_ms)"
  if _ssg_daemon_running; then
    _info "Governance daemon already running."
  else
    # Fully detach stdio from the installer's own descriptors (never /dev/null
    # alone — a bare `&` still inherits stdin/stdout, which on a `curl | sh`
    # or `$(...)`-captured invocation keeps that pipe's write end open and
    # the outer command/capture hanging long after this script exits).
    # setsid additionally detaches from the controlling session when present;
    # nohup (always present on linux/darwin) is the fallback.
    _SSG_HOME="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
    mkdir -p "$_SSG_HOME" 2>/dev/null || true
    _DAEMON_LOG="$_SSG_HOME/daemon-install.log"
    if command -v setsid >/dev/null 2>&1; then
      setsid "$INSTALL_DIR/ssg" daemon </dev/null >"$_DAEMON_LOG" 2>&1 &
    else
      nohup "$INSTALL_DIR/ssg" daemon </dev/null >"$_DAEMON_LOG" 2>&1 &
    fi
    _DAEMON_UP=0
    _DAEMON_WAITED=0
    while [ "$_DAEMON_WAITED" -lt 10000 ]; do
      if _ssg_daemon_running; then
        _DAEMON_UP=1
        break
      fi
      sleep 0.25
      _DAEMON_WAITED=$((_DAEMON_WAITED + 250))
    done
    if [ "$_DAEMON_UP" = "1" ]; then
      _info "Governance daemon started."
    else
      _warn "Governance daemon did not come up within 10s. Start it manually: ssg daemon"
      _RESULT="partial"
    fi
  fi
  _DAEMON_MS=$(($(_now_ms) - _T_PHASE))

  # ── Account link ─────────────────────────────────────────────────────────
  if [ -n "${SSG_INSTALL_TOKEN:-}" ]; then
    _T_PHASE="$(_now_ms)"
    if "$INSTALL_DIR/ssg" auth login --no-browser; then
      _AUTH_MS=$(($(_now_ms) - _T_PHASE))
    else
      _AUTH_RC=$?
      _AUTH_MS=$(($(_now_ms) - _T_PHASE))
      _warn "Account link failed (exit $_AUTH_RC). Rerun: ssg auth login"
      _RESULT="partial"
    fi
    # Token hygiene: single-use / short-TTL — drop it from this process's
    # environment the moment the child that consumes it has returned.
    unset SSG_INSTALL_TOKEN
  elif [ "$NO_AUTH" = "0" ] && [ -t 1 ] && [ -r /dev/tty ]; then
    _T_BEFORE_AUTH="$(_now_ms)"
    if "$INSTALL_DIR/ssg" auth login </dev/tty; then
      :
    else
      _warn "Account link failed. Rerun: ssg auth login"
      _RESULT="partial"
    fi
    _AUTH_EXCLUDE_MS=$(($(_now_ms) - _T_BEFORE_AUTH))
    _AUTH_MS="interactive"
  else
    printf '%sNext steps:%s  ssg auth login  →  ssg status\n\n' "$_BOLD" "$_RESET" >&2
  fi

  # ── Status ───────────────────────────────────────────────────────────────
  _T_PHASE="$(_now_ms)"
  if "$INSTALL_DIR/ssg" status; then
    :
  else
    _STATUS_RC=$?
    _warn "ssg status reported a problem (exit $_STATUS_RC). Check: ssg status"
    _RESULT="partial"
  fi
  _STATUS_MS=$(($(_now_ms) - _T_PHASE))
else
  printf '%sNext steps:%s  ssg auth login  →  ssg init  →  ssg status\n\n' "$_BOLD" "$_RESET" >&2
fi

# ── Timing line + JSON ─────────────────────────────────────────────────────────
# result=partial means a STEP FAILED (daemon didn't come up, auth failed, or
# status failed) above — never for a step skipped by design (--no-setup,
# --no-auth, or no token/no TTY for auth). over-budget is purely a total_ms
# vs target_ms signal and does not itself imply any step failed.
_TARGET_MS=60000
_TOTAL_MS=$(($(_now_ms) - _T_SCRIPT_START - _AUTH_EXCLUDE_MS))
if [ "$_TOTAL_MS" -gt "$_TARGET_MS" ]; then
  _RESULT="over-budget"
fi

printf 'ssg-install-timing: installer=sh os=%s arch=%s version=%s fetch_ms=skipped download_ms=%s verify_ms=%s extract_ms=%s init_ms=%s daemon_ms=%s auth_ms=%s status_ms=%s total_ms=%s target_ms=%s result=%s\n' \
  "$_OS" "$_ARCH" "$VERSION" "$_DOWNLOAD_MS" "$_VERIFY_MS" "$_EXTRACT_MS" "$_INIT_MS" "$_DAEMON_MS" "$_AUTH_MS" "$_STATUS_MS" "$_TOTAL_MS" "$_TARGET_MS" "$_RESULT" >&2

_TIMING_JSON_DIR="${SIGMASHAKE_HOME:-$HOME/.sigmashake}"
_TIMING_JSON_PATH="$_TIMING_JSON_DIR/install-timing.json"
_RECORDED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")"
if mkdir -p "$_TIMING_JSON_DIR" 2>/dev/null; then
  cat > "$_TIMING_JSON_PATH" 2>/dev/null <<EOF || true
{
  "installer": "sh",
  "os": "$_OS",
  "arch": "$_ARCH",
  "version": "$VERSION",
  "fetch_ms": "skipped",
  "download_ms": $(_json_val "$_DOWNLOAD_MS"),
  "verify_ms": $(_json_val "$_VERIFY_MS"),
  "extract_ms": $(_json_val "$_EXTRACT_MS"),
  "init_ms": $(_json_val "$_INIT_MS"),
  "daemon_ms": $(_json_val "$_DAEMON_MS"),
  "auth_ms": $(_json_val "$_AUTH_MS"),
  "status_ms": $(_json_val "$_STATUS_MS"),
  "total_ms": $(_json_val "$_TOTAL_MS"),
  "target_ms": $_TARGET_MS,
  "result": "$_RESULT",
  "recorded_at": "$_RECORDED_AT"
}
EOF
fi

# result=partial is the only non-fatal-but-non-zero outcome: a real step
# failed above (its own _warn/next-step hint was already printed at the
# point of failure). over-budget and steps skipped by design both exit 0.
if [ "$_RESULT" = "partial" ]; then
  exit 3
fi
exit 0
