#!/usr/bin/env sh
# Shared helpers for the backup-tools installer and uninstaller.
#
# This file is inlined into the published install.sh / uninstall.sh by
# apps/unix/build.sh, so the online, offline, update and uninstall paths all use
# exactly the same implementation (FR-16.3, FR-16.13).

PROJECT_NAME="backup-tools"
COMMAND="backuptools"
PLATFORM="unix"
BASE_URL_DEFAULT="https://files.tymler.work"
VERSION_FALLBACK="v2.2.2"

INSECURE="${BACKUP_TOOLS_INSECURE:-0}"
PREFIX="${HOME}/.local"

log() { printf '%s\n' "$*" >&2; }
warn() { printf 'warning: %s\n' "$*" >&2; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
step() { printf '[%s/%s] %s\n' "$1" "$2" "$3" >&2; }

# platform_now maps uname -s to the release platform (Darwin -> macos, anything
# else -> unix).
platform_now() {
    case "$(uname -s)" in
        Darwin) printf 'macos' ;;
        *) printf 'unix' ;;
    esac
}

# binary_os maps a release platform to the GOOS used in bundled binary names.
binary_os() {
    case "$1" in
        unix) printf 'linux' ;;
        macos) printf 'darwin' ;;
        *) die "unknown platform: $1" ;;
    esac
}

arch_now() {
    case "$(uname -m)" in
        x86_64|amd64) printf 'amd64' ;;
        arm64|aarch64) printf 'arm64' ;;
        *) die "unsupported architecture: $(uname -m)" ;;
    esac
}

# resolve_base_url applies the documented priority:
# --base-url > BACKUP_TOOLS_BASE_URL > ~/.config/<project>/install.conf > injected.
resolve_base_url() {
    bt_base=$(resolve_base_url_optional)
    if [ -z "$bt_base" ]; then
        die "no base URL configured; pass --base-url URL, set BACKUP_TOOLS_BASE_URL, or add base_url= to $HOME/.config/$PROJECT_NAME/install.conf"
    fi
    printf '%s' "$bt_base"
}

resolve_base_url_optional() {
    bt_base=""
    if [ -n "${BASE_URL_CLI:-}" ]; then
        bt_base=$BASE_URL_CLI
    elif [ -n "${BACKUP_TOOLS_BASE_URL:-}" ]; then
        bt_base=$BACKUP_TOOLS_BASE_URL
    elif [ -n "${BACKUPTOOLS_BASE_URL:-}" ]; then
        bt_base=$BACKUPTOOLS_BASE_URL
    elif [ -f "$HOME/.config/$PROJECT_NAME/install.conf" ]; then
        bt_base=$(sed -n 's/^[[:space:]]*base_url[[:space:]]*=[[:space:]]*//p' \
            "$HOME/.config/$PROJECT_NAME/install.conf" | tail -n 1)
    elif [ -n "$BASE_URL_DEFAULT" ]; then
        bt_base=$BASE_URL_DEFAULT
    fi
    bt_base=$(printf '%s' "$bt_base" | tr -d '\r' | sed 's|/*$||')
    printf '%s' "$bt_base"
}

# download fetches url to dest, honouring --insecure (curl -k / wget
# --no-check-certificate). Progress goes to stderr.
download() {
    bt_dl_url=$1
    bt_dl_dest=$2
    mkdir -p "$(dirname "$bt_dl_dest")" || die "create download directory"
    if command -v curl >/dev/null 2>&1; then
        if [ "$INSECURE" = "1" ]; then
            curl -kfL --progress-bar "$bt_dl_url" -o "$bt_dl_dest" || die "download failed: $bt_dl_url"
        else
            curl -fL --progress-bar "$bt_dl_url" -o "$bt_dl_dest" || die "download failed: $bt_dl_url"
        fi
    elif command -v wget >/dev/null 2>&1; then
        if [ "$INSECURE" = "1" ]; then
            wget --no-check-certificate "$bt_dl_url" -O "$bt_dl_dest" || die "download failed: $bt_dl_url"
        else
            wget "$bt_dl_url" -O "$bt_dl_dest" || die "download failed: $bt_dl_url"
        fi
    else
        die "neither curl nor wget is available; use the offline archive instead"
    fi
}

sha256_of() {
    if command -v sha256sum >/dev/null 2>&1; then
        sha256sum "$1" | awk '{print $1}'
    elif command -v shasum >/dev/null 2>&1; then
        shasum -a 256 "$1" | awk '{print $1}'
    elif command -v openssl >/dev/null 2>&1; then
        openssl dgst -sha256 "$1" | awk '{print $NF}'
    else
        die "no sha256 tool available (need sha256sum, shasum or openssl)"
    fi
}

shell_kind() {
    case "${1##*/}" in
        bash) printf 'bash' ;;
        zsh) printf 'zsh' ;;
    esac
}

# detect_shell prefers $SHELL and falls back to the parent process (piped runs
# often have no SHELL).
detect_shell() {
    bt_shell=""
    if [ -n "${SHELL:-}" ]; then
        bt_shell=$(shell_kind "$SHELL")
    fi
    if [ -z "$bt_shell" ]; then
        bt_parent=$(ps -o ppid= -p $$ 2>/dev/null | tr -d ' ')
        if [ -n "$bt_parent" ]; then
            bt_comm=$(ps -o comm= -p "$bt_parent" 2>/dev/null | tr -d ' ')
            bt_shell=$(shell_kind "$bt_comm")
        fi
    fi
    [ -n "$bt_shell" ] || return 1
    printf '%s' "$bt_shell"
}

rc_file() {
    case "$1" in
        zsh) printf '%s/.zshrc' "${ZDOTDIR:-$HOME}" ;;
        bash) printf '%s/.bashrc' "$HOME" ;;
        *) die "unsupported shell: $1" ;;
    esac
}

# remove_marked_block deletes every block delimited by the given markers,
# keeping the rest of the rc file and its permissions intact.
remove_marked_block() {
    bt_rc=$1
    bt_begin=$2
    bt_end=$3
    [ -f "$bt_rc" ] || return 0
    grep -qF "$bt_begin" "$bt_rc" || return 0
    bt_tmp="$bt_rc.tmp.$$"
    cp -p "$bt_rc" "$bt_tmp" || die "copy $bt_rc"
    awk -v b="$bt_begin" -v e="$bt_end" '
        $0 == b { skip = 1; next }
        $0 == e { skip = 0; next }
        !skip { print }
    ' "$bt_rc" > "$bt_tmp" || die "rewrite $bt_rc"
    mv -f "$bt_tmp" "$bt_rc" || die "replace $bt_rc"
}

# remove_block deletes the current project-managed block.
remove_block() {
    remove_marked_block "$1" "# >>> $PROJECT_NAME >>>" "# <<< $PROJECT_NAME <<<"
}

# append_block appends one managed block through an atomic rename.
append_block() {
    bt_rc=$1
    bt_block=$2
    bt_dir=$(dirname "$bt_rc")
    [ -d "$bt_dir" ] || mkdir -p "$bt_dir"
    bt_tmp="$bt_rc.tmp.$$"
    if [ -f "$bt_rc" ]; then
        cp -p "$bt_rc" "$bt_tmp" || die "copy $bt_rc"
        { cat "$bt_tmp"; printf '%s\n' "$bt_block"; } > "$bt_tmp.new" || die "write $bt_rc"
        mv -f "$bt_tmp.new" "$bt_tmp" || die "rewrite $bt_rc"
    else
        printf '%s\n' "$bt_block" > "$bt_tmp" || die "write $bt_rc"
    fi
    mv -f "$bt_tmp" "$bt_rc" || die "replace $bt_rc"
}

record_dir() { printf '%s/share/%s' "$PREFIX" "$PROJECT_NAME"; }
record_path() { printf '%s/install-record' "$(record_dir)"; }

record_version() {
    bt_rec=$(record_path)
    [ -f "$bt_rec" ] || return 1
    sed -n '1s/^# version: //p' "$bt_rec"
}

# write_record reads installed paths from stdin (one per line) and writes the
# record atomically. $1 is the installed version.
write_record() {
    bt_rec=$(record_path)
    bt_dir=$(dirname "$bt_rec")
    mkdir -p "$bt_dir" || die "create $bt_dir"
    bt_tmp="$bt_rec.tmp.$$"
    {
        printf '# version: %s\n' "$1"
        cat
    } > "$bt_tmp" || die "write $bt_rec"
    chmod 0600 "$bt_tmp" 2>/dev/null || true
    mv -f "$bt_tmp" "$bt_rec" || die "replace $bt_rec"
}

# prune_dirs removes empty parent directories, never leaving the prefix.
prune_dirs() {
    bt_p=$1
    bt_root=$2
    while [ -n "$bt_p" ] && [ "$bt_p" != "/" ] && [ "$bt_p" != "$bt_root" ]; do
        case "$bt_p" in
            "$bt_root"/*) ;;
            *) break ;;
        esac
        rmdir "$bt_p" 2>/dev/null || break
        bt_p=$(dirname "$bt_p")
    done
}

# cleanup_legacy removes completion files and rc blocks written by v1
# installers. The shared zsh rc block is only reported, because other tools may
# use it. It runs before the new files are installed and skips paths that are
# current install destinations, so the default prefix (where the v1 bash path
# equals the v2 one) never removes the new completion.
cleanup_legacy() {
    bt_new_bash="$PREFIX/share/bash-completion/completions/$COMMAND"
    bt_new_zsh="$PREFIX/share/zsh/site-functions/_$COMMAND"
    for bt_old in \
        "$HOME/.zfunc/_$COMMAND" \
        "${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions/$COMMAND"; do
        # Never treat a current install destination as a legacy path.
        [ "$bt_old" = "$bt_new_bash" ] && continue
        [ "$bt_old" = "$bt_new_zsh" ] && continue
        if [ -e "$bt_old" ]; then
            rm -f "$bt_old" || die "remove legacy file $bt_old"
            log "removed legacy completion: $bt_old"
        fi
    done
    bt_zshrc="${ZDOTDIR:-$HOME}/.zshrc"
    if [ -f "$bt_zshrc" ] && grep -qF '# >>> backuptools shell completion >>>' "$bt_zshrc"; then
        remove_marked_block "$bt_zshrc" \
            '# >>> backuptools shell completion >>>' \
            '# <<< backuptools shell completion <<<'
        log "removed legacy rc block '# >>> backuptools shell completion >>>' from $bt_zshrc"
    fi
    if [ -f "$bt_zshrc" ] && grep -qF '# >>> zsh user completions >>>' "$bt_zshrc"; then
        warn "your zsh rc still has the legacy shared 'zsh user completions' block; remove it manually if no other tool uses it"
    fi
}

version_gt() {
    awk -v a="$1" -v b="$2" '
        function n(s) { return (s == "" ? 0 : s + 0) }
        BEGIN {
            sub(/^v/, "", a); sub(/^v/, "", b)
            split(a, x, "."); split(b, y, ".")
            for (i = 1; i <= 3; i++) {
                if (n(x[i]) > n(y[i])) exit 0
                if (n(x[i]) < n(y[i])) exit 1
            }
            exit 1
        }'
}

version_lt() {
    version_gt "$2" "$1"
}
# backup-tools uninstaller.
#
# Removes the files recorded by the installer plus the managed shell rc block.
# User configuration (auth, config.json, sources.json) and backup data are never
# removed; their locations are printed instead.
set -eu

PREFIX_OPT=""
SHELL_OPT=""
BASE_URL_CLI=""
FORCE=0
INSECURE=0

usage() {
    cat <<EOF
Usage: uninstall.sh [options]

Remove $COMMAND $PROJECT_NAME (platform $PLATFORM) from this machine.

Options:
  --base-url URL   Release root (only used to print the correct platform URL)
  --prefix DIR     Prefix used at install time (default: \$HOME/.local)
  --shell bash|zsh Shell whose rc block should be removed (auto-detected)
  --reinstall      Accepted for compatibility; ignored by the uninstaller
  --force          Also remove known files when no install record exists
  --insecure       Accepted for compatibility; ignored by the uninstaller
  -h, --help       Show this help
EOF
}

parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --base-url)
                [ $# -ge 2 ] || { usage >&2; exit 2; }
                BASE_URL_CLI=$2
                shift 2
                ;;
            --prefix)
                [ $# -ge 2 ] || { usage >&2; exit 2; }
                PREFIX_OPT=$2
                shift 2
                ;;
            --shell)
                [ $# -ge 2 ] || { usage >&2; exit 2; }
                SHELL_OPT=$2
                shift 2
                ;;
            --reinstall)
                log "note: --reinstall is ignored by the uninstaller"
                shift
                ;;
            --force)
                FORCE=1
                shift
                ;;
            --insecure)
                INSECURE=1
                shift
                ;;
            -h|--help)
                usage
                exit 0
                ;;
            -*)
                printf 'error: unknown option: %s\n' "$1" >&2
                usage >&2
                exit 2
                ;;
            *)
                printf 'error: unexpected argument: %s\n' "$1" >&2
                usage >&2
                exit 2
                ;;
        esac
    done
    if [ -n "$PREFIX_OPT" ]; then
        PREFIX=$PREFIX_OPT
    fi
    if [ -n "$SHELL_OPT" ]; then
        case "$SHELL_OPT" in
            bash|zsh) ;;
            *)
                printf 'error: --shell must be bash or zsh\n' >&2
                exit 2
                ;;
        esac
    fi
}

remove_record_files() {
    bt_rec=$1
    bt_list=$(mktemp "${TMPDIR:-/tmp}/$PROJECT_NAME-uninstall.XXXXXX")
    sed -n '2,$p' "$bt_rec" > "$bt_list"
    bt_removed=0
    while IFS= read -r bt_path; do
        [ -n "$bt_path" ] || continue
        case "$bt_path" in
            "$PREFIX"/*) ;;
            *)
                warn "skipping path outside $PREFIX: $bt_path"
                continue
                ;;
        esac
        if [ -e "$bt_path" ] || [ -L "$bt_path" ]; then
            rm -f "$bt_path" || die "remove $bt_path"
            bt_removed=$((bt_removed + 1))
            prune_dirs "$(dirname "$bt_path")" "$PREFIX"
        fi
    done < "$bt_list"
    rm -f "$bt_list"
    rm -f "$bt_rec" || die "remove $bt_rec"
    prune_dirs "$(dirname "$bt_rec")" "$PREFIX"
    printf '%s' "$bt_removed"
}

remove_known_paths() {
    bt_count=0
    for bt_path in \
        "$PREFIX/bin/$COMMAND" \
        "$PREFIX/share/bash-completion/completions/$COMMAND" \
        "$PREFIX/share/zsh/site-functions/_$COMMAND"; do
        if [ -e "$bt_path" ]; then
            rm -f "$bt_path" || die "remove $bt_path"
            bt_count=$((bt_count + 1))
            prune_dirs "$(dirname "$bt_path")" "$PREFIX"
        fi
    done
    rm -rf "$PREFIX/share/$PROJECT_NAME/config-templates"
    prune_dirs "$PREFIX/share/$PROJECT_NAME" "$PREFIX"
    printf '%s' "$bt_count"
}

main() {
    parse_args "$@"

    local_platform=$(platform_now)
    if [ "$PLATFORM" != "$local_platform" ]; then
        bt_base=$(resolve_base_url_optional)
        die "this uninstaller targets $PLATFORM but this machine is $local_platform; use ${bt_base:-<base>}/$PROJECT_NAME/$local_platform/uninstall.sh"
    fi

    bt_shell=$(detect_shell) || bt_shell=$SHELL_OPT
    [ -n "$bt_shell" ] || die "cannot determine the current shell; re-run with --shell bash|zsh"

    bt_rec=$(record_path)
    if [ ! -f "$bt_rec" ]; then
        if [ "$FORCE" = 1 ]; then
            log "no install record under $PREFIX; removing known $PROJECT_NAME paths because --force was given"
            bt_removed=$(remove_known_paths)
        else
            log "no install record under $PREFIX; nothing to uninstall"
            exit 0
        fi
    else
        bt_removed=$(remove_record_files "$bt_rec")
    fi

    bt_rc=$(rc_file "$bt_shell")
    remove_block "$bt_rc"

    bt_config="$HOME/.config/$PROJECT_NAME"
    case "$(uname -s)" in
        Darwin) bt_config="$HOME/Library/Application Support/$PROJECT_NAME" ;;
    esac
    log "removed $bt_removed file(s); cleaned the $PROJECT_NAME block from $bt_rc"
    log "kept user configuration and data:"
    log "  config: $bt_config"
    log "  data:   ${XDG_DATA_HOME:-$HOME/.local/share}/$PROJECT_NAME"
    log "  state:  ${XDG_STATE_HOME:-$HOME/.local/state}/$PROJECT_NAME"
}

main "$@"
