#!/bin/bash

# omarchy:summary=Retry a system package update that hit a conflict
# omarchy:hidden=true

# Internal to omarchy-update-system-pkgs. Not a command to run by hand: it acts
# on a pacman error report, and an old or hand-written one would move live files
# out of the way for an upgrade that is not happening.
#
# Paths written by a script rather than installed by a package -- an earlier
# release's installer, an in-place edit -- belong to nobody, and pacman refuses
# to install over a file it doesn't own. This clears them and runs the upgrade
# again.
#
# Packages that conflict with each other are the other blocked upgrade, and that
# one is a decision rather than a cleanup, so it goes back to the person
# watching the update.

set -e

[[ ${OMARCHY_UPDATE_CONFLICT:-} == 1 ]] || {
  echo "omarchy-update-system-pkgs-when-conflicted runs as part of omarchy update" >&2
  exit 1
}

errors=${1:?usage: omarchy-update-system-pkgs-when-conflicted <pacman-error-report>}

# Where a file pacman is taking over gets moved, mirroring its full path. Kept
# out of the directory it came from: SDDM reads every file in sddm.conf.d
# whatever its extension, and systemd-sleep runs every executable in
# system-sleep, so a copy left beside the original would still be live.
replaced=${OMARCHY_REPLACED_DIR:-/var/lib/omarchy/replaced}

# Anything moved that the upgrade didn't end up installing goes back, whether
# the retry failed or the move loop itself was interrupted partway. Nothing here
# should leave configuration inactive that was live when it started.
#
# -e follows symlinks, so a dangling one reads as absent. -L catches those: a
# link pacman installed, and a relative link that no longer resolves from inside
# the quarantine.
moved=()
restore_moved() {
  local path restorable=()

  for path in "${moved[@]}"; do
    if [[ ! -e $path && ! -L $path ]] && [[ -e $replaced$path || -L $replaced$path ]]; then
      restorable+=("$path")
    fi
  done
  ((${#restorable[@]})) || return 0

  echo -e "\e[33m\nPutting back what the upgrade didn't take:\e[0m"
  for path in "${restorable[@]}"; do
    sudo mv -T "$replaced$path" "$path"
    echo "  $path"
  done
}

# exec'd into, so the caller's EXIT trap never ran and the report is still here.
# Cleaning it up is this script's job from now on.
trap 'restore_moved; rm -f "$errors"' EXIT
trap 'exit 1' INT TERM

# Pacman answers its own conflict question with No under --noconfirm, so one
# retired package can stop every upgrade after it. Which package to drop is a
# decision -- an upstream split retires one, but so does a package the user
# picked on purpose -- so put the question to whoever started the update rather
# than guess at it. Pacman printed what conflicts just above.
if grep -q 'unresolvable package conflicts detected' "$errors"; then
  # -y promised not to ask anything, and without a terminal there is nowhere to
  # ask; either way pacman would sit on a prompt nobody answers. An upgrade that
  # isn't running --noconfirm puts its questions on stderr and reads the answers
  # from stdin, so those are the two that have to be a terminal.
  if [[ ${OMARCHY_UPDATE_UNATTENDED:-} == 1 || ! -t 0 || ! -t 2 ]]; then
    echo -e "\e[33m\nThis upgrade needs an answer. Run omarchy update interactively to give it.\e[0m" >&2
    exit 1
  fi

  echo -e "\e[33m\nA package conflict stopped this upgrade. Running it again so you can answer:\e[0m"
  OMARCHY_UPDATE_INTERACTIVE=1 omarchy-update-system-pkgs
  exit 0
fi

# Paths one of these packages installs that pacman doesn't own. Moving them out
# of the way is what lets the upgrade through, and works on a leftover directory
# as well as a file.
#
# "pkg: /path exists in filesystem" is unowned; the same line plus "(owned by
# x)" is not, so the end anchor takes only ours. -Qo re-checks the live database
# before anything moves.
mapfile -t leftovers < <(
  grep -oP '^omarchy(-dev|-settings|-settings-dev)?: \K.+(?= exists in filesystem$)' "$errors" |
    while IFS= read -r path; do pacman -Qo "$path" &>/dev/null || echo "$path"; done
)
((${#leftovers[@]})) || exit 1

# All of them or none: moving a subset leaves the retry blocked by the rest, and
# the moved files inactive for nothing.
((${#leftovers[@]} == $(grep -c ' exists in filesystem' "$errors"))) || exit 1

echo -e "\e[33m\nTaking over files pacman doesn't own yet:\e[0m"
for path in "${leftovers[@]}"; do
  sudo mkdir -p "$replaced${path%/*}"
  # -T so an existing directory at the destination is replaced, not moved into.
  sudo mv -T --backup=numbered "$path" "$replaced$path"
  moved+=("$path")
  echo "  $path -> $replaced$path"
done
echo

if OMARCHY_UPDATE_RETRY=1 omarchy-update-system-pkgs; then
  moved=()
  exit 0
fi
exit 1
