#!/usr/bin/env bash
# installagent — install CLI AI coding agents inside asandbox
# Usage: installagent <name> | installagent list | installagent status
set -euo pipefail

VERSION="1.1.0"

if [[ -t 2 ]]; then
  C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YLW=$'\033[33m'
  C_BLU=$'\033[34m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m'; C_BLD=$'\033[1m'
else
  C_RED=; C_GRN=; C_YLW=; C_BLU=; C_DIM=; C_RST=; C_BLD=
fi

die()  { echo "${C_RED}installagent:${C_RST} $*" >&2; exit 1; }
info() { echo "${C_BLU}installagent:${C_RST} $*" >&2; }
ok()   { echo "${C_GRN}installagent:${C_RST} $*" >&2; }
warn() { echo "${C_YLW}installagent:${C_RST} $*" >&2; }

ensure_local_path() {
  mkdir -p "${HOME}/.local/bin" "${HOME}/.local/lib" "${HOME}/.npm-global"
  # Prefer user-local npm so installs survive container recreate (via persist mount)
  if command -v npm >/dev/null 2>&1; then
    npm config set prefix "${HOME}/.npm-global" >/dev/null 2>&1 || true
  fi
  export PATH="${HOME}/.local/bin:${HOME}/.npm-global/bin:${PATH}"
}

npm_g() {
  ensure_local_path
  npm install -g --prefix "${HOME}/.npm-global" "$@"
}

have_cmd() { command -v "$1" >/dev/null 2>&1; }

# ── catalog ─────────────────────────────────────────────────────────
# id|aliases|binary|description
catalog() {
  cat <<'EOF'
antigravity|agy,antigravity-cli|agy|Google Antigravity CLI
cursor|cursor-cli,cursor-agent|agent|Cursor Agent CLI
opencode|open-code|opencode|OpenCode open-source coding agent
claude|claude-code,claude-cli|claude|Anthropic Claude Code
gemini|gemini-cli,google-gemini|gemini|Google Gemini CLI
codex|openai-codex|codex|OpenAI Codex CLI
aider|aider-chat|aider|Aider Python coding agent
qwen|qwen-code,qwen-cli|qwen|Qwen Code CLI
amp|sourcegraph-amp|amp|Sourcegraph Amp CLI
goose|block-goose|goose|Block Goose agent
continue|cn|cn|Continue CLI
EOF
}

normalize_name() {
  echo "$1" | tr '[:upper:]' '[:lower:]' | tr '_' '-'
}

resolve_id() {
  local want
  want=$(normalize_name "$1")
  local line id aliases
  while IFS= read -r line; do
    [[ -z "$line" || "$line" == \#* ]] && continue
    id="${line%%|*}"
    aliases="${line#*|}"
    aliases="${aliases%%|*}"
    if [[ "$want" == "$id" ]]; then
      echo "$id"
      return 0
    fi
    local a
    IFS=',' read -ra _als <<< "$aliases"
    for a in "${_als[@]}"; do
      a=$(echo "$a" | tr -d ' ')
      if [[ "$want" == "$a" ]]; then
        echo "$id"
        return 0
      fi
    done
  done < <(catalog)
  return 1
}

agent_binary() {
  local id="$1" line
  while IFS= read -r line; do
    [[ "${line%%|*}" == "$id" ]] || continue
    # id|aliases|binary|desc
    local rest="${line#*|}"
    rest="${rest#*|}"
    echo "${rest%%|*}"
    return 0
  done < <(catalog)
}

agent_desc() {
  local id="$1" line
  while IFS= read -r line; do
    [[ "${line%%|*}" == "$id" ]] || continue
    echo "${line##*|}"
    return 0
  done < <(catalog)
}

# ── installers ──────────────────────────────────────────────────────
install_antigravity() {
  info "installing Antigravity CLI (agy)..."
  ensure_local_path
  curl -fsSL https://antigravity.google/cli/install.sh | bash
  # Some installers put binary only in a custom dir — ensure on PATH
  if ! have_cmd agy; then
    # common locations
    for p in \
      "${HOME}/.local/bin/agy" \
      "${HOME}/.antigravity/bin/agy" \
      "${HOME}/.agy/bin/agy" \
      /usr/local/bin/agy; do
      if [[ -x "$p" ]]; then
        mkdir -p "${HOME}/.local/bin"
        ln -sfn "$p" "${HOME}/.local/bin/agy"
        break
      fi
    done
  fi
  have_cmd agy || die "agy not found after install — check installer output"
  ok "Antigravity CLI ready: $(command -v agy)"
  agy --version 2>/dev/null || agy version 2>/dev/null || true
}

install_cursor() {
  info "installing Cursor Agent CLI..."
  ensure_local_path
  curl -fsSL https://cursor.com/install | bash
  if ! have_cmd agent; then
    for p in \
      "${HOME}/.local/bin/agent" \
      "${HOME}/.cursor/bin/agent" \
      "${HOME}/.cursor-agent/bin/agent"; do
      if [[ -x "$p" ]]; then
        mkdir -p "${HOME}/.local/bin"
        ln -sfn "$p" "${HOME}/.local/bin/agent"
        break
      fi
    done
  fi
  have_cmd agent || die "agent not found after install"
  ok "Cursor CLI ready: $(command -v agent)"
  agent --version 2>/dev/null || true
}

install_opencode() {
  info "installing OpenCode..."
  ensure_local_path
  if have_cmd npm; then
    npm_g opencode-ai
  else
    curl -fsSL https://opencode.ai/install | bash
  fi
  if ! have_cmd opencode; then
    for p in "${HOME}/.npm-global/bin/opencode" "${HOME}/.local/bin/opencode" /usr/local/bin/opencode; do
      [[ -x "$p" ]] && ln -sfn "$p" "${HOME}/.local/bin/opencode" && break
    done
  fi
  have_cmd opencode || die "opencode not found after install"
  ok "OpenCode ready: $(command -v opencode)"
  opencode --version 2>/dev/null || true
}

install_claude() {
  info "installing Claude Code..."
  have_cmd npm || die "npm required (Node.js)"
  npm_g @anthropic-ai/claude-code
  have_cmd claude || die "claude not found after install"
  ok "Claude Code ready: $(command -v claude)"
  claude --version 2>/dev/null || true
}

install_gemini() {
  info "installing Gemini CLI..."
  have_cmd npm || die "npm required (Node.js)"
  npm_g @google/gemini-cli
  have_cmd gemini || die "gemini not found after install"
  ok "Gemini CLI ready: $(command -v gemini)"
  gemini --version 2>/dev/null || true
}

install_codex() {
  info "installing OpenAI Codex CLI..."
  have_cmd npm || die "npm required (Node.js)"
  npm_g @openai/codex
  have_cmd codex || die "codex not found after install"
  ok "Codex ready: $(command -v codex)"
  codex --version 2>/dev/null || true
}

install_aider() {
  info "installing Aider..."
  ensure_local_path
  if have_cmd uv; then
    uv tool install aider-chat
  elif have_cmd pip3; then
    pip3 install --user --break-system-packages aider-chat \
      || pip3 install --user aider-chat
  else
    die "need uv or pip3 to install aider"
  fi
  # uv tools go to ~/.local/bin
  have_cmd aider || die "aider not found after install — ensure ~/.local/bin is on PATH"
  ok "Aider ready: $(command -v aider)"
  aider --version 2>/dev/null || true
}

install_qwen() {
  info "installing Qwen Code..."
  have_cmd npm || die "npm required (Node.js)"
  npm_g @qwen-code/qwen-code
  # binary name may be qwen or qwen-code
  if ! have_cmd qwen && have_cmd qwen-code; then
    ln -sfn "$(command -v qwen-code)" "${HOME}/.local/bin/qwen"
  fi
  if have_cmd qwen || have_cmd qwen-code; then
    ok "Qwen Code ready: $(command -v qwen 2>/dev/null || command -v qwen-code)"
  else
    die "qwen binary not found after install"
  fi
}

install_amp() {
  info "installing Amp (Sourcegraph)..."
  ensure_local_path
  # Official installer when available
  if curl -fsSL https://ampcode.com/install.sh 2>/dev/null | bash; then
    :
  elif have_cmd npm; then
    npm_g @sourcegraph/amp 2>/dev/null || npm_g amp 2>/dev/null || true
  fi
  have_cmd amp || warn "amp may need manual install — see https://ampcode.com"
  have_cmd amp && ok "Amp ready: $(command -v amp)"
}

install_goose() {
  info "installing Goose..."
  ensure_local_path
  curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh 2>/dev/null | bash \
    || curl -fsSL https://raw.githubusercontent.com/block/goose/main/download_cli.sh 2>/dev/null | bash \
    || true
  if ! have_cmd goose; then
    # fallback cargo if present
    if have_cmd cargo; then
      cargo install goose-cli 2>/dev/null || true
    fi
  fi
  have_cmd goose || die "goose not found after install — see https://github.com/block/goose"
  ok "Goose ready: $(command -v goose)"
}

install_continue() {
  info "installing Continue CLI..."
  ensure_local_path
  have_cmd npm || die "npm required"
  npm_g @continuedev/cli 2>/dev/null || npm_g continue-cli 2>/dev/null || true
  if have_cmd cn || have_cmd continue; then
    ok "Continue ready: $(command -v cn 2>/dev/null || command -v continue)"
  else
    warn "Continue CLI package name may have changed — try: npm install -g @continuedev/cli"
  fi
}

do_install() {
  local id="$1"
  case "$id" in
    antigravity) install_antigravity ;;
    cursor)      install_cursor ;;
    opencode)    install_opencode ;;
    claude)      install_claude ;;
    gemini)      install_gemini ;;
    codex)       install_codex ;;
    aider)       install_aider ;;
    qwen)        install_qwen ;;
    amp)         install_amp ;;
    goose)       install_goose ;;
    continue)    install_continue ;;
    *) die "no installer for: $id" ;;
  esac
}

cmd_list() {
  echo "${C_BLD}Available agents${C_RST}  (installagent <name>)"
  echo
  printf "  ${C_BLD}%-14s${C_RST} %-12s %s\n" "NAME" "BINARY" "DESCRIPTION"
  printf "  %-14s %-12s %s\n" "----" "------" "-----------"
  local line id aliases bin desc
  while IFS= read -r line; do
    id="${line%%|*}"
    rest="${line#*|}"
    aliases="${rest%%|*}"
    rest="${rest#*|}"
    bin="${rest%%|*}"
    desc="${rest#*|}"
    local mark=" "
    if have_cmd "$bin"; then
      mark="${C_GRN}✓${C_RST}"
    else
      mark="${C_DIM}·${C_RST}"
    fi
    printf "  %s %-12s %-12s %s\n" "$mark" "$id" "$bin" "$desc"
    if [[ -n "$aliases" ]]; then
      printf "    ${C_DIM}aliases: %s${C_RST}\n" "$aliases"
    fi
  done < <(catalog)
  echo
  echo "Examples:"
  echo "  installagent claude"
  echo "  installagent antigravity"
  echo "  installagent cursor"
  echo "  installagent opencode"
  echo "  installagent all          # install core set"
}

cmd_status() {
  ensure_local_path
  echo "${C_BLD}Installed agents${C_RST}"
  local line id bin
  while IFS= read -r line; do
    id="${line%%|*}"
    rest="${line#*|}"
    rest="${rest#*|}"
    bin="${rest%%|*}"
    if have_cmd "$bin"; then
      printf "  ${C_GRN}✓${C_RST} %-14s %s\n" "$id" "$(command -v "$bin")"
    else
      printf "  ${C_DIM}·${C_RST} %-14s (not installed)\n" "$id"
    fi
  done < <(catalog)
  echo
  echo "PATH includes:"
  echo "  ${HOME}/.local/bin"
  echo "  ${HOME}/.npm-global/bin"
}

cmd_all() {
  # Core set commonly used
  local core=(claude gemini codex opencode cursor antigravity aider)
  local name
  for name in "${core[@]}"; do
    info "── $name ──"
    if do_install "$name"; then
      ok "$name done"
    else
      warn "$name failed (continuing)"
    fi
  done
  cmd_status
}

usage() {
  cat <<EOF
installagent v${VERSION} — install CLI AI agents inside asandbox

USAGE
  installagent <name>       Install agent by name or alias
  installagent list         Show catalog
  installagent status       Show what is installed
  installagent all          Install core agents
  installagent help         This help

AGENTS
  antigravity (agy)   Google Antigravity CLI
  cursor (agent)      Cursor Agent CLI
  opencode            OpenCode
  claude              Claude Code
  gemini              Gemini CLI
  codex               OpenAI Codex
  aider               Aider
  qwen                Qwen Code
  amp                 Sourcegraph Amp
  goose               Block Goose
  continue            Continue CLI

Installs go to ~/.local and ~/.npm-global (persisted by asandbox).
EOF
}

main() {
  ensure_local_path

  local cmd="${1:-list}"
  shift || true

  case "$cmd" in
    list|ls|-l) cmd_list ;;
    status|st)  cmd_status ;;
    all)        cmd_all ;;
    help|-h|--help) usage ;;
    version|-V|--version) echo "installagent $VERSION" ;;
    *)
      local id
      if ! id=$(resolve_id "$cmd"); then
        die "unknown agent: $cmd  (try: installagent list)"
      fi
      info "resolved '$cmd' → $id ($(agent_desc "$id"))"
      do_install "$id"
      echo
      ok "run: $(agent_binary "$id")"
      ;;
  esac
}

main "$@"
