#!/bin/sh
# script/bootstrap: install all dependencies needed to build and develop
# this repo. Base tooling (git, make, go) comes from nix, apt, brew, or
# apk (detected in that order) and is installed only when absent;
# assumes nothing is present. golangci-lint and goimports are always
# (re)installed via `go install` at the same pinned commits the
# Dockerfile uses (never "latest") -- a presence check cannot tell the
# pinned build from an arbitrary one already on PATH, so guarding them
# would make the pins inert. Idempotent either way: running this twice
# succeeds both times and leaves the same result.
set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"

# Pinned versions, 2026-08-07 (same pins as the Dockerfile)
# golangci-lint v2.12.2
GOLANGCI_LINT_REF="github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5"
# goimports v0.42.0
GOIMPORTS_REF="golang.org/x/tools/cmd/goimports@009367f5c17a8d4c45a961a3a509277190a9a6f0"

PKGMGR=""
SUDO=""
APT_UPDATED=""

detect_pkgmgr() {
    [ -n "$PKGMGR" ] && return 0
    if command -v nix-env >/dev/null 2>&1; then
        PKGMGR="nix"
    elif command -v apt-get >/dev/null 2>&1; then
        PKGMGR="apt"
    elif command -v brew >/dev/null 2>&1; then
        PKGMGR="brew"
    elif command -v apk >/dev/null 2>&1; then
        PKGMGR="apk"
    else
        echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
        exit 1
    fi
    if [ "$PKGMGR" = "apt" ]; then
        export DEBIAN_FRONTEND=noninteractive
        if [ "$(id -u)" != "0" ]; then
            SUDO="sudo"
        fi
    fi
}

# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
pkg_install() {
    detect_pkgmgr
    case "$PKGMGR" in
        nix) nix-env -iA "nixpkgs.$1" ;;
        apt)
            if [ -z "$APT_UPDATED" ]; then
                $SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
                APT_UPDATED=1
            fi
            $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2"
            ;;
        brew) brew install "$3" ;;
        apk) apk add --no-cache "$4" ;;
    esac
}

missing() {
    ! command -v "$1" >/dev/null 2>&1
}

# go_bin_dir: directory `go install` writes binaries to.
go_bin_dir() {
    gobin="$(go env GOBIN)"
    if [ -n "$gobin" ]; then
        echo "$gobin"
    else
        echo "$(go env GOPATH)/bin"
    fi
}

# warn_if_shadowed <tool> <dir>: the pinned build was just installed
# into <dir>. If PATH resolves <tool> anywhere else, that other copy is
# what `make lint` and `make fmt` will actually run, and it is not the
# pinned version. Warn loudly rather than failing, since the fix is the
# user's PATH and not anything this script can do.
warn_if_shadowed() {
    resolved="$(command -v "$1" 2>/dev/null || true)"
    if [ "$resolved" != "$2/$1" ]; then
        echo "bootstrap: WARNING: installed pinned $1 to $2/$1, but PATH" >&2
        echo "bootstrap: WARNING: resolves $1 to ${resolved:-(not on PATH)};" >&2
        echo "bootstrap: WARNING: put $2 first on PATH or lint results will" >&2
        echo "bootstrap: WARNING: not match CI." >&2
    fi
}

main() {
    cd "$ROOT"

    if missing git; then pkg_install git git git git; fi
    if missing make; then pkg_install gnumake make make make; fi
    if missing go; then pkg_install go golang go go; fi

    # Lint/format tools, pinned via go install. These are installed
    # unconditionally: `command -v` only proves *some* build is on PATH,
    # and a wrong golangci-lint either cannot parse our v2-schema
    # .golangci.yml at all or silently disagrees with CI. Installing at
    # a fixed commit ref is idempotent and cheap with a warm module
    # cache, so there is nothing to save by skipping it.
    GOBIN_DIR="$(go_bin_dir)"
    go install "$GOLANGCI_LINT_REF"
    go install "$GOIMPORTS_REF"
    warn_if_shadowed golangci-lint "$GOBIN_DIR"
    warn_if_shadowed goimports "$GOBIN_DIR"

    go mod download

    echo "bootstrap complete"
}

main "$@"
