# shellcheck shell=bash
# Shared lazy-install logic sourced by each tool wrapper.
# Reads version pins from /etc/devcage/versions.conf (mounted from host).

# ── Version pin lookup ──────────────────────────────────────────
get_version_pin() {
    local key="$1"
    local conf="/etc/devcage/versions.conf"
    [ -f "$conf" ] || return 0
    local val
    val="$(awk -F= -v k="$key" '{gsub(/\r/,"")} $1 == k {print substr($0, index($0,"=")+1)}' "$conf" 2>/dev/null | tail -1)"
    if [ "$val" != "" ] && ! [[ "$val" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
        echo "Warning: rejecting version pin '${key}=${val}' — invalid characters" >&2
        return 0
    fi
    printf '%s' "$val"
}

# ── Strip wrapper directory from PATH ────────────────────────────
# Returns a PATH string with $HOME/.devcage/bin removed, handling
# all positions: beginning, middle, end, sole entry, and duplicates.
_path_without_wrappers() {
    local _wdir="$HOME/.devcage/bin"
    local _p=":${PATH}:"
    _p="${_p//":${_wdir}:"/:}"
    _p="${_p#:}"; _p="${_p%:}"
    printf '%s' "$_p"
}

# ── Resolve real binary and exec ─────────────────────────────────
# Args: <wrapper_name> <exec_binary> [args...]
_exec_real() {
    local _wrapper="$1" _bin="$2"; shift 2
    local _real
    _real="$(PATH="$(_path_without_wrappers)" command -v "$_bin" 2>/dev/null || true)"
    if [ "$_real" = "" ] || [ ! -x "$_real" ]; then
        echo "Error: ${_bin} not found after install — wrapper kept for retry" >&2
        return 1
    fi
    # NOTE: The wrapper is intentionally kept in place.  Removing it
    # breaks the parent shell's command hash table — bash caches the
    # wrapper path and returns "No such file or directory" on the next
    # invocation.  The wrapper is a fast pass-through once the real
    # binary is installed.
    exec "$_real" "$@"
}

# ── GitHub release binary → ~/.local/bin/ ───────────────────────
# Args: <repo> <binary_name> <version_key> <url_pattern> [tar_path]
#   url_pattern placeholders: {ver} (tag as-is, e.g. v1.2.3 or 1.2.3),
#   {rawver} (no v prefix), {arch} (amd64/arm64), {xarch} (x86_64/aarch64)
#   tar_path: if set, extract from tarball (glob pattern); if empty, raw binary
# SECURITY: Downloads use HTTPS (transport integrity) + best-effort checksum verification.
# Many GitHub releases publish checksums.txt or SHA256SUMS alongside binaries.
# Pin versions via versions.conf to reduce exposure to supply-chain attacks.

# Attempt checksum verification if a sums file exists (best-effort)
_verify_checksum_best_effort() {
    local bin="$1" repo="$2" ver="$3" dl_path="$4"
    local sums_url sums_file sha_expected sha_actual
    # Try common checksum file names
    for sums_name in "checksums.txt" "SHA256SUMS" "${bin}_${ver#v}_checksums.txt"; do
        sums_url="https://github.com/${repo}/releases/download/${ver}/${sums_name}"
        sums_file="$(mktemp "${TMPDIR:-/tmp}/devcage-sums-XXXXXXXX")"
        if curl -fsSL "$sums_url" -o "$sums_file" 2>/dev/null; then
            sha_expected="$(grep -i "$(basename "$dl_path")" "$sums_file" | awk '{print $1}' | head -1)"
            if [ "$sha_expected" != "" ]; then
                sha_actual="$(sha256sum "$dl_path" | awk '{print $1}')"
                rm -f "$sums_file"
                if [ "$sha_expected" = "$sha_actual" ]; then
                    return 0  # verified
                else
                    echo "Error: checksum mismatch for ${bin} (expected ${sha_expected}, got ${sha_actual})" >&2
                    return 1  # mismatch
                fi
            fi
        fi
        rm -f "$sums_file"
    done
    return 0  # no checksum file found — allow (best-effort)
}

install_github_release() {
    local repo="$1" bin="$2" ver_key="$3" url_pattern="$4" tar_path="${5:-}"
    if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        return 0
    fi
    # Serialize concurrent installs of the same binary
    local _lockfile="${TMPDIR:-/tmp}/devcage-install-${bin}.lock"
    (
        flock -w 120 9 || { echo "Error: timed out waiting for install lock for ${bin}" >&2; return 1; }
        # Re-check after acquiring lock (another process may have installed it)
        if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
            return 0
        fi
        local rawver; rawver="$(get_version_pin "$ver_key")"
    rawver="${rawver#v}"  # strip leading v to prevent double-v in URLs
    local ver
    if [ "$rawver" != "" ]; then
        # Pinned version: construct tag. URL patterns using {ver} get "v<rawver>",
        # patterns using {rawver} get the bare number. Repos with bare tags (no v)
        # should use {rawver} in their URL patterns.
        ver="v${rawver}"
    else
        ver="$(curl -fsSI -o /dev/null -w '%{redirect_url}' \
          "https://github.com/${repo}/releases/latest" \
          | grep -oP '[^/]+$')" || true
        rawver="${ver#v}"
    fi
    if [ "$ver" = "" ] || [ "$rawver" = "" ]; then
        echo "Error: could not determine version for ${bin} (${repo})" >&2
        return 1
    fi
    local arch; arch="$([ "$(uname -m)" = "aarch64" ] && echo "arm64" || echo "amd64")"
    local xarch; xarch="$([ "$(uname -m)" = "aarch64" ] && echo "aarch64" || echo "x86_64")"
    local url="$url_pattern"
    url="${url//\{ver\}/$ver}"
    url="${url//\{rawver\}/$rawver}"
    url="${url//\{arch\}/$arch}"
    url="${url//\{xarch\}/$xarch}"
    echo "Installing ${bin} ${ver}..." >&2
    mkdir -p "$HOME/.local/bin" 2>/dev/null || true
    if [ "$tar_path" != "" ]; then
        local _dl _found _rc=0
        _dl="$(mktemp -d "${TMPDIR:-/tmp}/devcage-dl-XXXXXXXX")" || { echo "Error: mktemp failed" >&2; return 1; }
        # shellcheck disable=SC2064
        trap "rm -rf '${_dl}'" RETURN
        curl -fsSL "$url" -o "$_dl/archive.tar.gz" && \
        _verify_checksum_best_effort "$bin" "$repo" "$ver" "$_dl/archive.tar.gz" && \
        tar --no-same-owner -xzf "$_dl/archive.tar.gz" -C "$_dl" && \
        _found="$(find "$_dl" -name "$bin" -type f ! -type l | head -1)" && \
        [ "$_found" != "" ] && cp "$_found" "$HOME/.local/bin/$bin" && \
        chmod +x "$HOME/.local/bin/$bin" || _rc=$?
        if [ "$_rc" -eq 0 ] && [ ! -s "$HOME/.local/bin/$bin" ]; then
            echo "Error: downloaded ${bin} binary is empty" >&2
            rm -f "$HOME/.local/bin/$bin"
            _rc=1
        fi
        rm -rf "$_dl"
        trap - RETURN
        return "$_rc"
    else
        # Atomic download: temp file in same dir ensures mv is same-filesystem
        local _tmpbin _rc=0
        _tmpbin="$(mktemp "$HOME/.local/bin/.${bin}-XXXXXXXX")" || { echo "Error: mktemp failed" >&2; return 1; }
        curl -fsSL "$url" -o "$_tmpbin" || _rc=$?
        if [ "$_rc" -eq 0 ] && [ -s "$_tmpbin" ]; then
            _verify_checksum_best_effort "$bin" "$repo" "$ver" "$_tmpbin" || _rc=$?
        fi
        if [ "$_rc" -eq 0 ]; then
            chmod +x "$_tmpbin" || _rc=$?
        fi
        if [ "$_rc" -ne 0 ] || [ ! -s "$_tmpbin" ]; then
            [ "$_rc" -eq 0 ] && echo "Error: downloaded ${bin} binary is empty" >&2
            rm -f "$_tmpbin"
            return 1
        fi
        mv -f "$_tmpbin" "$HOME/.local/bin/$bin" || { rm -f "$_tmpbin"; return 1; }
        return 0
    fi
    ) 9>"$_lockfile"
}

# ── Install lock helper ─────────────────────────────────────────
# Serializes concurrent installs of the same binary (flock pattern
# matching install_github_release). Usage:
#   _with_install_lock <binary_name> <install_function> [args...]
_with_install_lock() {
    local _lock_bin="$1"; shift
    local _lockfile="${TMPDIR:-/tmp}/devcage-install-${_lock_bin}.lock"
    (
        flock -w 120 9 || { echo "Error: timed out waiting for install lock for ${_lock_bin}" >&2; return 1; }
        # Re-check after acquiring lock (another process may have installed it)
        if PATH="$(_path_without_wrappers)" command -v "$_lock_bin" >/dev/null 2>&1; then
            return 0
        fi
        "$@"
    ) 9>"$_lockfile"
}

# ── npm global package ──────────────────────────────────────────
# Args: <npm_package> <binary_name> <version_key>
install_npm() {
    local pkg="$1" bin="$2" ver_key="$3"
    if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        return 0
    fi
    _with_install_lock "$bin" _install_npm_inner "$pkg" "$bin" "$ver_key"
}
_install_npm_inner() {
    local pkg="$1" bin="$2" ver_key="$3"
    local ver; ver="$(get_version_pin "$ver_key")"
    local spec="${pkg}${ver:+@${ver}}"
    echo "Installing ${spec}..." >&2
    npm install -g "$spec" --silent 2>&1 | grep -vE '^(added|removed|changed|audited|up to date)' || true
}

# ── pip user package ────────────────────────────────────────────
# Args: <pip_package> <binary_name> <version_key>
# Uses --only-binary :all: to prevent setup.py execution (supply-chain
# mitigation). Full hash verification is impractical for lazy-installed
# tools with transitive deps.
install_pip() {
    local pkg="$1" bin="$2" ver_key="$3"
    if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        return 0
    fi
    _with_install_lock "$bin" _install_pip_inner "$pkg" "$bin" "$ver_key"
}
_install_pip_inner() {
    local pkg="$1" bin="$2" ver_key="$3"
    local ver; ver="$(get_version_pin "$ver_key")"
    local spec="${pkg}${ver:+==${ver}}"
    echo "Installing ${spec}..." >&2
    PIP_REQUIRE_HASHES=0 pip install --user --quiet --only-binary :all: "$spec"
}

# ── Go module binary ───────────────────────────────────────────
# Args: <module_path> <binary_name> <version_key>
install_go() {
    local mod="$1" bin="$2" ver_key="$3"
    if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        return 0
    fi
    if ! command -v go >/dev/null 2>&1; then
        echo "Error: Go toolchain not available (Go not included? See --lang flag). Cannot install $bin." >&2
        return 1
    fi
    _with_install_lock "$bin" _install_go_inner "$mod" "$bin" "$ver_key"
}
_install_go_inner() {
    local mod="$1" bin="$2" ver_key="$3"
    local ver; ver="$(get_version_pin "$ver_key")"
    echo "Installing ${mod}@${ver:+v}${ver:-latest}..." >&2
    go install "${mod}@${ver:+v}${ver:-latest}" 2>&1 | grep -vE '^(go: downloading|go: finding)' || true
}

# ── Homebrew formula ────────────────────────────────────────────
# Args: <formula> <binary_name>
# No version pinning (Homebrew doesn't support it cleanly).
install_brew() {
    local formula="$1" bin="$2"
    if PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        return 0
    fi
    _with_install_lock "$bin" _install_brew_inner "$formula" "$bin"
}
_install_brew_inner() {
    local formula="$1" bin="$2"
    echo "Installing ${formula} via Homebrew..." >&2
    brew install "$formula" --quiet 2>&1 | grep -vE '^(==>|Pouring|🍺)' || true
}

# ── SDKMAN tool ──────────────────────────────────────────────────
# Args: <sdk_candidate> <binary_name> <wrapper_name> [args...]
# Ensures SDKMAN is installed, sources it, installs the candidate
# if the binary isn't available, then exec's the real binary.
install_sdkman_tool() {
    local candidate="$1" bin="$2" wrapper="$3"; shift 3
    local SDKMAN_INIT="${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh"
    local _expected_marker="${SDKMAN_DIR:-$HOME/.sdkman}/.devcage-sdkman-installed"
    if [ ! -s "$SDKMAN_INIT" ] && [ ! -f "$_expected_marker" ]; then
        echo "Installing SDKMAN..." >&2
        local _sdkman_installer
        _sdkman_installer="$(mktemp "${TMPDIR:-/tmp}/sdkman-install-XXXXXXXX.sh")"
        trap 'rm -f "$_sdkman_installer"' RETURN
        curl -fsSL "https://get.sdkman.io?rcupdate=false" -o "$_sdkman_installer"
        # Basic sanity: must be a shell script, not an error page
        if ! head -1 "$_sdkman_installer" | grep -q '^#!'; then
            echo "Error: SDKMAN installer does not look like a shell script" >&2
            rm -f "$_sdkman_installer"
            trap - RETURN
            return 1
        fi
        SDKMAN_QUIET_MODE=true bash "$_sdkman_installer" 2>&1 | grep -vE '^(Downloading|Installing|All done!)' || true
        rm -f "$_sdkman_installer"
        trap - RETURN
        touch "$_expected_marker"
        SDKMAN_INIT="${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh"
    fi
    # shellcheck disable=SC1090
    . "$SDKMAN_INIT"
    if ! PATH="$(_path_without_wrappers)" command -v "$bin" >/dev/null 2>&1; then
        echo "Installing ${candidate} via SDKMAN..." >&2
        SDKMAN_QUIET_MODE=true sdk install "$candidate" 2>&1 | grep -vE '^(Downloading|Installing|Repackaging|Done)' || true
    fi
    _exec_real "$wrapper" "$bin" "$@"
}
