#!/usr/bin/env bash
# Deadlock Mod Fixer - Linux / Bash
# Ported from Fix-DeadlockMods.ps1. Requires Bash 4+ and vpkmerge v0.22.1+.
# By default no log file is written. Use --log (or -Log) to save one.

set -uo pipefail
shopt -s nullglob

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" || exit 1
LOG_ENABLED=false
GAME_PATH=''
MODE=''
STAMP="$(date '+%Y-%m-%d_%H%M%S')"
LOG_FILE="$SCRIPT_DIR/FixLog_$STAMP.txt"
BACKUP_ROOT="$SCRIPT_DIR/ModBackups"
STAGE="$SCRIPT_DIR/Staging"
BACKUP="$BACKUP_ROOT/$STAMP"

usage() {
    cat <<'HELP'
Usage: ./Fix-DeadlockMods.sh [options]

Options:
  --log, -Log             Save a text log (console output always shown)
  --game-path PATH        Deadlock installation folder (contains game/citadel)
  -GamePath PATH          Same as --game-path (PowerShell-compatible name)
  --scan                  Scan only (no mod changes)
  --fix                   Scan and repair stale attachments, with backups
  --restore               Restore the most recent backup (asks for confirmation)
  -h, --help              Show this help

Without --scan/--fix/--restore the script presents an interactive menu.
Game changes require a native Linux vpkmerge CLI, v0.22.1 or newer.
Download: https://github.com/Slush97/vpkmerge/releases/tag/v0.22.1
HELP
}

while (( $# )); do
    case "$1" in
        --log|-Log) LOG_ENABLED=true; shift ;;
        --game-path|-GamePath)
            if (( $# < 2 )); then printf 'Missing value after %s\n' "$1" >&2; exit 2; fi
            GAME_PATH=$2; shift 2 ;;
        --game-path=*) GAME_PATH=${1#*=}; shift ;;
        --scan|--fix|--restore)
            if [[ -n "$MODE" ]]; then printf 'Choose only one mode.\n' >&2; exit 2; fi
            MODE=${1#--}; shift ;;
        -h|--help) usage; exit 0 ;;
        *) printf 'Unknown argument: %s\n' "$1" >&2; usage; exit 2 ;;
    esac
done

log() {
    printf '%s\n' "$1"
    if [[ "$LOG_ENABLED" == true ]]; then
        printf '%s\n' "$1" >> "$LOG_FILE" || { printf 'Cannot write log: %s\n' "$LOG_FILE" >&2; exit 1; }
    fi
}

# Remove surrounding quotes; expand a leading ~ if entered literally.
normalize_game_path() {
    GAME_PATH=${GAME_PATH#\"}; GAME_PATH=${GAME_PATH%\"}
    if [[ "$GAME_PATH" == '~/'* ]]; then GAME_PATH="$HOME/${GAME_PATH#\~/}"; fi
    GAME_PATH=${GAME_PATH%/}
}

valid_game_path() {
    [[ -f "$1/game/citadel/pak01_dir.vpk" && -d "$1/game/citadel/addons" ]]
}

# Probe Steam locations, including the Deck, Flatpak Steam, and Steam libraryfolders.vdf.
find_game_path() {
    local steam_root libfile folder candidate
    local -a steam_roots=(
        "$HOME/.steam/steam"
        "$HOME/.steam/root"
        "$HOME/.local/share/Steam"
        "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"
    )

    for steam_root in "${steam_roots[@]}"; do
        candidate="$steam_root/steamapps/common/Deadlock"
        if valid_game_path "$candidate"; then GAME_PATH=$candidate; return 0; fi

        libfile="$steam_root/steamapps/libraryfolders.vdf"
        [[ -f "$libfile" ]] || continue
        while IFS= read -r folder; do
            [[ -n "$folder" ]] || continue
            candidate="$folder/steamapps/common/Deadlock"
            if valid_game_path "$candidate"; then GAME_PATH=$candidate; return 0; fi
        done < <(sed -nE 's/^[[:space:]]*"path"[[:space:]]*"([^"]*)".*/\1/p' "$libfile")
    done

    for candidate in \
        /run/media/mmcblk0p1/steamapps/common/Deadlock \
        /run/media/deck/mmcblk0p1/steamapps/common/Deadlock; do
        if valid_game_path "$candidate"; then GAME_PATH=$candidate; return 0; fi
    done
    return 1
}

check_running() {
    local name
    if command -v pgrep >/dev/null 2>&1; then
        for name in project8 deadlock_linux64 DeadlockModManager deadlock_mod_manager CitadelModManager; do
            if pgrep -x "$name" >/dev/null 2>&1; then
                log "Close Deadlock and Deadlock Mod Manager before making changes (running: $name)."
                return 1
            fi
        done
        if pgrep -fi 'deadlock.?mod.?manager|citadelmodmanager' >/dev/null 2>&1; then
            log 'Close Deadlock Mod Manager before making changes.'
            return 1
        fi
    else
        log 'WARNING: pgrep is unavailable; verify manually that Deadlock and the mod manager are closed.'
    fi
    return 0
}

# Run vpkmerge; capture stdout+stderr without treating normal stderr as a failure.
# Results stored in VPK_OUTPUT and VPK_EXIT in the current shell.
run_vpkmerge() {
    VPK_OUTPUT=''
    VPK_EXIT=0
    if VPK_OUTPUT=$("$EXE" "$@" 2>&1); then
        VPK_EXIT=0
    else
        VPK_EXIT=$?
    fi
    if [[ -n "$VPK_OUTPUT" ]]; then
        while IFS= read -r line; do log "    $line"; done <<< "$VPK_OUTPUT"
    fi
}

# Result stored in CHECK_COUNT; returns failure on a bad exit or unrecognized output.
check_vpk() {
    local file=$1 summary_regex
    CHECK_COUNT=''
    run_vpkmerge attachments check --base "$BASE" "$file"
    if (( VPK_EXIT != 0 )); then
        log "  ERROR: vpkmerge check exited with code $VPK_EXIT"
        return 1
    fi
    summary_regex='([0-9]+)[[:space:]]+of[[:space:]]+[0-9]+[[:space:]]+mods?[[:space:]]+ships?[[:space:]]+stale[[:space:]]+model[[:space:]]+attachments'
    if [[ "$VPK_OUTPUT" =~ $summary_regex ]]; then
        CHECK_COUNT=${BASH_REMATCH[1]}
        return 0
    fi
    log '  ERROR: vpkmerge check output was not recognized'
    return 1
}

restore_latest() {
    local last='' dir file name failures=0 restored=0
    if [[ -d "$BACKUP_ROOT" ]]; then
        for dir in "$BACKUP_ROOT"/*; do
            if [[ -d "$dir" && "$dir" > "$last" ]]; then last=$dir; fi
        done
    fi
    if [[ -z "$last" ]]; then log 'No backups found.'; return 0; fi
    log "Restore backup: $last"
    local confirmation
    read -r -p 'Type RESTORE to confirm: ' confirmation || return 1
    [[ "$confirmation" == RESTORE ]] || { log 'Restore cancelled.'; return 0; }
    for file in "$last"/pak*_dir.vpk; do
        [[ -f "$file" ]] || continue
        name=${file##*/}
        [[ "$name" =~ ^pak[0-9]+_dir\.vpk$ ]] || continue
        if cp -- "$file" "$ADDONS/$name" && cmp -s -- "$file" "$ADDONS/$name"; then
            log "RESTORED $name"
            ((restored+=1))
        else
            log "ERROR restoring $name"
            ((failures+=1))
        fi
    done
    log "Restore finished: $restored restored, $failures failed."
    if [[ "$LOG_ENABLED" == true ]]; then log "Log file: $LOG_FILE"; fi
    (( failures == 0 ))
}

printf '\nDEADLOCK MOD FIXER - Violet / Paint the Town update (Linux)\n'

if [[ -n "$GAME_PATH" ]]; then normalize_game_path; else find_game_path || :; fi
if [[ -z "$GAME_PATH" ]] || ! valid_game_path "$GAME_PATH"; then
    printf 'Deadlock not found automatically.\n'
    read -r -p 'Enter Deadlock install folder (contains game/citadel): ' GAME_PATH || exit 1
    normalize_game_path
fi
if ! valid_game_path "$GAME_PATH"; then
    printf 'Deadlock base VPK or addons folder not found. No changes made.\n' >&2
    exit 1
fi
BASE="$GAME_PATH/game/citadel/pak01_dir.vpk"
ADDONS="$GAME_PATH/game/citadel/addons"
printf 'Game: %s\n' "$GAME_PATH"
printf 'Close Deadlock and Deadlock Mod Manager before making changes.\n'
printf 'Scan and fix will back up original mods before replacement.\n'

if [[ -z "$MODE" ]]; then
    printf '\n1 - Scan only\n2 - Scan and fix\n3 - Restore latest backup\nQ - Quit\n'
    read -r -p 'Choose an option: ' choice || exit 0
    case "${choice^^}" in
        1) MODE=scan ;;
        2) MODE=fix ;;
        3) MODE=restore ;;
        *) exit 0 ;;
    esac
fi

if [[ "$MODE" == fix || "$MODE" == restore ]]; then
    check_running || exit 1
fi
if [[ "$MODE" == restore ]]; then
    restore_latest
    exit $?
fi

# The Linux CLI, not the Windows .exe, is required for scan/repair.
if [[ -f "$SCRIPT_DIR/vpkmerge-linux-x86_64" ]]; then
    EXE="$SCRIPT_DIR/vpkmerge-linux-x86_64"
elif [[ -f "$SCRIPT_DIR/vpkmerge" ]]; then
    EXE="$SCRIPT_DIR/vpkmerge"
elif command -v vpkmerge >/dev/null 2>&1; then
    EXE="$(command -v vpkmerge)"
else
    printf 'Missing Linux vpkmerge CLI. Download vpkmerge-linux-x86_64 from:\n' >&2
    printf 'https://github.com/Slush97/vpkmerge/releases/tag/v0.22.1\n' >&2
    printf 'Put it next to this script and run: chmod +x vpkmerge-linux-x86_64\n' >&2
    exit 1
fi
if [[ ! -x "$EXE" ]]; then
    printf 'Not executable: %s\nRun: chmod +x "%s"\n' "$EXE" "$EXE" >&2
    exit 1
fi

mods=()
for file in "$ADDONS"/pak*_dir.vpk; do
    [[ -f "$file" ]] || continue
    name=${file##*/}
    if [[ "$name" =~ ^pak[0-9]+_dir\.vpk$ ]]; then mods+=("$file"); fi
done
log "Found ${#mods[@]} mod VPK(s)."

clean=0 stale=0 fixed=0 skipped=0 failed=0
for mod in "${mods[@]}"; do
    name=${mod##*/}
    log ""
    log "Checking $name"
    [[ "$name" =~ ^pak([0-9]+)_dir\.vpk$ ]] || continue
    number=${BASH_REMATCH[1]}

    chunks=( "$ADDONS"/"pak${number}_"[0-9][0-9][0-9].vpk )
    if (( ${#chunks[@]} > 0 )); then
        log '  SKIPPED: chunked VPK'
        ((skipped+=1))
        continue
    fi

    if ! check_vpk "$mod"; then
        log '  ERROR: check failed or output unrecognized'
        ((failed+=1))
        continue
    fi
    if (( CHECK_COUNT == 0 )); then
        log '  CLEAN'
        ((clean+=1))
        continue
    fi

    log "  STALE: $CHECK_COUNT mod(s) with stale attachments"
    if [[ "$MODE" == scan ]]; then
        ((stale+=1))
        continue
    fi

    if ! mkdir -p -- "$STAGE"; then
        log '  ERROR: cannot create staging directory'
        ((failed+=1))
        continue
    fi
    output="$STAGE/pak${number}_fixed_dir.vpk"
    if ! rm -f -- "$output"; then
        log '  ERROR: cannot clean old staging file'
        ((failed+=1))
        continue
    fi
    run_vpkmerge attachments heal --base "$BASE" "$mod" --output "$output"
    if (( VPK_EXIT != 0 )) || [[ ! -s "$output" ]]; then
        log '  ERROR: healing failed or generated an empty/missing file'
        ((failed+=1))
        continue
    fi
    if ! check_vpk "$output" || (( CHECK_COUNT != 0 )); then
        log '  ERROR: healed VPK failed verification; original untouched'
        ((failed+=1))
        continue
    fi

    if ! mkdir -p -- "$BACKUP"; then
        log '  ERROR: cannot create backup directory; original untouched'
        ((failed+=1))
        continue
    fi
    original_backup="$BACKUP/$name"
    if ! cp -p -- "$mod" "$original_backup" || ! cmp -s -- "$mod" "$original_backup"; then
        log '  ERROR: backup failed verification; original untouched'
        ((failed+=1))
        continue
    fi

    # Copy into addons first, verify bytes, then atomically rename into place.
    # This avoids partially overwriting an installed mod if the copy fails.
    if ! temp_mod=$(mktemp "$ADDONS/.pak${number}_fixed.XXXXXXXX"); then
        log '  ERROR: could not create temporary VPK; original untouched'
        ((failed+=1))
        continue
    fi
    if ! cp -- "$output" "$temp_mod" || ! cmp -s -- "$output" "$temp_mod" || \
       ! chmod --reference="$mod" "$temp_mod" || ! mv -f -- "$temp_mod" "$mod"; then
        rm -f -- "$temp_mod"
        log '  ERROR: installing repaired VPK failed; restoring backup'
        if ! cp -- "$original_backup" "$mod"; then
            log "  CRITICAL: restore failed! Original saved at $original_backup"
        fi
        ((failed+=1))
        continue
    fi
    if ! cmp -s -- "$output" "$mod"; then
        log '  ERROR: installed VPK verification failed; restoring backup'
        if ! cp -- "$original_backup" "$mod"; then
            log "  CRITICAL: restore failed! Original saved at $original_backup"
        fi
        ((failed+=1))
        continue
    fi
    log '  FIXED and verified (original backed up)'
    ((fixed+=1))
done

log ""
log "SUMMARY: Clean=$clean, Stale=$stale, Fixed=$fixed, Skipped=$skipped, Failed=$failed"
if (( fixed > 0 )); then log "Backup folder: $BACKUP"; fi
if [[ "$LOG_ENABLED" == true ]]; then log "Log file: $LOG_FILE"; fi
printf '\nDone.\n'
if (( failed > 0 )); then exit 1; fi
exit 0
