#!/bin/bash

# mkhint - Manage hint files for slackrepo scripts
#
# Usage:
#   ./mkhint --version VERSION --hintfile FILE    Update existing hint file
#   ./mkhint --version VERSION --new FILE         Create new hint file
#   ./mkhint --new FILE                           Create new hint file (no version)
#   ./mkhint --hintfile FILE                      Update hint, suggest latest version via nvchecker
#   ./mkhint --check [FILE...]                     Check all (or named) hints for upstream updates
#   ./mkhint --list                               List hint files
#   ./mkhint --clean                              Remove .bak files from HINT_DIR
#   ./mkhint --no-dl --hintfile FILE              Update hint, skip downloads, add NODOWNLOAD=yes
#   ./mkhint --no-dl --new FILE                   Create hint with NODOWNLOAD=yes
#   ./mkhint --help                               Show this help

set -e

# Default configuration
REPO_DIR="/var/lib/sbopkg/SBo-danix/"
HINT_DIR="/etc/slackrepo/SBo-danix/hintfiles/"
TMP_DIR="/tmp/mkhint"
NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml"

# create the temp dir if not existing
if [[ ! -d $TMP_DIR ]]; then
    mkdir $TMP_DIR
fi

# Variables
VERSION=""
HINT_FILE=""
NEW_HINT_FILE=""
DELETE_HINT_FILES=()
MATCHED_PKGS=()
SHOW_LIST=""
RUN_REVIEW=""
COMMAND=""
NO_DL=0

# Show help message
show_help() {
    cat <<EOF
mkhint - Manage hint files for slackrepo scripts

Usage:
  ./mkhint --version VERSION --hintfile FILE    Update existing hint file
  ./mkhint --version VERSION --new FILE         Create new hint file
  ./mkhint --new FILE                           Create new hint file (no version)
  ./mkhint --hintfile FILE                      Update hint, suggest latest version via nvchecker
  ./mkhint --check [FILE...]                     Check all (or named) hints for upstream updates
  ./mkhint --list                               List hint files
  ./mkhint --review                             Review hints matching SBo version, keep/delete each
  ./mkhint --clean                              Remove .bak files from HINT_DIR
  ./mkhint --no-dl --hintfile FILE              Update hint, skip downloads, add NODOWNLOAD=yes
  ./mkhint --no-dl --new FILE                   Create hint with NODOWNLOAD=yes
  ./mkhint --help                               Show this help

Options:
  --version, -v VERSION    New version string (required for --hintfile)
  --hintfile, -f FILE      Path to existing hint file (required with --version)
  --new, -n FILE           Create new hint file (required with --version or standalone)
  --list, -l               List all hint files in the default directory
  --review, -R             Review hints whose version matches the SBo .info; diff + keep/delete
  --clean, -c              Remove all .bak files from HINT_DIR
  --check, -C [FILE...]    Check hints for upstream updates via nvchecker, update interactively
  --delete, -d FILE        Delete a hint file (and .bak if present)
  --no-dl, -N              Skip downloads; add NODOWNLOAD=yes to hint file (use with -f or -n)
  --help, -h               Show this help message

Hint files are stored in: $HINT_DIR
Temporary files are stored in: $TMP_DIR

Variables order in hint files:
  VERSION, ARCH, DOWNLOAD, MD5SUM, DOWNLOAD_x86_64, MD5SUM_x86_64

Exit codes:
  0 - Success
  1 - Invalid arguments or missing required options
  2 - File not found
  3 - File already exists
  4 - required tool not available (wget / nvchecker / nvtake / jq)
EOF
}

# List hint files
list_hint_files() {
    if [[ ! -d "$HINT_DIR" ]]; then
        echo "Error: Hint directory does not exist: $HINT_DIR" >&2
        exit 2
    fi

    # Color only on a TTY, or when forced for tests. tput optional.
    local c_on="" c_off=""
    if [[ -n "$MKHINT_FORCE_COLOR" || -t 1 ]]; then
        if command -v tput &>/dev/null && tput setaf 3 &>/dev/null; then
            c_on=$(tput setaf 3); c_off=$(tput sgr0)
        else
            c_on=$'\033[33m'; c_off=$'\033[0m'
        fi
    fi

    echo "Hint files in: $HINT_DIR"
    echo "======================================================="
    printf "%-40s  %10s  %10s  %-20s  %s\n" "File" "HintVer" "SBOVer" "Category" "Created"
    echo "-------------------------------------------------------"

    MATCHED_PKGS=()
    local count=0 matched=0
    for file in "$HINT_DIR"/*.hint; do
        if [[ -f "$file" ]]; then
            local VER=$(grep "^VERSION" "$file" |cut -d '"' -f2)
            local name=$(basename "$file")
            local pkg="${name%.hint}"
            local info_file
            info_file=$(find "$REPO_DIR" -mindepth 2 -name "${pkg}.info" 2>/dev/null | head -1)
            local SBO_VER=""
            local category=""
            if [[ -f "$info_file" ]]; then
                SBO_VER=$(grep "^VERSION" "$info_file" | cut -d '"' -f2)
                category=$(basename "$(dirname "$(dirname "$info_file")")")
            fi
            local date=$(stat -c "%y" "$file" | cut -d'.' -f1)
            local row
            row=$(printf "%-40s  %10s  %10s  %-20s  %s" "$name" "$VER" "$SBO_VER" "$category" "$date")
            if [[ -n "$VER" && "$VER" == "$SBO_VER" ]]; then
                printf "%s%s%s\n" "$c_on" "$row" "$c_off"
                MATCHED_PKGS+=("$pkg")
                matched=$((matched + 1))
            else
                printf "%s\n" "$row"
            fi
            count=$((count + 1))
        fi
    done

    if [[ $count -eq 0 ]]; then
        echo "  (no hint files found)"
    fi

    echo "======================================================="
    echo "Total: $count file(s)"
    if [[ $matched -gt 0 && -n "$c_on" ]]; then
        echo "(highlighted = hint version matches SBo .info)"
    fi
}

# Show each matched hint side-by-side with its .info, prompt Keep/Delete/Skip.
# Relies on MATCHED_PKGS populated by list_hint_files.
review_hint_files() {
    if [[ ${#MATCHED_PKGS[@]} -eq 0 ]]; then
        echo "No hints match their SBo version; nothing to review."
        return 0
    fi

    local deleted=0 kept=0
    local pkg
    for pkg in "${MATCHED_PKGS[@]}"; do
        local hint="${HINT_DIR%/}/${pkg}.hint"
        local info
        info=$(find "$REPO_DIR" -mindepth 2 -name "${pkg}.info" 2>/dev/null | head -1)
        [[ -f "$hint" ]] || continue

        echo ""
        echo "=== $pkg ==="
        if command -v git &>/dev/null; then
            git diff --no-index --color=auto "$hint" "$info" || true
        else
            diff -y --width="${COLUMNS:-160}" "$hint" "$info" || true
        fi

        local ans
        read -r -p "Review $pkg: [K]eep / [D]elete / [S]kip (default Keep): " ans
        case "$ans" in
            [Dd])
                _remove_hint "$hint"
                deleted=$((deleted + 1))
                ;;
            [Ss]|[Kk]|"")
                echo "Kept: $pkg"
                kept=$((kept + 1))
                ;;
            *)
                echo "Unrecognised answer; keeping $pkg"
                kept=$((kept + 1))
                ;;
        esac
    done

    echo ""
    echo "Reviewed ${#MATCHED_PKGS[@]} hint(s): deleted $deleted, kept $kept."
}

# Validate wget availability
check_wget() {
    if ! command -v wget &> /dev/null; then
        echo "Error: wget is not installed. Please install wget first." >&2
        exit 4
    fi
}

# Validate nvchecker toolchain availability
check_nvchecker() {
    local missing=()
    command -v nvchecker &> /dev/null || missing+=("nvchecker")
    command -v nvtake   &> /dev/null || missing+=("nvtake")
    command -v jq       &> /dev/null || missing+=("jq")
    if [[ ${#missing[@]} -gt 0 ]]; then
        echo "Error: required tool(s) not installed: ${missing[*]}" >&2
        echo "Install nvchecker (provides nvchecker + nvtake) and jq." >&2
        exit 4
    fi
    if [[ ! -f "$NVCHECKER_CONFIG" ]]; then
        echo "Error: nvchecker config not found: $NVCHECKER_CONFIG" >&2
        exit 2
    fi
}

# Echo the newver-keyfile path declared in [__config__] of NVCHECKER_CONFIG
_nvchecker_newver_path() {
    # Grab the `newver = "..."` value; tolerate spaces around =
    local line
    line=$(grep -E '^[[:space:]]*newver[[:space:]]*=' "$NVCHECKER_CONFIG" | head -1)
    [[ -z "$line" ]] && return 1
    # extract the quoted path
    local path
    path=$(printf '%s\n' "$line" | sed -E 's/^[^"]*"([^"]*)".*/\1/')
    [[ -z "$path" ]] && return 1
    # expand a leading ~ to $HOME
    path="${path/#\~/$HOME}"
    # nvchecker resolves a relative keyfile path against the config file's
    # directory (not the CWD), so do the same here.
    if [[ "$path" != /* ]]; then
        path="$(dirname "$NVCHECKER_CONFIG")/$path"
    fi
    printf '%s\n' "$path"
}

# Echo the latest version nvchecker found for a package, or return non-zero
# Usage: latest=$(nvchecker_latest pkg) || handle "no version"
nvchecker_latest() {
    local pkg="$1"
    local keyfile
    keyfile=$(_nvchecker_newver_path) || return 1
    [[ -f "$keyfile" ]] || return 1
    local ver
    ver=$(jq -r --arg p "$pkg" '.data[$p].version // empty' "$keyfile" 2>/dev/null)
    [[ -z "$ver" ]] && return 1
    printf '%s\n' "$ver"
}

# download files
download_file() {
    local url="$1"
    local dlfile="${TMP_DIR}/download"

    if [[ -f $dlfile ]]; then
        rm $dlfile
    fi

    # Download the file
    if [[ ! -z $1 ]]; then
        wget -O "$dlfile" "$url" || return 1
    fi

    # calculate md5
    local md5=$(md5sum "$dlfile" | awk '{print $1}')
    rm $dlfile
    echo $md5
}

# Create new hint file
create_new_hint_file() {
    cd $HINT_DIR
    local file="$1"
    local normalized_file="${file}"

    if [[ "$file" != *.hint ]]; then
        normalized_file="${file}.hint"
    fi

    # search repository for .info file
    info=$(find $REPO_DIR -mindepth 2 -name ${normalized_file%.hint}.info)

    # Check if file exists
    if [[ ! -f "$normalized_file" ]]; then
        # the hint file we want to create doesn't exists, so we can check
        # the sbo repository for a .info file and use that as hint
        if [[ -n $info ]]; then
            cp $info $normalized_file
            # remove unwanted lines from hint file
            sed -i -e "/^PRGNAM=/d" \
            -e "/^HOMEPAGE=/d" \
            -e "/^MAINTAINER=/d" \
            -e "/^EMAIL=/d" \
            -e "s/^REQUIRES=/#REQUIRES=/" \
            "${normalized_file}"

            if grep -q '^ARCH=' $normalized_file; then
                sed -i 's/^ARCH=.*/ARCH="x86_64"/' $normalized_file
            else
                echo 'ARCH="x86_64"' >> $normalized_file
            fi

            if [[ -n "$VERSION" ]]; then
                local old_version
                old_version=$(grep '^VERSION=' "$normalized_file" | sed 's/VERSION="//;s/"$//')
                sed -i "s/${old_version}/${VERSION}/g" "$normalized_file"
                update_checksums "$normalized_file"
            fi

            if [[ $NO_DL -eq 1 ]]; then
                add_nodownload "$normalized_file"
            fi

            echo "generated $normalized_file from $(basename $info)."
            echo "Check variables before using."
            add_nvchecker_section "${normalized_file%.hint}" "$info"
        fi
    else
        echo "Hint file exists: $normalized_file" >&2
        mv "$normalized_file" "${normalized_file}.bak"
        echo "Backed up to: ${normalized_file}.bak" >&2
        # Create new hint file with empty variables
        cat > "$normalized_file" <<EOF
VERSION="${VERSION}"
ARCH="x86_64"
DOWNLOAD=""
MD5SUM=""
DOWNLOAD_x86_64=""
MD5SUM_x86_64=""
EOF
        if [[ $NO_DL -eq 1 ]]; then
            add_nodownload "$normalized_file"
        fi

        echo "Created new hint file [EMPTY]: $normalized_file"

    fi
}

# Emit the TOML section label for a package: bare if the name is a valid
# bare key ([A-Za-z0-9_] only), otherwise double-quoted. nvchecker (and TOML)
# require quoting for names containing '.', '-', etc.
_nvchecker_label() {
    local pkg="$1"
    if [[ "$pkg" =~ ^[A-Za-z0-9_]+$ ]]; then
        printf '[%s]' "$pkg"
    else
        printf '["%s"]' "$pkg"
    fi
}

# Return 0 if NVCHECKER_CONFIG already has a section for pkg (bare or quoted)
_has_nvchecker_section() {
    local pkg="$1"
    [[ -f "$NVCHECKER_CONFIG" ]] || return 1
    local label; label=$(_nvchecker_label "$pkg")
    # fixed-string match of the exact label at line start, trailing space allowed
    grep -qE "^$(printf '%s' "$label" | sed 's/[][\.*^$/]/\\&/g')[[:space:]]*$" \
        "$NVCHECKER_CONFIG"
}

# Append an nvchecker [pkg] section to NVCHECKER_CONFIG, auto-detecting the
# source from the package's .info DOWNLOAD/HOMEPAGE. No-op if section exists.
add_nvchecker_section() {
    local pkg="$1"
    local info_file="$2"

    # Ensure config dir/file exist (do not create __config__; user owns that)
    mkdir -p "$(dirname "$NVCHECKER_CONFIG")"
    touch "$NVCHECKER_CONFIG"

    local label; label=$(_nvchecker_label "$pkg")

    # Skip if section already present
    if _has_nvchecker_section "$pkg"; then
        echo "nvchecker: ${label} already present in $NVCHECKER_CONFIG"
        return 0
    fi

    local download="" homepage=""
    if [[ -f "$info_file" ]]; then
        download=$(grep -E '^(DOWNLOAD|DOWNLOAD_x86_64)=' "$info_file" | head -1)
        homepage=$(grep -E '^HOMEPAGE=' "$info_file" | head -1)
    fi
    local haystack="${download} ${homepage}"

    local section=""
    if [[ "$haystack" =~ github\.com/([A-Za-z0-9._-]+)/([A-Za-z0-9._-]+) ]]; then
        local owner="${BASH_REMATCH[1]}"
        local repo="${BASH_REMATCH[2]}"
        repo="${repo%.git}"
        section=$(cat <<EOF

${label}
source = "github"
github = "${owner}/${repo}"
use_max_tag = true
EOF
)
    elif [[ "$haystack" =~ (pypi\.org|files\.pythonhosted\.org) ]]; then
        section=$(cat <<EOF

${label}
source = "pypi"
pypi = "${pkg}"
EOF
)
    else
        section=$(cat <<EOF

${label}
# TODO: configure nvchecker source for "${pkg}"
# source = "regex"
# url = "..."
# regex = "..."
# see https://nvchecker.readthedocs.io/en/latest/usage.html
EOF
)
    fi

    printf '%s\n' "$section" >> "$NVCHECKER_CONFIG"
    echo "nvchecker: review/fill ${label} section in $NVCHECKER_CONFIG"
}

# Add NODOWNLOAD=yes after MD5SUM_x86_64 line if not already present
add_nodownload() {
    local file="$1"
    if ! grep -q '^NODOWNLOAD=' "$file"; then
        sed -i '/^MD5SUM_x86_64=/a NODOWNLOAD=yes' "$file"
    fi
}

# Parse multiline variable value (handles \ continuation lines)
# Prints each whitespace-separated token on its own line
parse_multiline_var() {
    local varname="$1"
    local file="$2"
    # Join continuation lines, strip variable name and quotes, print one token per line
    awk -v var="${varname}" '
        BEGIN { found=0; buf="" }
        !found && $0 ~ "^"var"=\"" {
            found=1
            buf=$0
            sub("^"var"=\"", "", buf)
            if (buf !~ /\\[[:space:]]*$/) {
                gsub(/"[[:space:]]*$/, "", buf)
                n=split(buf, arr, /[[:space:]]+/)
                for (k=1;k<=n;k++) if(arr[k]!="") print arr[k]
                found=0; buf=""
            } else {
                gsub(/\\[[:space:]]*$/, "", buf)
            }
            next
        }
        found {
            if ($0 ~ /\\[[:space:]]*$/) {
                line=$0; gsub(/\\[[:space:]]*$/, "", line)
                buf=buf" "line
            } else {
                line=$0; gsub(/"[[:space:]]*$/, "", line)
                buf=buf" "line
                n=split(buf, arr, /[[:space:]]+/)
                for (k=1;k<=n;k++) if(arr[k]!="") print arr[k]
                found=0; buf=""
            }
        }
    ' "$file"
}

# Prompt user for updated continuation URLs; returns updated URLs via nameref array
# First URL is always kept as-is (already updated by version sed before this call)
prompt_continuation_urls() {
    local -n _urls="$1"   # nameref: array of current URLs
    local varname="$2"

    local i
    for (( i=1; i<${#_urls[@]}; i++ )); do
        local current="${_urls[$i]}"
        echo ""
        echo "  ${varname} line $((i+1)) (current): $current"
        read -r -p "  New URL (leave blank to keep): " new_url
        if [[ -n "$new_url" ]]; then
            _urls[$i]="$new_url"
        fi
    done
}

# Build multiline variable string for writing back to file
# Usage: build_multiline_value urls_array -> prints quoted multiline value
build_multiline_value() {
    local -n _arr="$1"
    local count=${#_arr[@]}
    local i
    for (( i=0; i<count; i++ )); do
        if (( i == 0 )); then
            printf '"%s' "${_arr[$i]}"
        else
            printf ' \\\n    %s' "${_arr[$i]}"
        fi
    done
    printf '"\n'
}

# Query nvchecker for a package's latest version and let the user accept or
# override it. Echoes the chosen version on stdout. Returns non-zero if the
# user declines or no version is available (caller decides what to do).
suggest_version() {
    local pkg="$1"

    # Refresh nvchecker results (stderr only; keep stdout clean for the echo)
    nvchecker -c "$NVCHECKER_CONFIG" >&2 || true

    local latest
    latest=$(nvchecker_latest "$pkg") || {
        echo "Error: no nvchecker result for '$pkg'. Add/fix its [${pkg}] section in $NVCHECKER_CONFIG" >&2
        return 1
    }

    # Read current version from the hint file (best effort, for display)
    local hintpath="${HINT_DIR%/}/${pkg}.hint"
    local current=""
    [[ -f "$hintpath" ]] && current=$(grep '^VERSION=' "$hintpath" | sed 's/VERSION="//;s/"$//')

    local answer
    read -r -p "current ${current:-?}, latest ${latest}. Use ${latest}? [Y/n] (or type a version) " answer >&2
    answer="${answer:-Y}"
    case "$answer" in
        [Yy]) printf '%s\n' "$latest" ;;
        [Nn]) return 1 ;;
        *)    printf '%s\n' "$answer" ;;
    esac
}

# Download files and update MD5SUM/MD5SUM_x86_64 in hint file
update_checksums() {
    local file="$1"

    _process_download_var "DOWNLOAD" "MD5SUM" "$file"
    _process_download_var "DOWNLOAD_x86_64" "MD5SUM_x86_64" "$file"
}

# Process one DOWNLOAD/MD5SUM variable pair in a hint file
_process_download_var() {
    local dl_var="$1"
    local md5_var="$2"
    local file="$3"

    # Read current URLs into array
    mapfile -t urls < <(parse_multiline_var "$dl_var" "$file")

    [[ ${#urls[@]} -eq 0 ]] && return
    [[ "${urls[0]}" == "UNSUPPORTED" || "${urls[0]}" == "UNTESTED" ]] && return

    # Read current md5sums into array (parallel to urls)
    mapfile -t md5s < <(parse_multiline_var "$md5_var" "$file")

    # Save original URLs for change detection after prompt
    local orig_urls=("${urls[@]}")

    # Prompt user to update continuation URLs if present
    if (( ${#urls[@]} > 1 )); then
        echo ""
        echo "Multiline ${dl_var} detected in $(basename "$file")."
        prompt_continuation_urls urls "$dl_var"
    fi

    # Download and calculate md5 for each URL
    local new_md5s=()
    local i
    for (( i=0; i<${#urls[@]}; i++ )); do
        local url="${urls[$i]}"
        if (( i == 0 )); then
            # Always re-download first URL
            echo "Downloading: $url"
            new_md5s+=( "$(download_file "$url")" )
        else
            # Only re-download if URL changed from original
            if [[ "$url" != "${orig_urls[$i]}" ]]; then
                echo "Downloading (updated): $url"
                new_md5s+=( "$(download_file "$url")" )
            else
                echo "Keeping existing md5 for: $url"
                new_md5s+=( "${md5s[$i]}" )
            fi
        fi
    done

    # Rebuild and write back DOWNLOAD variable (may have updated continuation URLs)
    local new_dl_value
    new_dl_value=$(build_multiline_value urls)
    # Strip surrounding quotes — perl wraps them in the substitution
    new_dl_value="${new_dl_value#\"}"
    new_dl_value="${new_dl_value%\"}"
    perl -i -0pe 'BEGIN{$v=shift} s|^'"${dl_var}"'="[^"]*(?:\\\n[^"]*)*"|'"${dl_var}"'="$v"|m' \
        "$new_dl_value" "$file"

    # Rebuild and write back MD5SUM variable
    local new_md5_value
    new_md5_value=$(build_multiline_value new_md5s)
    new_md5_value="${new_md5_value#\"}"
    new_md5_value="${new_md5_value%\"}"
    perl -i -0pe 'BEGIN{$v=shift} s|^'"${md5_var}"'="[^"]*(?:\\\n[^"]*)*"|'"${md5_var}"'="$v"|m' \
        "$new_md5_value" "$file"
}

# Update existing hint file
update_hint_file() {
    cd "$HINT_DIR"
    local file="$1"
    local new_version="$2"
    local old_version=""

    local normalized_file="${file}"

    if [[ "$file" != *.hint ]]; then
        normalized_file="${file}.hint"
    fi

    # Check if file exists
    if [[ ! -f "$normalized_file" ]]; then
        echo "Error: Hint file does not exist: $normalized_file" >&2
        exit 2
    fi

    # Force backup as precaution before modifying
    echo "Hint file exists: $normalized_file" >&2
    cp "$normalized_file" "${normalized_file}.bak"
    echo "Backed up to: ${normalized_file}.bak" >&2

    # Extract current version from hint file
    old_version=$(grep '^VERSION=' "$normalized_file" | sed 's/VERSION="//;s/"$//')

    # Use sed for global replacement of OLD_VERSION in all variables
    sed -i "s/${old_version}/${new_version}/g" "$normalized_file"

    update_checksums "$normalized_file"

    if [[ $NO_DL -eq 1 ]]; then
        add_nodownload "$normalized_file"
    fi

    hf=$(cat $normalized_file)
    echo "Updated hint file: $normalized_file"
    echo "=========================================="
    echo -n "$hf"
    echo; echo "=========================================="
}

# Prompt to run slackrepo update after a hint file update
prompt_slackrepo() {
    local pkg="$1"
    local answer
    read -r -p "Run 'slackrepo update $pkg'? [Y/n] " answer
    answer="${answer:-Y}"
    if [[ "$answer" =~ ^[Yy]$ ]]; then
        slackrepo update "$pkg"
    fi
}

# Remove a hint file and its .bak if present. No existence guard, no exit —
# safe to call inside loops. Echoes what was removed.
_remove_hint() {
    local full_path="$1"
    rm "$full_path"
    echo "Deleted: $full_path"
    local bak_path="${full_path}.bak"
    if [[ -f "$bak_path" ]]; then
        rm "$bak_path"
        echo "Deleted: $bak_path"
    fi
}

# Delete a hint file (and .bak if present)
delete_hint_file() {
    local file="$1"
    local normalized_file="${file}"

    if [[ "$file" != *.hint ]]; then
        normalized_file="${file}.hint"
    fi

    local full_path="${HINT_DIR}/${normalized_file}"

    if [[ ! -f "$full_path" ]]; then
        echo "Error: Hint file does not exist: $full_path" >&2
        exit 2
    fi

    _remove_hint "$full_path"
}

# Clean .bak files from HINT_DIR
clean_bak_files() {
    if [[ ! -d "$HINT_DIR" ]]; then
        echo "Error: Hint directory does not exist: $HINT_DIR" >&2
        exit 2
    fi

    local count=0
    for bakfile in "$HINT_DIR"/*.bak; do
        if [[ -f "$bakfile" ]]; then
            rm "$bakfile"
            count=$((count + 1))
        fi
    done

    echo "Removed $count .bak file(s) from $HINT_DIR"
}

# Bulk-check hint files for upstream updates and apply interactively.
# Usage: check_updates [pkg...]   (no args = all *.hint in HINT_DIR)
check_updates() {
    check_nvchecker

    if [[ ! -d "$HINT_DIR" ]]; then
        echo "Error: Hint directory does not exist: $HINT_DIR" >&2
        exit 2
    fi

    # Build the target package list
    local targets=()
    if [[ $# -gt 0 ]]; then
        targets=("$@")
    else
        local f
        for f in "$HINT_DIR"/*.hint; do
            [[ -f "$f" ]] || continue
            local b; b=$(basename "$f"); targets+=("${b%.hint}")
        done
    fi

    # Refresh nvchecker results once for everything
    echo "Running nvchecker..."
    nvchecker -c "$NVCHECKER_CONFIG" >&2 || true

    # Classify each target
    local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=()
    local missing_sections=()
    local pkg
    for pkg in "${targets[@]}"; do
        local hintpath="${HINT_DIR%/}/${pkg}.hint"
        [[ -f "$hintpath" ]] || { echo "skip ${pkg}: no hint file"; continue; }
        local current; current=$(grep '^VERSION=' "$hintpath" | sed 's/VERSION="//;s/"$//')
        local latest
        if ! latest=$(nvchecker_latest "$pkg"); then
            if _has_nvchecker_section "$pkg"; then
                echo "skip ${pkg}: no nvchecker result"
            else
                echo "skip ${pkg}: no nvchecker section"
                missing_sections+=("$pkg")
            fi
            continue
        fi
        [[ "$current" == "$latest" ]] && continue   # up to date
        # determine direction with sort -V
        local newest; newest=$(printf '%s\n%s\n' "$current" "$latest" | sort -V | tail -1)
        local flag="update"
        [[ "$newest" == "$current" ]] && flag="?downgrade"
        outdated_pkgs+=("$pkg")
        outdated_old+=("$current")
        outdated_new+=("$latest")
        outdated_flag+=("$flag")
    done

    # Offer to populate nvchecker.toml for packages with no section
    if [[ ${#missing_sections[@]} -gt 0 ]]; then
        echo ""
        echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}"
        local answer
        read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer
        answer="${answer:-Y}"
        if [[ "$answer" =~ ^[Yy]$ ]]; then
            local mp info
            for mp in "${missing_sections[@]}"; do
                info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1)
                if [[ -z "$info" ]]; then
                    echo "skip ${mp}: no .info found in $REPO_DIR"
                    continue
                fi
                add_nvchecker_section "$mp" "$info"
            done
            echo ""
            echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'."
            return 0
        fi
    fi

    if [[ ${#outdated_pkgs[@]} -eq 0 ]]; then
        echo "all up to date"
        return 0
    fi

    # Report
    echo ""
    echo "Updates available:"
    local i
    for (( i=0; i<${#outdated_pkgs[@]}; i++ )); do
        local note=""; [[ "${outdated_flag[$i]}" == "?downgrade" ]] && note=" (?downgrade)"
        printf "  %-30s %s -> %s%s\n" "${outdated_pkgs[$i]}" "${outdated_old[$i]}" "${outdated_new[$i]}" "$note"
    done
    echo ""

    # Per-package confirm + update
    local updated=()
    for (( i=0; i<${#outdated_pkgs[@]}; i++ )); do
        local p="${outdated_pkgs[$i]}"
        local note=""; [[ "${outdated_flag[$i]}" == "?downgrade" ]] && note=" (?downgrade)"
        local answer
        read -r -p "${p}  ${outdated_old[$i]} -> ${outdated_new[$i]}${note}. Update? [Y/n] " answer
        answer="${answer:-Y}"
        if [[ "$answer" =~ ^[Yy]$ ]]; then
            update_hint_file "$p" "${outdated_new[$i]}"
            nvtake -c "$NVCHECKER_CONFIG" "$p" >&2 || true
            updated+=("$p")
        fi
    done

    # Single slackrepo prompt for everything updated
    if [[ ${#updated[@]} -gt 0 ]]; then
        local answer
        read -r -p "Run 'slackrepo update ${updated[*]}'? [Y/n] " answer
        answer="${answer:-Y}"
        if [[ "$answer" =~ ^[Yy]$ ]]; then
            slackrepo update "${updated[@]}"
        fi
    fi
}

# Main function
main() {
    local parsed
    parsed=$(getopt -o v:f:n:lcCdNhR \
        --long version:,hintfile:,new:,list,clean,check,delete,no-dl,help,review \
        -n 'mkhint' -- "$@") || { show_help; exit 1; }
    eval set -- "$parsed"

    while true; do
        case "$1" in
            --version|-v)
                VERSION="$2"
                shift 2
                ;;
            --hintfile|-f)
                HINT_FILE="$2"
                shift 2
                ;;
            --new|-n)
                NEW_HINT_FILE="$2"
                shift 2
                ;;
            --list|-l)
                SHOW_LIST=1
                shift
                ;;
            --review|-R)
                RUN_REVIEW=1
                shift
                ;;
            --clean|-c)
                COMMAND="clean"
                shift
                ;;
            --check|-C)
                COMMAND="check"
                shift
                ;;
            --delete|-d)
                COMMAND="delete"
                shift
                ;;
            --no-dl|-N)
                NO_DL=1
                shift
                ;;
            --help|-h)
                COMMAND="help"
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Unknown option: $1" >&2
                show_help
                exit 1
                ;;
        esac
    done

    # Collect remaining positional args as delete targets
    while [[ $# -gt 0 ]]; do
        DELETE_HINT_FILES+=("$1")
        shift
    done

    if [[ -z "$COMMAND" ]]; then
        # Default to update hint file if VERSION and HINT_FILE are provided
        if [[ -n "$HINT_FILE" ]]; then
            COMMAND="update"
        elif [[ -n "$NEW_HINT_FILE" ]]; then
            COMMAND="new"
        elif [[ ${#DELETE_HINT_FILES[@]} -gt 0 ]]; then
            COMMAND="delete"
        fi
    fi

    if [[ $NO_DL -eq 1 && -z "$HINT_FILE" && -z "$NEW_HINT_FILE" ]]; then
        echo "Error: --no-dl requires --hintfile or --new" >&2
        exit 1
    fi

    if [[ "$COMMAND" == "check" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then
        echo "Error: --check cannot be combined with --version/--hintfile/--new" >&2
        exit 1
    fi

    if [[ -n "$SHOW_LIST" || -n "$RUN_REVIEW" ]]; then
        [[ -n "$SHOW_LIST" ]] && list_hint_files
        if [[ -n "$RUN_REVIEW" ]]; then
            # ensure MATCHED_PKGS is populated even when -l was not given
            [[ -z "$SHOW_LIST" ]] && list_hint_files >/dev/null
            review_hint_files
        fi
        exit $?
    fi

    case "$COMMAND" in
        help)
            show_help
            ;;
        clean)
            clean_bak_files
            ;;
        check)
            check_updates "${DELETE_HINT_FILES[@]}"
            ;;
        update)
            check_wget
            if [[ -z "$VERSION" ]]; then
                check_nvchecker
                VERSION=$(suggest_version "$HINT_FILE") || { echo "Aborted." >&2; exit 0; }
                check_nvchecker_take=1
            fi
            update_hint_file "$HINT_FILE" "$VERSION"
            if [[ "${check_nvchecker_take:-0}" -eq 1 ]]; then
                nvtake -c "$NVCHECKER_CONFIG" "$HINT_FILE" >&2 || true
            fi
            prompt_slackrepo "$HINT_FILE"
            ;;
        new)
            if [[ -n "$VERSION" ]]; then
                check_wget
            fi
            create_new_hint_file "$NEW_HINT_FILE"
            ;;
        delete)
            for f in "${DELETE_HINT_FILES[@]}"; do
                delete_hint_file "$f"
            done
            ;;
        *)
            echo "Error: Unknown command: $COMMAND" >&2
            show_help
            exit 1
            ;;
    esac
}

main "$@"
