#!/bin/bash

# Package updater for EndeavourOS and Arch.
# Handles db lock file, keyrings, syncing, optionally AUR with given AUR helper, and more.

echo2()   { echo -e "$@" >&2 ; }
printf2() {
    # shellcheck disable=2059
    printf "$@" >&2
}

INFO()    { echo2info    "==> $progname: info: $1"; }
WARN()    { echo2warning "==> $progname: warning: $1"; }
DIE()     { echo2error   "==> $progname: error: $1"; exit 1;  }

UseColor() { [ "$use_colors" = yes ] && eos-color "$@" 2 ; }

echo2error()   { UseColor error;   echo2 "$@"; UseColor reset ; }
echo2info()    { UseColor info;    echo2 "$@"; UseColor reset ; }
echo2warning() { UseColor warning; echo2 "$@"; UseColor reset ; }
echo2tip()     { UseColor tip;     echo2 "$@"; UseColor reset ; }

SetHelper() {
    for helper in "$@" "$EOS_AUR_HELPER" "$EOS_AUR_HELPER_OTHER" yay paru pacman ; do
        which "${helper%% *}" &>/dev/null && return 0
    done
    WARN "AUR helper not found"
    helper=pacman
}

CmdShow() {
    echo2info "==>" "$@"
    "$@"
}
TRY() {                           # Show a command, run it, and exit on issues.
    CmdShow "$@" || DIE "'$*' failed."
}

MirrorCheckResult() {
    local ml="$1"
    local ret=0

    echo2 "==> $ml contains:"
    echo2 "    available mirrors:   ${#supported[@]}"
    echo2 "    unavailable mirrors: ${#unsupported[@]}"
    [ ${#unsupported[@]} -gt 0 ] && printf "%s\n" "${unsupported[@]}" | sed 's|^[0-9]*:|      -> Server = |' >&2

    if [ ${#unsupported[@]} -eq 0 ] && [ ${#supported[@]} -gt 0 ] ; then     # create the return code, 0=success, 1=fail
        return 0
    else
        if [ ${#unsupported[@]} -gt 0 ] ; then
            EditMirrorlist "$ml" "${unsupported[0]%%:*}"   # second parameter is the line number
        else
            EditMirrorlist "$ml"
        fi
        return 1
    fi
}

Hardware-x86_64() { [ "$(eos_GetArch)" = x86_64 ] || DIE "sorry, this implementation supports only x86_64 environment"; }

CheckYourArchMirrorlist() {
    Hardware-x86_64
    local url="https://archlinux.org/mirrorlist/?use_mirror_status=on"
    local lm
    local all_working_mirrors
    local local_mirrors
    local supported=()
    local unsupported=()

    # get working mirrors from the Arch mirrorlist page
    all_working_mirrors=$(curl --fail -Lsm 30 "$url") || DIE "failed to fetch available mirrors (curl code $?)"
    [ "$all_working_mirrors" ] || DIE "-> archlinux.org/mirrorlist failed"
    readarray -t all_working_mirrors < <(echo "$all_working_mirrors" | tail -n +7 | grep "^#Server = " | awk '{print $NF}')

    # get mirrors from local mirrorlist
    readarray -t local_mirrors < <(grep -n "^[ \t]*Server[ \t]*=[ \t]*" $aml | sed 's|[ \t]*Server[ \t]*=[ \t]*||')

    for lm in "${local_mirrors[@]}" ; do
        if printf "%s\n" "${all_working_mirrors[@]}" | grep "${lm#*:}" >/dev/null ; then
            supported+=("${lm#*:}")
        else
            unsupported+=("$lm")   # includes the line number: "nr:https://..."
        fi
    done
    MirrorCheckResult $aml
}

CheckYourEndeavourosMirrorlist() {
    Hardware-x86_64
    local -r mirrors=(  # for downloading the latest endeavouros-mirrorlist package
        https://mirror.alpix.eu/endeavouros/repo/endeavouros/x86_64     # Germany
        https://mirror.moson.org/endeavouros/repo/endeavouros/x86_64    # Germany
        https://mirrors.gigenet.com/endeavouros/repo/endeavouros/x86_64 # United States
    )
    local url data
    local -r tmpfile=/tmp/eos-ml.p
    local good_mirrors local_mirrors
    local lm
    local unsupported=() supported=()

    for url in "${mirrors[@]}" ; do
        data=$(curl -Lsm 30 -o- "$url") || continue
        if [ "$data" ] ; then
            data=$(echo "$data" | grep "endeavouros-mirrorlist.*\.sig")   # the line that contains the pkg file name
            data=${data#*\"}                                              # skip prefix
            data=${data%%\.sig*}                                          # skip suffix
            if [ "$data" ] ; then
                unsupported=()
                supported=()

                url+="/$data"
                curl -Lsm 30 -o"$tmpfile" "$url"

                good_mirrors=$(tar xvf $tmpfile ${eml:1} -O 2>/dev/null | grep "^Server = " | sed 's|^Server = ||')
                rm -f $tmpfile
                local_mirrors=$(grep -n "^[ \t]*Server[ \t]*=[ \t]*" $eml | sed 's|[ \t]*Server[ \t]*=[ \t]*||')

                for lm in $local_mirrors ; do
                    if echo "$good_mirrors" | grep -q "${lm#*:}" ; then
                        supported+=("${lm#*:}")
                    else
                        unsupported+=("$lm")  # includes the line number: "nr:https://..."
                    fi
                done
                MirrorCheckResult $eml
                return
            fi
            
        fi
    done
    [ "$data" ] || DIE "sorry, cannot fetch the latest EndeavourOS mirrorlist data"
}

EditMirrorlist() {
    local list="$1"      # arch, endeavouros, or the full path of the mirrorlist
    local linenr="$2"    # for the editor; may be empty
    local editor=/usr/bin/rnano
    [ -x $editor ] || { WARN "package 'nano' is required for editing $list"; return 1; }

    case "$list" in
        arch | "$aml")        list="$aml" ;;
        endeavouros | "$eml") list="$eml" ;;
        "") DIE "supported mirrorlists: arch, endeavouros" ;;
    esac
    if [ "$linenr" ] ; then
        if [ -z "${linenr//[0-9]/}" ] ; then
            linenr="+$linenr"
        else
            WARN "detected line number '$linenr' in $list is flawed!"
            linenr=""
        fi
    fi
    RunInTerminal "echo '==> Editing $list with $editor:'; sudo $editor $linenr $list"
}

ResetKeyrings() {
    TRY sudo mv /etc/pacman.d/gnupg /root/pacman-key.bak."$(date +%Y%m%d-%H%M)".by."$progname"
    TRY sudo pacman-key --init
    TRY sudo pacman-key --populate archlinux endeavouros
    TRY sudo pacman -Syy --noconfirm archlinux-keyring endeavouros-keyring
    TRY sudo pacman -Syu
    echo2 "Keyrings reset."
    exit 0
}

ClearDatabases() {
    TRY sudo rm -r /var/lib/pacman/sync
    echo2 "Package databases cleared."
    exit 0
}
CheckPackageDatabases() {
    local -r dir=/var/lib/pacman/sync
    local -r out="$(file $dir/*.{db,files} | grep -Pv "(gzip|Zstandard|XZ) compressed data")"
    if [ "$out" ] ; then
        echo2error "$out"
        echo2 "==> one or more files $dir/*.{db,files} corrupted.\n    '$progname --clear-databases' may fix it."
        exit 1
    fi
    echo2 "==> package database: success, compressed data as expected."
    exit 0
}

CheckReposPacmanConf() {
    local problem_repos=(   # these should be removed
        community
        community-testing
        testing
        testing-debug
        staging
        staging-debug
    )
    local needed_repos=(    # these should always be included for EndeavourOS
        endeavouros
        core
        extra
    )
    local toberemoved=() tobeadded=()
    local -r file=$pacmanconf
    local stop=no

    for item in "${problem_repos[@]}" ; do
        grep "^\[$item\]" "$file" >/dev/null && toberemoved+=("[$item]")
    done
    for item in "${needed_repos[@]}" ; do
        grep "^\[$item\]" "$file" >/dev/null || tobeadded+=("[$item]")
    done
    if [ "${tobeadded[0]}" ] ; then
        echo2error "==> $progname: error: the following repo definitions must be added to $file:"
        printf2  "    %s\n" "${tobeadded[@]}"
        stop=yes
    fi
    if [ "${toberemoved[0]}" ] ; then
        echo2error "==> $progname: error: the following repo definitions must be removed from $file:"
        printf2  "    %s\n" "${toberemoved[@]}"
        stop=yes
    fi
    [ $stop = yes ] && exit 1
}

FaillockCheck() {
    if [ $faillockcheck = yes ] ; then
        if [ "$(LANG=C faillock --user "$LOGNAME" | grep -EA1 "^When[ ]+Type[ ]+Source" | wc -l)" -gt 1 ] ; then
            sleep 1
            # shellcheck disable=2162
            read -p "Info: elevating privileges is locked, press ENTER to unlock, or Ctrl-C to quit: " >&2
            case "$?" in
                0) faillock --user "$LOGNAME" --reset ; sleep 2 ;;
                *) exit 0 ;;
            esac
        fi
    fi
}

PacCache() {
    case "$paccache_limit" in
        1|2|3|4|5)
            # pacman 7.1.0.r7.gb9f7d4a-1 started creating subfolders named 'download-*', so remove them
            local leftover_dirs=()
            readarray -t leftover_dirs < <(/bin/sudo /bin/find "${pkg_cache_dir%/}" -type d -empty -name 'download-*')
            if [ "${leftover_dirs[0]}" ] ; then
                UseColor warning 2
                echo2 "==> Removing empty folders created by pacman:"
                /bin/ls -ld "${leftover_dirs[@]}" | sed 's|^|    |'
                sudo rmdir "${leftover_dirs[@]}"
                UseColor reset 2
            fi

            echo2info "==> paccache -rk$paccache_limit"
            /usr/bin/paccache --verbose -rk"$paccache_limit"

            local amount
            amount=$(/bin/du -hd1 "$pkg_cache_dir" | awk '{print $1}')
            UseColor info 2
            printf2 "==> Disk space used in the package cache: %s [%s]\n" "$amount" "${pkg_cache_dir%/}"
            UseColor reset 2
            ;;
    esac
}

ResetPacmanConf() {
    local -r pacmanconf_url="https://raw.githubusercontent.com/endeavouros-team/EndeavourOS-ISO/refs/heads/main/airootfs/etc/pacman.conf"
    local -r tmpfile=$(mktemp)
    wget -qO "$tmpfile" "$pacmanconf_url" || DIE "cannot fetch $pacmanconf_url"
    local -r time=$(date +%Y%m%d-%H%M%S)
    sudo mkdir -p /etc/.EOS_BACKUPDIR
    sudo mv $pacmanconf /etc/.EOS_BACKUPDIR/${pacmanconf##*/}."$time"
    sudo gzip "/etc/.EOS_BACKUPDIR/${pacmanconf##*/}.$time"
    sudo cp "$tmpfile" "$pacmanconf"
    rm -f "$tmpfile"
}

RankWithUserCommand() {
    if [ -x $rank_command_file ] ; then
        $rank_command_file
    elif [ -r $rank_command_file ] && [[ "$(file $rank_command_file)" =~ "Bourne-Again shell script" ]] ; then
        bash $rank_command_file
    else
        [ -e $rank_command_file ] || DIE "$rank_command_file does not exist"
        WARN "unable to execute bash script $rank_command_file"
    fi
}

YourFavPkgs() {
    echo2info "==> Handling your favorite packages."

    local favsfile=/etc/${progname}-favorites.conf     # alternatively an url?
    if [ ! -r "$favsfile" ] ; then
        echo2warning "sorry, file '$favsfile' is not readable"
        exit 1
    fi

    # shellcheck disable=1090
    source "$favsfile"
    local cmd=""
    if [ "$FAVORITES_UNINSTALL" ] ; then
        local to_uninstall
        to_uninstall=$(/bin/expac %n "${FAVORITES_UNINSTALL[@]}")
        if [ "$to_uninstall" ] ; then
            cmd="sudo pacman -R $to_uninstall"
            echo2info "==> Running command: $cmd"
            $cmd
        else
            echo2info "==> Nothing to uninstall."
        fi
    else
        echo2warning "==> FAVORITES_UNINSTALL is empty in $favsfile"
    fi
    if [ "$FAVORITES_INSTALL" ] ; then
        local already_installed to_install
        already_installed=$(/bin/expac %n "${FAVORITES_INSTALL[@]}")
        to_install=$(printf "%s\n" "${FAVORITES_INSTALL[@]}" | grep -vf <(printf "%s\n" "${already_installed[@]}"))
        if [ "$to_install" ] ; then
            cmd="sudo pacman -Syu --needed $to_install"
            echo2info "==> Running command: $cmd"
            $cmd
        else
            echo2info "==> Nothing to install."
        fi
    else
        echo2warning "==> FAVORITES_INSTALL is empty in $favsfile"
    fi
    exit 0
}

DeprecatedOption() {
    local -r option="$1"
    local date_depr date_now
    date_depr="$(/bin/date --date="${DeprecationDates[$option]}" +%s)"
    date_now="$(/bin/date +%s)"

    if [ "$date_now" -lt "$date_depr" ] ; then
        WARN "option $option is deprecated and will be removed at ${DeprecationDates[$option]}."
        return 0
    else
        WARN "option $option has been removed at ${DeprecationDates[$option]}."
        return 1
    fi
}

SingleOptions() {
    local ret=0
    while [ "$1" ] ; do
        case "$1" in
            --reset-pacmanconf)                  ResetPacmanConf
                                                 exit 0
                                                 ;;
            --rank-mirrors)                      DeprecatedOption "$1" && RankWithUserCommand
                                                 exit
                                                 ;;
            --check-mirrors)                     CheckYourEndeavourosMirrorlist || ((ret++))
                                                 CheckYourArchMirrorlist        || ((ret++))
                                                 exit $ret
                                                 ;;
            --check-mirrors-arch)                CheckYourArchMirrorlist || ((ret++))
                                                 exit $ret
                                                 ;;
            --check-mirrors-eos)                 CheckYourEndeavourosMirrorlist || ((ret++))
                                                 exit $ret
                                                 ;;
            --edit-mirrorlist)                   EditMirrorlist "$2"
                                                 exit 0
                                                 ;;
            --tips)                              xdg-open "/usr/share/doc/$progname/${progname}-tips.html"
                                                 exit 0
                                                 ;;
            --show-only-fixed)                   which arch-audit >/dev/null && arch-audit -u
                                                 exit
                                                 ;;
            --show-upstream-news)                local br
                                                 br=$(eos_select_browser)
                                                 [ "$br" ] && $br https://archlinux.org/news
                                                 exit
                                                 ;;
            --dump-options)                      lopts="${lopts//:/}"
                                                 lopts="--${lopts//,/ --}"
                                                 sopts="${sopts//:/}"
                                                 sopts="$(echo "$sopts" | sed -E 's|([a-z])| -\1|g')"
                                                 echo "$lopts $sopts"
                                                 exit 0
                                                 ;;
            --conf-filename)                     if [ -e "$eosupdateconf" ] ; then
                                                     echo "$eosupdateconf"
                                                     exit 0
                                                 else
                                                     INFO "file $eosupdateconf does not exist"
                                                     exit 1
                                                 fi
                                                 ;;
            --other-updates)                     other_updates=yes ;;   # does not exit and it is OK
            --colors)                            use_colors="$2"
                                                 case "$use_colors" in
                                                     yes | no) ;;
                                                     *) DIE "value '$use_colors' for option $1 is not supported; use 'yes' or 'no'" ;;
                                                 esac
                                                 FillColorFile
                                                 shift
                                                 ;;
            -h | --help)                         Usage 0 ;;
        esac
        shift
    done
}

FillColorFile() {
    echo "_EOS_UPDATE_USE_COLORS=$use_colors" > "$colorfile"   # used by eos-update-lib.bash
}

Options() {
    local tmp

    tmp="$(LANG=C /usr/bin/getopt -o=$sopts --longoptions $lopts --name "$progname" -- "$@" 2>&1)" || {
        # First, fix a getopt bug about missing argument for short options.
        tmp="$(echo "$tmp" | head -n1)"
        case "$tmp" in
            "$progname: option requires an argument -- '"[a-z]"'")
                tmp=$(echo "$tmp" | sed -E "s|.*'([a-z])'.*|\1|")
                tmp="$progname: option '-$tmp' requires an argument"
                ;;
        esac
        echo2error  "$tmp\n"
        if false ; then
            UseColor warning
            Usage
            UseColor reset
        else
            echo2tip "==> more info with command: $progname --help"
        fi
        exit 1
    }
    eval set -- "$tmp"

    local ret=0
    while true ; do
        case "$1" in
            --reset-pacmanconf|--check-mirrors|--check-mirrors-arch|--check-mirrors-eos) ;;        # handled in SingleOptions
            --edit-mirrorlist|--show-only-fixed|--show-upstream-news|--dump-options|-h|--help) ;;  # handled in SingleOptions
            --other-updates|--conf-filename|--rank-mirrors) ;;                                     # already in SingleOptions
            --colors)                            shift ;;                                          # already in SingleOptions

            --fast)                              eufast=yes ;;
            --nvidia)                            nvidia=yes ;;
            --no-keyring)                        keyring=no ;;
            --descriptions | -d)                 descriptions=yes ;;
            --faillock-check)                    faillockcheck=yes ;;
            --no-sync)                           sync=":" ;;
            --keyrings-reset)                    ResetKeyrings ;;
            --clear-databases)                   ClearDatabases ;;
            --check-databases)                   CheckPackageDatabases ;;
            --run-mode)                          mode="$2"
                                                 case "$mode" in
                                                     plain|root|login) ;;
                                                     *) DIE "unsupported value '$mode' for option $1" ;;
                                                 esac
                                                 shift
                                                 ;;
            --min-free-bytes)                    min_free_bytes="$2" ; shift ;;
            --helper)                            SetHelper "$2" ; shift ;;
            --aur)                               SetHelper; eufast_opt+=("$1") ;;
            --paru | --yay | --pacman)           SetHelper "${1/--/}" ;;
            --pacdiff)                           DeprecatedOption "$1" && pacdiffer="$pacdiffer_cmd_default" ;;
            --cache-limit | -l)                  paccache_limit="$2"
                                                 case "$paccache_limit" in
                                                     1|2|3|4|5) ;;
                                                     *) DIE "value '$paccache_limit' for option $1 is not supported; use numbers 1 to 5" ;;
                                                 esac
                                                 shift
                                                 ;;
            
            --) shift ; break ;;
        esac
        shift
    done
}

Usage() {
    cat <<EOF >&2
$progname is a package updater for EndeavourOS and Arch.

$progname is implemented as a wrapper around commands pacman and optionally yay/paru.
Essentially runs commands 'pacman -Syu' and optionally 'yay -Sua' or 'paru -Sua'.

$progname includes (by default) special help in the following situations:
- Prevents reboot, shutdown, hibernate, and suspend while $progname is running.
- A dangling pacman db lock file (/var/lib/pacman/db.lck).
- Disk space availability for updates (with a configurable minimum space).
- Keyring package updating before updating other packages.
- Running the 'sync' command after update.

Optional help:
- Can clear package databases in case of constant problems with them.
- Can reset keyrings in case of constant problems with them.
- Can check the validity of the locally configured lists of mirrors.
- Updates AUR packages (with option --helper, see Usage below).
- Ad hoc check for Nvidia GPU driver vs. kernel updates (non-dkms only).
- Provides a way for users to include additional commands before and after $progname does its essentials.
and more, see below.

Usage: $progname [options]
Options:
  --help, -h              This help.
  --tips                  Tips and examples for $progname.
  --rank-mirrors          (Deprecated, see --other-updates. Will be removed at ${DeprecationDates[--rank-mirrors]}).
                          Executes user-written bash script $rank_command_file which may contain
                          ranking commands for both Arch and EndeavourOS mirrors, and exits.
                          Example commands:
                              reflector -phttps -c "your-country-spec" | sudo tee $aml
                              eos-rankmirrors --timeout 4
                          See 'reflector --list-countries' to see supported country codes and names
                          for "your-country-spec".
    
  --check-mirrors         Check files $eml and $aml
                          for unsupported mirrors.
                          This may be useful when one or more mirrors start failing unexpectedly.
                          Note: only x86_64 hardware is supported.
  --check-mirrors-eos     Check file $eml for unsupported mirrors.
  --check-mirrors-arch    Check file $aml for unsupported mirrors.
  --edit-mirrorlist <val> Edit chosen mirrorlist file. Supported values: arch, endeavouros. No default.
  --reset-pacmanconf      Replace your current $pacmanconf with the EndeavourOS original.
                          Note: do this only if you need to restore the original file.
  --fast                  Do a *fast check* for updates in order to reduce the amount of network traffic.
                          Note that the fast check
                             * applies to only updates on EndeavourOS and Arch repos, and optionally on AUR
                             * in special cases may not detect available updates; if so, run
                               $progname without option --fast
  --descriptions, -d      Show the descriptions of the packages to be updated. Note: AUR not supported.
  --nvidia                Check also nvidia driver vs. kernel updates. Useful only with the Nvidia GPU.
  --check-databases       Check for the package database being corrupted or not.
  --clear-databases       Clears package database files.
                          Use this only if package database issues constantly make system update fail.
  --keyrings-reset        Resets Arch and EndeavourOS keyrings.
                          Use this only if keyring issues constantly make system update fail.
  --no-keyring            Do not try to update keyrings first.
  --no-sync               Do not run 'sync' after update.
  --keep-system-running   Protects your system from unintentional sleep, hibernate, reboot, or shutdown
                          if '$progname' is still running. This is the default.
  --no-keep-system-running
                          The opposite of --keep-system-running.
                          Allows (unintentional) sleep, hibernate, reboot, or shutdown to crash
                          the update process, which obviously can create severe system stability issues.
                          Don't use this unwittingly.
  --run-mode <val>        Specifies the mode how the updating commands will be executed.
                          Supported values:
                             plain   Use current privileges. Uses 'sudo' for commands that need it.
                                     If $progname was executed as root, mode changes from plain to login.
                             root    Use elevated privileges. Impersonate $LOGNAME for the AUR helper.
                             login   Use elevated privileges. Login as $LOGNAME for the AUR helper.
                          Default: plain.
  --show-only-fixed       Show only packages that have already been fixed (runs: arch-audit -u) and exit.
  --show-upstream-news    Show the news page of the upstream site and exit.
  --pacdiff               (Deprecated, see --other-updates. Will be removed at ${DeprecationDates[--pacdiff]}).
                          Run eos-pacdiff in the end of $progname. See also EOS_UPDATE_PACDIFFER
  			  in file /etc/eos-script-lib-yad.conf.
  --cache-limit, -l <val> Runs 'paccache -rk <val>' after updates. Limits the size of the package cache.
                          Supported numbers for <val>: 1..5.
  --colors <val>          Enable or disable using additional colors in output. By default they are enabled.
                          Supported values: "yes" or "no".
  --helper <val>          AUR helper name. Supported values: yay, paru, pacman.
                          Default: pacman
                          Other AUR helpers supporting option -Sua like yay should work as well.
  --paru                  Same as --helper=paru.
  --yay                   Same as --helper=yay.
  --aur                   Uses the AUR helper configured in /etc/eos-script-lib-yad.conf.
  --pacman                Same as --helper=pacman. Default. (Note: pacman does not support AUR directly).
  --min-free-bytes <val>  Minimum amount of free space (in bytes) that the root partition should have
                          before updating. Otherwise a warning message will be displayed.
                          Default: $min_free_bytes
  --other-updates         Enable support for *user-defined* other updates (e.g. 'flatpak update').
                          Note that user-defined commands can be performed in the beginning and end
                          of execution of eos-update, see $otherUpdatesFile.
  --conf-filename         Show the name of the configuration file.
                          Tip: to edit the config file with 'nano':
                              sudo nano \$($progname --conf-filename)

Tip: preferred options can be added to variable EOS_UPDATE_OPTIONS in file $eosupdateconf.

EOF
    [ "$1" ] && exit "$1"
}

DiskSpace() {
    local available min
    available=$(findmnt / -nbo avail)
    min=$min_free_bytes

    if [ "$available" -lt "$min" ] ; then
        {
            WARN "your root partition (/) has only $available bytes of free space."
            if [ "$(du -b -d0 "$pkg_cache_dir" | awk '{print $1}')" -gt "$min" ] ; then
                printf "\nFor example, cleaning up the package cache may help.\n"
                printf "Command 'sudo paccache -rk1' would do this:"
                paccache -dk1
                printf "Command 'sudo paccache -ruk0' would do this:"
                paccache -duk0
                echo ""
            fi
        } >&2
    fi
}

SystemValidityChecks_error() {
    local files_required=("$pacmanconf" "$aml" "$eml")
    local file
    for file in "${files_required[@]}" ; do
        [ -e "$file" ]                     || DIE "file '$file' is missing!"
        [ "$(stat -c %s "$file")" -gt 0 ]  || DIE "file '$file' is empty!"
        [ -r "$file" ]                     || DIE "file '$file' is not readable!"
    done
    CheckReposPacmanConf
}

RunUserCommands() {
    if [ $other_updates = yes ] ; then
        local -r when="$1"
        if [ -r "$otherUpdatesFile" ] ; then
            bash "$otherUpdatesFile" "$when"
        else
            [ $((runusercommands_warning_count++)) -eq 0 ] && WARN "script $otherUpdatesFile does not exist"
        fi
    fi
}

_EosUpdate_Main2() {
    # Some deprecated features:
    declare -A DeprecationDates=(
        [--pacdiff]="2026-09-01"
        [--rank-mirrors]="2026-09-01"
    )

    DebugBreak
    local -r progname=${0##*/}
    local -r pacmanconf=/etc/pacman.conf
    local -r eosupdateconf=/etc/$progname.conf
    local -r eml=/etc/pacman.d/endeavouros-mirrorlist
    local -r aml=/etc/pacman.d/mirrorlist
    local -r rank_command_file=/bin/eos-update-rank-mirrors
    local -r otherUpdatesFile=/etc/eos-update-other.bash
    local min_free_bytes=$((1000 * 1000 * 1000))  # default: 1 GB

    local lopts="aur,clear-databases,descriptions,dump-options,fast,keyrings-reset,nvidia,no-keyring,no-sync,helper:"
    lopts+=",min-free-bytes:,pacdiff,paru,yay,pacman,help,run-mode:,faillock-check"
    lopts+=",show-only-fixed,show-upstream-news,check-mirrors-arch,check-mirrors-eos,check-mirrors,edit-mirrorlist:"
    lopts+=",check-databases,reset-pacmanconf,other-updates,colors:,cache-limit:,rank-mirrors"
    lopts+=",keep-system-running,no-keep-system-running"
    lopts+=",tips,conf-filename"
    local sopts="dhl:"

    local use_colors=yes
    local colorfile="$HOME/.cache/${progname}_use-colors.txt"
    FillColorFile
    local helper="pacman"
    local lock=/var/lib/pacman/db.lck
    local rmopt=f
    local mode=plain
    local pacdiffer=""                            # default = "" because pacdiff can change critical files
    local -r pacdiffer_cmd_default="eos-pacdiff --quiet"
    local subopts=()
    local keyring=yes                             # user may disable keyring check with --no-keyring
    local nvidia=no                               # user may enable Nvidia check with --nvidia
    local descriptions=no                         # user may enable showing descriptions of the package updates
    local faillockcheck=no
    local other_updates=no                        # user can add update commands for other sources
    local sync="sync"
    local eufast=no
    local eufast_opt=(--arch --endeavouros)
    local paccache_limit=""
    local runusercommands_warning_count=0         # limit warnings to 1 from RunUserCommands
    local use_config_file=yes
    local EOS_UPDATE_OPTIONS=()
    local args=("$@")

    [ "$EOS_UPDATE_PACDIFFER" ] && pacdiffer="$EOS_UPDATE_PACDIFFER"

    SystemValidityChecks_error

    source /usr/share/endeavouros/scripts/eos-script-lib-yad || exit 1
    source /usr/share/endeavouros/scripts/translations.bash || exit 1

    if [[ "${args[*]}" =~ "--no-config" ]] ; then
        # --no-config is an option for testing
        use_config_file=no
        readarray -t args < <(printf "%s\n" "${args[@]}" | grep -v -- "^--no-config$")
    fi

    if [ "$use_config_file" = yes ] && [ -e "$eosupdateconf" ] ; then
        # shellcheck disable=1090
        source "$eosupdateconf"
        local tmp=("${args[@]}")
        args=("${EOS_UPDATE_OPTIONS[@]}" "${tmp[@]}")
    fi

    SingleOptions "${args[@]}"

    echo2tip "$progname: package updater with additional features."
    echo2tip "Command: $progname" "${args[@]}" "\n"

    RunUserCommands beginning

    DebugBreak
    # shellcheck disable=2119
    _init_translations

    Options "${args[@]}"

    if [ $eufast = yes ] ; then
        eos-update-fast "${eufast_opt[@]}"
        case "$?" in
            2) echo2 "$(ltr sysup_no)" ; exit ;;
        esac
    fi

    local pkg_cache_dir
    pkg_cache_dir=$(GetCacheDir)

    if [ $EUID -eq 0 ] && [ $mode = plain ] ; then    # mode can change based on current privileges
        mode=login
    fi

    [ $descriptions  = yes ] && subopts+=(--descriptions)
    [ $nvidia  = yes ] && subopts+=(--nvidia)
    [ $keyring = yes ] && subopts+=(--keyrings)

    DiskSpace

    if [ -e $lock ] && sudo fuser $lock &>/dev/null ; then
        echo2error "==> File $lock is in use.\n    Check if another process is still managing packages."
        exit 1
        # DIE "file $lock is in use. Check if another process is still managing packages."
        #rmopt=i
    fi

    FaillockCheck                # reset faillock if user wants

    sudo rm -$rmopt $lock
    if [ ! -e $lock ] ; then
        RunUpdateCommands        # run package update commands
        $pacdiffer               # run 'eos-pacdiff'
        $sync                    # run 'sync'
    fi
    PacCache                     # limit growth of the package cache

    # support for user-defined other updates
    RunUserCommands              # deprecated, only for backwords compatibility
    RunUserCommands end

    echo2tip "==> All done!"
    echo ""                      # this line is meant to reset cursor color to default
}

RunUpdateCommands() {
    ## Handle native packages first
    TRY sudo pacman -Sy
    HandleExtraCommands "${subopts[@]}"
    TRY sudo pacman -Su

    ## Handle AUR packages next
    local aur_helper=()
    # shellcheck disable=2206
    case "$helper" in
	pacman*) ;;                        # echo2info "Updating native apps..."
	*) aur_helper=($helper -Sua) ;;    # echo2info "Updating native and AUR apps..."
    esac
    if [ "${aur_helper[0]}" ] ; then
        case "$mode" in
            plain) TRY                                "${aur_helper[@]}" ;;
            root)  TRY sudo         --user "$LOGNAME" "${aur_helper[@]}" ;;
            login) TRY sudo --login --user "$LOGNAME" "${aur_helper[@]}" ;;
        esac
    fi
}

HandleExtraCommands() {
    local updates=()
    readarray -t updates < <(pacman -Quq)
    [ "${updates[0]}" ] || return 0

    local item
    local update_first=()   # these will be updated before others

    while [ "$1" ] ; do
        case "$1" in
            --descriptions)
                CmdShow eos-update-descriptions --title "descriptions" --header no "${updates[@]}"
                ;;
            --nvidia)
                CmdShow eos-kernel-nvidia-update-check "${updates[@]}" || WARN "'Nvidia & kernel' check failed."
                ;;
            --keyrings | --keyring)
                # Update certain packages before others.
                for item in archlinux-keyring endeavouros-keyring ; do
                    printf "%s\n" "${updates[@]}" | grep -q "^$item$" && update_first+=("$item")
                done
                [ "${update_first[0]}" ] && TRY sudo pacman -S --noconfirm "${update_first[@]}"
                ;;
            *)
                WARN "parameter '$1' ignored."
                ;;
        esac
        shift
    done
    return 0
}

DebugBreak() { : ; }

Main() {
    local prog_name="${0##*/}"
    local -r eosupdateconf=/etc/$prog_name.conf
    local keep_running=yes      # default execution mode
    local use_config_file=yes
    local params=()
    local arg
    local phase=1
    local EOS_UPDATE_OPTIONS=()

    if [[ "$*" =~ "--no-config" ]] ; then
        use_config_file=no
    fi
    if [ "$use_config_file" = yes ] && [ -e "$eosupdateconf" ] ; then
        # shellcheck disable=1090
        source "$eosupdateconf"
        for arg in "${EOS_UPDATE_OPTIONS[@]}" ; do
            case "$arg" in
                --keep-system-running)    keep_running=yes ;;
                --no-keep-system-running) keep_running=no ;;
            esac
        done
    fi
    for arg in "$@" ; do
        case "$arg" in
            --phase=2)                phase=2 ;;
            --keep-system-running)    keep_running=yes ;;
            --no-keep-system-running) keep_running=no ;;
            *)                        params+=("$arg") ;;
        esac
    done
    if [ "$keep_running" = yes ] && [ $phase = 1 ] ; then
        local cmd=(systemd-inhibit --who="$prog_name" --why="Updating." "$prog_name" --phase=2 "${params[@]}")
        "${cmd[@]}"
    else
        _EosUpdate_Main2 "${params[@]}"
    fi
}

Main "$@"
