#!/usr/bin/env bash
#
# jsm installer - Jeffrey's Skills Manager
#
# One-liner install (with cache buster):
#   curl -fsSL "https://jeffreys-skills.md/install.sh?$(date +%s)" | bash
#
# Or without cache buster:
#   curl -fsSL https://jeffreys-skills.md/install.sh | bash
#
# Options:
#   --version vX.Y.Z   Install specific version (default: latest)
#   --dest DIR         Install to DIR (default: ~/.local/bin)
#   --system           Install to /usr/local/bin (requires sudo)
#   --easy-mode        Auto-update PATH in shell rc files
#   --verify           Run self-test after install
#   --from-source      Build from source instead of downloading binary (explicit only)
#   --quiet            Suppress non-error output
#   --no-gum           Disable gum formatting even if available
#   --no-modify-path   Do not update shell rc files
#   --no-verify        Skip checksum + signature verification (for testing only)
#   --completions      Install shell completions after install
#   --telemetry        Enable anonymous install telemetry (opt-in)
#   --force            Force reinstall even if same version is installed
#   --offline [FILE]   Airgap mode: skip network checks, or install from local tarball
#   --uninstall        Remove the installed binary, config dir, and scheduler artifacts
#   -y, --yes          Skip the uninstall confirmation prompt
#
set -euo pipefail
umask 022
shopt -s lastpipe 2>/dev/null || true

# ═══════════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════════

OWNER="${OWNER:-Dicklesworthstone}"
REPO="${REPO:-jeffreys-skills.md}"
DEFAULT_INSTALL_DIR="$HOME/.local/bin"
DEFAULT_DOWNLOAD_BASE_URL="https://jeffreys-skills.md/api/v1/downloads/jsm"
DEFAULT_TELEMETRY_URL="https://jeffreys-skills.md/api/telemetry/install"
LOCK_FILE="/tmp/jsm-install.lock"

VERSION="${JSM_VERSION:-latest}"
DEST="${JSM_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
DOWNLOAD_BASE_URL="${JSM_DOWNLOAD_BASE_URL:-$DEFAULT_DOWNLOAD_BASE_URL}"
DOWNLOAD_BASE_URL="${DOWNLOAD_BASE_URL%/}"
LATEST_URL="${JSM_LATEST_URL:-${DOWNLOAD_BASE_URL}/latest.txt}"

# Telemetry: opt-in only (set JSM_TELEMETRY=1 to enable)
TELEMETRY_ENABLED="${JSM_TELEMETRY:-0}"
TELEMETRY_URL="${JSM_TELEMETRY_URL:-$DEFAULT_TELEMETRY_URL}"
INSTALL_START_TIME=$(date +%s)

# Flags
EASY=0
QUIET=0
VERIFY=0
FROM_SOURCE=0
SYSTEM=0
NO_GUM=0
NO_MODIFY_PATH=0
NO_CHECKSUM=0
FORCE_INSTALL=0
INSTALL_COMPLETIONS=0
OFFLINE="${JSM_OFFLINE:-0}"
OFFLINE_TARBALL="${OFFLINE_TARBALL:-}"
AGENT_VERSION_LOOKUP="${JSM_INSTALLER_AGENT_VERSIONS:-0}"
AGENT_VERSION_TIMEOUT="${JSM_INSTALLER_AGENT_VERSION_TIMEOUT:-1}"
UNINSTALL=0
ASSUME_YES=0

# Sigstore (best-effort)
SIGSTORE_BUNDLE_URL="${SIGSTORE_BUNDLE_URL:-}"
COSIGN_IDENTITY_RE="${COSIGN_IDENTITY_RE:-^https://github.com/${OWNER}/${REPO}/.github/workflows/.*$}"
COSIGN_OIDC_ISSUER="${COSIGN_OIDC_ISSUER:-https://token.actions.githubusercontent.com}"

# ═══════════════════════════════════════════════════════════════════════════════
# Proxy Support
# ═══════════════════════════════════════════════════════════════════════════════

PROXY_ARGS=()

setup_proxy() {
  PROXY_ARGS=()
  if [[ -n "${HTTPS_PROXY:-}" ]]; then
    PROXY_ARGS=(--proxy "$HTTPS_PROXY")
  elif [[ -n "${HTTP_PROXY:-}" ]]; then
    PROXY_ARGS=(--proxy "$HTTP_PROXY")
  fi
  # Support custom CA certificates (corporate proxies)
  if [[ -n "${SSL_CERT_FILE:-}" ]] && [[ -f "${SSL_CERT_FILE}" ]]; then
    PROXY_ARGS+=(--cacert "$SSL_CERT_FILE")
  elif [[ -n "${REQUESTS_CA_BUNDLE:-}" ]] && [[ -f "${REQUESTS_CA_BUNDLE}" ]]; then
    PROXY_ARGS+=(--cacert "$REQUESTS_CA_BUNDLE")
  elif [[ -n "${CURL_CA_BUNDLE:-}" ]] && [[ -f "${CURL_CA_BUNDLE}" ]]; then
    PROXY_ARGS+=(--cacert "$CURL_CA_BUNDLE")
  fi
}

setup_proxy

# ═══════════════════════════════════════════════════════════════════════════════
# Gum Detection + ANSI Fallback Output System
# ═══════════════════════════════════════════════════════════════════════════════

HAS_GUM=0
if command -v gum &> /dev/null && [ -t 1 ]; then
  HAS_GUM=1
fi

log() { [ "$QUIET" -eq 1 ] && return 0; echo -e "$@"; }

detect_host_os() {
  local uname_s
  uname_s=$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
  case "$uname_s" in
    darwin*) printf '%s\n' "darwin" ;;
    linux*) printf '%s\n' "linux" ;;
    msys*|mingw*|cygwin*) printf '%s\n' "windows" ;;
    *) printf '%s\n' "$uname_s" ;;
  esac
}

display_home_relative() {
  local path="$1"
  if [[ "$path" == "$HOME" ]]; then
    printf '~'
    return 0
  fi
  if [[ "$path" == "$HOME/"* ]]; then
    printf '%s/%s' '~' "${path#"$HOME"/}"
    return 0
  fi
  printf '%s' "$path"
}

installer_config_dir() {
  local os="${1:-${OS:-}}"
  if [[ -z "$os" ]]; then
    os=$(detect_host_os)
  fi
  case "$os" in
    darwin) printf '%s\n' "$HOME/Library/Application Support/jsm" ;;
    windows)
      if [[ -n "${APPDATA:-}" ]]; then
        printf '%s\n' "${APPDATA%/}/jsm"
      else
        printf '%s\n' "$HOME/AppData/Roaming/jsm"
      fi
      ;;
    *) printf '%s\n' "${XDG_CONFIG_HOME:-$HOME/.config}/jsm" ;;
  esac
}

installer_logs_dir() {
  local os="${1:-${OS:-}}"
  if [[ -z "$os" ]]; then
    os=$(detect_host_os)
  fi
  case "$os" in
    darwin) printf '%s\n' "$HOME/Library/Application Support/jsm/logs" ;;
    windows)
      if [[ -n "${LOCALAPPDATA:-}" ]]; then
        printf '%s\n' "${LOCALAPPDATA%/}/jsm/logs"
      else
        printf '%s\n' "$HOME/AppData/Local/jsm/logs"
      fi
      ;;
    *) printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/jsm/logs" ;;
  esac
}

installer_binary_name() {
  local os="${1:-}"
  if [[ -z "$os" ]]; then
    os=$(detect_host_os)
  fi
  if [[ "$os" == "windows" ]]; then
    printf '%s\n' "jsm.exe"
  else
    printf '%s\n' "jsm"
  fi
}

declare -a UNINSTALL_PLAN=()
declare -a UNINSTALL_REMOVED=()
declare -a UNINSTALL_SKIPPED=()
declare -a UNINSTALL_FAILED=()
UNINSTALL_HOST_OS=""
UNINSTALL_BINARY_PATH=""
UNINSTALL_CONFIG_PATH=""
UNINSTALL_LOGS_PATH=""

append_uninstall_plan() {
  local kind="$1"
  local path="$2"
  local label="$3"
  UNINSTALL_PLAN+=("${kind}|${path}|${label}")
}

has_managed_crontab_entry() {
  command -v crontab >/dev/null 2>&1 || return 1
  local current
  current=$(crontab -l 2>/dev/null || true)
  [[ "$current" == *"# jsm-auto-update"* ]]
}

remove_managed_crontab_entry() {
  command -v crontab >/dev/null 2>&1 || return 1
  local current updated
  current=$(crontab -l 2>/dev/null || true)
  [[ "$current" == *"# jsm-auto-update"* ]] || return 1

  updated=$(printf '%s\n' "$current" | awk '
    BEGIN { skip = 0 }
    /^# jsm-auto-update/ { skip = 1; next }
    skip == 1 { skip = 0; next }
    { print }
  ')

  if [[ -z "${updated//[$'\t\r\n ']/}" ]]; then
    crontab -r >/dev/null 2>&1
  else
    printf '%s\n' "$updated" | crontab - >/dev/null 2>&1
  fi
}

find_schtasks_cmd() {
  if command -v schtasks.exe >/dev/null 2>&1; then
    printf '%s\n' "schtasks.exe"
    return 0
  fi
  if command -v schtasks >/dev/null 2>&1; then
    printf '%s\n' "schtasks"
    return 0
  fi
  return 1
}

has_windows_scheduled_task() {
  local schtasks_cmd
  schtasks_cmd=$(find_schtasks_cmd) || return 1
  "$schtasks_cmd" /query /tn "JsmAutoUpdate" >/dev/null 2>&1
}

remove_windows_scheduled_task() {
  local schtasks_cmd
  schtasks_cmd=$(find_schtasks_cmd) || return 1
  "$schtasks_cmd" /delete /tn "JsmAutoUpdate" /f >/dev/null 2>&1
}

build_uninstall_plan() {
  UNINSTALL_PLAN=()
  UNINSTALL_HOST_OS=$(detect_host_os)

  local binary_name dest_dir
  binary_name=$(installer_binary_name "$UNINSTALL_HOST_OS")
  dest_dir="$DEST"
  if [[ "$dest_dir" != "/" ]]; then
    dest_dir="${dest_dir%/}"
  fi
  UNINSTALL_BINARY_PATH="${dest_dir}/${binary_name}"
  UNINSTALL_CONFIG_PATH=$(installer_config_dir "$UNINSTALL_HOST_OS")
  UNINSTALL_LOGS_PATH=$(installer_logs_dir "$UNINSTALL_HOST_OS")

  if [[ -e "$UNINSTALL_BINARY_PATH" ]]; then
    append_uninstall_plan "file" "$UNINSTALL_BINARY_PATH" "Binary"
  fi
  if [[ -e "$UNINSTALL_CONFIG_PATH" ]]; then
    append_uninstall_plan "dir" "$UNINSTALL_CONFIG_PATH" "Config directory"
  fi

  case "$UNINSTALL_HOST_OS" in
    darwin)
      local launchd_plist stdout_log stderr_log
      launchd_plist="$HOME/Library/LaunchAgents/com.jeffreys-skills.jsm-auto-update.plist"
      stdout_log="$UNINSTALL_LOGS_PATH/auto-update-launchd.stdout.log"
      stderr_log="$UNINSTALL_LOGS_PATH/auto-update-launchd.stderr.log"
      if [[ -e "$launchd_plist" ]]; then
        append_uninstall_plan "launchd_plist" "$launchd_plist" "launchd auto-update plist"
      fi
      if [[ -e "$stdout_log" ]]; then
        append_uninstall_plan "file" "$stdout_log" "launchd auto-update stdout log"
      fi
      if [[ -e "$stderr_log" ]]; then
        append_uninstall_plan "file" "$stderr_log" "launchd auto-update stderr log"
      fi
      ;;
    linux)
      local systemd_dir service_unit timer_unit
      systemd_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
      service_unit="$systemd_dir/jsm-auto-update.service"
      timer_unit="$systemd_dir/jsm-auto-update.timer"
      if [[ -e "$service_unit" ]]; then
        append_uninstall_plan "systemd_service" "$service_unit" "systemd user service"
      fi
      if [[ -e "$timer_unit" ]]; then
        append_uninstall_plan "systemd_timer" "$timer_unit" "systemd user timer"
      fi
      if has_managed_crontab_entry; then
        append_uninstall_plan "crontab" "crontab" "managed crontab auto-update entry"
      fi
      ;;
    windows)
      if has_windows_scheduled_task; then
        append_uninstall_plan "taskscheduler" "Task Scheduler/JsmAutoUpdate" "Task Scheduler auto-update task"
      fi
      ;;
  esac
}

print_uninstall_plan() {
  echo ""
  if [[ ${#UNINSTALL_PLAN[@]} -eq 0 ]]; then
    info "No existing jsm install artifacts were found for $(display_home_relative "$DEST")."
    return 1
  fi

  warn "The following jsm artifacts will be removed:"
  local item kind path label
  for item in "${UNINSTALL_PLAN[@]}"; do
    IFS='|' read -r kind path label <<< "$item"
    case "$kind" in
      crontab|taskscheduler)
        echo "  - $label: $path"
        ;;
      *)
        echo "  - $label: $(display_home_relative "$path")"
        ;;
    esac
  done
}

confirm_uninstall() {
  if [[ "$ASSUME_YES" -eq 1 ]]; then
    return 0
  fi
  if [[ ! -t 0 ]]; then
    err "Refusing to uninstall without an interactive confirmation prompt. Re-run with --yes after reviewing the plan."
    return 1
  fi
  if [[ "$HAS_GUM" -eq 1 ]] && [[ "$NO_GUM" -eq 0 ]]; then
    gum confirm "Proceed with uninstall?"
    return $?
  fi
  printf 'Proceed with uninstall? [y/N]: '
  local ans=""
  read -r ans || return 1
  case "$ans" in
    y|Y|yes|YES) return 0 ;;
    *) return 1 ;;
  esac
}

disable_auto_update_before_uninstall() {
  if [[ -x "$UNINSTALL_BINARY_PATH" ]]; then
    if "$UNINSTALL_BINARY_PATH" auto-update disable >/dev/null 2>&1; then
      UNINSTALL_REMOVED+=("Auto-update scheduler via $(display_home_relative "$UNINSTALL_BINARY_PATH") auto-update disable")
    else
      warn "Failed to disable auto-update via $(display_home_relative "$UNINSTALL_BINARY_PATH"); continuing with direct scheduler cleanup"
    fi
  fi
}

print_uninstall_hint() {
  local command
  if [[ "$SYSTEM" -eq 1 ]]; then
    command='curl -fsSL https://jeffreys-skills.md/install.sh | sudo bash -s -- --uninstall --system'
  elif [[ "$DEST" == "$DEFAULT_INSTALL_DIR" ]]; then
    command='curl -fsSL https://jeffreys-skills.md/install.sh | bash -s -- --uninstall'
  else
    command="curl -fsSL https://jeffreys-skills.md/install.sh | bash -s -- --uninstall --dest \"$DEST\""
  fi

  if [[ "$HAS_GUM" -eq 1 ]] && [[ "$NO_GUM" -eq 0 ]]; then
    gum style --foreground 245 --italic -- "Uninstall: $command"
    gum style --foreground 245 --italic -- "The uninstall flow previews targets and asks for confirmation."
  else
    echo -e "\033[0;90mUninstall: $command\033[0m"
    echo -e "\033[0;90mThe uninstall flow previews targets and asks for confirmation.\033[0m"
  fi
}

run_uninstall() {
  build_uninstall_plan
  if ! print_uninstall_plan; then
    ok "Nothing to uninstall."
    exit 0
  fi

  if ! confirm_uninstall; then
    err "Uninstall aborted."
    exit 1
  fi

  UNINSTALL_REMOVED=()
  UNINSTALL_SKIPPED=()
  UNINSTALL_FAILED=()

  disable_auto_update_before_uninstall
  build_uninstall_plan

  local systemd_touched=0
  local item kind path label display_path
  for item in "${UNINSTALL_PLAN[@]}"; do
    IFS='|' read -r kind path label <<< "$item"
    display_path="$path"
    case "$kind" in
      crontab|taskscheduler) : ;;
      *) display_path=$(display_home_relative "$path") ;;
    esac

    case "$kind" in
      file)
        if [[ -e "$path" ]]; then
          if rm -f -- "$path"; then
            UNINSTALL_REMOVED+=("$label: $display_path")
          else
            UNINSTALL_FAILED+=("$label: $display_path")
          fi
        else
          UNINSTALL_SKIPPED+=("$label: $display_path (already absent)")
        fi
        ;;
      dir)
        if [[ -e "$path" ]]; then
          if rm -rf -- "$path"; then
            UNINSTALL_REMOVED+=("$label: $display_path")
          else
            UNINSTALL_FAILED+=("$label: $display_path")
          fi
        else
          UNINSTALL_SKIPPED+=("$label: $display_path (already absent)")
        fi
        ;;
      launchd_plist)
        if [[ -e "$path" ]]; then
          if command -v launchctl >/dev/null 2>&1; then
            launchctl bootout "gui/$(id -u 2>/dev/null || echo 0)" "$path" >/dev/null 2>&1 || true
            launchctl bootout "gui/$(id -u 2>/dev/null || echo 0)/com.jeffreys-skills.jsm-auto-update" >/dev/null 2>&1 || true
            launchctl unload "$path" >/dev/null 2>&1 || true
          fi
          if rm -f -- "$path"; then
            UNINSTALL_REMOVED+=("$label: $display_path")
          else
            UNINSTALL_FAILED+=("$label: $display_path")
          fi
        else
          UNINSTALL_SKIPPED+=("$label: $display_path (already absent)")
        fi
        ;;
      systemd_service|systemd_timer)
        if [[ -e "$path" ]]; then
          if command -v systemctl >/dev/null 2>&1; then
            systemctl --user disable --now jsm-auto-update.timer >/dev/null 2>&1 || true
            systemctl --user stop jsm-auto-update.service >/dev/null 2>&1 || true
          fi
          if rm -f -- "$path"; then
            UNINSTALL_REMOVED+=("$label: $display_path")
            systemd_touched=1
          else
            UNINSTALL_FAILED+=("$label: $display_path")
          fi
        else
          UNINSTALL_SKIPPED+=("$label: $display_path (already absent)")
        fi
        ;;
      crontab)
        if remove_managed_crontab_entry; then
          UNINSTALL_REMOVED+=("$label: $path")
        else
          UNINSTALL_SKIPPED+=("$label: $path (already absent)")
        fi
        ;;
      taskscheduler)
        if remove_windows_scheduled_task; then
          UNINSTALL_REMOVED+=("$label: $path")
        else
          UNINSTALL_SKIPPED+=("$label: $path (already absent)")
        fi
        ;;
    esac
  done

  if [[ "$systemd_touched" -eq 1 ]] && command -v systemctl >/dev/null 2>&1; then
    systemctl --user daemon-reload >/dev/null 2>&1 || true
  fi

  echo ""
  if [[ ${#UNINSTALL_REMOVED[@]} -gt 0 ]]; then
    ok "Removed:"
    for item in "${UNINSTALL_REMOVED[@]}"; do
      echo "  - $item"
    done
  else
    info "Nothing was removed."
  fi

  if [[ ${#UNINSTALL_SKIPPED[@]} -gt 0 ]]; then
    info "Skipped:"
    for item in "${UNINSTALL_SKIPPED[@]}"; do
      echo "  - $item"
    done
  fi

  if [[ ${#UNINSTALL_FAILED[@]} -gt 0 ]]; then
    err "Failed:"
    for item in "${UNINSTALL_FAILED[@]}"; do
      echo "  - $item" >&2
    done
    exit 1
  fi

  ok "jsm uninstall complete."
  exit 0
}

info() {
  [ "$QUIET" -eq 1 ] && return 0
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    gum style --foreground 39 -- "-> $*" >&2
  else
    echo -e "\033[0;34m->\033[0m $*" >&2
  fi
}

ok() {
  [ "$QUIET" -eq 1 ] && return 0
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    gum style --foreground 42 -- "ok $*" >&2
  else
    echo -e "\033[0;32mok\033[0m $*" >&2
  fi
}

warn() {
  [ "$QUIET" -eq 1 ] && return 0
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    gum style --foreground 214 -- "!! $*" >&2
  else
    echo -e "\033[1;33m!!\033[0m $*" >&2
  fi
}

err() {
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    gum style --foreground 196 -- "xx $*" >&2
  else
    echo -e "\033[0;31mxx\033[0m $*" >&2
  fi
}

run_with_spinner() {
  local title="$1"
  shift
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ] && [ "$QUIET" -eq 0 ]; then
    gum spin --spinner dot --title "$title" -- "$@"
  else
    info "$title"
    "$@"
  fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# Box Drawing
# ═══════════════════════════════════════════════════════════════════════════════

draw_box() {
  local color="$1"
  shift
  local lines=("$@")
  local max_width=0
  local esc
  esc=$(printf '\033')
  local strip_ansi_sed="s/${esc}\\[[0-9;]*m//g"

  for line in "${lines[@]}"; do
    local stripped
    stripped=$(printf '%b' "$line" | LC_ALL=C sed "$strip_ansi_sed")
    local len=${#stripped}
    if [ "$len" -gt "$max_width" ]; then
      max_width=$len
    fi
  done

  local inner_width=$((max_width + 4))
  local border=""
  for ((i=0; i<inner_width; i++)); do
    border+="═"
  done

  printf "\033[%sm╔%s╗\033[0m\n" "$color" "$border"

  for line in "${lines[@]}"; do
    local stripped
    stripped=$(printf '%b' "$line" | LC_ALL=C sed "$strip_ansi_sed")
    local len=${#stripped}
    local padding=$((max_width - len))
    local pad_str=""
    for ((i=0; i<padding; i++)); do
      pad_str+=" "
    done
    printf "\033[%sm║\033[0m  %b%s  \033[%sm║\033[0m\n" "$color" "$line" "$pad_str" "$color"
  done

  printf "\033[%sm╚%s╝\033[0m\n" "$color" "$border"
}

# ═══════════════════════════════════════════════════════════════════════════════
# Telemetry Functions (opt-in only)
# ═══════════════════════════════════════════════════════════════════════════════

json_escape() {
  local s="$1"
  s=${s//\\/\\\\}
  s=${s//\"/\\\"}
  s=${s//$'\n'/ }
  s=${s//$'\r'/ }
  s=${s//$'\t'/ }
  printf '%s' "$s"
}

collect_platform_info() {
  local os arch distro distro_version
  os=$(uname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
  arch=$(uname -m 2>/dev/null || echo "unknown")
  distro=""
  distro_version=""

  if [[ -f /etc/os-release ]]; then
    distro=$(. /etc/os-release && echo "${ID:-}")
    distro_version=$(. /etc/os-release && echo "${VERSION_ID:-}")
  fi

  printf '{"os":"%s","arch":"%s","distro":"%s","distro_version":"%s"}' \
    "$(json_escape "$os")" \
    "$(json_escape "$arch")" \
    "$(json_escape "$distro")" \
    "$(json_escape "$distro_version")"
}

generate_install_id() {
  if command -v uuidgen >/dev/null 2>&1; then
    uuidgen 2>/dev/null | tr '[:upper:]' '[:lower:]'
  elif [[ -f /proc/sys/kernel/random/uuid ]]; then
    cat /proc/sys/kernel/random/uuid 2>/dev/null
  else
    printf '%s-%s' "$(date +%s)" "$RANDOM"
  fi
}

send_telemetry() {
  [[ "${TELEMETRY_ENABLED}" == "1" ]] || return 0
  local payload="$1"
  curl -s -X POST ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} "${TELEMETRY_URL}" \
    -H "Content-Type: application/json" \
    -d "$payload" \
    --max-time 5 \
    >/dev/null 2>&1 &
}

report_success() {
  [[ "${TELEMETRY_ENABLED}" == "1" ]] || return 0
  local end_time duration platform install_id
  end_time=$(date +%s)
  duration=$((end_time - INSTALL_START_TIME))
  platform=$(collect_platform_info)
  install_id=$(generate_install_id)
  local version_escaped
  version_escaped=$(json_escape "${version_tag:-unknown}")

  send_telemetry "{
    \"event\": \"install_success\",
    \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
    \"install_id\": \"${install_id}\",
    \"platform\": ${platform},
    \"install\": {
      \"version\": \"${version_escaped}\",
      \"method\": \"curl_pipe_bash\",
      \"duration_seconds\": ${duration}
    }
  }"
}

report_failure() {
  [[ "${TELEMETRY_ENABLED}" == "1" ]] || return 0
  local stage="$1"
  local error_code="${2:-unknown}"
  local error_message="${3:-}"
  local platform install_id
  local version_attempted="${version_tag:-$VERSION}"
  platform=$(collect_platform_info)
  install_id=$(generate_install_id)
  error_message=$(json_escape "$error_message")
  stage=$(json_escape "$stage")
  error_code=$(json_escape "$error_code")
  version_attempted=$(json_escape "$version_attempted")

  send_telemetry "{
    \"event\": \"install_failure\",
    \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
    \"install_id\": \"${install_id}\",
    \"platform\": ${platform},
    \"failure\": {
      \"stage\": \"${stage}\",
      \"error_code\": \"${error_code}\",
      \"error_message\": \"${error_message}\",
      \"version_attempted\": \"${version_attempted}\"
    }
  }"
}

fail_with_telemetry() {
  local stage="$1"
  local message="$2"
  report_failure "$stage" "script_error" "$message"
  err "$message"
  exit 1
}

# ═══════════════════════════════════════════════════════════════════════════════
# AI Agent Detection
# ═══════════════════════════════════════════════════════════════════════════════

DETECTED_AGENTS=()
CLAUDE_VERSION=""
CODEX_VERSION=""
GEMINI_VERSION=""
AIDER_VERSION=""
CONTINUE_VERSION=""
CURSOR_VERSION=""
CURSOR_SKILLS_READY=0
CURSOR_SKILLS_CONFIGURED=0
CURSOR_SKILLS_DIR=""
COPILOT_VERSION=""

print_agent_scan_notice() {
  [ "$QUIET" -eq 1 ] && return 0

  local line1="Scanning for installed AI coding agents..."
  local line2="This checks for Claude Code, Codex, Gemini, Cursor, and more."

  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    echo ""
    gum style \
      --border normal \
      --border-foreground 244 \
      --padding "0 1" \
      -- \
      "$(gum style --foreground 212 --bold -- 'Agent scan')" \
      "$(gum style --foreground 247 -- "$line1")" \
      "$(gum style --foreground 245 -- "$line2")"
    echo ""
  else
    echo ""
    draw_box "0;36" "$line1" "$line2"
    echo ""
  fi
}

try_version() {
  local cmd="$1"
  [[ "$AGENT_VERSION_LOOKUP" == "1" ]] || return 0
  command -v "$cmd" >/dev/null 2>&1 || return 0

  local timeout_secs="${AGENT_VERSION_TIMEOUT:-1}"
  if ! [[ "$timeout_secs" =~ ^[0-9]+$ ]]; then
    timeout_secs=1
  fi

  if command -v timeout >/dev/null 2>&1; then
    timeout "$timeout_secs" "$cmd" --version 2>/dev/null | head -1 || true
  elif command -v gtimeout >/dev/null 2>&1; then
    gtimeout "$timeout_secs" "$cmd" --version 2>/dev/null | head -1 || true
  else
    "$cmd" --version 2>/dev/null | head -1 || true
  fi
}

detect_agents() {
  DETECTED_AGENTS=()
  CURSOR_SKILLS_READY=0
  CURSOR_SKILLS_CONFIGURED=0
  CURSOR_SKILLS_DIR="$HOME/.cursor/skills"

  if [[ -d "$HOME/.claude" ]] || command -v claude &>/dev/null; then
    DETECTED_AGENTS+=("claude-code")
    CLAUDE_VERSION=$(try_version claude)
  fi

  if [[ -n "${CODEX_HOME:-}" && -d "${CODEX_HOME}" ]] || [[ -d "$HOME/.codex" ]] || command -v codex &>/dev/null; then
    DETECTED_AGENTS+=("codex-cli")
    CODEX_VERSION=$(try_version codex)
  fi

  if [[ -d "$HOME/.gemini" ]] || [[ -d "$HOME/.gemini-cli" ]] || command -v gemini &>/dev/null || command -v agy &>/dev/null; then
    # The legacy Gemini CLI (`gemini`, retiring 2026-06-18) and its successor the
    # Antigravity CLI (`agy`) share ~/.gemini/ and the ~/.gemini/skills/ dir.
    DETECTED_AGENTS+=("gemini-cli")
    GEMINI_VERSION=$(try_version gemini)
    [[ -z "$GEMINI_VERSION" ]] && GEMINI_VERSION=$(try_version agy)
  fi

  if command -v aider &>/dev/null; then
    DETECTED_AGENTS+=("aider")
    AIDER_VERSION=$(try_version aider)
  fi

  if command -v copilot &>/dev/null || [[ -d "$HOME/.copilot" ]]; then
    DETECTED_AGENTS+=("github-copilot-cli")
    COPILOT_VERSION=$(try_version copilot)
  fi

  if [[ -d "$HOME/.continue" ]]; then
    DETECTED_AGENTS+=("continue")
    if [[ -f "$HOME/.continue/config.json" ]]; then
      CONTINUE_VERSION="config present"
    fi
  fi

  local cursor_detected=0
  local cursor_settings_found=0
  local cursor_settings_default="$HOME/.cursor/settings.json"
  local cursor_settings_mac="$HOME/Library/Application Support/Cursor/User/settings.json"
  local cursor_settings_linux="$HOME/.config/Cursor/User/settings.json"
  if [[ -f "$cursor_settings_default" ]] || [[ -f "$cursor_settings_mac" ]] || [[ -f "$cursor_settings_linux" ]]; then
    cursor_detected=1
    cursor_settings_found=1
  elif command -v cursor &>/dev/null; then
    cursor_detected=1
  elif command -v pgrep >/dev/null 2>&1; then
    if pgrep -fl "[Cc]ursor" 2>/dev/null | grep -qv 'CursorUIViewService\|/System/Library/'; then
      cursor_detected=1
    fi
  fi

  if [ "$cursor_detected" -eq 1 ]; then
    DETECTED_AGENTS+=("cursor-ide")
    CURSOR_VERSION=$(try_version cursor)
    CURSOR_SKILLS_READY="$cursor_settings_found"
  fi
}

maybe_configure_detected_agent_targets() {
  local has_cursor=0
  for agent in "${DETECTED_AGENTS[@]}"; do
    if [[ "$agent" == "cursor-ide" ]]; then
      has_cursor=1
      break
    fi
  done

  if [[ "$has_cursor" != "1" ]]; then
    return 0
  fi

  local existing_cursor_dir=""
  existing_cursor_dir="$("$DEST/$BINARY_NAME" config get skills.cursor_dir 2>/dev/null || true)"
  existing_cursor_dir="$(printf '%s' "$existing_cursor_dir" | tr -d '\r')"

  if [[ -n "${existing_cursor_dir//[[:space:]]/}" ]]; then
    CURSOR_SKILLS_DIR="$existing_cursor_dir"
    CURSOR_SKILLS_READY=1
    return 0
  fi

  if [[ "$CURSOR_SKILLS_READY" == "1" ]]; then
    return 0
  fi

  local target_cursor_dir="$HOME/.cursor/skills"
  if "$DEST/$BINARY_NAME" config set skills.cursor_dir "$target_cursor_dir" >/dev/null 2>&1; then
    CURSOR_SKILLS_READY=1
    CURSOR_SKILLS_CONFIGURED=1
    CURSOR_SKILLS_DIR="$target_cursor_dir"
    info "Configured Cursor skills target: ${CURSOR_SKILLS_DIR}"
  else
    warn "Detected Cursor, but could not persist the Cursor skills target automatically"
  fi
}

print_detected_agents() {
  if [[ ${#DETECTED_AGENTS[@]} -eq 0 ]]; then
    info "No AI coding agents detected"
    return
  fi

  local count=${#DETECTED_AGENTS[@]}
  local plural=""
  [[ $count -gt 1 ]] && plural="s"

  local agent_lines=()
  for agent in "${DETECTED_AGENTS[@]}"; do
    case "$agent" in
      claude-code)
        local v=""; [[ -n "$CLAUDE_VERSION" ]] && v=" ($CLAUDE_VERSION)"
        agent_lines+=("Claude Code${v}")
        ;;
      codex-cli)
        local v=""; [[ -n "$CODEX_VERSION" ]] && v=" ($CODEX_VERSION)"
        agent_lines+=("Codex CLI${v}")
        ;;
      gemini-cli)
        local v=""; [[ -n "$GEMINI_VERSION" ]] && v=" ($GEMINI_VERSION)"
        agent_lines+=("Gemini CLI${v}")
        ;;
      aider)
        local v=""; [[ -n "$AIDER_VERSION" ]] && v=" ($AIDER_VERSION)"
        agent_lines+=("Aider${v}")
        ;;
      github-copilot-cli)
        local v=""; [[ -n "$COPILOT_VERSION" ]] && v=" ($COPILOT_VERSION)"
        agent_lines+=("GitHub Copilot CLI${v}")
        ;;
      continue)
        local v=""; [[ -n "$CONTINUE_VERSION" ]] && v=" ($CONTINUE_VERSION)"
        agent_lines+=("Continue${v}")
        ;;
      cursor-ide)
        local v=""; [[ -n "$CURSOR_VERSION" ]] && v=" ($CURSOR_VERSION)"
        if [[ "$CURSOR_SKILLS_READY" == "1" ]]; then
          agent_lines+=("Cursor IDE${v}")
        else
          agent_lines+=("Cursor IDE${v} (skills target will be auto-configured if needed)")
        fi
        ;;
    esac
  done

  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    echo ""
    gum style --foreground 39 --bold -- "Detected AI Coding Agent${plural}:"
    for line in "${agent_lines[@]}"; do
      gum style --foreground 42 -- "  ok $line"
    done
    echo ""
  else
    echo ""
    echo -e "\033[1;39mDetected AI Coding Agent${plural}:\033[0m"
    for line in "${agent_lines[@]}"; do
      echo -e "  \033[0;32mok\033[0m $line"
    done
    echo ""
  fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# Platform Detection
# ═══════════════════════════════════════════════════════════════════════════════

OS=""
ARCH=""
TARGET=""
LINUX_FALLBACK_TARGET=""

detect_platform() {
  OS=$(uname -s | tr 'A-Z' 'a-z')
  ARCH=$(uname -m)
  case "$OS" in
    linux*) OS="linux" ;;
    darwin*) OS="darwin" ;;
    msys*|mingw*|cygwin*) OS="windows" ;;
  esac
  case "$ARCH" in
    x86_64|amd64) ARCH="x86_64" ;;
    arm64|aarch64) ARCH="aarch64" ;;
    *) warn "Unknown arch $ARCH, using as-is" ;;
  esac

  TARGET=""
  LINUX_FALLBACK_TARGET=""
  case "${OS}-${ARCH}" in
    linux-x86_64)
      TARGET="x86_64-unknown-linux-musl"
      LINUX_FALLBACK_TARGET="x86_64-unknown-linux-gnu"
      ;;
    linux-aarch64)
      TARGET="aarch64-unknown-linux-gnu"
      LINUX_FALLBACK_TARGET="aarch64-unknown-linux-musl"
      ;;
    darwin-x86_64)  TARGET="x86_64-apple-darwin" ;;
    darwin-aarch64) TARGET="aarch64-apple-darwin" ;;
    windows-x86_64) TARGET="x86_64-pc-windows-msvc" ;;
    *) :;;
  esac

  # WSL detection
  if [[ "$OS" == "linux" ]] && grep -qi microsoft /proc/version 2>/dev/null; then
    warn "WSL detected. Some features may need additional configuration"
  fi

  if [ -z "$TARGET" ] && [ "$FROM_SOURCE" -eq 0 ]; then
    fail_with_telemetry "platform" "No published release artifact for ${OS}/${ARCH}. This installer only supports prebuilt release binaries."
  fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# Version Resolution
# ═══════════════════════════════════════════════════════════════════════════════

version_tag=""

resolve_version() {
  if [ "$VERSION" != "latest" ]; then
    if [[ "$VERSION" == v* ]]; then
      version_tag="$VERSION"
    else
      version_tag="v$VERSION"
    fi
    return 0
  fi

  info "Resolving latest version..."

  # Primary: latest.txt from the site-hosted download relay
  local tag
  if tag=$(curl -fsSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} "${LATEST_URL}" 2>/dev/null | head -n 1 | tr -d '\r\n'); then
    if [ -n "$tag" ]; then
      version_tag="$tag"
      info "Resolved latest version: $version_tag"
      return 0
    fi
  fi

  # Fallback: GitHub API
  local api_url="https://api.github.com/repos/${OWNER}/${REPO}/releases/latest"
  if tag=$(curl -fsSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} -H "Accept: application/vnd.github.v3+json" "$api_url" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'); then
    if [ -n "$tag" ]; then
      version_tag="$tag"
      info "Resolved latest version via GitHub: $version_tag"
      return 0
    fi
  fi

  # Fallback: redirect-based
  local redirect_url="https://github.com/${OWNER}/${REPO}/releases/latest"
  if tag=$(curl -fsSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} -o /dev/null -w '%{url_effective}' "$redirect_url" 2>/dev/null | sed -E 's|.*/tag/||'); then
    if [ -n "$tag" ] && [[ "$tag" =~ ^v[0-9] ]] && [[ "$tag" != *"/"* ]]; then
      version_tag="$tag"
      info "Resolved latest version via redirect: $version_tag"
      return 0
    fi
  fi

  fail_with_telemetry "version" "Could not resolve the latest jsm version from the site relay or GitHub fallbacks."
}

# ═══════════════════════════════════════════════════════════════════════════════
# Preflight Checks
# ═══════════════════════════════════════════════════════════════════════════════

check_disk_space() {
  local min_kb=10240
  local path="$DEST"
  if [ ! -d "$path" ]; then
    path=$(dirname "$path")
  fi
  if command -v df >/dev/null 2>&1; then
    local avail_kb
    avail_kb=$(df -Pk "$path" | awk 'NR==2 {print $4}')
    if [ -n "$avail_kb" ] && [ "$avail_kb" -lt "$min_kb" ]; then
      err "Insufficient disk space in $path (need at least 10MB)"
      exit 1
    fi
  fi
}

check_write_permissions() {
  if [ ! -d "$DEST" ]; then
    if ! mkdir -p "$DEST" 2>/dev/null; then
      err "Cannot create $DEST (insufficient permissions)"
      err "Try running with sudo or choose a writable --dest"
      exit 1
    fi
  fi
  if [ ! -w "$DEST" ]; then
    err "No write permission to $DEST"
    err "Try running with sudo or choose a writable --dest"
    exit 1
  fi
}

check_existing_install() {
  if [ -x "$DEST/jsm" ]; then
    local current
    current=$("$DEST/jsm" --version 2>/dev/null | head -1 || echo "")
    if [ -n "$current" ]; then
      info "Existing jsm detected: $current"
    fi
  fi
}

check_installed_version() {
  local target_version="$1"
  if [ ! -x "$DEST/jsm" ]; then
    return 1
  fi

  local installed_version
  installed_version=$("$DEST/jsm" --version 2>/dev/null | head -1 | sed 's/.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/')

  if [ -z "$installed_version" ]; then
    return 1
  fi

  local target_clean="${target_version#v}"
  local installed_clean="${installed_version#v}"

  if [ "$target_clean" = "$installed_clean" ]; then
    return 0
  fi

  return 1
}

check_network() {
  if [ "$OFFLINE" -eq 1 ]; then
    info "Offline mode enabled; skipping network preflight"
    return 0
  fi
  if [ "$FROM_SOURCE" -eq 1 ]; then
    return 0
  fi
  if ! command -v curl >/dev/null 2>&1; then
    warn "curl not found; skipping network check"
    return 0
  fi
  local test_url="${DOWNLOAD_BASE_URL}/latest.txt"
  if ! curl -fsSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} --connect-timeout 3 --max-time 5 -o /dev/null "$test_url" 2>/dev/null; then
    warn "Network check failed for $test_url"
    warn "Continuing; download may fail"
  fi
}

preflight_checks() {
  info "Running preflight checks"
  check_disk_space
  check_write_permissions
  check_existing_install
  check_network
}

# ═══════════════════════════════════════════════════════════════════════════════
# Checksum & Signature Verification
# ═══════════════════════════════════════════════════════════════════════════════

verify_checksum() {
  local file="$1"
  local expected="$2"
  local actual=""

  if [ ! -f "$file" ]; then
    err "File not found: $file"
    return 1
  fi

  if command -v sha256sum &>/dev/null; then
    actual=$(sha256sum "$file" | cut -d' ' -f1)
  elif command -v shasum &>/dev/null; then
    actual=$(shasum -a 256 "$file" | cut -d' ' -f1)
  else
    err "No SHA256 tool found (sha256sum or shasum). Cannot verify downloaded artifact."
    return 2
  fi

  if [ "$actual" != "$expected" ]; then
    err "Checksum verification FAILED!"
    err "Expected: $expected"
    err "Got:      $actual"
    err "The downloaded file may be corrupted or tampered with."
    rm -f "$file"
    return 1
  fi

  ok "Checksum verified: ${actual:0:16}..."
  return 0
}

verify_sigstore_bundle() {
  local file="$1"
  local artifact_url="$2"

  if ! command -v cosign &>/dev/null; then
    info "cosign not found; skipping signature verification"
    return 0
  fi

  local bundle_url="$SIGSTORE_BUNDLE_URL"
  if [ -z "$bundle_url" ]; then
    bundle_url="${artifact_url}.sigstore.json"
  fi
  SIGSTORE_BUNDLE_EFFECTIVE_URL="$bundle_url"

  local bundle_file=""
  bundle_file="$TMP/$(basename "$bundle_url")"
  local bundle_http_status=""
  info "Fetching sigstore bundle from ${bundle_url}"
  bundle_http_status="$(curl -sSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} -w '%{http_code}' -o "$bundle_file" "$bundle_url" 2>/dev/null || true)"
  SIGSTORE_BUNDLE_HTTP_STATUS="$bundle_http_status"
  if [ "$bundle_http_status" = "404" ]; then
    info "Sigstore bundle not available for this release; skipping signature verification"
    return 0
  elif [ "$bundle_http_status" != "200" ]; then
    rm -f "$bundle_file"
    err "Failed to fetch sigstore bundle from ${bundle_url} (HTTP ${bundle_http_status:-unknown})"
    return 2
  fi

  if ! cosign verify-blob \
    --bundle "$bundle_file" \
    --certificate-identity-regexp "$COSIGN_IDENTITY_RE" \
    --certificate-oidc-issuer "$COSIGN_OIDC_ISSUER" \
    "$file"; then
    rm -f "$bundle_file"
    return 1
  fi

  ok "Signature verified (cosign)"
  return 0
}

# ═══════════════════════════════════════════════════════════════════════════════
# Build From Source
# ═══════════════════════════════════════════════════════════════════════════════

ensure_rust() {
  if command -v cargo >/dev/null 2>&1; then return 0; fi
  if [ "$EASY" -ne 1 ] && [ -t 0 ]; then
    echo -n "Install Rust via rustup? (y/N): "
    read -r ans
    case "$ans" in y|Y) :;; *) warn "Skipping rustup install"; return 1;; esac
  fi
  info "Installing rustup"
  curl --proto '=https' --tlsv1.2 -sSf ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
  export PATH="$HOME/.cargo/bin:$PATH"
}

# ═══════════════════════════════════════════════════════════════════════════════
# Shell Completions
# ═══════════════════════════════════════════════════════════════════════════════

detect_default_shell() {
  local shell="${SHELL:-}"
  [ -z "$shell" ] && return 1
  shell=$(basename "$shell")
  case "$shell" in
    bash|zsh|fish) echo "$shell"; return 0 ;;
    *) return 1 ;;
  esac
}

install_completions_for_shell() {
  local shell="$1"
  local bin="$DEST/jsm"
  if [ ! -x "$bin" ]; then
    warn "jsm binary not found at $bin; skipping completions"
    return 1
  fi

  if ! "$bin" completions --help >/dev/null 2>&1; then
    info "Shell completions: skipped (not supported in this version)"
    return 0
  fi

  local target=""
  case "$shell" in
    bash)
      target="${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions/jsm"
      ;;
    zsh)
      target="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions/_jsm"
      ;;
    fish)
      target="${XDG_CONFIG_HOME:-$HOME/.config}/fish/completions/jsm.fish"
      ;;
    *)
      return 1
      ;;
  esac

  if ! mkdir -p "$(dirname "$target")" 2>/dev/null; then
    warn "Failed to create completions directory for $shell"
    return 1
  fi

  local output
  if output=$("$bin" completions "$shell" 2>&1) && [ -n "$output" ]; then
    printf '%s\n' "$output" > "$target"
    ok "Installed $shell completions to $target"
    return 0
  fi

  warn "Failed to install $shell completions"
  return 1
}

maybe_install_completions() {
  local shell=""
  if ! shell=$(detect_default_shell); then
    info "Shell completions: skipped (unknown shell)"
    return 0
  fi

  install_completions_for_shell "$shell" || true
}

# ═══════════════════════════════════════════════════════════════════════════════
# PATH Setup
# ═══════════════════════════════════════════════════════════════════════════════

maybe_add_path() {
  if [[ "$DEST" == "/usr/local/bin" || "$DEST" == "/usr/bin" ]]; then
    return 0
  fi

  case ":$PATH:" in
    *:"$DEST":*) return 0;;
    *)
      if [ "$EASY" -eq 1 ] || [ "$NO_MODIFY_PATH" -eq 0 ]; then
        local UPDATED=0
        local shell_rc=""
        case "${SHELL:-}" in
          */zsh) shell_rc="$HOME/.zshrc" ;;
          */bash) shell_rc="$HOME/.bashrc" ;;
          */fish) shell_rc="$HOME/.config/fish/config.fish" ;;
        esac

        if [ -n "$shell_rc" ] && [ -w "$shell_rc" ]; then
          if ! grep -qF "$DEST" "$shell_rc" 2>/dev/null; then
            {
              echo ""
              echo "# Added by jsm installer"
              if [[ "$shell_rc" == *fish* ]]; then
                echo "set -gx PATH \"$DEST\" \$PATH"
              else
                echo "export PATH=\"$DEST:\$PATH\""
              fi
            } >> "$shell_rc"
            UPDATED=1
          fi
        fi

        # Also try other rc files in easy mode
        if [ "$EASY" -eq 1 ]; then
          for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do
            if [ -e "$rc" ] && [ -w "$rc" ] && [ "$rc" != "$shell_rc" ]; then
              if ! grep -qF "$DEST" "$rc" 2>/dev/null; then
                echo "export PATH=\"$DEST:\$PATH\"" >> "$rc"
                UPDATED=1
              fi
            fi
          done
        fi

        if [ "$UPDATED" -eq 1 ]; then
          warn "PATH updated in shell rc; restart shell to use jsm"
        else
          warn "Add $DEST to PATH to use jsm"
        fi
      else
        warn "Add $DEST to PATH to use jsm"
      fi
    ;;
  esac
}

# ═══════════════════════════════════════════════════════════════════════════════
# Usage / Help
# ═══════════════════════════════════════════════════════════════════════════════

usage() {
  cat <<EOFU
Usage: install.sh [options]

One-liner install:
  curl -fsSL "https://jeffreys-skills.md/install.sh?$(date +%s)" | bash
  curl -fsSL "https://jeffreys-skills.md/install.sh?$(date +%s)" | bash -s -- [options]

Options:
  --version vX.Y.Z   Install specific version (default: latest)
  --dest DIR         Install to DIR (default: ~/.local/bin)
  --system           Install to /usr/local/bin (requires sudo)
  --easy-mode        Auto-update PATH in shell rc files
  --verify           Run self-test after install
  --from-source      Build from source instead of downloading binary (explicit only)
  --completions      Install shell completions after install
  --telemetry        Enable anonymous install telemetry (opt-in)
  --quiet            Suppress non-error output
  --no-gum           Disable gum formatting even if available
  --no-modify-path   Do not update shell rc files
  --no-verify        Skip checksum + signature verification (for testing only)
  --force            Force reinstall even if same version is installed
  --offline [FILE]   Airgap mode: skip network checks, or install from local tarball
  --uninstall        Remove the installed binary, config dir, and scheduler artifacts
  -y, --yes          Skip the uninstall confirmation prompt
  -h, --help         Show this help

Environment:
  JSM_VERSION             Version to install
  JSM_INSTALL_DIR         Override install directory
  JSM_DOWNLOAD_BASE_URL   Override download base (default: site download relay)
  JSM_LATEST_URL          Override latest version URL
  JSM_TELEMETRY           Set to 1 to enable anonymous install telemetry
  JSM_OFFLINE             Set to 1 to skip network checks
  HTTPS_PROXY             HTTPS proxy for all downloads
  HTTP_PROXY              HTTP proxy fallback
EOFU
}

# ═══════════════════════════════════════════════════════════════════════════════
# Argument Parsing
# ═══════════════════════════════════════════════════════════════════════════════

while [ $# -gt 0 ]; do
  case "$1" in
    -v|--version) VERSION="$2"; shift 2;;
    -d|--dir|--dest) DEST="$2"; shift 2;;
    --system) SYSTEM=1; DEST="/usr/local/bin"; shift;;
    --easy-mode) EASY=1; shift;;
    --verify) VERIFY=1; shift;;
    --from-source) FROM_SOURCE=1; shift;;
    --completions) INSTALL_COMPLETIONS=1; shift;;
    --telemetry) TELEMETRY_ENABLED=1; shift;;
    --quiet|-q) QUIET=1; shift;;
    --no-gum) NO_GUM=1; shift;;
    --no-modify-path) NO_MODIFY_PATH=1; shift;;
    --no-verify) NO_CHECKSUM=1; shift;;
    --force) FORCE_INSTALL=1; shift;;
    --uninstall) UNINSTALL=1; shift;;
    -y|--yes) ASSUME_YES=1; shift;;
    --offline)
      OFFLINE=1
      # Accept optional tarball path argument
      if [[ $# -ge 2 && ! "$2" =~ ^-- ]]; then
        OFFLINE_TARBALL="$2"
        shift
      fi
      shift
      ;;
    -h|--help) usage; exit 0;;
    *) shift;;
  esac
done

DEST="${DEST/#\~/$HOME}"

if [[ "$UNINSTALL" -eq 1 ]]; then
  run_uninstall
fi

# ═══════════════════════════════════════════════════════════════════════════════
# Main Installer Flow
# ═══════════════════════════════════════════════════════════════════════════════

# Show branded header
if [ "$QUIET" -eq 0 ]; then
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    gum style \
      --border normal \
      --border-foreground 39 \
      --padding "0 1" \
      --margin "1 0" \
      -- \
      "$(gum style --foreground 42 --bold -- 'jsm installer')" \
      "$(gum style --foreground 245 -- "Jeffrey's Skills Manager for Claude Code")"
  else
    echo ""
    echo -e "\033[1;32mjsm installer\033[0m"
    echo -e "\033[0;90mJeffrey's Skills Manager for Claude Code\033[0m"
    echo ""
  fi
fi

# Prerequisite checks
command -v curl >/dev/null 2>&1 || { err "curl is required"; exit 1; }
command -v tar >/dev/null 2>&1 || { err "tar is required"; exit 1; }

# Detect AI coding agents (informational)
print_agent_scan_notice
detect_agents
if [ "$QUIET" -eq 0 ]; then
  print_detected_agents
fi

# Resolve version and platform
resolve_version
detect_platform

# Set up artifact URL
ARCHIVE_EXT="tar.gz"
BINARY_NAME="jsm"
if [[ "$OS" == "windows" ]]; then
  ARCHIVE_EXT="zip"
  BINARY_NAME="jsm.exe"
  command -v unzip >/dev/null 2>&1 || { err "unzip is required for Windows archives"; exit 1; }
fi

ARCHIVE_NAME="jsm-${TARGET}.${ARCHIVE_EXT}"
ARTIFACT_URL="${DOWNLOAD_BASE_URL}/${version_tag}/${ARCHIVE_NAME}"

# Ensure the destination directory hierarchy exists
mkdir -p "$DEST" 2>/dev/null || true

# Run preflight checks
preflight_checks

# Check if already at target version (skip download, still run integration)
if [ "$FORCE_INSTALL" -eq 0 ] && check_installed_version "$version_tag"; then
  ok "jsm $version_tag is already installed at $DEST/jsm"
  info "Use --force to reinstall"

  # Still run shell completions and PATH setup (idempotent)
  if [ "$INSTALL_COMPLETIONS" -eq 1 ]; then
    maybe_install_completions
  fi

  report_success

  # Show brief summary
  echo ""
  ok "jsm is ready to use."
  info "Run 'jsm login' to authenticate, then 'jsm list' to browse skills."
  exit 0
fi

# ═══════════════════════════════════════════════════════════════════════════════
# Atomic Locking
# ═══════════════════════════════════════════════════════════════════════════════

LOCK_DIR="${LOCK_FILE}.d"
LOCKED=0
if mkdir "$LOCK_DIR" 2>/dev/null; then
  LOCKED=1
  echo $$ > "$LOCK_DIR/pid"
else
  if [ -f "$LOCK_DIR/pid" ]; then
    OLD_PID=$(cat "$LOCK_DIR/pid" 2>/dev/null || echo "")
    if [ -n "$OLD_PID" ] && ! kill -0 "$OLD_PID" 2>/dev/null; then
      rm -rf "$LOCK_DIR"
      if mkdir "$LOCK_DIR" 2>/dev/null; then
        LOCKED=1
        echo $$ > "$LOCK_DIR/pid"
      fi
    fi
  fi
  if [ "$LOCKED" -eq 0 ]; then
    err "Another installer is running (lock $LOCK_DIR)"
    exit 1
  fi
fi

cleanup() {
  rm -rf "$TMP"
  if [ "$LOCKED" -eq 1 ]; then rm -rf "$LOCK_DIR"; fi
}

TMP=$(mktemp -d)
trap cleanup EXIT

# ═══════════════════════════════════════════════════════════════════════════════
# Download / Build from Source
# ═══════════════════════════════════════════════════════════════════════════════

# Airgap: install from local tarball if provided
if [[ -n "$OFFLINE_TARBALL" ]]; then
  if [[ ! -f "$OFFLINE_TARBALL" ]]; then
    fail_with_telemetry "offline" "Tarball not found: $OFFLINE_TARBALL"
  fi
  info "Installing from local tarball: $OFFLINE_TARBALL"
  cp "$OFFLINE_TARBALL" "$TMP/$ARCHIVE_NAME"
  # Skip download, go straight to verification + extract
elif [ "$FROM_SOURCE" -eq 0 ]; then
  info "Downloading $ARCHIVE_NAME ($version_tag)..."

  download_success=0
  download_error_log="$TMP/download.err"
  : > "$download_error_log"

  if curl \
    --retry 3 \
    --retry-delay 1 \
    --retry-connrefused \
    --connect-timeout 10 \
    --max-time 120 \
    -fsSL \
    ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} \
    "$ARTIFACT_URL" \
    -o "$TMP/$ARCHIVE_NAME" \
    2>"$download_error_log"; then
    download_success=1
  elif [ "$OS" = "linux" ] && [ -n "$LINUX_FALLBACK_TARGET" ] && [ "$LINUX_FALLBACK_TARGET" != "$TARGET" ]; then
    # Try the alternate Linux artifact form for older or staged releases.
    local_fallback_archive="jsm-${LINUX_FALLBACK_TARGET}.${ARCHIVE_EXT}"
    local_fallback_url="${DOWNLOAD_BASE_URL}/${version_tag}/${local_fallback_archive}"
    info "Primary Linux artifact unavailable, trying ${LINUX_FALLBACK_TARGET}..."
    if curl \
      --retry 3 \
      --retry-delay 1 \
      --retry-connrefused \
      --connect-timeout 10 \
      --max-time 120 \
      -fsSL \
      ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} \
      "$local_fallback_url" \
      -o "$TMP/$local_fallback_archive" \
      2>"$download_error_log"; then
      ARCHIVE_NAME="$local_fallback_archive"
      ARTIFACT_URL="$local_fallback_url"
      download_success=1
    fi
  fi

  if [ "$download_success" -ne 1 ]; then
    if [ -s "$download_error_log" ]; then
      warn "Artifact download failed: $(tail -n 1 "$download_error_log")"
    fi
    fail_with_telemetry "download" "Failed to download published release artifact ${ARTIFACT_URL}. This installer will not fall back to a source build."
  fi
fi

if [ "$FROM_SOURCE" -eq 1 ]; then
  info "Building from source (requires git, cargo)"
  ensure_rust || { fail_with_telemetry "source_build" "Rust toolchain required for source builds"; }
  command -v git >/dev/null 2>&1 || { fail_with_telemetry "source_build" "git is required for source builds"; }

  run_with_spinner "Cloning repository" git clone --depth 1 "https://github.com/${OWNER}/${REPO}.git" "$TMP/src"
  run_with_spinner "Building jsm (this may take a few minutes)" bash -c "cd '$TMP/src/cli' && cargo build --release"

  BIN="$TMP/src/cli/target/release/$BINARY_NAME"
  [ -x "$BIN" ] || { fail_with_telemetry "source_build" "Build failed: binary not produced"; }
  install -m 0755 "$BIN" "$DEST/$BINARY_NAME"
  ok "Installed to $DEST/$BINARY_NAME (source build)"

  maybe_add_path

  if [ "$VERIFY" -eq 1 ]; then
    "$DEST/$BINARY_NAME" --version
    ok "Self-test complete"
  fi

  if [ "$INSTALL_COMPLETIONS" -eq 1 ]; then
    maybe_install_completions
  fi

  report_success

  # Jump to final summary
  echo ""
  ok "jsm installed successfully from source."
  info "Run 'jsm login' to authenticate, then 'jsm list' to browse skills."

  echo ""
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    print_uninstall_hint
  else
    print_uninstall_hint
  fi
  exit 0
fi

# ═══════════════════════════════════════════════════════════════════════════════
# Checksum + Signature Verification
# ═══════════════════════════════════════════════════════════════════════════════

if [ "$NO_CHECKSUM" -eq 1 ]; then
  warn "Verification skipped (--no-verify)"
else
  CHECKSUM_URL="${DOWNLOAD_BASE_URL}/${version_tag}/SHA256SUMS"
  info "Fetching checksum from ${CHECKSUM_URL}"
  CHECKSUM_FILE="$TMP/SHA256SUMS"
  CHECKSUM_HTTP_STATUS="$(curl -sSL ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} -w '%{http_code}' -o "$CHECKSUM_FILE" "$CHECKSUM_URL" 2>/dev/null || true)"

  if [ "$CHECKSUM_HTTP_STATUS" = "200" ]; then
    EXPECTED_CHECKSUM=$(awk -v name="$ARCHIVE_NAME" '
      $2 == name || $2 == ("dist/" name) || $2 ~ ("(^|/)" name "$") {
        print $1
        exit
      }
    ' "$CHECKSUM_FILE")
    if [ -z "$EXPECTED_CHECKSUM" ]; then
      report_failure "verify" "checksum_metadata_missing" "SHA256SUMS did not contain an entry for $ARCHIVE_NAME"
      err "Installation aborted: SHA256SUMS did not contain an entry for $ARCHIVE_NAME"
      exit 1
    fi
    if verify_checksum "$TMP/$ARCHIVE_NAME" "$EXPECTED_CHECKSUM"; then
      :
    else
      verify_rc=$?
      if [ "$verify_rc" -eq 2 ]; then
        report_failure "verify" "checksum_tool_missing" "No SHA256 tool found (sha256sum or shasum) to verify $ARCHIVE_NAME"
        err "Installation aborted: no SHA256 tool found to verify $ARCHIVE_NAME"
      else
        report_failure "verify" "checksum_mismatch" "Checksum verification failed"
        err "Installation aborted due to checksum failure"
      fi
      exit 1
    fi
  elif [ "$CHECKSUM_HTTP_STATUS" = "404" ]; then
    report_failure "verify" "checksum_metadata_unavailable" "SHA256SUMS was not published for ${version_tag}"
    err "Installation aborted: SHA256SUMS was not published for ${version_tag}"
    err "Re-run with --no-verify only if you intentionally accept an unverified artifact."
    exit 1
  else
    rm -f "$CHECKSUM_FILE"
    report_failure "verify" "checksum_download_failed" "Failed to fetch SHA256SUMS from ${CHECKSUM_URL} (HTTP ${CHECKSUM_HTTP_STATUS:-unknown})"
    err "Installation aborted: failed to fetch SHA256SUMS from ${CHECKSUM_URL} (HTTP ${CHECKSUM_HTTP_STATUS:-unknown})"
    exit 1
  fi

  # Sigstore verification (best-effort)
  if verify_sigstore_bundle "$TMP/$ARCHIVE_NAME" "$ARTIFACT_URL"; then
    :
  else
    sigstore_rc=$?
    if [ "$sigstore_rc" -eq 2 ]; then
      report_failure "verify" "sigstore_bundle_download_failed" "Failed to fetch sigstore bundle from ${SIGSTORE_BUNDLE_EFFECTIVE_URL:-unknown} (HTTP ${SIGSTORE_BUNDLE_HTTP_STATUS:-unknown})"
      err "Installation aborted: failed to fetch sigstore bundle"
    else
      report_failure "verify" "signature_verification_failed" "Sigstore verification failed"
      err "Signature verification failed"
      err "The downloaded file may be corrupted or tampered with."
    fi
    exit 1
  fi
fi

# ═══════════════════════════════════════════════════════════════════════════════
# Extract & Install
# ═══════════════════════════════════════════════════════════════════════════════

info "Extracting"
if [ "$ARCHIVE_EXT" = "tar.gz" ]; then
  tar -xzf "$TMP/$ARCHIVE_NAME" -C "$TMP"
else
  unzip -q "$TMP/$ARCHIVE_NAME" -d "$TMP"
fi

BIN="$TMP/$BINARY_NAME"
if [ ! -x "$BIN" ] && [ ! -f "$BIN" ]; then
  # Search for binary in subdirectories
  BIN=$(find "$TMP" -maxdepth 3 -type f -name "$BINARY_NAME" | head -n 1)
fi

if [ ! -f "$BIN" ]; then
  report_failure "extract" "binary_missing" "Expected binary not found in archive"
  fail_with_telemetry "extract" "Expected binary ($BINARY_NAME) not found in archive"
fi

install -m 0755 "$BIN" "$DEST/$BINARY_NAME"
ok "Installed to $DEST/$BINARY_NAME"

maybe_add_path

# ═══════════════════════════════════════════════════════════════════════════════
# Post-Install Verification
# ═══════════════════════════════════════════════════════════════════════════════

info "Verifying install..."
if ! "$DEST/$BINARY_NAME" --version >/dev/null 2>&1; then
  report_failure "verify" "binary_failed" "Failed to run jsm"
  fail_with_telemetry "verify" "Installed binary failed to execute"
fi
"$DEST/$BINARY_NAME" --version

if [ "$VERIFY" -eq 1 ]; then
  info "Running self-test..."
  "$DEST/$BINARY_NAME" doctor 2>/dev/null || true
  ok "Self-test complete"
fi

maybe_configure_detected_agent_targets

# ═══════════════════════════════════════════════════════════════════════════════
# Shell Completions
# ═══════════════════════════════════════════════════════════════════════════════

if [ "$INSTALL_COMPLETIONS" -eq 1 ]; then
  maybe_install_completions
fi

# Report success telemetry
report_success

# ═══════════════════════════════════════════════════════════════════════════════
# Final Summary
# ═══════════════════════════════════════════════════════════════════════════════

echo ""

summary_lines=()
summary_lines+=("Binary:     $DEST/$BINARY_NAME")
summary_lines+=("Version:    $version_tag")
summary_lines+=("Platform:   ${OS}/${ARCH} (${TARGET})")

if [[ ${#DETECTED_AGENTS[@]} -gt 0 ]]; then
  summary_lines+=("")
  summary_lines+=("Detected agents:")
  for agent in "${DETECTED_AGENTS[@]}"; do
    case "$agent" in
      claude-code)     summary_lines+=("  Claude Code   Skills dir: ~/.claude/skills/") ;;
      codex-cli)
        if [[ -n "${CODEX_HOME:-}" ]]; then
          summary_lines+=("  Codex CLI     Skills dir: ${CODEX_HOME%/}/skills/")
        else
          summary_lines+=("  Codex CLI     Skills dir: ~/.codex/skills/")
        fi
        ;;
      gemini-cli)      summary_lines+=("  Gemini CLI    Skills dir: ~/.gemini/skills/") ;;
      aider)           summary_lines+=("  Aider") ;;
      github-copilot-cli) summary_lines+=("  GitHub Copilot CLI") ;;
      continue)        summary_lines+=("  Continue") ;;
      cursor-ide)
        if [[ "$CURSOR_SKILLS_READY" == "1" ]]; then
          if [[ "$CURSOR_SKILLS_CONFIGURED" == "1" ]]; then
            summary_lines+=("  Cursor IDE     Skills dir: ${CURSOR_SKILLS_DIR%/}/ (configured by installer)")
          else
            summary_lines+=("  Cursor IDE     Skills dir: ${CURSOR_SKILLS_DIR%/}/")
          fi
        else
          summary_lines+=("  Cursor IDE     Detected, but mirroring activates after first launch")
          summary_lines+=("                 or: jsm config set skills.cursor_dir ~/.cursor/skills")
        fi
        ;;
    esac
  done
fi

summary_lines+=("")
summary_lines+=("Next steps:")
summary_lines+=("  1. jsm login     Authenticate with Google OAuth")
summary_lines+=("  2. jsm list      Browse available skills")
summary_lines+=("  3. jsm sync      Download all saved skills")

if [ "$QUIET" -eq 0 ]; then
  if [ "$HAS_GUM" -eq 1 ] && [ "$NO_GUM" -eq 0 ]; then
    {
      gum style --foreground 42 --bold -- "jsm installed successfully!"
      echo ""
      for line in "${summary_lines[@]}"; do
        if [ -z "$line" ]; then
          echo ""
        else
          gum style --foreground 245 -- "$line"
        fi
      done
    } | gum style --border normal --border-foreground 42 --padding "1 2"
  else
    draw_box "0;32" \
      "\033[1;32mjsm installed successfully!\033[0m" \
      "" \
      "${summary_lines[@]}"
  fi

  # Uninstall instructions
  echo ""
  print_uninstall_hint
fi
