#!/usr/bin/env sh
# Installer for the gkit CLI binary.
# Usage:
#   curl -fsSL https://get.gkit.dev | sh
#   GKIT_VERSION=v0.2.0 curl -fsSL https://get.gkit.dev | sh
#   GKIT_INSTALL_DIR=$HOME/.local/bin curl -fsSL https://get.gkit.dev | sh
#   NO_COLOR=1 curl -fsSL https://get.gkit.dev | sh
#   GKIT_NO_INIT=1 curl -fsSL https://get.gkit.dev | sh   # skip post-install host init prompt
#   GKIT_NO_PREREQ_INSTALL=1 curl -fsSL https://get.gkit.dev | sh   # skip Docker / cosign prereq prompts
#
# Source: https://github.com/Gorillized/gorillakit
# Verify the script by inspecting it before piping to sh:
#   curl -fsSL https://get.gkit.dev > install.sh && less install.sh
#
# Verification model: each tarball's sha256 is verified against the
# release's checksums.txt (mandatory — install aborts on mismatch).
# On top of that, full Sigstore-keyless verification of checksums.txt
# runs when ALL of the following hold:
#   - cosign is on PATH (`command -v cosign`)
#   - cosign supports the bundle format (≥ 2.5 — has the `--bundle`
#     flag in `verify-blob --help`)
#   - the release shipped a checksums.bundle (releases ≥ v0.5.0)
# Any of those failing prints a friendly warning and falls back to
# sha256-only verification. The script aborts (exit 1) only when
# sha256 fails OR cosign verification was attempted and returned a
# verification error (malformed bundle, wrong identity, tampered
# signature). Install cosign 2.5+ and pin GKIT_VERSION to v0.5.0 or
# later for end-to-end Sigstore identity attestation. Step 2's prereq
# check offers to install cosign from sigstore's GitHub releases on
# Linux — that's the same trust root cosign verifies against, so the
# trust boundary doesn't widen beyond what the operator already accepts
# by using cosign at all. GKIT_NO_PREREQ_INSTALL=1 opts out.
set -eu

# Repo is referenced only by the cosign identity regex and the docs URL.
# The actual binary, checksums, and signature files are fetched from the
# CDN below — that decoupling lets the source repository stay private
# while distribution remains anonymous, and it pins us to a CloudFront
# edge cache instead of github.com's anonymous rate limits.
REPO="Gorillized/gorillakit"
CDN="https://get.gkit.dev"
VERSION="${GKIT_VERSION:-latest}"
INSTALL_DIR="${GKIT_INSTALL_DIR:-/usr/local/bin}"

# ---------------------------------------------------------------------------
# Presentation. ANSI escapes are enabled only when stdout is a TTY and
# NO_COLOR (https://no-color.org) is unset. The status glyphs are plain
# Unicode (U+2713, U+26A0, U+2717, U+2192) — not emoji — so they render
# in any monospace font, including older Linux consoles.
# ---------------------------------------------------------------------------

if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  C_RESET=$(printf '\033[0m')
  C_BOLD=$(printf '\033[1m')
  C_DIM=$(printf '\033[2m')
  C_RED=$(printf '\033[31m')
  C_GREEN=$(printf '\033[32m')
  C_YELLOW=$(printf '\033[33m')
  C_CYAN=$(printf '\033[36m')
else
  C_RESET=""; C_BOLD=""; C_DIM=""
  C_RED=""; C_GREEN=""; C_YELLOW=""; C_CYAN=""
fi

TOTAL_STEPS=5
CURRENT_STEP=0

# Pinned cosign version used by the prereq auto-install path. The
# installer downloads cosign-linux-<arch> from sigstore's GitHub releases
# (the same trust root cosign verifies against — no widening of the
# trust boundary beyond what the operator already accepts by using
# cosign at all). 2.5.0 is the floor that supports the bundle format
# install.sh's verify path expects.
COSIGN_PREREQ_VERSION="v2.5.0"

banner() {
  printf '\n'
  printf '  %sGorilla Kit (gkit) · Installer%s\n' "$C_BOLD" "$C_RESET"
  printf '  %sOpen-source toolkit of single-purpose app-services,%s\n' "$C_DIM" "$C_RESET"
  printf '  %sbuilt for AI agents.%s\n' "$C_DIM" "$C_RESET"
  printf '\n'
}

step() {
  CURRENT_STEP=$((CURRENT_STEP + 1))
  printf '\n%s[%d/%d]%s %s%s%s\n' \
    "$C_CYAN$C_BOLD" "$CURRENT_STEP" "$TOTAL_STEPS" "$C_RESET" \
    "$C_BOLD" "$1" "$C_RESET"
}
ok()   { printf '      %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$1"; }
note() { printf '      %s%s%s\n' "$C_DIM" "$1" "$C_RESET"; }
warn() { printf '      %s⚠%s %s\n' "$C_YELLOW" "$C_RESET" "$1"; }
err()  {
  printf '\n  %s✗ error:%s %s\n\n' "$C_RED$C_BOLD" "$C_RESET" "$1" >&2
  exit 1
}

# ---------------------------------------------------------------------------

banner

# --- step 1: detect platform ------------------------------------------------
step "Detecting platform"

OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
  linux|darwin) ;;
  *) err "unsupported OS: $OS (linux and darwin only)" ;;
esac

ARCH="$(uname -m)"
case "$ARCH" in
  x86_64|amd64) ARCH=amd64 ;;
  arm64|aarch64) ARCH=arm64 ;;
  *) err "unsupported arch: $ARCH (amd64 and arm64 only)" ;;
esac

ok "$OS / $ARCH"

# --- step 2: check prerequisites -------------------------------------------
# Operator-friendly prereq surface: detect missing Docker / cosign and
# offer to install them via the system package manager (Linux) before
# step 4 even tries to download. macOS gets a "install Docker Desktop
# manually" note. Operator declines, no auto-install path, or
# GKIT_NO_PREREQ_INSTALL=1 → continue with the existing fallback
# behaviour (cosign-absent fallback in step 4 covers the no-cosign path;
# Docker errors surface later when `gkit host init` actually needs it).
step "Checking prerequisites"

# Probe /dev/tty openability and open it once as fd 3. Shared by the
# prereq prompts here and the post-install walkthrough below — opening
# once avoids the under-test-harness issue where /dev/tty is
# sed-rewritten to a regular file and re-opening would re-seek to offset
# 0 and read the same line forever. The probe is a subshell-redirect
# rather than `[ -e /dev/tty ]`: the device node exists on most systems
# even when no controlling session is attached, but opening it fails.
TTY_OK=0
if ( true </dev/tty ) 2>/dev/null; then
  exec 3</dev/tty
  TTY_OK=1
fi

# PREREQ_PROMPT enabled iff (a) we have a controlling tty AND (b) the
# operator hasn't opted out via GKIT_NO_PREREQ_INSTALL=1.
PREREQ_PROMPT=0
if [ "$TTY_OK" = "1" ] && [ "${GKIT_NO_PREREQ_INSTALL:-}" != "1" ]; then
  PREREQ_PROMPT=1
fi

# Detect Linux package manager. macOS gets PKG_MGR="" by design — Docker
# Desktop has its own dance (no auto-install) and cosign on macOS is left
# to the operator. Linux without dnf/apt-get also falls through to "".
PKG_MGR=""
if [ "$OS" = "linux" ]; then
  if command -v dnf >/dev/null 2>&1; then
    PKG_MGR=dnf
  elif command -v apt-get >/dev/null 2>&1; then
    PKG_MGR=apt
  fi
fi

# prereq_yn — read a y/N answer from fd 3 (default Y). Prints "y" or "n".
prereq_yn() {
  ans=""
  IFS= read -r ans <&3 || ans=""
  case "${ans:-Y}" in
    ''|y|Y|yes|YES|Yes) printf 'y' ;;
    *) printf 'n' ;;
  esac
}

# prereq_sudo_run — run a privileged command, escalating with sudo when
# we're not already root. <&3 supplies sudo's password prompt input
# (same pattern as run_sudo_gkit below). Returns the wrapped command's
# exit code, or 1 if no escalation path exists.
prereq_sudo_run() {
  if [ "$(id -u)" = "0" ]; then
    "$@"
  elif command -v sudo >/dev/null 2>&1; then
    # shellcheck disable=SC2024 # <&3 supplies sudo's password prompt input
    sudo "$@" <&3
  else
    return 1
  fi
}

# install_docker_via_pkg — install Docker through the detected package
# manager + enable the daemon. Best-effort: a failed install surfaces a
# warning and lets the script continue (the operator will see the
# downstream `gkit host init` Docker-not-reachable error if Docker is
# still unavailable).
install_docker_via_pkg() {
  case "$PKG_MGR" in
    dnf)
      prereq_sudo_run dnf install -y docker || return 1
      prereq_sudo_run systemctl enable --now docker || true
      ;;
    apt)
      prereq_sudo_run apt-get update -y || return 1
      prereq_sudo_run apt-get install -y docker.io || return 1
      prereq_sudo_run systemctl enable --now docker || true
      ;;
    *) return 1 ;;
  esac
}

# install_cosign_from_github — fetch cosign-linux-<arch> from sigstore's
# GitHub releases and install to /usr/local/bin. Pinned to
# COSIGN_PREREQ_VERSION (2.5.0+ supports the bundle format the verify
# path expects). On success, the next step's `command -v cosign` probe
# picks up the freshly-installed binary and the full Sigstore identity
# attestation runs against the gkit release bundle.
install_cosign_from_github() {
  cosign_arch="$1"
  cosign_url="https://github.com/sigstore/cosign/releases/download/${COSIGN_PREREQ_VERSION}/cosign-linux-${cosign_arch}"
  cosign_dir="$(mktemp -d)"
  if ! curl -fsSL "$cosign_url" -o "${cosign_dir}/cosign" 2>"${cosign_dir}/curl.err"; then
    cat "${cosign_dir}/curl.err" >&2 2>/dev/null || true
    rm -rf "$cosign_dir"
    return 1
  fi
  chmod 0755 "${cosign_dir}/cosign"
  if [ -w /usr/local/bin ] || [ "$(id -u)" = "0" ]; then
    install -m 0755 "${cosign_dir}/cosign" /usr/local/bin/cosign
    rc=$?
  elif command -v sudo >/dev/null 2>&1; then
    # shellcheck disable=SC2024 # <&3 supplies sudo's password prompt input
    sudo install -m 0755 "${cosign_dir}/cosign" /usr/local/bin/cosign <&3
    rc=$?
  else
    rc=1
  fi
  rm -rf "$cosign_dir"
  return $rc
}

# Docker check.
if ! command -v docker >/dev/null 2>&1; then
  warn "Docker not on PATH"
  if [ "$PREREQ_PROMPT" = "1" ] && [ -n "$PKG_MGR" ]; then
    printf '      %sInstall Docker now via %s?%s [Y/n] ' "$C_BOLD" "$PKG_MGR" "$C_RESET"
    if [ "$(prereq_yn)" = "y" ]; then
      if install_docker_via_pkg; then
        ok "Docker installed"
      else
        warn "Docker install failed — install manually before \`gkit host init\`"
      fi
    else
      note "skipped — install Docker manually before \`gkit host init\`"
    fi
  elif [ "$OS" = "darwin" ]; then
    note "install Docker Desktop manually before \`gkit host init\`:"
    note "  https://docs.docker.com/desktop/install/mac-install/"
  else
    note "install Docker manually before \`gkit host init\`"
  fi
else
  ok "docker"
fi

# cosign check.
if ! command -v cosign >/dev/null 2>&1; then
  warn "cosign not on PATH (Sigstore signature verification disabled)"
  if [ "$PREREQ_PROMPT" = "1" ] && [ "$OS" = "linux" ]; then
    printf '      %sInstall cosign now?%s [Y/n] ' "$C_BOLD" "$C_RESET"
    if [ "$(prereq_yn)" = "y" ]; then
      if install_cosign_from_github "$ARCH"; then
        ok "cosign ${COSIGN_PREREQ_VERSION} installed"
      else
        warn "cosign install failed — continuing with checksum-only verification"
      fi
    else
      note "skipped — release signature verification will fall back to sha256-only"
    fi
  elif [ "$OS" = "darwin" ]; then
    note "install cosign manually for Sigstore identity attestation:"
    note "  https://docs.sigstore.dev/cosign/installation"
  fi
else
  ok "cosign"
fi

# --- step 3: resolve version ------------------------------------------------
step "Resolving version"

if [ "$VERSION" = "latest" ]; then
  # ${CDN}/latest.txt is a single-line file (`v<x.y.z>\n`) written by
  # host-release.yml's mirror step after each successful release. It
  # carries a 5-min Cache-Control on the edge — racing a brand-new
  # release will see the previous version for up to that window.
  note "resolving latest release"
  # Migrated to the same capture-and-dump shape as the curl/sha/cosign/
  # tar/install blocks below: network failure → operator sees the
  # underlying curl diagnostic before the indented err(); parse failure
  # (empty body) → its own distinct headline. (See the maintainer note
  # at the start of step 4 for why the assignment + `|| { ... }` must
  # stay one logical statement.)
  LATEST_OUT=$(curl -fsSL "${CDN}/latest.txt" 2>&1) || {
    printf '%s\n' "$LATEST_OUT" >&2
    err "could not fetch ${CDN}/latest.txt; pin GKIT_VERSION=v<x.y.z>"
  }
  VERSION="$(printf '%s' "$LATEST_OUT" | head -n1 | tr -d '[:space:]')"
  [ -n "$VERSION" ] || err "${CDN}/latest.txt returned no version; pin GKIT_VERSION=v<x.y.z>"
fi
case "$VERSION" in
  v*) ;;
  *)  err "GKIT_VERSION must start with 'v' (got '$VERSION')" ;;
esac

ok "$VERSION"

# Versioned path is immutable on the CDN (1y immutable Cache-Control set
# by the mirror step), so once a release lands at the edge there's no
# revalidation cost on subsequent installs.
BASE="${CDN}/releases/${VERSION}"
TAR="gkit_${VERSION}_${OS}_${ARCH}.tar.gz"

# --- step 4: download + verify ---------------------------------------------
step "Downloading + verifying"
note "$BASE/$TAR"

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# Capture curl's stderr so a network/CDN failure ("curl: (22) … 403",
# DNS errors, etc.) doesn't leak left-flush. On success the captured
# output is empty (curl is silent under -s); on failure it's dumped to
# stderr verbatim so the operator can see the underlying reason before
# the script's own indented err().
#
# Tradeoff: a non-fatal warning emitted alongside exit 0 — say a
# deprecation notice from a future cosign / tar / sha256sum — is also
# discarded by this pattern. We accept that for the curl|sh-installer
# shape: the success path is supposed to be quiet, and exit-0
# warnings rarely require operator action mid-install. Failures
# always surface verbatim.
#
# Maintainer note: keep `OUT=$(cmd 2>&1) || { ... }` on one logical
# statement. Splitting the assignment from the `|| { ... }` across two
# statements makes `set -e` behaviour depend on the shell — dash (which
# is /bin/sh on Debian/Ubuntu) silently bails on a failed command
# substitution before the handler runs. The `||` short-circuit pins
# this into POSIX's "inspected context" rule and keeps the error
# handling reliable. Same pattern is reused for sha256 / cosign / tar /
# install below; preserve the shape, don't refactor it apart.
TAR_DL_OUT=$(curl -fsSL "${BASE}/${TAR}" -o "${TMP}/${TAR}" 2>&1) || {
  printf '%s\n' "$TAR_DL_OUT" >&2
  err "download failed: ${TAR}"
}
SUMS_DL_OUT=$(curl -fsSL "${BASE}/checksums.txt" -o "${TMP}/checksums.txt" 2>&1) || {
  printf '%s\n' "$SUMS_DL_OUT" >&2
  err "download failed: checksums.txt"
}
ok "downloaded ${TAR}"

# sha256 verify (mandatory). Two distinct failure modes get distinct
# err() headlines:
#   1. The tarball isn't listed in checksums.txt (catastrophe: release was
#      published without our platform's entry).
#   2. The listed sha doesn't match what we downloaded (corrupt download
#      or — much worse — a tampered tarball).
# -F treats the pattern as a fixed string so the dots in the filename
# don't match arbitrary chars; -- guards against a tarball name that
# somehow starts with a dash. Each line of checksums.txt is exactly
# `<hash>  <filename>`, one filename per line, so no anchor is needed.
grep -F -- "  ${TAR}" "${TMP}/checksums.txt" > "${TMP}/${TAR}.sha256" \
  || err "no checksum entry for ${TAR} in checksums.txt"

# Subshell's combined output is captured so a checksum mismatch
# ("FILE: FAILED") doesn't leak left-flush — dumped to stderr only on
# failure, before the indented err().
SHA_OUT=$(
  cd "$TMP"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum -c "${TAR}.sha256" 2>&1
  else
    shasum -a 256 -c "${TAR}.sha256" 2>&1
  fi
) || {
  printf '%s\n' "$SHA_OUT" >&2
  err "sha256 verification failed for ${TAR}"
}
ok "sha256 verified"

# cosign verify (best-effort). Only runs if cosign is on PATH; we don't
# pull cosign down ourselves because that would expand the trust
# boundary of this script (now you'd be trusting a second installer).
if command -v cosign >/dev/null 2>&1; then
  COSIGN_HELP="$(cosign verify-blob --help 2>&1 || true)"
  if ! printf '%s' "$COSIGN_HELP" | grep -q -- '--bundle'; then
    # cosign 2.4 or older — no bundle support.
    warn "cosign too old (need 2.5+); falling back to checksum-only verification."
    note "upgrade cosign for Sigstore identity attestation."
  elif ! curl -fsSL "${BASE}/checksums.bundle" -o "${TMP}/checksums.bundle" 2>/dev/null; then
    # bundle 404 — old release without a bundle (e.g. v0.4.x).
    warn "no signature bundle available for ${VERSION}; falling back to checksum-only verification."
    note "tarball sha256 was verified above; Sigstore identity attestation was skipped."
  else
    # cosign 2.5+ with bundle. Heuristic: presence of `--new-bundle-format`
    # in help → cosign 2.5–3.0.5 (flag still accepted/required for opt-in).
    # Absence → cosign 3.0.6+ (flag was removed). Future cosign releases
    # that change help-text formatting could break this detection — the
    # ultimate guardrail is cosign's own exit code (verification fails
    # loudly via err()).
    #
    # Identity is pinned to tag-triggered runs only. workflow_dispatch
    # from a branch (e.g. main) would mint a cert with @refs/heads/main,
    # which this regex rejects. The supported re-cut path is "Run
    # workflow" with the tag selected as the source ref — that yields
    # github.ref = refs/tags/v<x>, which matches.
    # Capture cosign's combined stdout+stderr. On success it's discarded so
    # the script's own `ok`/`err` are the only visible output (cosign emits
    # "setting TUF refresh period…" + "Verified OK" otherwise — left-flush
    # noise that breaks the column-aligned look). On failure we dump it to
    # stderr before err() so the operator sees what cosign actually rejected.
    if printf '%s' "$COSIGN_HELP" | grep -q -- '--new-bundle-format'; then
      COSIGN_OUT=$(cosign verify-blob \
        --bundle "${TMP}/checksums.bundle" \
        --new-bundle-format \
        --certificate-identity-regexp "^https://github.com/${REPO}/.github/workflows/host-release.yml@refs/tags/v" \
        --certificate-oidc-issuer     "https://token.actions.githubusercontent.com" \
        "${TMP}/checksums.txt" 2>&1) || {
        printf '%s\n' "$COSIGN_OUT" >&2
        err "cosign verification failed"
      }
    else
      COSIGN_OUT=$(cosign verify-blob \
        --bundle "${TMP}/checksums.bundle" \
        --certificate-identity-regexp "^https://github.com/${REPO}/.github/workflows/host-release.yml@refs/tags/v" \
        --certificate-oidc-issuer     "https://token.actions.githubusercontent.com" \
        "${TMP}/checksums.txt" 2>&1) || {
        printf '%s\n' "$COSIGN_OUT" >&2
        err "cosign verification failed"
      }
    fi
    ok "cosign signature verified"
  fi
else
  warn "cosign not on PATH; falling back to checksum-only verification."
  note "tarball sha256 was verified above; Sigstore identity attestation was skipped."
  note "install cosign and re-run for end-to-end Sigstore-signed verification:"
  note "  https://docs.sigstore.dev/cosign/installation"
fi

# --- step 5: install --------------------------------------------------------
step "Installing"

TAR_OUT=$(tar -xzf "${TMP}/${TAR}" -C "${TMP}" 2>&1) || {
  printf '%s\n' "$TAR_OUT" >&2
  err "could not extract ${TAR}"
}
[ -f "${TMP}/gkit" ] || err "tarball missing 'gkit' binary"

if [ -w "$INSTALL_DIR" ] || { mkdir -p "$INSTALL_DIR" 2>/dev/null && [ -w "$INSTALL_DIR" ]; }; then
  INSTALL_OUT=$(install -m 0755 "${TMP}/gkit" "${INSTALL_DIR}/gkit" 2>&1) || {
    printf '%s\n' "$INSTALL_OUT" >&2
    err "install ${INSTALL_DIR}/gkit failed"
  }
elif command -v sudo >/dev/null 2>&1 && [ -e /dev/tty ]; then
  # Under `curl … | sh`, stdin is the script pipe — sudo's password
  # prompt would have nowhere to read from. Redirecting from /dev/tty
  # restores interactivity. Falls through to err() if there's no
  # controlling terminal at all (daemonised installs, CI without a TTY).
  #
  # We deliberately DON'T wrap the sudo install in the capture-and-dump
  # pattern used elsewhere: sudo writes its password prompt and any
  # auth-failure messages directly to the controlling tty (not strictly
  # stderr), and the operator needs to see both interactively. A 2>&1
  # capture would swallow the prompt. install's own failures (read-only
  # mount, SELinux denial, ENOSPC) post-auth do leak left-flush as a
  # tradeoff — rare, and the error is operator-actionable anyway.
  warn "$INSTALL_DIR not writeable — escalating with sudo"
  # shellcheck disable=SC2024 # </dev/tty supplies sudo's password prompt with input, not output
  sudo install -m 0755 "${TMP}/gkit" "${INSTALL_DIR}/gkit" </dev/tty
elif command -v sudo >/dev/null 2>&1; then
  err "$INSTALL_DIR is not writeable and no controlling TTY is available for sudo. Re-run as: curl -fsSL https://get.gkit.dev > install.sh && sudo sh install.sh"
else
  err "$INSTALL_DIR is not writeable and sudo is unavailable; set GKIT_INSTALL_DIR to a writeable path"
fi

ok "${INSTALL_DIR}/gkit"

# --- done -------------------------------------------------------------------

printf '\n'
printf '  %s%s gkit %s installed%s\n' "$C_GREEN$C_BOLD" "→" "$VERSION" "$C_RESET"
printf '\n'

# print_get_started — fallback hand-off when we can't (or were told not to)
# orchestrate the walkthrough. Prints the three commands an operator would
# run by hand and the docs link.
print_get_started() {
  printf '  %sGet started:%s\n' "$C_BOLD" "$C_RESET"
  printf '    %s$%s gkit host init --domain <your-domain>\n' "$C_DIM" "$C_RESET"
  printf '    %s$%s sudo gkit host service install\n' "$C_DIM" "$C_RESET"
  printf '    %s$%s gkit add survey\n' "$C_DIM" "$C_RESET"
  printf '\n'
  printf '  %sDocs:%s    %shttps://github.com/%s%s\n' "$C_BOLD" "$C_RESET" "$C_DIM" "$REPO" "$C_RESET"
  printf '\n'
}

# Skip the post-install walkthrough under either:
#   - GKIT_NO_INIT=1 (CI / automation opt-out)
#   - no openable controlling terminal (TTY_OK=0 from step 2's probe —
#     piped without /dev/tty has the same blast radius as the opt-out,
#     since we can't ask the operator anything anyway).
# fd 3 was opened in step 2 if TTY_OK=1, so subsequent reads use it
# directly without a second `exec 3</dev/tty` here.
if [ "${GKIT_NO_INIT:-}" = "1" ] || [ "$TTY_OK" != "1" ]; then
  print_get_started
  exit 0
fi

# run_gkit / run_sudo_gkit — small dispatch helpers. Cleaner than
# scattering `if [ "$(id -u)" = "0" ]` branches through three call sites.
# `gkit host init` writes to /var/lib/gkit and `gkit host service install`
# touches systemd; both need root. `gkit add` talks to the supervisor
# over MCP and inherits whatever socket / token permissions the operator
# has — we keep it un-elevated.
run_sudo_gkit() {
  if [ "$(id -u)" = "0" ]; then
    gkit "$@" <&3
  elif command -v sudo >/dev/null 2>&1; then
    # shellcheck disable=SC2024 # <&3 supplies sudo's password prompt input
    sudo gkit "$@" <&3
  else
    err "sudo is unavailable — re-run this installer as root, or hand-run: sudo gkit $*"
  fi
}

run_gkit() { gkit "$@" <&3; }

# Step 1/3 — domain prompt + `gkit host init --domain <answer>`.
printf '  %sInitialize a host now?%s [Y/n] ' "$C_BOLD" "$C_RESET"
IFS= read -r init_answer <&3 || init_answer=""
case "${init_answer:-Y}" in
  ''|y|Y|yes|YES|Yes) ;;
  *) print_get_started; exit 0 ;;
esac

printf '  %sDomain%s (must already DNS-resolve to this host'\''s public IP, e.g. gkit.example.com): ' "$C_BOLD" "$C_RESET"
IFS= read -r domain_answer <&3 || domain_answer=""
if [ -z "$domain_answer" ]; then
  warn "no domain provided — skipping host init."
  print_get_started
  exit 0
fi

# Drop the TMP cleanup trap before the first sub-command runs so a
# failure mid-walkthrough doesn't leave the temp dir orphaned (each
# sub-command runs in our process via run_*; only an exec would defeat
# the trap, and we don't exec anymore).
rm -rf "$TMP"
trap - EXIT

# No `printf '\n'` before each run_*_gkit invocation: ui.Banner emits
# its own leading blank line (golden-tested in src/cmd/gkit/internal/ui),
# so adding one here would double up. Same shape as the trailing blank
# Banner emits — installer prompts that follow gkit output already see
# breathing room from gkit's terminal-line output (NextSteps / Headline).
run_sudo_gkit host init --domain "$domain_answer" \
  || err "host init failed — see output above"

# Step 2/3 — confirm + `sudo gkit host service install`.
printf '\n'
printf '  %sInstall gkit as a system service so it restarts after reboot?%s [Y/n] ' "$C_BOLD" "$C_RESET"
IFS= read -r svc_answer <&3 || svc_answer=""
case "${svc_answer:-Y}" in
  ''|y|Y|yes|YES|Yes)
    run_sudo_gkit host service install \
      || err "host service install failed — see output above"
    ;;
  *)
    note "skipped service install — run \`sudo gkit host service install\` when ready."
    ;;
esac

# Step 3/3 — per-component confirm + `gkit add <name>`. The catalog is
# hardcoded today; CDN-served replacement tracked in #127. Each entry
# fires its own confirm so an operator can pick-and-choose. Before
# prompting we probe `gkit list --json` — re-running install.sh against
# a data dir that survived an earlier run would otherwise prompt the
# operator to re-install survey, then `gkit add` would fail with
# E_COMPONENT_ALREADY_INSTALLED and the walkthrough would end on a ✗.
COMPONENTS="survey"
for comp in $COMPONENTS; do
  if run_gkit list --json 2>/dev/null | grep -qE "\"name\"[[:space:]]*:[[:space:]]*\"$comp\""; then
    printf '\n'
    note "$comp already installed — skipping."
    continue
  fi
  printf '\n'
  printf '  %sInstall the %s component?%s [Y/n] ' "$C_BOLD" "$comp" "$C_RESET"
  IFS= read -r comp_answer <&3 || comp_answer=""
  case "${comp_answer:-Y}" in
    ''|y|Y|yes|YES|Yes)
      run_gkit add "$comp" \
        || warn "gkit add $comp failed — continuing"
      ;;
    *)
      note "skipped $comp — run \`gkit add $comp\` when ready."
      ;;
  esac
done

# Replace the v0.5.4 "→ walkthrough complete" line with the same
# NextSteps-shaped block gkit subcommands print (Bold "Next steps:" at
# 2-col, `$ <command>` at 4-col with an inline `# comment`). Keeps the
# install.sh hand-off shape aligned with the rest of the CLI.
printf '\n'
printf '  %sNext steps:%s\n' "$C_BOLD" "$C_RESET"
printf '    %s$%s gkit host status            %s# check overall health%s\n' "$C_DIM" "$C_RESET" "$C_DIM" "$C_RESET"
printf '    %s$%s gkit list                   %s# list installed components%s\n' "$C_DIM" "$C_RESET" "$C_DIM" "$C_RESET"
printf '    %s$%s gkit add <component>        %s# add another component%s\n' "$C_DIM" "$C_RESET" "$C_DIM" "$C_RESET"
printf '\n'

# Final hand-off — the MCP wiring snippet for an agent client. Paste-once
# completes the on-ramp: by this point the operator has admin-token +
# mcp-url on the box, but constructing the `claude mcp add ...` line by
# hand is the friction #159 closes. `gkit host mcp-config` reads the same
# on-disk files and emits the URL + bearer + claude-code template.
#
# Best-effort: if mcp-config fails (host not init'd, token unreadable
# without sudo, etc.) we fall back to the generic "Get started" block so
# the operator at least sees a path forward.
printf '  %sWire your agent:%s\n' "$C_BOLD" "$C_RESET"
printf '\n'
if ! run_sudo_gkit host mcp-config; then
  warn "could not render the MCP wiring snippet — re-run \`sudo gkit host mcp-config\` once the host is healthy."
fi
printf '\n'
