#!/bin/sh
# Trunk CLI installer. Served at https://trunk.io/install.sh; run as:
#   curl -fsSL https://trunk.io/install.sh | sh
#   curl -fsSL https://trunk.io/install.sh | sh -s -- --version 1.2.3
# POSIX sh only (this is piped to sh, not bash).
#
# Flags:
#   --version X.Y.Z   install an exact version and pin it (disables self-update)
#   --channel NAME    release channel (default prod; e.g. staging, dev) — recorded for self-update
#   --url-base URL    release host (default https://trunk.io; override only for rehearsal)
#   --install-dir DIR install location (default: /usr/local/bin if writable, else ~/.local/bin)
#   --force           overwrite a pinned install without supplying --version

set -eu

url_base="https://trunk.io"
channel="prod"
version=""
install_dir=""
force=""

while [ $# -gt 0 ]; do
	case "$1" in
	--version)
		version="${2:?--version needs a value}"
		shift 2
		;;
	--channel)
		channel="${2:?--channel needs a value}"
		shift 2
		;;
	--url-base)
		url_base="${2:?--url-base needs a value}"
		shift 2
		;;
	--install-dir)
		install_dir="${2:?--install-dir needs a value}"
		shift 2
		;;
	--force)
		force="yes"
		shift
		;;
	*)
		echo "install.sh: unknown option: $1" >&2
		exit 1
		;;
	esac
done

# --- platform detection ------------------------------------------------------
kernel="$(uname -s | tr '[:upper:]' '[:lower:]')"
machine="$(uname -m)"
case "${machine}" in
aarch64 | arm64) arch="arm64" ;;
x86_64 | amd64) arch="x64" ;;
*)
	echo "trunk: unsupported architecture: ${machine}" >&2
	exit 1
	;;
esac

case "${kernel}" in
darwin)
	# darwin-arm64 only: Intel Macs cannot run arm64 binaries and stay on the
	# legacy CLI, which keeps working unchanged.
	if [ "${arch}" != "arm64" ]; then
		echo "trunk: Intel Macs are not supported by the new CLI; the legacy trunk CLI" >&2
		echo "       continues to work — see docs.trunk.io/cli" >&2
		exit 1
	fi
	# Minimum macOS = the Rust toolchain's aarch64-apple-darwin floor (currently 11).
	if command -v sw_vers >/dev/null 2>&1; then
		osx="$(sw_vers -productVersion 2>/dev/null || echo 0)"
		if [ "${osx%%.*}" -lt 11 ] 2>/dev/null; then
			echo "trunk: macOS ${osx} is below the minimum supported version (11)" >&2
			exit 1
		fi
	fi
	platform="darwin-${arch}"
	;;
# The linux artifact is statically linked against musl, so one build per arch runs
# everywhere (alpine, minimal containers, old glibc) — no libc probe needed.
linux) platform="linux-${arch}" ;;
*)
	echo "trunk: unsupported platform: ${kernel} (linux and macos only)" >&2
	exit 1
	;;
esac

# --- existing-trunk detection ------------------------------------------------
# The new binary reports `trunk x.y.z` (clap's default); the old CLI/launcher reports a bare `x.y.z`.
# If an OLD trunk is already on PATH, don't silently shadow or clobber it.
existing="$(command -v trunk 2>/dev/null || true)"
if [ -n "${existing}" ]; then
	existing_version="$("${existing}" --version 2>/dev/null || true)"
	case "${existing_version}" in
	"trunk "*)
		# A prior new CLI. Never silently unpin a deliberate pin: a trunk.pinned sentinel
		# next to it blocks an unpinned reinstall unless --version or --force is given.
		existing_dir="$(dirname "${existing}")"
		if [ -f "${existing_dir}/trunk.pinned" ] && [ -z "${version}" ] && [ -z "${force}" ]; then
			pinned_to="$(cat "${existing_dir}/trunk.pinned" 2>/dev/null || true)"
			echo "trunk: the install at ${existing} is pinned to ${pinned_to:-an exact version}." >&2
			# curl|sh leaves stdin bound to the pipe, so prompt on the controlling terminal when
			# there is one; with no tty (CI, non-interactive) fall back to a clear instruction.
			if [ -r /dev/tty ]; then
				printf '       Overwrite it and track the latest? [y/N] ' >&2
				read -r reply </dev/tty || reply=""
				case "${reply}" in
				[Yy] | [Yy][Ee][Ss]) ;;
				*)
					echo "       Aborted; the pinned install is unchanged." >&2
					exit 1
					;;
				esac
			else
				echo "       Re-run with --version X.Y.Z to change the pin, or --force to unpin" >&2
				echo "       and track latest." >&2
				exit 1
			fi
		fi
		;;
	*)
		echo "warning: an existing 'trunk' is on your PATH at ${existing}" >&2
		echo "         (${existing_version:-unknown version} — looks like the legacy CLI)." >&2
		echo "         Installing the new CLI now; make sure its install dir comes first on your" >&2
		echo "         PATH (or remove the old one). Continuing." >&2
		;;
	esac
fi

# --- download ----------------------------------------------------------------
installer_ua="trunk-cli-installer/1 (${platform})"
fetch() {
	# $1 = url, $2 = output path
	if command -v curl >/dev/null 2>&1; then
		if ! curl -fsSL -A "${installer_ua}" "$1" -o "$2"; then
			echo "trunk: failed to download $1" >&2
			exit 1
		fi
	elif command -v wget >/dev/null 2>&1; then
		if ! wget -q -U "${installer_ua}" "$1" -O "$2"; then
			echo "trunk: failed to download $1" >&2
			exit 1
		fi
	else
		echo "trunk: need curl or wget to install" >&2
		exit 1
	fi
}

require() {
	command -v "$1" >/dev/null 2>&1 || {
		echo "trunk: '$1' is required to install but was not found" >&2
		exit 1
	}
}
require tar

workdir="$(mktemp -d)"
trap 'rm -rf "${workdir}"' EXIT
trap 'rm -rf "${workdir}"; exit 130' INT
trap 'rm -rf "${workdir}"; exit 143' TERM

pinned=""
if [ -n "${version}" ]; then
	pinned="yes"
else
	fetch "${url_base}/releases/trunk-cli/${channel}/channel.json" "${workdir}/channel.json"
	# Flat, stable JSON by contract — extract with sed, no jq dependency.
	version="$(sed -n 's/.*"latest"[^"]*"\([^"]*\)".*/\1/p' "${workdir}/channel.json" | head -n 1)"
	if [ -z "${version}" ]; then
		echo "trunk: could not resolve the latest version on channel ${channel} from ${url_base}" >&2
		exit 1
	fi
fi

tarball="trunk-${version}-${platform}.tar.gz"
echo "Downloading trunk ${version} (${platform})..."
fetch "${url_base}/releases/trunk-cli/${channel}/${version}/${tarball}" "${workdir}/${tarball}"

# Verify the download's sha256 against the release manifest: a missing sha tool, a missing entry,
# or a mismatch all abort the install (guards against a corrupted/truncated download; origin
# integrity rests on TLS, and the core re-verifies KMS-signed metadata on self-update).
if command -v sha256sum >/dev/null 2>&1; then
	sha_tool="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
	sha_tool="shasum -a 256"
else
	echo "trunk: need sha256sum or shasum to verify the download" >&2
	exit 1
fi
fetch "${url_base}/releases/trunk-cli/${channel}/${version}/manifest.json" "${workdir}/manifest.json"
# Extract the sha for this platform from the pretty-printed manifest the release workflow emits
# (one field per line): awk (POSIX) scans to the platform key, then takes the next sha256 value.
expected="$(awk -v p="\"${platform}\"" '
	index($0, p) { seen = 1 }
	seen && /sha256/ {
		match($0, /"sha256"[^"]*"[0-9a-f]+"/)
		s = substr($0, RSTART, RLENGTH)
		match(s, /[0-9a-f]+"$/)
		print substr(s, RSTART, RLENGTH - 1)
		exit
	}
' "${workdir}/manifest.json")"
actual="$(${sha_tool} "${workdir}/${tarball}" | cut -d' ' -f1)"
if [ -z "${expected}" ] || [ "${expected}" != "${actual}" ]; then
	echo "trunk: sha256 verification failed for ${tarball}" >&2
	echo "  expected: ${expected:-<missing from manifest>}" >&2
	echo "  actual:   ${actual}" >&2
	exit 1
fi

# Extract into the workdir (cd avoids relying on tar's non-POSIX -C).
(cd "${workdir}" && tar -xzf "${tarball}")

# --- install -----------------------------------------------------------------
if [ -z "${install_dir}" ]; then
	if [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then
		install_dir="/usr/local/bin"
	else
		install_dir="${HOME}/.local/bin"
	fi
fi
mkdir -p "${install_dir}"
chmod +x "${workdir}/trunk"

# Pre-link healthcheck: the downloaded binary must report the intended version before it
# replaces anything. `--version` exits before the self-update hook; TRUNK_NO_AUTO_UPDATE keeps
# it that way defensively, so installing never triggers an immediate re-update.
reported="$(TRUNK_NO_AUTO_UPDATE=1 "${workdir}/trunk" --version 2>/dev/null || true)"
case "${reported}" in
"trunk ${version}") ;;
*)
	echo "trunk: downloaded binary failed the healthcheck (reported: ${reported:-nothing})" >&2
	exit 1
	;;
esac

mv -f "${workdir}/trunk" "${install_dir}/trunk"

# Record the channel next to the binary so self-update follows the same one. Prod (the
# default) writes no marker; a non-prod channel writes a marker the core reads.
if [ "${channel}" = "prod" ]; then
	rm -f "${install_dir}/trunk.channel"
else
	printf '%s' "${channel}" >"${install_dir}/trunk.channel"
fi

if [ -n "${pinned}" ]; then
	printf '%s' "${version}" >"${install_dir}/trunk.pinned"
	echo "Pinned to ${version} (self-update disabled; remove ${install_dir}/trunk.pinned to unpin)."
else
	rm -f "${install_dir}/trunk.pinned"
fi

case ":${PATH}:" in
*":${install_dir}:"*) ;;
*)
	echo ""
	echo "note: ${install_dir} is not on your PATH. Add it with:"
	echo "  export PATH=\"${install_dir}:\$PATH\""
	;;
esac

echo ""
TRUNK_NO_AUTO_UPDATE=1 "${install_dir}/trunk" --version
echo "Ready. Run 'trunk login' to get started."
