#!/usr/bin/env bash
#
# APS Install Script
# Multi-mode entrypoint: install the CLI, initialize a repo (minimal planning
# content by default), bootstrap a repo for an agent, upgrade, or add a tool.
#
# Usage:
#   curl -fsSL https://raw.githubusercontent.com/EddaCraft/anvil-plan-spec/main/scaffold/install | bash
#   curl ... | bash -s -- ./my-project
#   curl ... | bash -s -- --binary     # pre-built aps binary instead of bash CLI
#   curl ... | VERSION=0.2.0 bash
#
# For updating existing projects, use the update script instead.
#

set -euo pipefail

VERSION="${VERSION:-main}"
USE_BINARY=false
TARGET="."
# Mode is chosen by a flag, or by the TTY picker when none is given.
#   cli     install the APS CLI on this machine (was --global)
#   init    scaffold APS planning into this repo (the old default)
#   agent   minimal planning layer + agent next steps
#   upgrade upgrade an existing APS project
#   setup   add a tool integration (--setup <tool>)
MODE=""
SETUP_TARGET=""
# init footprint flags (INSTALL-011): minimal by default, opt in to extras.
USE_LOCAL_CLI=false
INSTALL_HOOKS=false

# Parse flags
while [[ $# -gt 0 ]]; do
  case "$1" in
    --cli|--global|-g) MODE="cli" ;;
    --init)            MODE="init" ;;
    --agent)           MODE="agent" ;;
    --upgrade)         MODE="upgrade" ;;
    --setup)
      MODE="setup"
      SETUP_TARGET="${2:-}"
      if [[ -z "$SETUP_TARGET" || "$SETUP_TARGET" == -* ]]; then
        echo "error: --setup requires a tool (e.g. --setup claude-code)" >&2
        exit 1
      fi
      shift
      ;;
    --binary|-b)       USE_BINARY=true ;;
    --local-cli|--bash) USE_LOCAL_CLI=true ;;
    --hooks)           INSTALL_HOOKS=true ;;
    -*) echo "error: unknown option: $1" >&2; exit 1 ;;
    *) TARGET="$1" ;;
  esac
  shift
done

# Reject an empty TARGET regardless of mode — "" would make PLANS_DIR=/plans
# and write to the filesystem root.
if [[ -z "$TARGET" ]]; then
  echo "error: TARGET must not be empty." >&2
  exit 1
fi

PLANS_DIR="$TARGET/plans"
BASE_URL="https://raw.githubusercontent.com/EddaCraft/anvil-plan-spec/$VERSION"

# Colors (disabled if not interactive)
if [[ -t 1 ]]; then
  GREEN='\033[0;32m'
  RED='\033[0;31m'
  BLUE='\033[0;34m'
  YELLOW='\033[1;33m'
  BOLD='\033[1m'
  NC='\033[0m'
else
  GREEN='' RED='' BLUE='' YELLOW='' BOLD='' NC=''
fi

info()  { echo -e "${GREEN}>${NC} $1"; }
warn()  { echo -e "${YELLOW}>${NC} $1"; }
error() { echo -e "${RED}error:${NC} $1" >&2; }
step()  { echo -e "${BLUE}==>${NC} ${BOLD}$1${NC}"; }

download() {
  local path="$1"
  local dest="$2"
  local url="$BASE_URL/scaffold/$path"
  mkdir -p "$(dirname "$dest")"
  if ! curl -fsSL "$url" -o "$dest"; then
    error "Failed to download '$path' from $url"
    echo "       Please check your network connectivity and ensure VERSION='$VERSION' is correct."
    exit 1
  fi
}

# Download from repo root (for non-scaffold files like bin/ and lib/)
download_root() {
  local path="$1"
  local dest="$2"
  local url="$BASE_URL/$path"
  mkdir -p "$(dirname "$dest")"
  if ! curl -fsSL "$url" -o "$dest"; then
    error "Failed to download '$path' from $url"
    echo "       Please check your network connectivity and ensure VERSION='$VERSION' is correct."
    exit 1
  fi
}

# Map this machine to a release binary target triple. Fails (rc 1) on
# platforms without published binaries so callers can fall back to bash.
detect_release_target() {
  local os arch
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$os/$arch" in
    Linux/x86_64)              echo "x86_64-unknown-linux-gnu" ;;
    Linux/aarch64|Linux/arm64) echo "aarch64-unknown-linux-gnu" ;;
    Darwin/arm64)              echo "aarch64-apple-darwin" ;;
    Darwin/x86_64)             echo "x86_64-apple-darwin" ;;
    *) return 1 ;;
  esac
}

# Download the pre-built aps binary from GitHub releases into $1/aps.
# Returns non-zero on any failure so the caller can fall back to the
# bash CLI.
install_binary() {
  local dest_dir="$1" target url tmp
  if ! target="$(detect_release_target)"; then
    warn "No release binary for $(uname -s)/$(uname -m); falling back to bash CLI"
    return 1
  fi

  if [[ "$VERSION" == "main" ]]; then
    url="https://github.com/EddaCraft/anvil-plan-spec/releases/latest/download/aps-$target.tar.gz"
  else
    url="https://github.com/EddaCraft/anvil-plan-spec/releases/download/v${VERSION#v}/aps-$target.tar.gz"
  fi

  tmp="$(mktemp -d)"
  if ! curl -fsSL "$url" -o "$tmp/aps.tar.gz"; then
    warn "Failed to download release binary from $url; falling back to bash CLI"
    rm -rf "$tmp"
    return 1
  fi
  if ! tar -xzf "$tmp/aps.tar.gz" -C "$tmp"; then
    warn "Failed to extract release binary; falling back to bash CLI"
    rm -rf "$tmp"
    return 1
  fi

  # Guard the install so a failure here cleans up $tmp and falls back to bash
  # rather than aborting the whole script under `set -e`.
  if ! mkdir -p "$dest_dir" || ! mv "$tmp/aps" "$dest_dir/aps"; then
    warn "Failed to install release binary; falling back to bash CLI"
    rm -rf "$tmp"
    return 1
  fi
  chmod +x "$dest_dir/aps"
  rm -rf "$tmp"
  info "aps native binary ($target) installed to $dest_dir/aps"
}

# Prompt user with a yes/no question. Returns 0 for yes, 1 for no.
ask_yn() {
  local prompt="$1"
  local default="${2:-n}"
  local answer yn_hint
  if [[ "$default" == "y" ]]; then yn_hint="Y/n"; else yn_hint="y/N"; fi

  if [[ -t 0 ]]; then
    printf "%s [%s] " "$prompt" "$yn_hint"
    read -r answer
    answer="${answer:-$default}"
    [[ "$answer" =~ ^[Yy] ]]
  elif { : < /dev/tty; } 2>/dev/null; then
    # `curl | bash` makes stdin a pipe; use the controlling terminal when it
    # can actually be opened (a node that merely exists is not enough).
    printf "%s [%s] " "$prompt" "$yn_hint" > /dev/tty
    read -r answer < /dev/tty
    answer="${answer:-$default}"
    [[ "$answer" =~ ^[Yy] ]]
  else
    # Non-interactive: use default
    [[ "$default" == "y" ]]
  fi
}

# Set up PATH so `aps` works without ./bin/ prefix
setup_path() {
  echo ""
  if command -v direnv &>/dev/null; then
    local envrc="$TARGET/.envrc"
    if [[ -f "$envrc" ]] && grep -q 'PATH_add bin' "$envrc" 2>/dev/null; then
      info "PATH already configured in .envrc"
    elif ask_yn "Set up direnv so you can run 'aps' without ./bin/ prefix?" "y"; then
      if [[ -f "$envrc" ]]; then
        echo 'PATH_add bin' >> "$envrc"
      else
        echo 'PATH_add bin' > "$envrc"
      fi
      info "Added 'PATH_add bin' to .envrc"
      echo "  Run 'direnv allow' to activate"
    else
      info "To run aps without the path prefix, add to your .envrc:"
      echo "  PATH_add bin"
    fi
  else
    info "To run 'aps' without ./bin/ prefix, either:"
    echo "  - Install direnv and add 'PATH_add bin' to .envrc"
    echo "  - Or add 'export PATH=\"./bin:\$PATH\"' to your shell config"
  fi
}

# Set up PATH for global installs (auto-detect shell, append to rc file)
setup_global_path() {
  local aps_home="$1"
  local bin_dir="$aps_home/bin"

  # Check if already on PATH
  if echo "$PATH" | tr ':' '\n' | grep -qx "$bin_dir" 2>/dev/null; then
    info "$bin_dir is already on your PATH"
    return
  fi

  # Detect shell and rc file
  local shell_name rc_file
  shell_name="$(basename "${SHELL:-/bin/bash}")"

  case "$shell_name" in
    zsh)  rc_file="$HOME/.zshrc" ;;
    bash)
      if [[ -f "$HOME/.bashrc" ]]; then
        rc_file="$HOME/.bashrc"
      else
        rc_file="$HOME/.bash_profile"
      fi
      ;;
    fish) rc_file="$HOME/.config/fish/config.fish" ;;
    *)    rc_file="$HOME/.profile" ;;
  esac

  # Idempotent: check if rc file already contains the path
  if [[ -f "$rc_file" ]] && grep -qF "$bin_dir" "$rc_file" 2>/dev/null; then
    info "PATH already configured in $rc_file"
    return
  fi

  if ask_yn "Add $bin_dir to PATH in $rc_file?" "y"; then
    mkdir -p "$(dirname "$rc_file")"
    if [[ "$shell_name" == "fish" ]]; then
      printf '\n# APS CLI\nfish_add_path %s\n' "$bin_dir" >> "$rc_file"
    else
      printf '\n# APS CLI\nexport PATH="%s:$PATH"\n' "$bin_dir" >> "$rc_file"
    fi
    info "Added to $rc_file"
    warn "Restart your shell or run: source $rc_file"
  else
    info "To add manually, put this in your shell config:"
    if [[ "$shell_name" == "fish" ]]; then
      echo "  fish_add_path $bin_dir"
    else
      echo "  export PATH=\"$bin_dir:\$PATH\""
    fi
  fi
}

# Global install: CLI only, add to shell PATH.
# Binary-first (D-034 / INSTALL-015): the global install ships the prebuilt
# release binary by default. The bash CLI is the fallback — used when no
# release binary exists for this platform, the download fails, or the user
# forces it with --bash / --local-cli.
install_global() {
  local aps_home="${APS_HOME:-$HOME/.aps}"
  local kind="bash"

  echo ""
  echo -e "${BOLD}Anvil Plan Spec (APS)${NC}"
  echo "Global CLI installation"
  echo ""

  step "Installing APS CLI to $aps_home"

  if [[ "$USE_LOCAL_CLI" != true ]] && install_binary "$aps_home/bin"; then
    kind="binary" # native release binary installed; bash CLI not needed
  elif [[ "$USE_BINARY" == true && "$USE_LOCAL_CLI" != true ]]; then
    # --binary requires the prebuilt binary: do not silently fall back.
    error "--binary requested but no release binary is available for $(uname -s)/$(uname -m)"
    echo "       Re-run without --binary to use the bash CLI fallback." >&2
    exit 1
  else
    for f in bin/aps lib/output.sh lib/lint.sh lib/orchestrate.sh lib/audit.sh lib/scaffold.sh lib/rules/common.sh lib/rules/module.sh lib/rules/index.sh lib/rules/workitem.sh lib/rules/issues.sh lib/rules/design.sh; do
      download_root "$f" "$aps_home/$f"
    done
    chmod +x "$aps_home/bin/aps"
    info "bin/aps + lib/ installed to $aps_home"
  fi

  setup_global_path "$aps_home"

  echo ""
  step "Global installation complete"
  echo ""
  if [[ "$kind" == "binary" ]]; then
    echo "  $aps_home/"
    echo "  └── bin/aps          <- CLI (native release binary)"
  else
    echo "  $aps_home/"
    echo "  ├── bin/aps          <- CLI (bash)"
    echo "  └── lib/             <- CLI libraries"
  fi
  echo ""
  info "To create a new APS project:"
  echo "  cd your-project && aps init"
  echo ""
}

# Agent bootstrap: minimal planning files plus agent-readable next steps.
# Mirrors `aps setup agent` (TUI-008) — no hooks, agents, CLI runtime, or
# tool integrations.
install_agent() {
  echo ""
  echo -e "${BOLD}Anvil Plan Spec (APS)${NC}"
  echo "Agent bootstrap"
  echo ""

  if [[ -d "$PLANS_DIR" ]]; then
    error "plans/ directory already exists at $TARGET"
    exit 1
  fi

  step "Creating minimal planning layer"
  mkdir -p "$PLANS_DIR/modules"
  mkdir -p "$PLANS_DIR/execution"
  download "plans/index.aps.md" "$PLANS_DIR/index.aps.md"
  download "plans/aps-rules.md" "$PLANS_DIR/aps-rules.md"
  download "plans/project-context.md" "$PLANS_DIR/project-context.md"
  info "index.aps.md, aps-rules.md, project-context.md"

  step "Writing agent next steps"
  cat > "$PLANS_DIR/agent-next-steps.md" <<'EOF'
# APS Agent Bootstrap — Next Steps

This repository was just initialized with a minimal APS planning layer
for an AI agent. Before implementing anything:

1. Read `plans/aps-rules.md` for the planning conventions.
2. Ask the operator for the project intent — what is being built and why.
3. Populate `plans/project-context.md` with that durable background.
4. Draft `plans/index.aps.md` (problem, outcomes, scope, modules).
5. Wait for an approved work item before writing any implementation code.

No hooks, agents, or tool integrations were installed. Run `aps setup`
to add them when needed.
EOF
  info "plans/agent-next-steps.md"

  echo ""
  step "Agent bootstrap complete"
  cat "$PLANS_DIR/agent-next-steps.md"
}

# Upgrade an existing APS project. The deep cleanup (backups, hook-path
# rewrites) is INSTALL-013's `scaffold/upgrade`; until it lands, hand off to
# the update entrypoint, which refreshes templates and runtime in place.
install_upgrade() {
  echo ""
  echo -e "${BOLD}Anvil Plan Spec (APS)${NC}"
  echo "Upgrade existing project"
  echo ""
  if [[ ! -d "$PLANS_DIR" ]]; then
    error "no plans/ directory at $TARGET — nothing to upgrade"
    echo "  Run with --init to scaffold a new project." >&2
    exit 1
  fi
  step "Refreshing templates and CLI via the update entrypoint"
  # Forward the version pin: a bare child bash would re-default VERSION to main
  # and silently refresh the payload from HEAD.
  curl -fsSL "$BASE_URL/scaffold/update" | VERSION="$VERSION" bash -s -- "$TARGET"
}

# Add a single tool integration to an existing project by delegating to
# `aps setup <tool>`. The `setup` command ships with the native binary (and
# is built out under INSTALL-012); the bash CLI does not provide it yet, so
# this gates on a setup-capable `aps` and writes nothing if none is found.
install_setup() {
  local tool="$1"
  echo ""
  echo -e "${BOLD}Anvil Plan Spec (APS)${NC}"
  echo "Add integration: $tool"
  echo ""

  local aps_bin=""
  if command -v aps &>/dev/null; then
    aps_bin="aps"
  elif [[ -x "$TARGET/bin/aps" ]]; then
    aps_bin="$TARGET/bin/aps"
  fi

  # Probe for the setup subcommand before doing anything destructive.
  if [[ -z "$aps_bin" ]] || ! "$aps_bin" setup --help >/dev/null 2>&1; then
    error "aps setup is not available from the installed CLI"
    echo "  Install the native CLI first (it ships 'aps setup'):" >&2
    echo "    curl -fsSL $BASE_URL/scaffold/install | bash -s -- --cli --binary" >&2
    echo "  then re-run: aps setup $tool" >&2
    exit 1
  fi

  step "Running aps setup $tool"
  (cd "$TARGET" && "$aps_bin" setup "$tool")
}

# Write the minimal `.aps/config.yml` project contract for a fresh init.
# The full schema lives in lib/scaffold.sh's write_config; the curl bootstrap
# has no bash lib to source, so it writes the bare required shape here.
write_min_config() {
  local today cli_version
  today="$(date +%Y-%m-%d)"
  # VERSION is a git ref ("main") by default; only use it as the contract pin
  # when it is an explicit semver. Otherwise fall back to the release version.
  case "$VERSION" in
    v[0-9]*|[0-9]*) cli_version="${VERSION#v}" ;;
    *)              cli_version="0.3.0" ;;
  esac
  mkdir -p "$TARGET/.aps"
  touch "$TARGET/.aps/.gitignore"
  grep -qxF 'context/' "$TARGET/.aps/.gitignore" || printf 'context/\n' >> "$TARGET/.aps/.gitignore"
  cat > "$TARGET/.aps/config.yml" <<EOF
# .aps/config.yml — written by installer, read by updater

# Project contract (INSTALL-014 / D-035): toolchain pin + runtime path
# defaults the global 'aps' binary discovers by walking up from cwd.
cli_version: "$cli_version"
plans_dir: plans/
docs_dir: docs/
tooling_root: .aps/

aps:
  version: "0.3.0"
  config_schema: 1
  installed: "$today"
  updated: "$today"

project:
  type: simple
  monorepo_tool: ~
  profile: solo

tools:
  - name: generic
    # No tool integration — run 'aps setup' to add one
EOF
}

# Default project scaffold (the `init` mode). Minimal by default (INSTALL-011):
# planning content + .aps/config.yml only. The CLI runtime and hooks are opt-in
# (--local-cli / --hooks); tool skills/agents are added later with `aps setup`.
install_init() {
# Header
echo ""
echo -e "${BOLD}Anvil Plan Spec (APS)${NC}"
echo "Lightweight specs for AI-assisted development"
echo ""

# Check for existing installation
if [[ -d "$PLANS_DIR" ]]; then
  error "plans/ directory already exists at $TARGET"
  echo ""
  echo "To update templates in an existing project:"
  echo "  curl -fsSL https://raw.githubusercontent.com/EddaCraft/anvil-plan-spec/main/scaffold/update | bash"
  echo ""
  echo "To reinstall from scratch:"
  echo "  rm -rf $PLANS_DIR && curl ... | bash"
  echo ""
  exit 1
fi

# Create directory structure
step "Creating directory structure"
mkdir -p "$PLANS_DIR/modules"
mkdir -p "$PLANS_DIR/execution"
mkdir -p "$PLANS_DIR/decisions"
mkdir -p "$PLANS_DIR/designs"

# Download plan files (v2 templates: rules + project context + trackers)
step "Downloading templates"
download "plans/index.aps.md" "$PLANS_DIR/index.aps.md"
download "plans/aps-rules-v2.md" "$PLANS_DIR/aps-rules.md"
download "plans/project-context.md" "$PLANS_DIR/project-context.md"
download "plans/issues.md" "$PLANS_DIR/issues.md"
download "plans/modules/.module.template.md" "$PLANS_DIR/modules/.module.template.md"
download "plans/modules/.simple.template.md" "$PLANS_DIR/modules/.simple.template.md"
download "plans/modules/.index-monorepo.template.md" "$PLANS_DIR/modules/.index-monorepo.template.md"
download "plans/execution/.actions.template.md" "$PLANS_DIR/execution/.actions.template.md"
touch "$PLANS_DIR/decisions/.gitkeep"
touch "$PLANS_DIR/designs/.gitkeep"
info "plans/ (rules, project-context, index, issues, templates)"

# Project contract
step "Writing project config"
write_min_config
info ".aps/config.yml + .aps/.gitignore"

# Optional: vendored bash CLI runtime (air-gap / pinned toolchains)
if [[ "$USE_LOCAL_CLI" == true ]]; then
  step "Vendoring bash CLI into .aps/"
  if [[ "$USE_BINARY" == true ]] && install_binary "$TARGET/.aps/bin"; then
    : # native binary installed
  else
    for f in bin/aps lib/output.sh lib/lint.sh lib/orchestrate.sh lib/audit.sh lib/scaffold.sh lib/rules/common.sh lib/rules/module.sh lib/rules/index.sh lib/rules/workitem.sh lib/rules/issues.sh lib/rules/design.sh; do
      download_root "$f" "$TARGET/.aps/$f"
    done
    chmod +x "$TARGET/.aps/bin/aps"
    info ".aps/bin/aps + .aps/lib/ (vendored CLI)"
  fi
fi

# Optional: hook scripts
if [[ "$INSTALL_HOOKS" == true ]]; then
  step "Installing hook scripts"
  for s in install-hooks.sh init-session.sh check-complete.sh pre-tool-check.sh post-tool-nudge.sh enforce-plan-update.sh; do
    download "aps-planning/scripts/$s" "$TARGET/.aps/scripts/$s"
  done
  chmod +x "$TARGET/.aps/scripts/"*.sh
  info ".aps/scripts/ (hook scripts)"
fi

# Success
echo ""
step "Installation complete"
echo ""
echo "  plans/"
echo "  ├── aps-rules.md              # Agent guidance (APS-managed)"
echo "  ├── project-context.md        # Your project context (edit this)"
echo "  ├── index.aps.md              # Your main plan (edit this)"
echo "  ├── issues.md                 # Issue & question tracker"
echo "  ├── modules/                  # Module templates"
echo "  ├── execution/                # Action plan template"
echo "  ├── decisions/                # ADRs"
echo "  └── designs/                  # Technical designs"
echo ""
echo "  .aps/config.yml               # Project contract (cli_version, paths)"
echo ""

echo ""
step "Next steps"
echo ""
if [[ "$USE_LOCAL_CLI" != true ]]; then
  printf '  1. Install the APS CLI: %bcurl -fsSL %s/scaffold/install | bash -s -- --cli%b\n' "$BOLD" "$BASE_URL" "$NC"
  printf '  2. Edit %bplans/index.aps.md%b to define your plan\n' "$BOLD" "$NC"
  printf '  3. Add hooks/agents/tool skills with %baps setup%b\n' "$BOLD" "$NC"
else
  printf '  1. Edit %bplans/index.aps.md%b to define your plan\n' "$BOLD" "$NC"
  printf '  2. Point your AI agent at %bplans/aps-rules.md%b, or run %baps next%b\n' "$BOLD" "$NC" "$BOLD" "$NC"
fi
echo ""
echo "Docs: https://github.com/EddaCraft/anvil-plan-spec"
echo ""
}

# Print the mode menu and read a choice from the controlling terminal.
usage_modes() {
  cat <<EOF
Anvil Plan Spec (APS) installer

Usage: curl -fsSL .../scaffold/install | bash -s -- [MODE] [TARGET]

Modes:
  --cli              Install the APS CLI on this machine
  --init             Scaffold APS planning into this repository
  --agent            Minimal planning layer + agent next steps
  --upgrade          Upgrade an existing APS project
  --setup <tool>     Add a tool integration (claude-code, copilot, codex, ...)

Options:
  --binary           Require the prebuilt release binary (cli mode: no fallback)
  --bash, --local-cli  Force the bash CLI: cli mode skips the binary; init mode
                       vendors the bash CLI into .aps/bin + .aps/lib
  -g, --global       Alias for --cli
  --hooks            (init) Also install hook scripts into .aps/scripts

--cli is binary-first (D-034): it installs the native release binary and
falls back to the bash CLI only when no binary exists for this platform or
--bash is given.

By default --init writes only planning content + .aps/config.yml; the global
'aps' binary drives the repo. Add hooks/agents/tool skills with 'aps setup'.

With no mode in an interactive terminal, a picker is shown.
EOF
}

pick_mode() {
  echo "" > /dev/tty
  echo -e "${BOLD}Anvil Plan Spec (APS)${NC}" > /dev/tty
  echo "What would you like to do?" > /dev/tty
  echo "" > /dev/tty
  echo "  1) Install the APS CLI on this machine" > /dev/tty
  echo "  2) Initialize APS planning in this repository" > /dev/tty
  echo "  3) Initialize this repository for an AI agent" > /dev/tty
  echo "  4) Upgrade an existing APS project" > /dev/tty
  echo "  5) Add a tool integration" > /dev/tty
  echo "" > /dev/tty
  printf "Choose [1-5]: " > /dev/tty
  local choice
  read -r choice < /dev/tty
  case "$choice" in
    1) MODE="cli" ;;
    2) MODE="init" ;;
    3) MODE="agent" ;;
    4) MODE="upgrade" ;;
    5)
      MODE="setup"
      printf "Tool (claude-code, copilot, codex, opencode, gemini): " > /dev/tty
      read -r SETUP_TARGET < /dev/tty
      if [[ -z "$SETUP_TARGET" ]]; then
        error "no tool given"
        exit 1
      fi
      ;;
    *) error "invalid choice: $choice"; exit 1 ;;
  esac
}

# Resolve the mode: explicit flag wins; otherwise pick interactively, or
# print usage and fail when there is no terminal to ask. `-r /dev/tty` is not
# enough — on a detached process the node can exist yet fail to open — so we
# probe by actually opening it.
have_tty() {
  [[ -t 0 ]] && return 0
  { : < /dev/tty; } 2>/dev/null
}

if [[ -z "$MODE" ]]; then
  if have_tty; then
    pick_mode
  else
    usage_modes >&2
    echo "" >&2
    error "no mode given and no terminal for the picker (use --cli/--init/--agent/--upgrade/--setup)"
    exit 1
  fi
fi

# Validate TARGET now that the mode is known. --cli installs machine-wide and
# ignores TARGET; project-scoped modes must stay inside the working tree.
if [[ "$MODE" == "cli" ]]; then
  [[ "$TARGET" != "." ]] && warn "--cli installs machine-wide; ignoring TARGET '$TARGET'"
else
  if [[ "$TARGET" == /* ]]; then
    error "Absolute paths are not allowed for TARGET; use a relative path (e.g., ./my-project)."
    exit 1
  fi
  if [[ "$TARGET" == *".."* ]]; then
    error "Parent directory references ('..') are not allowed in TARGET."
    exit 1
  fi
fi

case "$MODE" in
  cli)     install_global ;;
  init)    install_init ;;
  agent)   install_agent ;;
  upgrade) install_upgrade ;;
  setup)   install_setup "$SETUP_TARGET" ;;
  *) error "unknown mode: $MODE"; exit 1 ;;
esac
