#!/bin/bash
# Shared bounded depot cleanup. Requires GNU find; callers own logging/traps.

# Report one reaped find process. Counts remain useful on a partial failure.
function _sdp_cache_cleanup_report () {
   local CountFile=$1 Depot=$2 Start=$3 Result=$4 Removed
   Removed=$(wc -c < "$CountFile") || { echo "ERROR: Cannot count removals for $Depot." >&2; return 1; }
   Removed=${Removed//[[:space:]]/}
   SDP_CACHE_REMOVED=$((SDP_CACHE_REMOVED + Removed))
   if [[ "$Result" == 0 ]]; then
      printf '%s: removed %s file(s) in %s seconds.\n' "$Depot" "$Removed" "$((SECONDS - Start))"
   else
      printf 'ERROR: %s: cleanup failed/interrupted after removing %s file(s) in %s seconds.\n' "$Depot" "$Removed" "$((SECONDS - Start))" >&2
      return 1
   fi
}

function sdp_cache_cleanup_dirs () {
   local Age=$1 CrossDevice=$2 Limit=${SDP_CACHE_CLEAN_WORKERS:-4}
   shift 2
   local Depot Worker Status=0 Index=0 CountDir Result CancelSignal=
   local SavedTerm SavedInt SavedHup SignalNumber=0 Position ThisPID=${BASHPID:-$$}
   local -a Workers=() WorkerIndexes=() StartTimes=() FindArgs=() Depots=("$@")
   SDP_CACHE_REMOVED=0
   SDP_CACHE_DEPOTS=$#
   # Validate the decimal string before arithmetic so oversized input cannot
   # wrap around to an apparently valid worker count.
   while [[ ${#Limit} -gt 1 && $Limit == 0* ]]; do Limit=${Limit#0}; done
   if [[ ! "$Limit" =~ ^[0-9]{1,3}$ ]] || (( 10#$Limit < 1 || 10#$Limit > 256 )); then
      echo "SDP_CACHE_CLEAN_WORKERS must be between 1 and 256." >&2
      return 1
   fi
   Limit=$((10#$Limit))
   CountDir=$(mktemp -d "${P4TMP:-${TMPDIR:-/tmp}}/cache-clean.XXXXXX") || return 1
   [[ "$CrossDevice" == no ]] && FindArgs+=(-xdev)
   SavedTerm=$(trap -p TERM); SavedInt=$(trap -p INT); SavedHup=$(trap -p HUP)
   # A signal only marks cancellation here. In particular, a signal between
   # starting find and recording $! cannot leave an untracked child behind.
   # Cleanup then restores and replays the caller's original signal handler.
   trap 'CancelSignal=TERM' TERM
   trap 'CancelSignal=INT' INT
   trap 'CancelSignal=HUP' HUP
   echo "Cleaning $# depot(s) with at most $Limit workers."
   for Depot in "$@"; do
      [[ -z "$CancelSignal" ]] || break
      if (( ${#Workers[@]} >= Limit )); then
         Result=0
         wait "${Workers[0]}" || Result=$?
         [[ -z "$CancelSignal" ]] || break
         Position=${WorkerIndexes[0]}
         _sdp_cache_cleanup_report "$CountDir/$Position" "${Depots[Position]}" "${StartTimes[Position]}" "$Result" || Status=1
         Workers=("${Workers[@]:1}"); WorkerIndexes=("${WorkerIndexes[@]:1}")
      fi
      [[ -z "$CancelSignal" ]] || break
      StartTimes[Index]=$SECONDS
      # Track actual find PIDs, not wrapper subshells: cancellation must stop
      # the process deleting files and wait for it before returning to callers.
      # One byte per deletion handles filenames containing newlines correctly.
      find "$Depot/" "${FindArgs[@]}" -atime "+$Age" -type f -delete -printf '.' > "$CountDir/$Index" &
      Workers+=("$!"); WorkerIndexes+=("$Index")
      Index=$((Index + 1))
   done
   while (( ${#Workers[@]} )) && [[ -z "$CancelSignal" ]]; do
      Result=0
      wait "${Workers[0]}" || Result=$?
      [[ -z "$CancelSignal" ]] || break
      Position=${WorkerIndexes[0]}
      _sdp_cache_cleanup_report "$CountDir/$Position" "${Depots[Position]}" "${StartTimes[Position]}" "$Result" || Status=1
      Workers=("${Workers[@]:1}"); WorkerIndexes=("${WorkerIndexes[@]:1}")
   done
   if [[ -n "$CancelSignal" ]]; then
      echo "ERROR: Cache cleanup interrupted by $CancelSignal; stopping and reaping find workers." >&2
      # Background commands can inherit ignored SIGINT. TERM is intentional for
      # all cancellation signals; KILL bounds cleanup if a child ignores TERM.
      for Worker in "${Workers[@]}"; do kill -TERM "$Worker" 2>/dev/null || :; done
      if (( ${#Workers[@]} )); then sleep 0.1; fi
      for Worker in "${Workers[@]}"; do kill -KILL "$Worker" 2>/dev/null || :; done
      for ((Index=0; Index<${#Workers[@]}; Index++)); do
         wait "${Workers[Index]}" 2>/dev/null || :
         Position=${WorkerIndexes[Index]}
         _sdp_cache_cleanup_report "$CountDir/$Position" "${Depots[Position]}" "${StartTimes[Position]}" 1 || :
      done
      Status=1
      # Caller termination traps may exit before the normal summary path.
      if declare -F sdp_add_field >/dev/null; then
         sdp_add_field depot_count "$SDP_CACHE_DEPOTS"
         sdp_add_field files_removed "$SDP_CACHE_REMOVED"
      fi
      if declare -F sdp_set_error >/dev/null; then sdp_set_error "Cache cleanup interrupted by $CancelSignal"; fi
   fi
   rm -rf -- "$CountDir" || Status=1
   trap - TERM INT HUP
   # These strings are obtained only from Bash's own trap -p output.
   [[ -z "$SavedTerm" ]] || eval "$SavedTerm"
   [[ -z "$SavedInt" ]] || eval "$SavedInt"
   [[ -z "$SavedHup" ]] || eval "$SavedHup"
   if [[ -n "$CancelSignal" ]]; then
      case "$CancelSignal" in TERM) SignalNumber=15;; INT) SignalNumber=2;; HUP) SignalNumber=1;; esac
      kill -s "$CancelSignal" "$ThisPID"
      # If the caller deliberately ignores/handles the signal without exiting,
      # it still receives a nonzero result from this cancelled cleanup pass.
      return "$((128 + SignalNumber))"
   fi
   return "$Status"
}
