#!/usr/bin/env sh

set -eu
printf '\n'

BOLD="$(tput bold 2>/dev/null || printf '')"
GREY="$(tput setaf 0 2>/dev/null || printf '')"
UNDERLINE="$(tput smul 2>/dev/null || printf '')"
RED="$(tput setaf 1 2>/dev/null || printf '')"
GREEN="$(tput setaf 2 2>/dev/null || printf '')"
YELLOW="$(tput setaf 3 2>/dev/null || printf '')"
BLUE="$(tput setaf 4 2>/dev/null || printf '')"
MAGENTA="$(tput setaf 5 2>/dev/null || printf '')"
NO_COLOR="$(tput sgr0 2>/dev/null || printf '')"

SUPPORTED_TARGETS="x86_64-unknown-linux-gnu x86_64-unknown-linux-musl \
                  i686-unknown-linux-musl aarch64-unknown-linux-musl \
                  arm-unknown-linux-musleabihf x86_64-apple-darwin \
                  aarch64-apple-darwin x86_64-pc-windows-msvc \
                  i686-pc-windows-msvc aarch64-pc-windows-msvc \
                  x86_64-unknown-freebsd \
                  riscv64gc-unknown-linux-musl"

info() {
	printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*"
}

warn() {
	printf '%s\n' "${YELLOW}! $*${NO_COLOR}"
}

error() {
	printf '%s\n' "${RED}x $*${NO_COLOR}" >&2
}

completed() {
	printf '%s\n' "${GREEN}✓${NO_COLOR} $*"
}

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

curl_is_snap() {
	curl_path="$(command -v curl)"
	case "$curl_path" in
	/snap/*) return 0 ;;
	*) return 1 ;;
	esac
}

# Make sure user is not using zsh or non-POSIX-mode bash, which can cause issues
verify_shell_is_posix_or_exit() {
	if [ -n "${ZSH_VERSION+x}" ]; then
		error "Running installation script with \`zsh\` is known to cause errors."
		error "Please use \`sh\` instead."
		exit 1
	elif [ -n "${BASH_VERSION+x}" ] && [ -z "${POSIXLY_CORRECT+x}" ]; then
		error "Running installation script with non-POSIX \`bash\` may cause errors."
		error "Please use \`sh\` instead."
		exit 1
	else
		true # No-op: no issues detected
	fi
}

get_tmpfile() {
	suffix="$1"
	if has mktemp; then
		printf "%s.%s" "$(mktemp)" "${suffix}"
	else
		# No really good options here--let's pick a default + hope
		printf "/tmp/starship.%s" "${suffix}"
	fi
}

# Test if a location is writable by trying to write to it. Windows does not let
# you test writeability other than by writing: https://stackoverflow.com/q/1999988
test_writable() {
	path="${1:-}/test.txt"
	if touch "${path}" 2>/dev/null; then
		rm "${path}"
		return 0
	else
		return 1
	fi
}

download() {
	file="$1"
	url="$2"

	if has curl && curl_is_snap; then
		warn "curl installed through snap cannot download starship."
		warn "See https://github.com/starship/starship/issues/5403 for details."
		warn "Searching for other HTTP download programs..."
	fi

	if has curl && ! curl_is_snap; then
		cmd="curl --fail --silent --location --output $file $url"
	elif has wget; then
		cmd="wget --quiet --output-document=$file $url"
	elif has fetch; then
		cmd="fetch --quiet --output=$file $url"
	else
		error "No HTTP download program (curl, wget, fetch) found, exiting…"
		return 1
	fi

	$cmd && return 0 || rc=$?

	error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}"
	printf "\n" >&2
	case "${VERSION}" in
	latest) ;;
	v*) ;;
	*)
		info "Note: Release tags include the 'v' prefix (e.g., 'v1.2.3')."
		info "You specified '${VERSION}'. Did you mean 'v${VERSION}'?"
		printf "\n" >&2
		;;
	esac
	info "This is likely due to Starship not yet supporting your configuration."
	info "If you would like to see a build for your configuration,"
	info "please create an issue requesting a build for ${MAGENTA}${TARGET}${NO_COLOR}:"
	info "${BOLD}${UNDERLINE}https://github.com/starship/starship/issues/new/${NO_COLOR}"
	return $rc
}

unpack() {
	archive=$1
	bin_dir=$2
	sudo=${3-}

	case "$archive" in
	*.tar.gz)
		flags=$(test -n "${VERBOSE-}" && echo "-xzvof" || echo "-xzof")
		${sudo} tar "${flags}" "${archive}" -C "${bin_dir}"
		return 0
		;;
	*.zip)
		flags=$(test -z "${VERBOSE-}" && echo "-qqo" || echo "-o")
		UNZIP="${flags}" ${sudo} unzip "${archive}" -d "${bin_dir}"
		return 0
		;;
	esac

	error "Unknown package extension."
	printf "\n"
	info "This almost certainly results from a bug in this script--please file a"
	info "bug report at https://github.com/starship/starship/issues"
	return 1
}

usage() {
	printf "%s\n" \
		"install.sh [option]" \
		"" \
		"Fetch and install the latest version of starship, if starship is already" \
		"installed it will be updated to the latest version."

	printf "\n%s\n" "Options"
	printf "\t%s\n\t\t%s\n\n" \
		"-V, --verbose" "Enable verbose output for the installer" \
		"-f, -y, --force, --yes" "Skip the confirmation prompt during installation" \
		"-p, --platform" "Override the platform identified by the installer [default: ${PLATFORM}]" \
		"-b, --bin-dir" "Override the bin installation directory [default: ${BIN_DIR}]" \
		"-a, --arch" "Override the architecture identified by the installer [default: ${ARCH}]" \
		"-B, --base-url" "Override the base URL used for downloading releases [default: ${BASE_URL}]" \
		"-v, --version" "Install a specific version of starship (e.g. v1.2.3) [default: ${VERSION}]" \
		"-h, --help" "Display this help message"
}

elevate_priv() {
	if ! has sudo; then
		error 'Could not find the command "sudo", needed to get permissions for install.'
		info "If you are on Windows, please run your shell as an administrator, then"
		info "rerun this script. Otherwise, please run this script as root, or install"
		info "sudo."
		exit 1
	fi
	if ! sudo -v; then
		error "Superuser not granted, aborting installation"
		exit 1
	fi
}

install() {
	ext="$1"

	if test_writable "${BIN_DIR}"; then
		sudo=""
		msg="Installing Starship, please wait…"
	else
		warn "Escalated permissions are required to install to ${BIN_DIR}"
		elevate_priv
		sudo="sudo"
		msg="Installing Starship as root, please wait…"
	fi
	info "$msg"

	archive=$(get_tmpfile "$ext")

	# download to the temp file
	download "${archive}" "${URL}"

	# unpack the temp file to the bin dir, using sudo if required
	unpack "${archive}" "${BIN_DIR}" "${sudo}"
}

# Currently supporting:
#   - win (Git Bash)
#   - darwin
#   - linux
#   - linux_musl (Alpine)
#   - freebsd
detect_platform() {
	platform="$(uname -s | tr '[:upper:]' '[:lower:]')"

	case "${platform}" in
	msys_nt*) platform="pc-windows-msvc" ;;
	cygwin_nt*) platform="pc-windows-msvc" ;;
	# mingw is Git-Bash
	mingw*) platform="pc-windows-msvc" ;;
	# use the statically compiled musl bins on linux to avoid linking issues.
	linux) platform="unknown-linux-musl" ;;
	darwin) platform="apple-darwin" ;;
	freebsd) platform="unknown-freebsd" ;;
	esac

	printf '%s' "${platform}"
}

# Currently supporting:
#   - x86_64
#   - i386
#   - arm
#   - arm64
detect_arch() {
	arch="$(uname -m | tr '[:upper:]' '[:lower:]')"

	case "${arch}" in
	amd64) arch="x86_64" ;;
	armv*) arch="arm" ;;
	arm64) arch="aarch64" ;;
	riscv64) arch="riscv64gc" ;;
	esac

	# `uname -m` in some cases mis-reports 32-bit OS as 64-bit, so double check
	if [ "${arch}" = "x86_64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then
		arch=i686
	elif [ "${arch}" = "aarch64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then
		arch=arm
	fi

	printf '%s' "${arch}"
}

detect_target() {
	arch="$1"
	platform="$2"
	target="$arch-$platform"

	if [ "${target}" = "arm-unknown-linux-musl" ]; then
		target="${target}eabihf"
	fi

	printf '%s' "${target}"
}

confirm() {
	if [ -z "${FORCE-}" ]; then
		printf "%s " "${MAGENTA}?${NO_COLOR} $* ${BOLD}[y/N]${NO_COLOR}"
		set +e
		read -r yn </dev/tty
		rc=$?
		set -e
		if [ $rc -ne 0 ]; then
			error "Error reading from prompt (please re-run with the '--yes' option)"
			exit 1
		fi
		if [ "$yn" != "y" ] && [ "$yn" != "yes" ]; then
			error 'Aborting (please answer "yes" to continue)'
			exit 1
		fi
	fi
}

check_bin_dir() {
	bin_dir="${1%/}"

	if [ ! -d "$BIN_DIR" ]; then
		error "Installation location $BIN_DIR does not appear to be a directory"
		info "Make sure the location exists and is a directory, then try again."
		usage
		exit 1
	fi

	# https://stackoverflow.com/a/11655875
	good=$(
		IFS=:
		for path in $PATH; do
			if [ "${path%/}" = "${bin_dir}" ]; then
				printf 1
				break
			fi
		done
	)

	if [ "${good}" != "1" ]; then
		warn "Bin directory ${bin_dir} is not in your \$PATH"
	fi
}

print_install() {
	# if the shell does not fit the default case change the config file
	# and or the config cmd variable
	for s in "bash" "zsh" "ion" "tcsh" "xonsh" "fish"; do
		# shellcheck disable=SC2088
		# we don't want these '~' expanding
		config_file="~/.${s}rc"
		config_cmd="eval \"\$(starship init ${s})\""

		case ${s} in
		ion)
			# shellcheck disable=SC2088
			config_file="~/.config/ion/initrc"
			config_cmd="eval \$(starship init ${s})"
			;;
		fish)
			# shellcheck disable=SC2088
			config_file="~/.config/fish/config.fish"
			config_cmd="starship init fish | source"
			;;
		tcsh)
			config_cmd="eval \`starship init ${s}\`"
			;;
		xonsh)
			config_cmd="execx(\$(starship init xonsh))"
			;;
		esac

		printf "  %s\n  Add the following to the end of %s:\n\n\t%s\n\n" \
			"${BOLD}${UNDERLINE}${s}${NO_COLOR}" \
			"${BOLD}${config_file}${NO_COLOR}" \
			"${config_cmd}"
	done

	for s in "elvish" "nushell"; do

		warning="${BOLD}Warning${NO_COLOR}"
		case ${s} in
		elvish)
			# shellcheck disable=SC2088
			config_file="~/.config/elvish/rc.elv"
			config_cmd="eval (starship init elvish)"
			warning="${warning} Only elvish v0.17 or higher is supported."
			;;
		nushell)
			# shellcheck disable=SC2088
			config_file="${BOLD}your nu config file${NO_COLOR} (find it by running ${BOLD}\$nu.config-path${NO_COLOR} in Nushell)"
			config_cmd="mkdir (\$nu.data-dir | path join \"vendor/autoload\")
        starship init nu | save -f (\$nu.data-dir | path join \"vendor/autoload/starship.nu\")"
			warning="${warning} This will change in the future.
  Only Nushell v0.96 or higher is supported."
			;;
		esac
		printf "  %s\n  %s\n  And add the following to the end of %s:\n\n\t%s\n\n" \
			"${BOLD}${UNDERLINE}${s}${NO_COLOR}" \
			"${warning}" \
			"${config_file}" \
			"${config_cmd}"
	done

	printf "  %s\n  Add the following to the end of %s:\n  %s\n\n\t%s\n\n" \
		"${BOLD}${UNDERLINE}PowerShell${NO_COLOR}" \
		"${BOLD}Microsoft.PowerShell_profile.ps1${NO_COLOR}" \
		"You can check the location of this file by querying the \$PROFILE variable in PowerShell.
  Typically the path is ~\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 or ~/.config/powershell/Microsoft.PowerShell_profile.ps1 on -Nix." \
		"Invoke-Expression (&starship init powershell)"

	printf "  %s\n  You need to use Clink (v1.2.30+) with Cmd. Add the following to a file %s and place this file in Clink scripts directory:\n\n\t%s\n\n" \
		"${BOLD}${UNDERLINE}Cmd${NO_COLOR}" \
		"${BOLD}starship.lua${NO_COLOR}" \
		"load(io.popen('starship init cmd'):read(\"*a\"))()"

	printf "\n"
}

is_build_available() {
	arch="$1"
	platform="$2"
	target="$3"

	good=$(
		IFS=" "
		for t in $SUPPORTED_TARGETS; do
			if [ "${t}" = "${target}" ]; then
				printf 1
				break
			fi
		done
	)

	if [ "${good}" != "1" ]; then
		error "${arch} builds for ${platform} are not yet available for Starship"
		printf "\n" >&2
		info "If you would like to see a build for your configuration,"
		info "please create an issue requesting a build for ${MAGENTA}${target}${NO_COLOR}:"
		info "${BOLD}${UNDERLINE}https://github.com/starship/starship/issues/new/${NO_COLOR}"
		printf "\n"
		exit 1
	fi
}

# defaults
if [ -z "${PLATFORM-}" ]; then
	PLATFORM="$(detect_platform)"
fi

if [ -z "${BIN_DIR-}" ]; then
	BIN_DIR=/usr/local/bin
fi

if [ -z "${ARCH-}" ]; then
	ARCH="$(detect_arch)"
fi

if [ -z "${BASE_URL-}" ]; then
	BASE_URL="https://github.com/starship/starship/releases"
fi

if [ -z "${VERSION-}" ]; then
	VERSION="latest"
fi

# Non-POSIX shells can break once executing code due to semantic differences
verify_shell_is_posix_or_exit

# parse argv variables
while [ "$#" -gt 0 ]; do
	case "$1" in
	-p | --platform)
		PLATFORM="$2"
		shift 2
		;;
	-b | --bin-dir)
		BIN_DIR="$2"
		shift 2
		;;
	-a | --arch)
		ARCH="$2"
		shift 2
		;;
	-B | --base-url)
		BASE_URL="$2"
		shift 2
		;;
	-v | --version)
		VERSION="$2"
		shift 2
		;;

	-V | --verbose)
		VERBOSE=1
		shift 1
		;;
	-f | -y | --force | --yes)
		FORCE=1
		shift 1
		;;
	-h | --help)
		usage
		exit
		;;

	-p=* | --platform=*)
		PLATFORM="${1#*=}"
		shift 1
		;;
	-b=* | --bin-dir=*)
		BIN_DIR="${1#*=}"
		shift 1
		;;
	-a=* | --arch=*)
		ARCH="${1#*=}"
		shift 1
		;;
	-B=* | --base-url=*)
		BASE_URL="${1#*=}"
		shift 1
		;;
	-v=* | --version=*)
		VERSION="${1#*=}"
		shift 1
		;;
	-V=* | --verbose=*)
		VERBOSE="${1#*=}"
		shift 1
		;;
	-f=* | -y=* | --force=* | --yes=*)
		FORCE="${1#*=}"
		shift 1
		;;

	*)
		error "Unknown option: $1"
		usage
		exit 1
		;;
	esac
done

TARGET="$(detect_target "${ARCH}" "${PLATFORM}")"

is_build_available "${ARCH}" "${PLATFORM}" "${TARGET}"

printf "  %s\n" "${UNDERLINE}Configuration${NO_COLOR}"
info "${BOLD}Bin directory${NO_COLOR}: ${GREEN}${BIN_DIR}${NO_COLOR}"
info "${BOLD}Platform${NO_COLOR}:      ${GREEN}${PLATFORM}${NO_COLOR}"
info "${BOLD}Arch${NO_COLOR}:          ${GREEN}${ARCH}${NO_COLOR}"

# non-empty VERBOSE enables verbose untarring
if [ -n "${VERBOSE-}" ]; then
	VERBOSE=v
	info "${BOLD}Verbose${NO_COLOR}: yes"
else
	VERBOSE=
fi

printf '\n'

EXT=tar.gz
if [ "${PLATFORM}" = "pc-windows-msvc" ]; then
	EXT=zip
fi

if [ "${VERSION}" != "latest" ]; then
	URL="${BASE_URL}/download/${VERSION}/starship-${TARGET}.${EXT}"
else
	URL="${BASE_URL}/latest/download/starship-${TARGET}.${EXT}"
fi

info "Tarball URL: ${UNDERLINE}${BLUE}${URL}${NO_COLOR}"
confirm "Install Starship ${GREEN}${VERSION}${NO_COLOR} to ${BOLD}${GREEN}${BIN_DIR}${NO_COLOR}?"
check_bin_dir "${BIN_DIR}"

install "${EXT}"
completed "Starship ${VERSION} installed"

printf '\n'
info "Please follow the steps for your shell to complete the installation:"

print_install
