#!/bin/sh
# .ci/lib-temp.sh
# Centralized, idempotent helpers for temporary files and cleanup (POSIX /bin/sh).
# - Idempotent: safe to source multiple times.
# - Exports: TMPDIR_SAFE, _TMP_TRACKER, _REGISTER_FILE, CI_TMP_HELPERS_AVAILABLE, CI_TMP_REPODIR.
# - Public helpers: _mktemp_safe, _mktemp_safe_create, _mktempdir_safe, _register_tmpfile,
#   register_cleanup, _cleanup_tmpfiles, _cleanup_on_exit, acquire_lock, release_lock, atomic_replace_file.
# - Design: single-file for simple sourcing; organized in clear sections with minimal inline docs.
#
# NOTE: For extended documentation see docs/ci-lib-temp.md

# --- Idempotence guard ---
[ -n "${__CI_TMP_HELPERS_LOADED:-}" ] && return 0
__CI_TMP_HELPERS_LOADED=1

# --- Minimal portable utilities ---
_has() { command -v "$1" >/dev/null 2>&1; }
_portable_return_or_exit() { code="${1:-0}"; (return 0 2>/dev/null) && return "$code" || exit "$code"; }

_sanitize() {
  printf '%s' "$1" | tr -d '\r' | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}

# --- SECTION: Configuration ---
CI_BASEDIR="${CI_BASEDIR:-$(pwd 2>/dev/null || printf '%s' '')}"
CI_BASEDIR="$(_sanitize "${CI_BASEDIR:-}")"
CI_BASEDIR="${CI_BASEDIR%/}"
[ -n "${CI_BASEDIR:-}" ] || CI_BASEDIR="$(pwd 2>/dev/null || printf '%s' '')"

CI_TMP_REPODIR="${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}"
CI_TMP_REPODIR="$(_sanitize "${CI_TMP_REPODIR:-}")"
CI_TMP_REPODIR="${CI_TMP_REPODIR%/}"

CI_OUTDIR="${CI_OUTDIR:-${CI_BASEDIR%/}/docs/checkup}"
CI_OUTDIR="${CI_OUTDIR%/}"

CI_TMP_PROFILE="${CI_TMP_PROFILE:-balanced}"
KEEP_BACKUP="${KEEP_BACKUP:-0}"

LOCK_RETRIES="${LOCK_RETRIES:-20}"
LOCK_SLEEP="${LOCK_SLEEP:-1}"

CI_TMP_MAX_ENTRIES="${CI_TMP_MAX_ENTRIES:-10}"

_TMP_INIT_DONE="${_TMP_INIT_DONE:-}"
_REGISTER_FILE="${_REGISTER_FILE:-}"
_TMP_TRACKER="${_TMP_TRACKER:-}"
_PRUNE_IN_PROGRESS="${_PRUNE_IN_PROGRESS:-0}"

# Detect Termux/Android to avoid aggressive pruning there
_termux_detected=0
if [ -n "${TERMUX_VERSION:-}" ] || [ -n "${ANDROID_ROOT:-}" ] || [ -f "/data/data/com.termux/files/usr/bin/termux-info" ]; then
  _termux_detected=1
fi

# --- SECTION: Utilities ---
_get_mtime() {
  p="${1:-}"
  [ -n "${p:-}" ] || { printf '%s\n' 0; return 0; }
  if _has stat; then
    stat -c %Y "$p" 2>/dev/null && return 0
    stat -f %m "$p" 2>/dev/null && return 0
  fi
  printf '%s\n' 0
}

# --- SECTION: Prune helpers ---
_prune_repo_tmpdirs_keep_newest() {
  repo_base="${1:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}"
  keep="${2:-${CI_TMP_MAX_ENTRIES:-10}}"
  [ -n "${repo_base:-}" ] || return 0
  case "$keep" in ''|*[!0-9]*) keep=10 ;; esac
  [ -d "$repo_base" ] || return 0

  _PRUNE_IN_PROGRESS=1

  # Ensure repo_base exists to avoid mktemp failures on constrained envs
  [ -d "$repo_base" ] || mkdir -p "$repo_base" 2>/dev/null || true

  if _has mktemp; then
    tmpfile="$(mktemp -p "$repo_base" ".prune.XXXXXX" 2>/dev/null || printf '%s' "${repo_base%/}/.prune.$$")" || {
      _PRUNE_IN_PROGRESS=0
      return 0
    }
  else
    tmpfile="${repo_base%/}/.prune.$$"
  fi

  if _has find && _has stat && _has sort; then
    find "$repo_base" -maxdepth 1 -type d -name 'ci-tmp*' ! -path "$repo_base" 2>/dev/null | while IFS= read -r d; do
      case "$d" in "${repo_base%/}"/*) : ;; *) continue ;; esac
      mtime="$(stat -c %Y "$d" 2>/dev/null || stat -f %m "$d" 2>/dev/null || printf '%s' 0)"
      printf '%s\t%s\n' "$mtime" "$d" >> "$tmpfile"
    done
  else
    for d in "$repo_base"/ci-tmp*; do
      [ -d "$d" ] || continue
      case "$d" in "${repo_base%/}"/*) : ;; *) continue ;; esac
      mtime="$(_get_mtime "$d" 2>/dev/null || printf '%s' 0)"
      printf '%s\t%s\n' "$mtime" "$d" >> "$tmpfile"
    done
  fi

  total="$(wc -l < "$tmpfile" 2>/dev/null || printf '%s' 0)"
  if [ "$total" -le "$keep" ]; then
    rm -f "$tmpfile" 2>/dev/null || true
    _PRUNE_IN_PROGRESS=0
    return 0
  fi

  sort -nr "$tmpfile" | awk -F'\t' -v k="$keep" 'NR>k{print $2}' | while IFS= read -r old; do
    [ -z "${old:-}" ] && continue
    case "$old" in "${repo_base%/}"/*) : ;; *) continue ;; esac
    bn="$(basename "$old" 2>/dev/null || printf '%s' "$old")"
    case "$bn" in
      *capture*|*export*|*secrets*)
        printf '%s\n' "WARN: skipping sensitive path during prune: $old" >&2 || true
        continue
        ;;
    esac
    if [ -n "$old" ] && [ -d "$old" ]; then
      rm -rf -- "$old" 2>/dev/null || true
    fi
  done

  rm -f "$tmpfile" 2>/dev/null || true
  _PRUNE_IN_PROGRESS=0
  return 0
}

_prune_tracker_missing_entries() {
  tracker="${_TMP_TRACKER:-}"
  [ -f "$tracker" ] || return 0
  if _has mktemp; then
    tmpkeep="$(mktemp -p "$(dirname "$tracker")" "$(basename "$tracker").keep.XXXXXX" 2>/dev/null || printf '%s' "${tracker}.keep.$$")"
  else
    tmpkeep="${tracker}.keep.$$"
  fi
  while IFS= read -r p || [ -n "${p:-}" ]; do
    [ -z "${p:-}" ] && continue
    if [ -e "$p" ]; then
      printf '%s\n' "$p" >> "$tmpkeep"
    fi
  done < "$tracker"
  mv "$tmpkeep" "$tracker" 2>/dev/null || cp -a "$tmpkeep" "$tracker" 2>/dev/null || true
  rm -f "$tmpkeep" 2>/dev/null || true
  return 0
}

# --- SECTION: Initialization ---
_TMP_INIT() {
  if [ -n "${_TMP_INIT_DONE:-}" ] && [ -n "${TMPDIR_SAFE:-}" ] && [ -d "${TMPDIR_SAFE}" ]; then
    printf '%s\n' "${TMPDIR_SAFE}"
    return 0
  fi

  base="${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}"
  base="$(_sanitize "${base:-}")"
  base="${base%/}"
  if [ -z "${base}" ] || [ "${base}" = "/" ]; then
    base="${CI_BASEDIR%/}/.ci/tmp"
  fi

  mkdir -p "$base" 2>/dev/null || true
  chmod 700 "$base" 2>/dev/null || true

  CI_TMP_PRUNE_ON_INIT="${CI_TMP_PRUNE_ON_INIT:-0}"
  if [ "${CI_TMP_PRUNE_ON_INIT:-0}" -eq 1 ] && [ "${_termux_detected:-0}" -eq 0 ]; then
    _prune_repo_tmpdirs_keep_newest "$base" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
  else
    printf '%s\n' "INFO: repo tmp prune skipped on init (CI_TMP_PRUNE_ON_INIT=${CI_TMP_PRUNE_ON_INIT})" >/dev/stderr 2>/dev/null || true
  fi

  testfile="${base%/}/.ci-write-test.$$"
  if ! : > "$testfile" 2>/dev/null; then
    base="${CI_BASEDIR%/}/.ci/tmp"
    mkdir -p "$base" 2>/dev/null || true
    chmod 700 "$base" 2>/dev/null || true
  fi
  rm -f "$testfile" 2>/dev/null || true

  now="$(date +%s 2>/dev/null || printf '%s' "$$")"
  tmpd=""

  [ -d "$base" ] || mkdir -p "$base" 2>/dev/null || true

  if _has mktemp; then
    out="$(mktemp -d -p "$base" "ci-tmp.XXXXXX" 2>/dev/null || true)"
    if [ -n "${out:-}" ] && [ -d "$out" ]; then
      tmpd="$out"
    fi
  fi

  tmpd="${tmpd:-${base%/}/ci-tmp.$$.$now}"
  mkdir -p "$tmpd" 2>/dev/null || true
  chmod 700 "$tmpd" 2>/dev/null || true

  TMPDIR_SAFE="$tmpd"
  export TMPDIR_SAFE

  if command -v _register_tmpfile >/dev/null 2>&1; then
    _register_tmpfile "${TMPDIR_SAFE}" 2>/dev/null || true
  fi

  _TMP_TRACKER="${TMPDIR_SAFE%/}/.tracked.tmpfiles"
  mkdir -p "$(dirname "${_TMP_TRACKER}")" 2>/dev/null || true
  : > "${_TMP_TRACKER}" 2>/dev/null || true
  chmod 600 "${_TMP_TRACKER}" 2>/dev/null || true
  export _TMP_TRACKER

  _REGISTER_FILE="${TMPDIR_SAFE%/}/.registered.cleanups"
  mkdir -p "$(dirname "${_REGISTER_FILE}")" 2>/dev/null || true
  : > "${_REGISTER_FILE}" 2>/dev/null || true
  chmod 600 "${_REGISTER_FILE}" 2>/dev/null || true
  export _REGISTER_FILE

  _prune_tracker_missing_entries >/dev/null 2>&1 || true

  if [ -z "${_LIBTEMP_TRAP_SET:-}" ]; then
    # trap will call the unified _cleanup_on_exit (defined below)
    trap '_cleanup_on_exit' EXIT INT TERM HUP
    _LIBTEMP_TRAP_SET=1
  fi

  _TMP_INIT_DONE=1

  CI_TMP_HELPERS_AVAILABLE=1; export CI_TMP_HELPERS_AVAILABLE

  printf '%s\n' "${TMPDIR_SAFE}"
  return 0
}

# Ensure TMPDIR_SAFE is initialized
[ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || true

# --- SECTION: Hash helper ---
_sha256() {
  f="${1:-}"
  [ -n "${f:-}" ] || { printf '%s\n' "n/a"; return 0; }
  if _has sha256sum; then
    sha256sum -- "$f" 2>/dev/null | awk '{print $1}'
  elif _has shasum; then
    shasum -a 256 -- "$f" 2>/dev/null | awk '{print $1}'
  elif _has openssl; then
    openssl dgst -sha256 -- "$f" 2>/dev/null | awk '{print $NF}'
  else
    printf '%s\n' "n/a"
  fi
}

# --- SECTION: mktemp helpers ---
_mktemp_safe() {
  tmpl="${1:-tmp.XXXXXX}"
  [ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || return 1

  case "$tmpl" in
    */*)
      target_dir="$(dirname "$tmpl")"
      name_template="$(basename "$tmpl")"
      case "$target_dir" in
        /*) resolved_dir="$target_dir" ;;
        *) resolved_dir="${TMPDIR_SAFE%/}/${target_dir%/}" ;;
      esac
      [ -n "${resolved_dir:-}" ] || resolved_dir="${TMPDIR_SAFE%/}"
      mkdir -p "$resolved_dir" 2>/dev/null || true

      if _has mktemp; then
        out="$(mktemp -p "$resolved_dir" "$name_template" 2>/dev/null || true)"
        if [ -n "${out:-}" ]; then
          printf '%s\n' "$out"
          return 0
        fi
      fi

      printf '%s\n' "${resolved_dir%/}/${name_template%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
      return 0
      ;;
    *)
      base="${TMPDIR_SAFE%/}"
      if _has mktemp; then
        out="$(mktemp -p "$base" "$tmpl" 2>/dev/null || true)"
        if [ -n "${out:-}" ]; then
          printf '%s\n' "$out"
          return 0
        fi
      fi
      printf '%s\n' "${base%/}/${tmpl%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
      return 0
      ;;
  esac
}

_mktemp_safe_create() {
  tmpl="${1:-tmp.XXXXXX}"
  f="$(_mktemp_safe "$tmpl")" || return 1
  mkdir -p "$(dirname "$f")" 2>/dev/null || true

  old_umask="$(umask)"
  umask 077
  : > "$f" 2>/dev/null || true
  umask "$old_umask"
  chmod 600 "$f" 2>/dev/null || true

  bn="$(basename "$f" 2>/dev/null || printf '%s' "$f")"
  case "$bn" in
    *capture*|*export*|*secrets*)
      ;;
    *)
      printf '%s\n' "$f" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
      if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
        _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
      fi
      ;;
  esac

  printf '%s\n' "$f"
}

_mktempdir_safe() {
  tmpl="${1:-tmpdir.XXXXXX}"
  [ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || return 1

  case "$tmpl" in
    */*)
      target_dir="$(dirname "$tmpl")"
      name_template="$(basename "$tmpl")"
      case "$target_dir" in
        /*) resolved_dir="$target_dir" ;;
        *) resolved_dir="${TMPDIR_SAFE%/}/${target_dir%/}" ;;
      esac
      [ -n "${resolved_dir:-}" ] || resolved_dir="${TMPDIR_SAFE%/}"
      mkdir -p "$resolved_dir" 2>/dev/null || true
      if _has mktemp; then
        out="$(mktemp -d -p "$resolved_dir" "$name_template" 2>/dev/null || true)"
        if [ -n "${out:-}" ]; then
          printf '%s\n' "$out"
          bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
          case "$bn" in
            *capture*|*export*|*secrets*)
              ;;
            *)
              printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
              if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
                _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
              fi
              ;;
          esac
          return 0
        fi
      fi
      d="${resolved_dir%/}/${name_template%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
      mkdir -p "$d" 2>/dev/null || true
      chmod 700 "$d" 2>/dev/null || true
      printf '%s\n' "$d"
      bn="$(basename "$d" 2>/dev/null || printf '%s' "$d")"
      case "$bn" in
        *capture*|*export*|*secrets*)
          ;;
        *)
          printf '%s\n' "$d" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
          if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
            _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
          fi
          ;;
      esac
      return 0
      ;;
    *)
      base="${TMPDIR_SAFE%/}"
      if _has mktemp; then
        out="$(mktemp -d -p "$base" "$tmpl" 2>/dev/null || true)"
        if [ -n "${out:-}" ]; then
          printf '%s\n' "$out"
          bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
          case "$bn" in
            *capture*|*export*|*secrets*)
              ;;
            *)
              printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
              if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
                _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
              fi
              ;;
          esac
          return 0
        fi
      fi
      d="${base%/}/${tmpl%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
      mkdir -p "$d" 2>/dev/null || true
      chmod 700 "$d" 2>/dev/null || true
      printf '%s\n' "$d"
      bn="$(basename "$d" 2>/dev/null || printf '%s' "$d")"
      case "$bn" in
        *capture*|*export*|*secrets*)
          ;;
        *)
          printf '%s\n' "$d" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
          if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
            _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
          fi
          ;;
      esac
      return 0
      ;;
  esac
}

_mktemp_unique() {
  dir="${1:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}"
  prefix="${2:-tmp}"
  mkdir -p "$dir" 2>/dev/null || true
  if _has mktemp; then
    out="$(mktemp -p "$dir" "${prefix}.XXXXXX" 2>/dev/null || printf '%s\n' "${dir%/}/${prefix}.$$.$(date +%s 2>/dev/null || printf '%s' "$$")")"
  else
    out="${dir%/}/${prefix}.$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
  fi
  bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
  case "$bn" in
    *capture*|*export*|*secrets*)
      ;;
    *)
      printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
      if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
        _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
      fi
      ;;
  esac
  printf '%s\n' "$out"
}

_mktemp_for_find() { _mktempdir_safe "$@" || _mktemp_safe_create "$@"; }

# --- SECTION: Scoped tempfile helper ---
_with_tempfile() {
  varname="$1"; tmpl="${2:-tmp.XXXXXX}"
  f="$(_mktemp_safe_create "$tmpl")" || return 1

  bn="$(basename "$f" 2>/dev/null || printf '%s' "$f")"
  case "$bn" in
    *capture*|*export*|*secrets*)
      ;;
    *)
      printf '%s\n' "$f" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
      if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
        _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
      fi
      ;;
  esac

  eval "$varname=\"\$f\"" 2>/dev/null || return 1
  return 0
}

# --- SECTION: Writable check ---
_verify_writable() {
  path="${1:-}"
  [ -n "${path:-}" ] || return 1
  mkdir -p "$(dirname "$path")" 2>/dev/null || true
  : > "$path" 2>/dev/null
  return $?
}

# --- SECTION: Simple cleanup ---
_cleanup_tmpfiles() {
  for f in "$@"; do
    [ -n "${f:-}" ] && rm -rf "$f" 2>/dev/null || true
  done
  return 0
}

# --- SECTION: wait_for ---
wait_for() {
  pid="${1:-}"; timeout="${2:-5}"
  [ -n "$pid" ] || return 0
  i=0
  while ps -p "$pid" >/dev/null 2>&1 && [ "$i" -lt "$timeout" ]; do
    sleep 1
    i=$((i + 1))
  done
  if ps -p "$pid" >/dev/null 2>&1; then return 1; fi
  return 0
}

# --- SECTION: Lock helpers ---
acquire_lockdir() {
  lockdir="${1:-}"
  maxtries="${2:-${LOCK_RETRIES:-20}}"
  sleep_sec="${3:-${LOCK_SLEEP:-1}}"
  [ -n "${lockdir:-}" ] || return 1
  tries=0
  while [ "$tries" -lt "${maxtries}" ]; do
    if mkdir "$lockdir" 2>/dev/null; then
      printf '%s\n' "$$" > "${lockdir}/pid" 2>/dev/null || true
      return 0
    fi
    tries=$((tries + 1))
    sleep "${sleep_sec}" 2>/dev/null || sleep 1
  done
  return 1
}

release_lockdir() {
  lockdir="${1:-}"
  [ -n "${lockdir:-}" ] || return 1
  rm -f "${lockdir}/pid" 2>/dev/null || true
  rmdir "$lockdir" 2>/dev/null || true
  return 0
}

acquire_lock() {
  nowait=0
  timeout_secs=0
  while [ "${1:-}" != "" ]; do
    case "$1" in
      --nowait) nowait=1; shift ;;
      --timeout) shift; timeout_secs="${1:-0}"; shift ;;
      --) shift; break ;;
      -*) shift ;;
      *) break ;;
    esac
  done

  lockpath="$1"
  [ -n "${lockpath:-}" ] || return 1

  [ -n "${_REGISTER_FILE:-}" ] || _TMP_INIT >/dev/null 2>&1 || true
  : > "${_REGISTER_FILE}" 2>/dev/null || true

  start_ts="$(date +%s 2>/dev/null || printf '%s' 0)"
  tries=0

  while :; do
    if mkdir "${lockpath}.lockdir" 2>/dev/null; then
      printf '%s\n' "$$" > "${lockpath}.lockdir/pid" 2>/dev/null || true
      printf '%s\n' "rm -rf -- '${lockpath}.lockdir'" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
      return 0
    fi

    tries=$((tries + 1))

    if [ "${nowait:-0}" -eq 1 ]; then
      return 1
    fi

    if [ "${timeout_secs:-0}" -gt 0 ]; then
      now_ts="$(date +%s 2>/dev/null || printf '%s' 0)"
      if [ $((now_ts - start_ts)) -ge "${timeout_secs:-0}" ]; then
        return 1
      fi
    fi

    if [ "${LOCK_RETRIES:-0}" -gt 0 ] && [ "${tries}" -ge "${LOCK_RETRIES:-0}" ]; then
      return 1
    fi

    sleep "${LOCK_SLEEP:-1}" 2>/dev/null || sleep 1
  done
}

release_lock() {
  lockpath="${1:-}"
  [ -n "${lockpath:-}" ] || return 1
  rm -rf "${lockpath}.lockdir" 2>/dev/null || true
  return 0
}

# --- SECTION: register_cleanup (whitelisted commands only) ---
register_cleanup() {
  cmd="$1"
  [ -n "${cmd:-}" ] || return 1

  cmd="$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"

  case "$cmd" in
    rm\ -rf\ *|rm\ -f\ *|rm\ *|rmdir\ *|:|true)
      mkdir -p "$(dirname "${_REGISTER_FILE:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}")" 2>/dev/null || true
      printf '%s\n' "$cmd" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
      return 0
      ;;
    [A-Za-z_]*)
      mkdir -p "$(dirname "${_REGISTER_FILE:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}")" 2>/dev/null || true
      printf '%s\n' "$cmd" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
      return 0
      ;;
    *)
      printf '%s\n' "WARN: register_cleanup rejected unsafe command: $cmd" >&2 || true
      return 1
      ;;
  esac
}

# --- SECTION: Local register helper ---
_register_local_tmpfile() {
  f="$1"
  [ -n "${f:-}" ] || return 0

  if [ -n "${_TMP_TRACKER:-}" ]; then
    printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
  fi

  if command -v register_cleanup >/dev/null 2>&1; then
    register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
  fi

  if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
    _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
  fi

  return 0
}

# --- SECTION: Backwards-compatible aliases and shims ---
if [ -z "${_REGISTER_TMP_FILE_ALIAS_DONE:-}" ]; then
  if command -v _register_local_tmpfile >/dev/null 2>&1; then
    _register_tmp_file() { _register_local_tmpfile "$@"; return $?; }
  else
    _register_tmp_file() {
      f="$1"
      [ -n "${f:-}" ] || return 0
      [ -n "${_TMP_TRACKER:-}" ] && printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
      if command -v register_cleanup >/dev/null 2>&1; then
        register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
      fi
      if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
        _prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
      fi
      return 0
    }
  fi
  _REGISTER_TMP_FILE_ALIAS_DONE=1
fi

if ! command -v _register_tmpfile >/dev/null 2>&1; then
  _register_tmpfile() {
    if command -v _register_tmp_file >/dev/null 2>&1; then
      _register_tmp_file "$@" || return $?
      return 0
    fi
    if command -v _register_local_tmpfile >/dev/null 2>&1; then
      _register_local_tmpfile "$@" || return $?
      return 0
    fi
    f="$1"
    [ -n "${f:-}" ] || return 0
    [ -n "${_TMP_TRACKER:-}" ] && printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
    if command -v register_cleanup >/dev/null 2>&1; then
      register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
    fi
    return 0
  }
fi

# --- SECTION: Simple cleanup-on-exit (centralized) ---
_cleanup_on_exit() {
  candidates=""
  candidates="${candidates} ${_TMPFILES:-}"
  candidates="${candidates} ${_TMPFILES_TO_CLEANUP:-}"
  candidates="${candidates} ${TMPDIR_RUN:-}"
  candidates="${candidates} ${TMPDIR:-}"
  candidates="${candidates} ${TMPDIR_WORK:-}"
  candidates="${candidates} ${TMPLIST:-}"
  candidates="${candidates} ${BADGES_TMP:-}"
  candidates="${candidates} ${TMP_LOG:-}"
  candidates="${candidates} ${TMP_SECRETS:-}"
  candidates="${candidates} ${TMP_FOUND:-}"
  candidates="${candidates} ${TMP_FIXED:-}"
  candidates="${candidates} ${TMP_WARNED:-}"
  candidates="${candidates} ${TMP_BADGES:-}"
  candidates="${candidates} ${EXISTING:-}"
  candidates="${candidates} ${BADGES_MERGED:-}"
  candidates="${candidates} ${TMP_RUN_DIR:-}"
  candidates="${candidates} ${TMPDIR_RUN:-}"
  candidates="${candidates} ${TMP_TRACKER:-}"
  candidates="${candidates} ${TMP_TRACKER_FILE:-}"
  candidates="${candidates} ${_TMP_TRACKER:-}"
  candidates="${candidates} ${_REGISTER_FILE:-}"

  candidates="$(printf '%s' "${candidates}" | awk '{$1=$1;print}')"

  if command -v _cleanup_tmpfiles >/dev/null 2>&1; then
    oldifs="${IFS}"; IFS=' '
    # shellcheck disable=SC2086
    set -- ${candidates:-}
    IFS="${oldifs}"
    if [ "$#" -gt 0 ]; then
      _cleanup_tmpfiles "$@" >/dev/null 2>&1 || true
    fi
  else
    oldifs="${IFS}"; IFS=' '
    # shellcheck disable=SC2086
    set -- ${candidates:-}
    IFS="${oldifs}"
    for f in "$@"; do
      [ -n "${f:-}" ] || continue
      rm -rf -- "${f}" >/dev/null 2>&1 || true
    done
  fi

  if [ -n "${_REGISTER_FILE:-}" ] && [ -f "${_REGISTER_FILE}" ]; then
    while IFS= read -r cmd || [ -n "${cmd:-}" ]; do
      [ -z "${cmd:-}" ] && continue
      case "$cmd" in
        rm\ -rf\ *|rm\ -f\ *|rm\ *|rmdir\ *|:|true)
          sh -c -- "$cmd" 2>/dev/null || true
          ;;
        [A-Za-z_]*)
          if command -v "$cmd" >/dev/null 2>&1; then
            "$cmd" 2>/dev/null || true
          else
            printf '%s\n' "WARN: cleanup function not found: $cmd" >&2 || true
          fi
          ;;
        *)
          printf '%s\n' "WARN: skipped unsafe cleanup entry: $cmd" >&2 || true
          ;;
      esac
    done < "${_REGISTER_FILE}" 2>/dev/null || true
    rm -f "${_REGISTER_FILE}" 2>/dev/null || true
  fi

  for tracker in "${TMP_TRACKER:-}" "${TMP_TRACKER_FILE:-}" "${_TMP_TRACKER:-}"; do
    [ -n "${tracker:-}" ] || continue
    if [ -f "${tracker}" ]; then
      while IFS= read -r p || [ -n "${p:-}" ]; do
        [ -n "${p:-}" ] && rm -rf -- "${p}" 2>/dev/null || true
      done < "${tracker}" 2>/dev/null || true
      rm -f -- "${tracker}" 2>/dev/null || true
    fi
  done

  if [ -n "${TMPDIR_SAFE:-}" ]; then
    rm -f -- "${TMPDIR_SAFE%/}/ci-verify-perms.last" >/dev/null 2>&1 || true
    rm -f -- "${TMPDIR_SAFE%/}/ci-verify-perms.diag."* >/dev/null 2>&1 || true
  fi

  return 0
}

_libtemp_cleanup_on_exit() { _cleanup_on_exit "$@"; return $?; }

if [ -z "${_LIBTEMP_TRAP_SET:-}" ]; then
  trap '_cleanup_on_exit' EXIT INT TERM HUP
  _LIBTEMP_TRAP_SET=1
fi

# --- SECTION: Atomic replace helper ---
atomic_replace_file() {
  target="$1"
  tmp="$2"
  [ -n "${target:-}" ] || return 1
  [ -f "${tmp:-}" ] || return 1
  mkdir -p "$(dirname "$target")" 2>/dev/null || true
  if [ "${KEEP_BACKUP:-0}" -ne 0 ] && [ -f "$target" ]; then
    backup="${target}.bak-$(date -u +%Y%m%d%H%M%S 2>/dev/null || printf '%s' "$(date +%s)")"
    cp -a "$target" "$backup" 2>/dev/null || true
  fi
  mv "$tmp" "$target" 2>/dev/null || cp -a "$tmp" "$target" 2>/dev/null || true
  return 0
}

# --- SECTION: Utilities and aliases ---
ci_truncate_list() {
  list="${1:-}"; max="${2:-12}"
  [ -n "${list:-}" ] || { printf '%s\n' ''; return 0; }
  savedIFS="$IFS"; IFS=' '
  out=""
  i=0
  for p in $list; do
    i=$((i + 1))
    out="${out}${p} "
    [ "$i" -ge "$max" ] && break
  done
  IFS="$savedIFS"
  printf '%s\n' "${out% }"
}

if ! command -v acquirelock >/dev/null 2>&1 && command -v acquire_lockdir >/dev/null 2>&1; then
  acquirelock() { acquire_lockdir "$@"; }
fi
if ! command -v acquirelockdir >/dev/null 2>&1 && command -v acquire_lockdir >/dev/null 2>&1; then
  acquirelockdir() { acquire_lockdir "$@"; }
fi
if ! command -v releaselock >/dev/null 2>&1 && command -v release_lockdir >/dev/null 2>&1; then
  releaselock() { release_lockdir "$@"; }
fi
if ! command -v release_lockdir >/dev/null 2>&1 && command -v releaselock >/dev/null 2>&1; then
  release_lockdir() { releaselock "$@"; }
fi

if ! command -v _atomic_write >/dev/null 2>&1; then
  if command -v atomic_replace_file >/dev/null 2>&1; then
    _atomic_write() { atomic_replace_file "$@"; return $?; }
  fi
fi

if ! command -v compute_sha256 >/dev/null 2>&1; then
  if command -v _sha256 >/dev/null 2>&1; then
    compute_sha256() { _sha256 "$@"; return $?; }
  fi
fi

if ! command -v _mktemp_for_find >/dev/null 2>&1 && command -v _mktempdir_safe >/dev/null 2>&1; then
  _mktemp_for_find() { _mktempdir_safe "$@"; return $?; }
fi

# --- Export markers and variables ---
export TMPDIR_SAFE _TMP_TRACKER _REGISTER_FILE CI_TMP_HELPERS_AVAILABLE CI_TMP_REPODIR

# End of .ci/lib-temp.sh