#!/usr/bin/env bash
#
# clavue repair — fix npm global install corruption (ENOTEMPTY, missing bin,
# half-renamed `.clavue-<rand>` staging dirs).
#
# What npm does: `npm install -g <pkg>` renames the existing pkg dir to a
# hidden `.<pkg>-<rand>` staging path, then unpacks the new tarball into a
# fresh `<pkg>` dir. If the previous run was interrupted, or the OS refused
# the rename (APFS/ext4 with open handles, filesystem quirks, "directory
# not empty" on retry), the staging dir survives and every future install
# attempt fails with ENOTEMPTY at the SAME path.
#
# This script:
#   1. Locates the global node_modules dir (works under nvm, brew, asdf,
#      and system installs).
#   2. Removes the live `clavue/` dir AND every `.clavue-*` staging
#      leftover.
#   3. Removes the dangling `bin/clavue` symlink if it points nowhere.
#   4. Reinstalls clavue@latest (or the version passed as $1).
#
# Usage:
#   curl -fsSL https://unpkg.com/clavue/repair.sh | bash
#   curl -fsSL https://unpkg.com/clavue/repair.sh | bash -s -- 8.14.2
#
# Environment:
#   CLAVUE_PACKAGE  — override the package name (default: clavue)
#   CLAVUE_NO_INSTALL=1 — only clean, do not reinstall (useful for
#                          uninstall flows and for power users who want
#                          to install with a custom registry afterwards).
set -euo pipefail

PACKAGE="${CLAVUE_PACKAGE:-clavue}"
VERSION_INPUT="${1:-${CLAVUE_VERSION:-latest}}"

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    printf 'Missing required command: %s\n' "$1" >&2
    exit 1
  fi
}

resolve_node_modules() {
  local prefix
  prefix="$(npm prefix -g 2>/dev/null || true)"
  if [[ -z "$prefix" ]]; then
    printf 'Could not locate npm prefix. Is npm on PATH?\n' >&2
    exit 1
  fi

  if [[ -d "${prefix}/lib/node_modules" ]]; then
    printf '%s' "${prefix}/lib/node_modules"
  elif [[ -d "${prefix}/node_modules" ]]; then
    printf '%s' "${prefix}/node_modules"
  else
    # Fallback: the dir may not exist yet on a fresh prefix; pick the
    # canonical Linux/macOS layout so subsequent install can create it.
    printf '%s' "${prefix}/lib/node_modules"
  fi
}

needs_sudo_for() {
  [[ -d "$1" && ! -w "$1" ]]
}

clean_residue() {
  local node_modules="$1"
  local pkg_dir="${node_modules}/${PACKAGE}"
  local staging_glob="${node_modules}/.${PACKAGE}-*"
  local prefix
  prefix="$(npm prefix -g 2>/dev/null || true)"
  local bin_link="${prefix}/bin/${PACKAGE}"

  local found_any=0
  if [[ -e "$pkg_dir" ]]; then
    printf '  - removing %s\n' "$pkg_dir"
    found_any=1
  fi

  # shellcheck disable=SC2086
  for d in ${staging_glob}; do
    if [[ -e "$d" ]]; then
      printf '  - removing staging leftover %s\n' "$d"
      found_any=1
    fi
  done

  if [[ -L "$bin_link" && ! -e "$bin_link" ]]; then
    printf '  - removing dangling bin symlink %s\n' "$bin_link"
    found_any=1
  fi

  if [[ "$found_any" -eq 0 ]]; then
    printf 'No leftovers to clean in %s.\n' "$node_modules"
    return 0
  fi

  local sudo_cmd=""
  if needs_sudo_for "$node_modules"; then
    if command -v sudo >/dev/null 2>&1; then
      sudo_cmd="sudo"
      printf '%s is not user-writable; using sudo to clean.\n' "$node_modules"
    else
      printf 'Warning: %s is not user-writable and sudo is unavailable; clean may fail.\n' "$node_modules" >&2
    fi
  fi

  # The glob expands inside a sudo'd subshell so root sees the literal paths.
  $sudo_cmd bash -c "rm -rf \"$pkg_dir\" $staging_glob" || true
  if [[ -L "$bin_link" && ! -e "$bin_link" ]]; then
    $sudo_cmd rm -f "$bin_link" || true
  fi
}

resolve_version() {
  if [[ "$VERSION_INPUT" != "latest" ]]; then
    printf '%s' "${VERSION_INPUT#v}"
    return 0
  fi
  npm view "$PACKAGE" version
}

main() {
  need_cmd npm

  local node_modules
  node_modules="$(resolve_node_modules)"
  printf 'Repairing %s install in %s...\n' "$PACKAGE" "$node_modules"
  clean_residue "$node_modules"

  if [[ "${CLAVUE_NO_INSTALL:-0}" == "1" ]]; then
    printf '\nCleanup complete. CLAVUE_NO_INSTALL=1 set — skipping reinstall.\n'
    return 0
  fi

  local version
  version="$(resolve_version)"
  if [[ -z "$version" ]]; then
    printf 'Unable to resolve %s version from npm.\n' "$PACKAGE" >&2
    exit 1
  fi

  printf '\nInstalling %s@%s...\n' "$PACKAGE" "$version"
  if ! npm install -g "${PACKAGE}@${version}"; then
    printf '\nFirst install attempt failed. Cleaning again and retrying...\n' >&2
    clean_residue "$node_modules"
    npm install -g "${PACKAGE}@${version}"
  fi

  printf '\nInstalled %s@%s globally.\n' "$PACKAGE" "$version"
  if command -v "$PACKAGE" >/dev/null 2>&1; then
    "$PACKAGE" --version || true
  else
    printf 'Warning: %s is not on PATH. You may need to reopen your shell or check `npm bin -g`.\n' "$PACKAGE" >&2
  fi
}

main "$@"
