#!/usr/bin/env bash
# Shared daily/weekly/monthly (GFS) retention for timestamped backup
# files — see docs/PROJECT_PLAN.md §29's example policy (7/4/6). Sourced
# by backup-database.sh and backup-storage.sh; not meant to be run
# directly.
#
# Expects files named "<prefix>-YYYYmmdd-HHMMSS.<ext>" in a directory,
# and classifies them by the timestamp embedded in the *filename*, not
# the file's mtime — mtime changes on copy/rsync and would misclassify a
# backup after it's been transferred offsite, but the filename doesn't.
#
# Deliberately avoids bash 4+ features (associative arrays, mapfile) so
# it also runs under bash 3.2, still the default on macOS — useful for
# testing this locally, even though the Pi/VPS targets run a modern
# Linux bash.
#
# Usage: rotate_backups <dir> <glob> <keep_daily> <keep_weekly> <keep_monthly>
rotate_backups() {
  local dir="$1" glob="$2" keep_daily="$3" keep_weekly="$4" keep_monthly="$5"

  local files=()
  while IFS= read -r -d '' f; do
    files+=("$f")
  done < <(find "$dir" -maxdepth 1 -name "$glob" -type f -print0 | sort -zr)

  if [[ ${#files[@]} -eq 0 ]]; then
    return 0
  fi

  local keep=() seen_weeks="" seen_months=""
  local i week_count=0 month_count=0

  # Daily: keep the N most recent files outright, newest first.
  for ((i = 0; i < keep_daily && i < ${#files[@]}; i++)); do
    keep+=("${files[$i]}")
  done

  # Weekly: among files older than the daily window, keep the newest one
  # per ISO year-week, up to keep_weekly distinct weeks.
  for ((i = keep_daily; i < ${#files[@]}; i++)); do
    [[ $week_count -ge $keep_weekly ]] && break
    local yw
    yw="$(_rotate_backups_year_week "${files[$i]}")" || continue
    case " $seen_weeks " in
      *" $yw "*) ;;
      *)
        seen_weeks="$seen_weeks $yw"
        keep+=("${files[$i]}")
        week_count=$((week_count + 1))
        ;;
    esac
  done

  # Monthly: same idea, one per year-month, up to keep_monthly months.
  for ((i = keep_daily; i < ${#files[@]}; i++)); do
    [[ $month_count -ge $keep_monthly ]] && break
    local ym
    ym="$(_rotate_backups_date_part "${files[$i]}")" || continue
    ym="${ym:0:6}"
    case " $seen_months " in
      *" $ym "*) ;;
      *)
        seen_months="$seen_months $ym"
        keep+=("${files[$i]}")
        month_count=$((month_count + 1))
        ;;
    esac
  done

  local f keep_str=" "
  for f in "${keep[@]}"; do
    keep_str="$keep_str$f "
  done

  for f in "${files[@]}"; do
    case "$keep_str" in
      *" $f "*) ;;
      *)
        echo "Pruning old backup: $f"
        rm -f "$f"
        ;;
    esac
  done
}

# Extracts the "YYYYmmdd" part of a "<prefix>-YYYYmmdd-HHMMSS.<ext>"
# filename. Returns non-zero (and prints nothing) if the filename doesn't
# match, so callers can safely `continue` past a file that isn't one of
# ours rather than misclassifying it.
_rotate_backups_date_part() {
  local base
  base="$(basename "$1")"
  if [[ "$base" =~ ([0-9]{8})-[0-9]{6} ]]; then
    echo "${BASH_REMATCH[1]}"
    return 0
  fi
  return 1
}

# Cross-platform (GNU/BSD) ISO year-week for a file's embedded date.
_rotate_backups_year_week() {
  local date_part
  date_part="$(_rotate_backups_date_part "$1")" || return 1
  date -d "$date_part" +%G-%V 2>/dev/null || date -j -f '%Y%m%d' "$date_part" +%G-%V 2>/dev/null
}
