#!/usr/bin/env bash

set -o errexit
set -o nounset

SHELL="$(which bash)"
readonly SHELL

readonly THIS_CMD="${0##*/}"
readonly FZPAC_PACMAN="${FZPAC_PACMAN:-paru:yay:pacman}"
readonly FZPAC_FINDER="${FZPAC_FINDER:-fzf:sk:peco:gof:fzy}"
readonly STYLE_BOLD=$'\033[1m'
readonly STYLE_UNDERBAR=$'\033[4m'
readonly STYLE_RESET=$'\033[m'

_version() {
	readonly VERSION='2.0.2'
	readonly AUTHOR="sheepla"
	readonly LICENSE="MIT"
	readonly GITHUB_URL="https://github.com"

	cat <<EOF

${STYLE_BOLD}${THIS_CMD}${STYLE_RESET} -- ${STYLE_UNDERBAR}v${VERSION}${STYLE_RESET}

Released under the ${LICENSE} License.
    Repo:   ${GITHUB_URL}/${AUTHOR}/fzpac
    Author: ${AUTHOR} (${GITHUB_URL}/${AUTHOR})

* ${THIS_CMD} uses this command as a pacman-like command: $(_pacman)
EOF
}

_help() {
	cat <<EOF
${THIS_CMD} -- Arch Linux package finder with fuzzy finder

${STYLE_BOLD}USAGE${STYLE_RESET}
    ${THIS_CMD} ${STYLE_UNDERBAR}SUBCMD${STYLE_RESET} [${STYLE_UNDERBAR}KEYWORDS...${STYLE_RESET}]
    ${THIS_CMD} -h|--help
    ${THIS_CMD} -v|--version

${STYLE_BOLD}SUBCMD${STYLE_RESET}
    s, search        search for all available packages
    l, local         search for already installed packages
    S, install       select packages and INSTALL it
    R, remove        select packages and UNINSTALL it
    A, autoremove    select packages that are no longer needed and UNINSTALL it
    h, help          show help message
    v, version       show version

${STYLE_BOLD}OPTIONS${STYLE_RESET}
    -h, --help       show help message
    -v, --version    show version

${STYLE_BOLD}KEY BINDINGS${STYLE_RESET}
    ${STYLE_UNDERBAR}<C-j>${STYLE_RESET}, ${STYLE_UNDERBAR}<C-n>${STYLE_RESET}  move focus down
    ${STYLE_UNDERBAR}<C-k>${STYLE_RESET}, ${STYLE_UNDERBAR}<C-p>${STYLE_RESET}  move focus up
    ${STYLE_UNDERBAR}<C-s>${STYLE_RESET}         show information of the package
    ${STYLE_UNDERBAR}<Tab>${STYLE_RESET}         select a package
    ${STYLE_UNDERBAR}<CR>${STYLE_RESET}          confirm selection
EOF
}

_main() {
	if [[ "${#}" -lt 1 ]]; then
		_err "Must require arguments..."
		_help
		exit 1
	fi

	local finder
	finder="$(_finder)"

	local pac
	pac="$(_pacman)"

	for arg in "${@}"; do
		case "${arg}" in
		s | search)
			shift
			_search "${pac}" "${finder}" "${@}"
			break
			;;
		l | local)
			shift
			_search_local "${pac}" "${finder}" "${@}"
			break
			;;
		S | install)
			shift
			local pkgs
			pkgs=(
				"$(_search "${pac}" "${finder}" "${@}")"
			)
			_install "${pac}" "${pkgs[@]}"
			break
			;;
		R | remove)
			shift
			local pkgs
			pkgs=(
				"$(_search_local "${pac}" "${finder}" "${@}")"
			)
			_remove "${pac}" "${pkgs[@]}"
			break
			;;
		A | autoremove)
			shift
			local pkgs
			pkgs=(
				"$(_search_no_deps "${pac}" "${finder}" "${@}")"
			)
			_remove "${pac}" "${pkgs[@]}"
			break
			;;
		h | -h | help | --help)
			_help
			break
			;;
		v | -v | version | --version)
			_version
			break
			;;
		*)
			_err "Invalid sub command: ${1}."
			_help
			return 1
			;;
		esac
	done
}

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

_pacman() {
	for pac in ${FZPAC_PACMAN//:/ }; do
		if _has "${pac}"; then
			echo -n "${pac}"
			return 0
		fi
	done

	if [[ "${FZPAC_PACMAN}" == *:* ]]; then
		_err "${FZPAC_PACMAN//:/, } aren't found."
	else
		_err "${FZPAC_PACMAN} isn't found."
	fi
	exit 1
}

# 'Fuzzy finder' finder. Exit process if no fuzzy finder is found.
_finder() {
	for f in ${FZPAC_FINDER//:/ }; do
		if _has "${f}"; then
			echo "${f}"
			return 0
		fi
	done

	if [[ "${FZPAC_FINDER}" == *:* ]]; then
		_err "${FZPAC_FINDER//:/, } aren't found."
	else
		_err "${FZPAC_FINDER} isn't found."
	fi

	exit 1
}

# A simple abstraction between multiple fuzzy finders. This takes fuzzy finder
# and the options as arguments, then select only supported them by the finder
# and run the finder with selected options.
#
# Arguments
#   1: Fuzzy finder name or path
#   2-: Options for fuzzy finder
#
# Any options which aren't below are not supported and ignored.
#
# Supported options for fzf and sk:
# - --multi
# - --header STR
# - --preview COMMAND
# - --bind KEYBINDS
_fuzzyfinder() {
	local -r finder="${1}"
	shift

	local -a args=()

	if [[ "${finder}" =~ (fzf|sk) ]]; then
		while (($#)); do
			case "${1}" in
			-m | --multi)
				args+=(--multi)
				shift
				;;
			--header)
				args+=(--header "${2}")
				shift 2
				;;
			--preview)
				args+=(--preview "${2}")
				shift 2
				;;
			--bind)
				args+=(--bind "${2}")
				shift 2
				;;
			*)
				shift
				;;
			esac
		done
	fi

	env LANG=C "${finder}" "${args[@]}"
}

_err() {
	echo -e "[ \e[31mERR\e[m ] ${*}" >&2
}

_search() {
	readonly HEADER="<C-s>: show package info"

	local pac="${1}"
	local finder="${2}"
	shift 2

	export -f _show_info

	"${pac}" --sync --search --quiet "${@}" |
		_fuzzyfinder \
			"${finder}" \
			--multi \
			--header "${HEADER}" \
			--preview "_show_info ${pac} {}" \
			--bind "ctrl-s:execute(_show_info ${pac} {} | less)"
}

_search_local() {
	readonly HEADER="<C-s>: show package info"

	local pac="${1}"
	local finder="${2}"
	shift 2

	export -f _show_info_local

	"${pac}" --query --search --quiet "${@}" |
		_fuzzyfinder \
			"${finder}" \
			--multi \
			--header "${HEADER}" \
			--preview "_show_info_local ${pac} {}" \
			--bind "ctrl-s:execute(_show_info_local ${pac} {} | less)"
}

_search_no_deps() {
	readonly HEADER="<C-s>: show package info"

	local pac="${1}"
	local finder="${2}"
	shift 2

	export -f _show_info_local

	"${pac}" --query --deps --unrequired --quiet "${@}" |
		_fuzzyfinder \
			"${finder}" \
			--multi \
			--header "${HEADER}" \
			--preview "_show_info_local ${pac} {}" \
			--bind "ctrl-s:execute(_show_info_local ${pac} {} | less)"
}

_show_info() {
	local pac="${1}"
	local pkg="${2}"
	shift 1

	env LANG=C "${pac}" --sync --search --color=always "^${pkg}$"
	echo ""
	env LANG=C "${pac}" --sync --info --color=always "${pkg}"
}

_show_info_local() {
	local pac="${1}"
	local pkg="${2}"
	shift 1

	env LANG=C "${pac}" --query --search --color=always "^${pkg}$"
	echo ""
	env LANG=C "${pac}" --query --info --list --color=always "${pkg}"
}

_install() {
	local pac="${1}"
	shift 1

	if [ "${pac}" = 'pacman' ]; then
		sudo pacman --sync - <<<"${@}"
	else
		"${pac}" --sync - <<<"${@}"
	fi
}

_remove() {
	local pac="${1}"
	shift 1

	if [ "${pac}" = 'pacman' ]; then
		sudo pacman --remove --nosave - <<<"${@}"
	else
		"${pac}" --remove --nosave - <<<"${@}"
	fi
}

_main "${@}"
exit "${?}"
