#!/usr/bin/env bash
set -euo pipefail
shopt -s lastpipe 2>/dev/null || true
umask 022

# CLI Printing Press installer
#
# One-line install:
#   curl -fsSL https://raw.githubusercontent.com/mvanhorn/cli-printing-press/main/scripts/install.sh | bash
#
# Options:
#   --cli-only          Install or update only the Go generator binary.
#   --skills-only       Install or refresh only the Printing Press skills.
#   -a, --agent NAME    Install skills for an agent supported by `skills`.
#                       May be repeated. Defaults to claude-code.
#   --dry-run           Print commands without executing them.
#   --quiet             Suppress non-error output.
#   --no-gum            Disable gum formatting even when gum is installed.
#   -h, --help          Show help.

readonly PROJECT_NAME="CLI Printing Press"
readonly BINARY_NAME="cli-printing-press"
readonly GO_MIN_VERSION="1.26.4"
readonly GO_INSTALL_TARGET="github.com/mvanhorn/cli-printing-press/v4/cmd/cli-printing-press@latest"
readonly SKILL_SOURCE="mvanhorn/cli-printing-press/skills"
readonly SKILLS_PACKAGE="skills@latest"
readonly DEFAULT_AGENT="claude-code"

INSTALL_CLI=1
INSTALL_SKILLS=1
DRY_RUN=0
QUIET=0
NO_GUM=0
AGENTS=()
PROXY_ARGS=()
TMP_DIR=""
LOCK_DIR=""
CLI_STATUS="skipped"
SKILLS_STATUS="skipped"

cleanup() {
  if [[ -n "${LOCK_DIR:-}" && -d "$LOCK_DIR" ]]; then
    rm -f "$LOCK_DIR/pid" 2>/dev/null || true
    rmdir "$LOCK_DIR" 2>/dev/null || true
  fi
  if [[ -n "${TMP_DIR:-}" && -d "$TMP_DIR" ]]; then
    rm -rf "$TMP_DIR"
  fi
}
trap cleanup EXIT

usage() {
  cat <<'USAGE'
CLI Printing Press installer

Usage:
  bash scripts/install.sh [options]
  curl -fsSL https://raw.githubusercontent.com/mvanhorn/cli-printing-press/main/scripts/install.sh | bash
  curl -fsSL https://raw.githubusercontent.com/mvanhorn/cli-printing-press/main/scripts/install.sh | bash -s -- --cli-only

Options:
  --cli-only          Install or update only the Go generator binary.
  --skills-only       Install or refresh only the Printing Press skills.
  -a, --agent NAME    Install skills for an agent supported by `skills`.
                       May be repeated. Defaults to claude-code.
  --dry-run           Print commands without executing them.
  --quiet             Suppress non-error output.
  --no-gum            Disable gum formatting even when gum is installed.
  -h, --help          Show this help.
USAGE
}

for arg in "$@"; do
  case "$arg" in
    --cli-only)
      INSTALL_SKILLS=0
      ;;
    --skills-only)
      INSTALL_CLI=0
      ;;
    --dry-run)
      DRY_RUN=1
      ;;
    --quiet)
      QUIET=1
      ;;
    --no-gum)
      NO_GUM=1
      ;;
    --agent=*)
      AGENTS+=("${arg#--agent=}")
      ;;
    -a=*)
      AGENTS+=("${arg#-a=}")
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      # Handled below so "-a value" and "--agent value" can consume the next arg.
      ;;
  esac
done

# Re-parse options that consume a following value.
if (($# > 0)); then
  while (($# > 0)); do
    case "$1" in
      -a|--agent)
        shift
        if (($# == 0)); then
          echo "error: --agent requires a value" >&2
          exit 1
        fi
        AGENTS+=("$1")
        ;;
      --cli-only|--skills-only|--dry-run|--quiet|--no-gum|-h|--help|--agent=*|-a=*)
        ;;
      *)
        echo "error: unknown option: $1" >&2
        exit 1
        ;;
    esac
    shift || true
  done
fi

if [[ "$INSTALL_CLI" -eq 0 && "$INSTALL_SKILLS" -eq 0 ]]; then
  echo "error: choose only one of --cli-only or --skills-only" >&2
  exit 1
fi

if [[ "${#AGENTS[@]}" -eq 0 ]]; then
  AGENTS=("$DEFAULT_AGENT")
fi

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

info() {
  [[ "$QUIET" -eq 1 ]] && return 0
  if [[ "$HAS_GUM" -eq 1 ]]; then
    gum style --foreground 39 "-> $*"
  else
    printf '\033[0;34m->\033[0m %s\n' "$*"
  fi
}

ok() {
  [[ "$QUIET" -eq 1 ]] && return 0
  if [[ "$HAS_GUM" -eq 1 ]]; then
    gum style --foreground 42 "✓ $*"
  else
    printf '\033[0;32m✓\033[0m %s\n' "$*"
  fi
}

warn() {
  [[ "$QUIET" -eq 1 ]] && return 0
  if [[ "$HAS_GUM" -eq 1 ]]; then
    gum style --foreground 214 "! $*"
  else
    printf '\033[1;33m!\033[0m %s\n' "$*"
  fi
}

err() {
  if [[ "$HAS_GUM" -eq 1 ]]; then
    gum style --foreground 196 "✗ $*" >&2
  else
    printf '\033[0;31m✗\033[0m %s\n' "$*" >&2
  fi
}

strip_ansi() {
  sed -E $'s/\x1B\\[[0-9;]*[A-Za-z]//g'
}

draw_box() {
  local color="$1"
  shift
  local lines=("$@")
  local width=0
  local line clean len
  for line in "${lines[@]}"; do
    clean="$(printf '%s' "$line" | strip_ansi)"
    len=${#clean}
    ((len > width)) && width=$len
  done
  ((width < 24)) && width=24
  [[ "$QUIET" -eq 1 ]] && return 0
  if [[ "$HAS_GUM" -eq 1 ]]; then
    printf '%s\n' "${lines[@]}" | gum style --border double --border-foreground "$color" --padding "0 1"
    return 0
  fi
  printf '╔'
  printf '═%.0s' $(seq 1 $((width + 2)))
  printf '╗\n'
  for line in "${lines[@]}"; do
    clean="$(printf '%s' "$line" | strip_ansi)"
    printf '║ %s%*s ║\n' "$line" $((width - ${#clean})) ''
  done
  printf '╚'
  printf '═%.0s' $(seq 1 $((width + 2)))
  printf '╝\n'
}

run_with_spinner() {
  local title="$1"
  shift
  if [[ "$DRY_RUN" -eq 1 ]]; then
    local quoted=()
    local arg
    for arg in "$@"; do
      printf -v arg '%q' "$arg"
      quoted+=("$arg")
    done
    info "[dry-run] ${quoted[*]}"
    return 0
  fi
  if [[ "$HAS_GUM" -eq 1 && "$QUIET" -eq 0 ]]; then
    gum spin --spinner dot --title "$title" -- "$@"
  else
    info "$title"
    "$@"
  fi
}

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
}

detect_platform() {
  local os arch
  os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  arch="$(uname -m)"
  case "$arch" in
    x86_64|amd64) arch="x86_64" ;;
    arm64|aarch64) arch="aarch64" ;;
  esac
  case "$os" in
    darwin|linux) ;;
    *)
      err "Unsupported OS: $os"
      exit 1
      ;;
  esac
  case "$arch" in
    x86_64|aarch64) ;;
    *)
      err "Unsupported architecture: $arch"
      exit 1
      ;;
  esac
  if [[ "$os" == "linux" ]] && grep -qi microsoft /proc/version 2>/dev/null; then
    warn "WSL detected; continuing with Linux install."
  fi
}

check_disk_space() {
  local path="${TMPDIR:-/tmp}"
  local available
  available="$(df -Pk "$path" | awk 'NR==2 {print $4}')"
  if [[ -n "$available" && "$available" -lt 10240 ]]; then
    err "At least 10MB of free space is required in $path."
    exit 1
  fi
}

check_write_permissions() {
  local probe
  probe="$(mktemp "${TMPDIR:-/tmp}/cli-printing-press-install.XXXXXX")"
  rm -f "$probe"
}

check_network() {
  if [[ "$DRY_RUN" -eq 1 ]]; then
    return 0
  fi
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL --connect-timeout 5 ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} https://github.com >/dev/null || {
      err "Network check failed: could not reach github.com."
      exit 1
    }
    if [[ "$INSTALL_SKILLS" -eq 1 ]]; then
      curl -fsSL --connect-timeout 5 ${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} https://registry.npmjs.org/skills >/dev/null || {
        err "Network check failed: could not reach registry.npmjs.org."
        exit 1
      }
    fi
  fi
}

acquire_lock() {
  local lock_base="${XDG_STATE_HOME:-$HOME/.local/state}/cli-printing-press"
  mkdir -p "$lock_base"
  LOCK_DIR="$lock_base/install.lock"
  if mkdir "$LOCK_DIR" 2>/dev/null; then
    printf '%s\n' "$$" > "$LOCK_DIR/pid"
    return 0
  fi
  if [[ -f "$LOCK_DIR/pid" ]]; then
    local pid
    pid="$(cat "$LOCK_DIR/pid" 2>/dev/null || true)"
    if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then
      warn "Removing stale installer lock."
      rm -rf "$LOCK_DIR"
      mkdir "$LOCK_DIR"
      printf '%s\n' "$$" > "$LOCK_DIR/pid"
      return 0
    fi
  fi
  err "Another CLI Printing Press installer appears to be running."
  exit 1
}

parse_go_version() {
  sed -nE 's/^go version go([0-9]+(\.[0-9]+){1,2}).*/\1/p'
}

version_ge() {
  local left="$1"
  local right="$2"
  local IFS=.
  local -a l r
  read -r -a l <<< "$left"
  read -r -a r <<< "$right"
  local i lv rv
  for i in 0 1 2; do
    lv="${l[$i]:-0}"
    rv="${r[$i]:-0}"
    if ((10#$lv > 10#$rv)); then return 0; fi
    if ((10#$lv < 10#$rv)); then return 1; fi
  done
  return 0
}

check_go() {
  if ! command -v go >/dev/null 2>&1; then
    err "Go $GO_MIN_VERSION or newer is required."
    err "Install Go from https://go.dev/dl/, then re-run this installer."
    exit 1
  fi
  local version
  version="$(go version | parse_go_version)"
  if [[ -z "$version" ]]; then
    err "Could not determine the installed Go version. Ensure Go $GO_MIN_VERSION or newer is installed."
    exit 1
  fi
  if ! version_ge "$version" "$GO_MIN_VERSION"; then
    err "Go $GO_MIN_VERSION or newer is required; found Go $version."
    err "Install Go from https://go.dev/dl/, then re-run this installer."
    exit 1
  fi
}

check_node() {
  if ! command -v npm >/dev/null 2>&1 || ! command -v npx >/dev/null 2>&1; then
    err "Node/npm is required for skill installation."
    err "Install Node.js from https://nodejs.org/, then re-run this installer."
    exit 1
  fi
}

installed_cli_version() {
  if command -v "$BINARY_NAME" >/dev/null 2>&1; then
    "$BINARY_NAME" --version 2>/dev/null || true
  fi
}

install_cli() {
  local current
  current="$(installed_cli_version)"
  if [[ -n "$current" ]]; then
    info "Existing $BINARY_NAME detected: $current"
  fi
  run_with_spinner "Installing $BINARY_NAME with go install" go install "$GO_INSTALL_TARGET"
  if [[ "$DRY_RUN" -eq 1 ]]; then
    CLI_STATUS="planned"
  else
    CLI_STATUS="installed"
    ok "Installed $BINARY_NAME"
  fi
}

install_skills() {
  local cmd=(npx -y "$SKILLS_PACKAGE" add "$SKILL_SOURCE" --skill "*" -g -y)
  local agent
  for agent in "${AGENTS[@]}"; do
    cmd+=(-a "$agent")
  done
  run_with_spinner "Installing Printing Press skills" "${cmd[@]}"
  if [[ "$DRY_RUN" -eq 1 ]]; then
    SKILLS_STATUS="planned"
  else
    SKILLS_STATUS="installed"
    ok "Installed Printing Press skills for ${AGENTS[*]}"
  fi
}

preflight_checks() {
  info "Running preflight checks"
  detect_platform
  setup_proxy
  check_disk_space
  check_write_permissions
  check_network
  if [[ "$INSTALL_CLI" -eq 1 ]]; then
    check_go
  fi
  if [[ "$INSTALL_SKILLS" -eq 1 ]]; then
    check_node
  fi
}

print_header() {
  if [[ "$QUIET" -eq 1 ]]; then
    return 0
  fi
  if [[ "$HAS_GUM" -eq 1 ]]; then
    gum style \
      --border normal --border-foreground 39 \
      --padding "0 1" --margin "1 0" \
      "$(gum style --foreground 42 --bold "$PROJECT_NAME installer")" \
      "$(gum style --foreground 245 "Install the generator binary and Printing Press skills")"
  else
    printf '\033[1;32m%s installer\033[0m\n' "$PROJECT_NAME"
    printf '\033[0;90mInstall the generator binary and Printing Press skills\033[0m\n\n'
  fi
}

print_summary() {
  local lines=()
  lines+=("CLI: $CLI_STATUS")
  lines+=("Skills: $SKILLS_STATUS")
  if [[ "$INSTALL_SKILLS" -eq 1 ]]; then
    lines+=("Agents: ${AGENTS[*]}")
    lines+=("Restart or reload the target agent so refreshed skills are loaded.")
  fi
  draw_box 42 "${lines[@]}"
}

main() {
  TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cli-printing-press-install.XXXXXX")"
  print_header
  acquire_lock
  preflight_checks
  if [[ "$INSTALL_CLI" -eq 1 ]]; then
    install_cli
  fi
  if [[ "$INSTALL_SKILLS" -eq 1 ]]; then
    install_skills
  fi
  print_summary
}

main "$@"
