#!/usr/bin/env bash
#
# middleclick-autoscroll: middle-click autoscroll for Chromium-based
# applications and Firefox on Linux
#
# Blink has had Windows-style autoscroll for years, behind a runtime flag that
# is not turned on for Linux because middle click is taken by primary-selection
# paste there. Turning it on is a matter of one command line argument. The
# work is finding every application that needs it, putting the argument
# somewhere it survives an update, and doing the same for the two programs that
# do not take arguments at all. Firefox has the same feature behind a setting.
#
# Copyright (C) 2026 Felitendo
# SPDX-License-Identifier: GPL-3.0-or-later

set -uo pipefail

MCA_LIBDIR="${MCA_LIBDIR:-@LIBDIR@}"

for _mod in common config detect patch steam gecko kde apply menu; do
	# shellcheck source=/dev/null
	if ! source "$MCA_LIBDIR/$_mod.sh"; then
		printf 'middleclick-autoscroll: cannot load %s/%s.sh\n' "$MCA_LIBDIR" "$_mod" >&2
		exit 14
	fi
done
unset _mod

# Nothing here writes outside $HOME, so running it as root would patch root's
# own applications and leave the actual user without any.
if [[ $EUID -eq 0 && -z ${MCA_ALLOW_ROOT:-} ]]; then
	printf 'middleclick-autoscroll: run this as your own user, not as root.\n' >&2
	exit 2
fi

# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------

mca_do_enable() {
	mca_config_set Enabled yes || {
		mca_bad "$(mca_msg "Could not save the setting.")"
		return 1
	}
	mca_config_load

	mca_apply

	if [[ $CFG_WATCH == yes ]]; then
		if mca_watch_available; then
			mca_watch_enable || mca_note "$(mca_msg "Could not start the watcher for newly installed applications.")"
		else
			mca_note "$(mca_msg "No systemd user session, so newly installed applications will not be picked up on their own.")"
		fi
	fi

	mca_ok "$(mca_msg "Autoscroll is on for %d applications." "$MCA_N_ON")"

	[[ -n $MCA_IN_MENU ]] \
		|| mca_note "$(mca_msg "Applications that are already running pick this up when they are restarted.")"
}

mca_do_disable() {
	mca_config_set Enabled no || {
		mca_bad "$(mca_msg "Could not save the setting.")"
		return 1
	}

	mca_revert
	mca_watch_disable

	mca_ok "$(mca_msg "Autoscroll is off and everything that was changed has been put back.")"
}

mca_do_apply() {
	local rebuild=0 arg

	for arg in "$@"; do
		case "$arg" in
			--rebuild) rebuild=1 ;;
			--quiet)   MCA_QUIET=1 ;;
		esac
	done

	mca_config_load

	if [[ $CFG_ENABLED != yes ]]; then
		# Called by the watcher while switched off: there is nothing to do, and
		# nothing to complain about either.
		[[ -n $MCA_QUIET ]] && return 0
		mca_note "$(mca_msg "Autoscroll is off. Turn it on first.")"
		return 1
	fi

	# Start from a clean slate. Needed after a settings change, which can take
	# an application out of scope as well as bring one in and where only a full
	# undo can tell the difference, and it is what makes the repair a repair:
	# whatever drifted is written again rather than left because it is "already
	# applied".
	#
	# The watcher never does this. Rewriting files that are already correct
	# would change their timestamps, and it is watching those directories.
	if (( rebuild )); then
		mca_revert
	fi

	mca_apply

	if (( rebuild )); then
		mca_ok "$(mca_msg "Applied again to %d applications." "$MCA_N_ON")"
		[[ -n $MCA_IN_MENU ]] \
			|| mca_say "  $(mca_msg "Applications that are already running pick this up when they are restarted.")"
	elif (( MCA_CHANGES )); then
		mca_ok "$(mca_msg "Applied to %d applications." "$MCA_N_ON")"
		[[ -n $MCA_IN_MENU ]] \
			|| mca_say "  $(mca_msg "Applications that are already running pick this up when they are restarted.")"
	else
		mca_ok "$(mca_msg "Already applied to %d applications." "$MCA_N_ON")"
	fi

	if [[ $CFG_WATCH == yes ]] && mca_watch_available; then
		if mca_watch_enabled; then
			mca_watch_refresh
		else
			mca_watch_enable || true
		fi
	elif [[ $CFG_WATCH != yes ]] && mca_watch_enabled; then
		mca_watch_disable
	fi
}

mca_do_status() {
	mca_config_load
	mca_head "  $MCA_PRETTY"
	mca_ui_status
	printf '\n'
}

mca_do_list() {
	local i route label pad

	mca_config_load
	mca_scan_once
	mca_count_routes

	if (( ${#MCA_IDS[@]} == 0 )); then
		mca_note "$(mca_msg "No supported applications found.")"
		return 0
	fi

	mca_head "  $(mca_msg "Applications")"

	for i in "${!MCA_IDS[@]}"; do
		route="${MCA_ROUTES[i]}"
		label="${MCA_NAMES[i]}"
		pad=$(( 34 - ${#label} ))
		(( pad < 0 )) && pad=0
		printf '  %s%*s %s\n' "$label" "$pad" '' "$(mca_route_label "$route")"
	done
	printf '\n'
}

mca_do_help() {
	cat <<- EOF
	$MCA_PRETTY $MCA_VERSION

	$(mca_msg "Usage: middleclick-autoscroll [command]")

	$(mca_msg "Commands:")
	  enable              $(mca_msg "Turn autoscroll on")
	  disable             $(mca_msg "Turn it off and put everything back")
	  apply               $(mca_msg "Apply to anything installed since the last run")
	  apply --rebuild     $(mca_msg "Take everything back and apply it again")
	  status              $(mca_msg "Show what is covered")
	  list                $(mca_msg "List every application that was found")
	  -h, --help          $(mca_msg "Show this help")
	  -V, --version       $(mca_msg "Show the version")

	$(mca_msg "Without a command an interactive menu is shown.")
	EOF
}

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------

main() {
	local cmd="${1:-}"
	[[ $# -gt 0 ]] && shift

	case "$cmd" in
		enable)  mca_do_enable "$@" ;;
		disable) mca_do_disable "$@" ;;
		apply|refresh) mca_do_apply "$@" ;;
		status)  mca_do_status "$@" ;;
		list)    mca_do_list "$@" ;;

		-h|--help|help) mca_do_help ;;
		-V|--version)   printf '%s %s\n' "$MCA_NAME" "$MCA_VERSION" ;;

		'') mca_ui_menu ;;
		*)
			mca_bad "$(mca_msg "Unknown command: %s" "$cmd")"
			printf '\n'
			mca_do_help
			exit 1
			;;
	esac
}

main "$@"
