#!/usr/bin/env bash
# Common execution, safety, backup, and rollback helpers.

log() {
  if [[ -n "${LOG_DIR:-}" ]]; then printf '%s\n' "$*" | tee -a "$LOG_DIR/dep-upgrader.log" >&2
  else printf '%s\n' "$*" >&2
  fi
}
info() { log "${C_CYAN}INFO${C_RESET} $*"; }
ok() { log "${C_GREEN}OK${C_RESET} $*"; }
warn() { log "${C_YELLOW}WARN${C_RESET} $*"; }
err() { log "${C_RED}ERROR${C_RESET} $*"; FAILED=$((FAILED + 1)); }
has() { command -v "$1" >/dev/null 2>&1; }

run() {
  local label="$1" cwd="$2"; shift 2
  info "$label: $(printf '%q ' "$@")"
  if ((DRY_RUN)); then return 0; fi
  (cd "$cwd" && "$@") >>"$LOG_DIR/commands.log" 2>&1 || {
    local code=$?
    err "$label failed with exit code $code. See $LOG_DIR/commands.log"
    ((KEEP_GOING)) || exit "$code"
    return "$code"
  }
}

confirm() {
  local prompt="$1" answer
  ((ASSUME_YES)) && return 0
  [[ -t 0 ]] || return 1
  read -r -p "$prompt [y/N] " answer
  [[ "$answer" =~ ^[Yy]$ ]]
}

unique_append() {
  local array_name="$1" value="$2" existing
  local -n target_array="$array_name"
  for existing in "${target_array[@]}"; do
    [[ "$existing" == "$value" ]] && return 0
  done
  target_array+=("$value")
}

matches_filter() {
  [[ -z "$PROJECT_FILTER" ]] && return 0
  local value="${1,,}" needle="${PROJECT_FILTER,,}"
  [[ "$value" == *"$needle"* ]]
}

create_backup() {
  ((DRY_RUN)) && { info '[Dry run] A backup would be created.'; return; }
  local backup_dir="$ROOT/.maintenance/backups" list="$LOG_DIR/backup-files.txt" archive
  mkdir -p "$backup_dir"
  archive="$backup_dir/$STAMP.tar.gz"
  (cd "$ROOT" && find . -type d \( -name .git -o -name node_modules -o -name .next -o -name bin -o -name obj -o -name .maintenance \) -prune -o -type f \( \
    -name package.json -o -name pnpm-lock.yaml -o -name pnpm-workspace.yaml -o -name package-lock.json -o -name npm-shrinkwrap.json \
    -o -name '*.csproj' -o -name '*.fsproj' -o -name '*.vbproj' -o -name '*.sln' -o -name '*.slnx' \
    -o -name Directory.Packages.props -o -name Directory.Build.props -o -name global.json -o -iname NuGet.Config \
    -o -name '.env*' -o -name 'appsettings*.json' -o -name renovate.json \) -print0) >"$list"
  if [[ -s "$list" ]]; then
    (cd "$ROOT" && tar --null -T "$list" -czf "$archive") 2>>"$LOG_DIR/commands.log"
    ok "Backup: $archive"
  else
    warn 'No files eligible for backup were found.'
  fi
}

rollback() {
  [[ -n "$ROLLBACK_ARCHIVE" && -f "$ROLLBACK_ARCHIVE" ]] || { err "Backup archive is missing: $ROLLBACK_ARCHIVE"; return 1; }
  confirm "Restore the backup into $ROOT?" || { warn 'Rollback cancelled.'; return 1; }
  run 'Rollback' "$ROOT" tar -xzf "$ROLLBACK_ARCHIVE" -C "$ROOT"
}

git_preflight() {
  has git || return 0
  git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
  local status
  status="$(git -C "$ROOT" status --short 2>/dev/null || true)"
  [[ -z "$status" ]] && return 0
  warn 'The Git working tree contains uncommitted changes.'
  printf '%s\n' "$status" >"$LOG_DIR/git-status-before.txt"
  if ((STASH)); then run 'Git stash' "$ROOT" git stash push -u -m "dep-upgrader $STAMP" || true; fi
}

require_mutation_consent() {
  [[ "$MODULE" == deps || "$MODULE" == all ]] || return 0
  ((DRY_RUN)) && return 0
  ((NON_INTERACTIVE == 0)) && return 0
  ((ASSUME_YES)) || { err 'Non-interactive updates with cleanup require --yes or an initial --dry-run.'; return 1; }
}
