#!/bin/bash
#
# cleanup.sh — read-only by default macOS disk-space cleaner.
#
# Modes:
#   ./cleanup.sh                 same as --dry-run: print what WOULD be
#                                 deleted and its size. Deletes nothing.
#   ./cleanup.sh --dry-run       explicit dry run.
#   ./cleanup.sh --diagnose      run Step 1 diagnostics (disk / memory
#                                 pressure / swap). Deletes nothing.
#   ./cleanup.sh --execute       actually delete the safe-path items.
#   ./cleanup.sh --help          show this text.
#
# Safety model:
#   - Nothing is deleted unless --execute is passed explicitly.
#   - Every candidate path is checked against SAFE_PREFIXES before any rm.
#     If a resolved path does not start with one of those prefixes, it is
#     skipped and reported as skipped, never deleted.
#   - Only cache / derived-data / trash style paths are in scope. Downloads,
#     node_modules, Docker volumes, and app Containers are intentionally
#     NOT touched by this script — see README.md Step 2/3 for those, they
#     need a human to look at what's actually in them first.
#
set -u

MODE="dry-run"
case "${1:-}" in
  --execute)   MODE="execute" ;;
  --dry-run)   MODE="dry-run" ;;
  --diagnose)  MODE="diagnose" ;;
  --help|-h)
    sed -n '2,25p' "$0"
    exit 0
    ;;
  "")          MODE="dry-run" ;;
  *)
    echo "Unknown option: $1 (use --dry-run, --diagnose, --execute, or --help)"
    exit 1
    ;;
esac

HOME_DIR="${HOME}"

# Hard allowlist of prefixes this script is permitted to delete under.
# A resolved candidate path must start with one of these or it is skipped.
SAFE_PREFIXES=(
  "${HOME_DIR}/Library/Caches"
  "${HOME_DIR}/Library/Developer/Xcode/DerivedData"
  "${HOME_DIR}/.Trash"
)

# Candidate paths considered for deletion. Kept short and conservative on
# purpose — everything riskier (Downloads, node_modules, Docker, app
# Containers) is left to human judgment in the README, not automated here.
CANDIDATES=(
  "${HOME_DIR}/Library/Caches"
  "${HOME_DIR}/Library/Developer/Xcode/DerivedData"
  "${HOME_DIR}/.Trash"
)

is_safe_path() {
  local path="$1"
  local prefix
  for prefix in "${SAFE_PREFIXES[@]}"; do
    case "$path" in
      "$prefix"|"$prefix"/*) return 0 ;;
    esac
  done
  return 1
}

human_size() {
  # $1 = path
  du -sh "$1" 2>/dev/null | awk '{print $1}'
}

run_diagnose() {
  echo "== Disk space (/ volume) =="
  df -h / 2>/dev/null
  echo
  echo "== Memory pressure =="
  memory_pressure 2>/dev/null || echo "memory_pressure not available"
  echo
  echo "== Swap usage =="
  sysctl vm.swapusage 2>/dev/null
  echo
  echo "== Pageouts / Swapins / Swapouts (vm_stat) =="
  vm_stat 2>/dev/null | grep -E "Pageouts|Swapins|Swapouts"
  echo
  echo "Rule of thumb: below ~15% free on / AND elevated memory pressure or"
  echo "swap usage means a disk cleanup (this script) will likely help."
  echo "If disk space looks fine, this script will not fix your slowdown —"
  echo "see README.md for other things to check (CPU, thermal, failing disk)."
}

run_sweep() {
  local total_would_reclaim=0
  echo "Mode: ${MODE}"
  echo

  for path in "${CANDIDATES[@]}"; do
    if [ ! -e "$path" ]; then
      echo "SKIP (not found):   $path"
      continue
    fi

    if ! is_safe_path "$path"; then
      echo "SKIP (not allowlisted, refusing to touch): $path"
      continue
    fi

    size=$(human_size "$path")
    size="${size:-0}"

    if [ "$MODE" = "execute" ]; then
      # Remove contents, not the directory itself, so macOS/apps that
      # expect the folder to exist keep working after a relaunch.
      echo "DELETE contents of ($size):  $path"
      find "$path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
    else
      echo "WOULD DELETE contents of ($size):  $path"
    fi
  done

  echo
  if [ "$MODE" = "execute" ]; then
    echo "Done. Reboot to purge swap: sudo reboot"
  else
    echo "Dry run only — nothing was deleted."
    echo "Review the list above, then re-run with --execute to actually delete."
    echo "Run --diagnose first if you haven't confirmed this is a disk problem."
  fi
}

case "$MODE" in
  diagnose) run_diagnose ;;
  dry-run)  run_sweep ;;
  execute)  run_sweep ;;
esac
