#!/usr/bin/env bash

# We don't need return codes for "$(command)", only stdout is needed.
# Allow `[[ -n "$(command)" ]]`, `func "$(command)"`, pipes, etc.
# shellcheck disable=SC2312

set -uo pipefail

# global-scope vars
REQUIRED_NVIM_VERSION=0.11.0
REQUIRED_NVIM_VERSION_LEGACY=0.10.0
USE_SSH=1
CLONE_ATTR=("--progress")
DEST_DIR="${HOME}/.config/nvim"
BACKUP_DIR="${DEST_DIR}_backup-$(date +%Y%m%dT%H%M%S)"

abort() {
	printf "%s\n" "$@" >&2
	exit 1
}

# Fail fast with a concise message when not using bash
# Single brackets are needed here for POSIX compatibility
# shellcheck disable=SC2292
if [ -z "${BASH_VERSION:-}" ]; then
	abort "Bash is required to interpret this script."
fi

# Check if script is run with force-interactive mode in CI
if [[ -n "${CI-}" && -n "${INTERACTIVE-}" ]]; then
	abort "Cannot run force-interactive mode in CI."
fi

# String formatters
if [[ -t 1 ]]; then
	tty_escape() { printf "\033[%sm" "$1"; }
else
	tty_escape() { :; }
fi

tty_mkbold() { tty_escape "1;$1"; }
tty_underline="$(tty_escape "4;39")"
tty_yellow="$(tty_escape "0;33")"
tty_blue="$(tty_mkbold 34)"
tty_red="$(tty_mkbold 31)"
tty_bold="$(tty_mkbold 39)"
tty_reset="$(tty_escape 0)"

shell_join() {
	local arg
	printf "%s" "$1"
	shift
	for arg in "$@"; do
		printf " "
		printf "%s" "${arg// /\ }"
	done
}

execute() {
	if ! "$@"; then
		abort "$(printf "Failed during: %s" "$(shell_join "$@")")"
	fi
}

major_minor() {
	echo "${1%%.*}.$(
		x="${1#*.}"
		echo "${x%%.*}"
	)"
}

chomp() {
	printf "%s" "${1/"$'\n'"/}"
}

info() {
	printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")"
}

info_ext() {
	printf "${tty_bold}    %s${tty_reset}\n" "$(shell_join "$@")"
}

warn() {
	printf "${tty_yellow}Warning:${tty_reset} %s\n" "$(chomp "$1")"
}

warn_ext() {
	printf "         %s\n" "$(chomp "$1")"
}

getc() {
	local save_state
	save_state="$(/bin/stty -g)"
	/bin/stty raw -echo
	IFS='' read -r -n 1 -d '' "$@"
	/bin/stty "${save_state}"
}

ring_bell() {
	# Use the shell's audible bell
	if [[ -t 1 ]]; then
		printf "\a"
	fi
}

wait_for_user() {
	local c
	printf "\n"
	echo "Press ${tty_bold}RETURN${tty_reset}/${tty_bold}ENTER${tty_reset} to continue or any other key to abort..."
	getc c
	# we test for \r and \n because some stuff does \r instead
	if ! [[ "$c" == $'\r' || "$c" == $'\n' ]]; then
		abort "${tty_red}Aborted.${tty_reset}"
	fi
}

version_ge() {
	[[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]]
}

prompt_confirm() {
	local choice
	while true; do
		read -r -p "$1 [Y/n]: " choice
		case "$choice" in
		[yY][eE][sS] | [yY])
			return 1
			;;
		[nN][oO] | [nN])
			return 0
			;;
		*)
			if [[ -z "$choice" ]]; then
				return 1
			fi
			printf "${tty_red}%s\n\n${tty_reset}" "Input invalid! Please enter one of the following: '[y/yes]' or '[n/no]'."
			;;
		esac
	done
}

check_ssh() {
	info "Validating SSH connection..."
	ssh -T git@github.com &>/dev/null
	if ! [ $? -eq 1 ]; then
		info "We'll use HTTPS to fetch and update plugins."
		return 0
	else
		prompt_confirm "Do you prefer to use SSH to fetch and update plugins? (otherwise HTTPS)"
		return $?
	fi
}

clone_pref() {
	info "Checking 'git clone' preferences..."
	if ! prompt_confirm "Would you like to perform a shallow clone ('--depth=1')?"; then
		CLONE_ATTR+=("--depth=1")
	fi
}

check_nvim_version() {
	local nvim_version
	nvim_version="$(nvim --version | head -n1 | sed -e 's|^[^0-9]*||' -e 's| .*||')"
	if version_ge "$(major_minor "${nvim_version##* }")" "$(major_minor "$1")"; then
		return 0
	else
		return 1
	fi
}

clone_repo() {
	if check_nvim_version "${REQUIRED_NVIM_VERSION}"; then
		execute "git" "clone" "-b" "main" "${CLONE_ATTR[@]}" "$1" "${DEST_DIR}"
	elif check_nvim_version "${REQUIRED_NVIM_VERSION_LEGACY}"; then
		warn "You have outdated Nvim installed (< ${REQUIRED_NVIM_VERSION})."
		info "Automatically redirecting you to the latest compatible version..."
		execute "git" "clone" "-b" "0.10" "${CLONE_ATTR[@]}" "$1" "${DEST_DIR}"
	else
		warn "You have outdated Nvim installed (< ${REQUIRED_NVIM_VERSION_LEGACY})."
		abort "$(
			cat <<EOABORT
You have a legacy Neovim distribution installed.
Please make sure you have nvim v${REQUIRED_NVIM_VERSION_LEGACY} installed at the very least.
EOABORT
		)"
	fi
}

# Check if both `INTERACTIVE` and `NONINTERACTIVE` are set
# Always use single-quoted strings with `exp` expressions
# shellcheck disable=SC2016
if [[ -n "${INTERACTIVE-}" && -n "${NONINTERACTIVE-}" ]]; then
	abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.'
fi

# Check if script is run non-interactively (e.g. CI)
# If it is run non-interactively we should not prompt for confirmation.
# Always use single-quoted strings with `exp` expressions
# shellcheck disable=SC2016
if [[ -z "${NONINTERACTIVE-}" ]]; then
	if [[ -n "${CI-}" ]]; then
		warn 'Running in non-interactive mode because `$CI` is set.'
		NONINTERACTIVE=1
	elif [[ ! -t 0 ]]; then
		if [[ -z "${INTERACTIVE-}" ]]; then
			warn 'Running in non-interactive mode because `stdin` is not a TTY.'
			NONINTERACTIVE=1
		else
			warn 'Running in interactive mode despite `stdin` not being a TTY because `$INTERACTIVE` is set.'
		fi
	fi
else
	info 'Running in non-interactive mode because `$NONINTERACTIVE` is set.'
fi

if ! command -v perl >/dev/null; then
	abort "$(
		cat <<EOABORT
Perl is required to interpret this script. See:
  ${tty_underline}https://www.perl.org/get.html${tty_reset}
EOABORT
	)"
fi

if ! command -v nvim >/dev/null; then
	abort "$(
		cat <<EOABORT
You must install Neovim before installing this Nvim config. See:
  ${tty_underline}https://github.com/neovim/neovim/wiki/Installing-Neovim${tty_reset}
EOABORT
	)"
fi

if ! command -v git >/dev/null; then
	abort "$(
		cat <<EOABORT
You must install Git before installing this Nvim config. See:
  ${tty_underline}https://git-scm.com/${tty_reset}
EOABORT
	)"
fi

# Always use HTTPS when this script is run non-interactively (e.g. CI)
if [[ -n "${NONINTERACTIVE-}" ]]; then
	USE_SSH=0
fi

info "This script will install ayamir/nvimdots to:"
echo "${DEST_DIR}"

if [[ -d "${DEST_DIR}" ]]; then
	warn "The destination folder: \"${DEST_DIR}\" already exists."
	warn_ext "We will make a backup for you at \"${BACKUP_DIR}\"."
fi

if [[ -z "${NONINTERACTIVE-}" ]]; then
	ring_bell
	wait_for_user

	if check_ssh; then
		USE_SSH=0
	fi
	clone_pref
fi

if [[ -d "${DEST_DIR}" ]]; then
	execute "mv" "-f" "${DEST_DIR}" "${BACKUP_DIR}"
fi

info "Fetching in progress..."
if [[ "${USE_SSH}" -eq "1" ]]; then
	clone_repo "git@github.com:ayamir/nvimdots.git"
else
	clone_repo "https://github.com/ayamir/nvimdots.git"
fi

cd "${DEST_DIR}" || return
execute "cp" "-fRpP" "${DEST_DIR}/lua/user_template/" "${DEST_DIR}/lua/user"

if [[ "${USE_SSH}" -eq "0" ]]; then
	info "Changing default fetching method to HTTPS..."
	execute "perl" "-pi" "-e" "s/\[\"use_ssh\"\] \= true/\[\"use_ssh\"\] \= false/g" "${DEST_DIR}/lua/user/settings.lua"
fi

info "Spawning Neovim and fetching plugins... (You'll be redirected shortly)"
info "NOTE: Please make sure you have a Rust Toolchain installed ${tty_underline}via \`rustup\`${tty_reset}${tty_bold}! Otherwise, unexpected things may"
info_ext "      happen. See: ${tty_underline}https://www.rust-lang.org/tools/install${tty_reset}."
info_ext ""
info "If lazy.nvim failed to fetch any plugin(s), manunally execute \`:Lazy sync\` until everything is up-to-date."
cat <<EOS

Thank you for using this set of configuration!
- Project Homepage:
    ${tty_underline}https://github.com/ayamir/nvimdots${tty_reset}
- Further documentation (including executables you ${tty_bold}must${tty_reset} install for full functionality):
    ${tty_underline}https://github.com/ayamir/nvimdots/wiki/Prerequisites${tty_reset}
EOS

if [[ -z "${NONINTERACTIVE-}" ]]; then
	ring_bell
	wait_for_user
	nvim
fi
