#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

# --- Local configuration -----------------------------------------------------
# The Proton save and profile are the primary state and are always required.
#
# Steam remote copies are optional. They live under a per-account userdata
# directory, so STEAM_USER_ID has no default: leaving it unset skips the
# remote copy rather than guessing at a directory that belongs to someone
# else.
#
# To include Steam remote data:
#   STEAM_USER_ID=123456789 ./bg3-honour-backup.sh
#
# Find your numeric ID by listing ~/.local/share/Steam/userdata/.
# BG3_BACKUP_ROOT is optional and overrides where backups are written.
# The Proton save path is account-independent and needs no configuration.
STEAM_USER_ID="${STEAM_USER_ID:-}"
BACKUP_ROOT="${BG3_BACKUP_ROOT:-$HOME/Documents/misc/bg3-backups}"

PROTON_STORY="$HOME/.local/share/Steam/steamapps/compatdata/1086940/pfx/drive_c/users/steamuser/AppData/Local/Larian Studios/Baldur's Gate 3/PlayerProfiles/Public/Savegames/Story"
PROTON_PUBLIC="$(dirname "$(dirname "$PROTON_STORY")")"
PROTON_PROFILE="$PROTON_PUBLIC/profile8.lsf"

# The Steam remote paths are derived only when an ID is configured, so an
# unset value can never produce a path that looks real. Empty here means
# Steam remote collection is off for this run.
if [[ -n "$STEAM_USER_ID" ]]; then
  STEAM_REMOTE_ENABLED="yes"
  STEAM_REMOTE_STORY="$HOME/.local/share/Steam/userdata/$STEAM_USER_ID/1086940/remote/_SAVE_Public/Savegames/Story"
  STEAM_REMOTE_SAVE_PUBLIC="$(dirname "$(dirname "$STEAM_REMOTE_STORY")")"
  STEAM_REMOTE_PROFILE_PUBLIC="$HOME/.local/share/Steam/userdata/$STEAM_USER_ID/1086940/remote/_PROFILE_Public"
  STEAM_REMOTE_PROFILE="$STEAM_REMOTE_PROFILE_PUBLIC/profile8.lsf"
else
  STEAM_REMOTE_ENABLED="no"
  STEAM_REMOTE_STORY=""
  STEAM_REMOTE_SAVE_PUBLIC=""
  STEAM_REMOTE_PROFILE_PUBLIC=""
  STEAM_REMOTE_PROFILE=""
fi

TEMP_DIR=""

usage() {
  cat <<USAGE
Usage: $(basename "$0") [--help]

Creates a timestamped backup of the most recently updated Honour Mode save,
including profile8.lsf so the backup can also be used after a party wipe.
The backup is written under:
  $BACKUP_ROOT

Environment:
  STEAM_USER_ID    optional; your numeric Steam userdata directory, as listed
                   under ~/.local/share/Steam/userdata/. When set, the Steam
                   remote save and profile are collected as well. When unset,
                   they are skipped and the Proton backup is still made.
  BG3_BACKUP_ROOT  optional; defaults to $BACKUP_ROOT

Safety behavior:
  - Refuses to run if BG3/Steam Play helper processes look active.
  - Refuses to run if it cannot find both HonourMode.lsv and Proton profile8.lsf.
  - Writes into a temporary directory first, then renames atomically.
USAGE
}

cleanup() {
  if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
    rm -rf -- "$TEMP_DIR"
  fi
}
trap cleanup ERR INT TERM

is_game_running() {
  # Matches process NAMES, not command lines.
  #
  # The published script names contain "bg3", so a `pgrep -f` match (which
  # tests the whole command line) flags the running script itself and the
  # guard can never pass. Process names cannot self-match: a shell running
  # this script is named "bash", whatever the script file is called.
  #
  # Names are truncated to 15 characters by the kernel, so every pattern here
  # is short enough to be matchable. wine/wineserver are kept as the broad
  # safety net — any Wine runtime in use means the game may be running.
  pgrep -x 'bg3|bg3\.exe|bg3_dx11\.exe|wine|wine64|wine-preloader|wine64-preload|wineserver|proton|pressure-vessel' >/dev/null 2>&1
}

latest_honour_dir() {
  local best_dir=""
  local best_mtime=""
  local dir lsv mtime

  while IFS= read -r -d '' dir; do
    lsv="$dir/HonourMode.lsv"
    [[ -f "$lsv" ]] || continue
    mtime=$(stat -c '%Y' "$lsv")
    if [[ -z "$best_mtime" || "$mtime" -gt "$best_mtime" ]]; then
      best_mtime="$mtime"
      best_dir="$dir"
    fi
  done < <(find "$PROTON_STORY" -mindepth 1 -maxdepth 1 -type d -name '*__HonourMode' -print0)

  [[ -n "$best_dir" ]] || return 1
  printf '%s\n' "$best_dir"
}

unique_backup_dir() {
  local base="$1"
  local candidate="$BACKUP_ROOT/$base"
  local n=2
  while [[ -e "$candidate" ]]; do
    candidate="$BACKUP_ROOT/${base}__dup${n}"
    ((n++))
  done
  printf '%s\n' "$candidate"
}

main() {
  [[ "${1:-}" == "--help" ]] && { usage; exit 0; }
  [[ $# -eq 0 ]] || { usage; exit 1; }

  [[ -d "$PROTON_STORY" ]] || { echo "ERROR: Proton save directory not found: $PROTON_STORY" >&2; exit 1; }
  [[ -f "$PROTON_PROFILE" ]] || { echo "ERROR: Proton profile file not found: $PROTON_PROFILE" >&2; exit 1; }
  mkdir -p "$BACKUP_ROOT"

  if is_game_running; then
    cat >&2 <<ERR
ERROR: BG3 or related Wine/Proton processes appear to be running.
For a safe backup, close the game completely and run this script again.
ERR
    exit 1
  fi

  local run_dir run_name run_lsv run_mtime iso_stamp backup_id final_dir remote_run_dir
  local proton_profile_mtime remote_run_state remote_profile_state

  run_dir=$(latest_honour_dir)
  run_name=$(basename "$run_dir")
  run_lsv="$run_dir/HonourMode.lsv"
  run_mtime=$(stat -c '%Y' "$run_lsv")
  proton_profile_mtime=$(stat -c '%Y' "$PROTON_PROFILE")
  iso_stamp=$(date -d "@$run_mtime" '+%Y%m%d-%H%M%S')
  backup_id="${iso_stamp}__${run_name}"
  final_dir=$(unique_backup_dir "$backup_id")
  TEMP_DIR=$(mktemp -d "$BACKUP_ROOT/.${backup_id}.tmp.XXXXXX")

  mkdir -p "$TEMP_DIR/proton" "$TEMP_DIR/steam_remote"

  cp -a "$run_dir" "$TEMP_DIR/proton/"
  cp -a "$PROTON_PROFILE" "$TEMP_DIR/proton/"

  # Steam remote collection is entirely optional: without a configured ID the
  # Proton copies above are the whole backup, and the states below say so.
  if [[ "$STEAM_REMOTE_ENABLED" == yes ]]; then
    remote_run_dir="$STEAM_REMOTE_STORY/$run_name"

    if [[ -d "$remote_run_dir" ]]; then
      cp -a "$remote_run_dir" "$TEMP_DIR/steam_remote/"
      remote_run_state="present"
    else
      remote_run_state="missing"
    fi

    if [[ -f "$STEAM_REMOTE_PROFILE" ]]; then
      cp -a "$STEAM_REMOTE_PROFILE" "$TEMP_DIR/steam_remote/"
      remote_profile_state="present"
    else
      remote_profile_state="missing"
    fi
  else
    remote_run_state="skipped"
    remote_profile_state="skipped"
  fi

  [[ -f "$TEMP_DIR/proton/$run_name/HonourMode.lsv" ]] || {
    echo "ERROR: Backup verification failed: proton save copy is missing HonourMode.lsv" >&2
    exit 1
  }
  [[ -f "$TEMP_DIR/proton/profile8.lsf" ]] || {
    echo "ERROR: Backup verification failed: proton profile copy is missing profile8.lsf" >&2
    exit 1
  }

  cat > "$TEMP_DIR/metadata.txt" <<META
backup_created_at=$(date --iso-8601=seconds)
run_name=$run_name
proton_story=$PROTON_STORY
proton_public=$PROTON_PUBLIC
steam_remote_story=$STEAM_REMOTE_STORY
steam_remote_save_public=$STEAM_REMOTE_SAVE_PUBLIC
steam_remote_profile_public=$STEAM_REMOTE_PROFILE_PUBLIC
source_honour_lsv=$run_lsv
source_honour_lsv_mtime_epoch=$run_mtime
source_honour_lsv_mtime_iso=$(date -d "@$run_mtime" --iso-8601=seconds)
source_proton_profile=$PROTON_PROFILE
source_proton_profile_mtime_epoch=$proton_profile_mtime
source_proton_profile_mtime_iso=$(date -d "@$proton_profile_mtime" --iso-8601=seconds)
steam_remote_run_copy=$remote_run_state
steam_remote_profile_copy=$remote_profile_state
steam_remote_configured=$STEAM_REMOTE_ENABLED
META

  mv -- "$TEMP_DIR" "$final_dir"
  TEMP_DIR=""
  trap - ERR INT TERM

  cat <<DONE
Backup created successfully.
Backup folder:
  $final_dir

Saved run:
  $run_name

Included:
  - proton save directory
  - proton profile8.lsf
  - steam remote save directory ($remote_run_state)
  - steam remote profile8.lsf ($remote_profile_state)
DONE

  if [[ "$STEAM_REMOTE_ENABLED" != yes ]]; then
    cat <<NOTE

Steam remote copies were skipped because STEAM_USER_ID is not set.
To include them in the next backup:
  STEAM_USER_ID=123456789 $(basename "$0")
NOTE
  fi
}

main "$@"
