#!/bin/bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues:     https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
# shellcheck disable=SC1090
# --- BEGIN LIBRARY FILE: opt.sh ---

# An array of functions to call before returning from `shiftopt`.
#
# If one of these functions returns a successful exit code, the
# option will be transparently skipped instead of handled.
SHIFTOPT_HOOKS=()

# A setting to change how `shiftopt` will interpret short options that consist
# of more than one character.
#
# Values:
#
#     SPLIT  -- Splits the option into multiple single-character short options.
#               "-abc" -> ("-a" "-b" "-c")
#     
#     VALUE  -- Uses the remaining characters as the value for the short option.
#               "-abc" -> ("-a=bc")
#
#     CONV   -- Converts the argument to a long option.
#               "-abc" -> ("--abc")
#
#     PASS   -- Pass the argument along as-is.
#               "-abc" -> ("-abc")
#
SHIFTOPT_SHORT_OPTIONS="VALUE"

# Sets the internal _ARGV, _ARGV_INDEX, and _ARGV_LAST variables used when
# parsing options with the shiftopt and shiftval functions.
#
# Arguments:
#     ... -- The program arguments.
# 
# Example:
#     setargs "--long=3" "file.txt"
setargs() {
	_ARGV=("$@")
	_ARGV_LAST="$((${#_ARGV[@]} - 1))"
	_ARGV_INDEX=0
	_ARGV_SUBINDEX=1
}

# Gets all the remaining unparsed arguments and saves them to a variable.
#
# Arguments:
#     "-a" -- Append the arguments to the variable instead of replacing it.
#     $1   -- The variable to save the args to.
# 
# Example:
#     getargs remaining_args
getargs() {
	if [[ "$1" = "-a" || "$1" = "--append" ]]; then
		if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
			eval "$2=(\"\${$2[@]}\" $(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
		fi
	else
		if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
			eval "$1=($(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
		else
			eval "$1=()"
		fi
	fi
}

# Resets the internal _ARGV* variables to the original script arguments.
# This is the equivalent of storing the top-level $@ and using setargs with it.
resetargs() {
	setargs "${_ARGV_ORIGINAL[@]}"
}

# INTERNAL.
#
# Increments the argv index pointer used by `shiftopt`.  
_shiftopt_next() {
	_ARGV_SUBINDEX=1
	((_ARGV_INDEX++)) || true
}

# Gets the next option passed to the script.
#
# Variables:
#     OPT  -- The option name.
#
# Returns:
#     0  -- An option was read.
#     1  -- No more options were read.
#
# Example:
#     while shiftopt; do
#         shiftval
#         echo "$OPT = $OPT_VAL"
#     done
shiftopt() {
	# Read the top of _ARGV.
	[[ "$_ARGV_INDEX" -gt "$_ARGV_LAST" ]] && return 1
	OPT="${_ARGV[$_ARGV_INDEX]}"
	unset OPT_VAL

	if [[ "$OPT" =~ ^-[a-zA-Z0-9_-]+=.* ]]; then
		OPT_VAL="${OPT#*=}"
		OPT="${OPT%%=*}"
	fi
	
	# Handle short options.
	if [[ "$OPT" =~ ^-[^-]{2,} ]]; then
		case "$SHIFTOPT_SHORT_OPTIONS" in
		 	# PASS mode: "-abc=0" -> ("-abc=0")
			PASS) _shiftopt_next ;;

			# CONV mode: "-abc=0" -> ("--abc=0")
			CONV) OPT="-${OPT}"; _shiftopt_next ;; 

			# VALUE mode: "-abc=0" -> ("-a=bc=0")
			VALUE) {
				OPT="${_ARGV[$_ARGV_INDEX]}"
				OPT_VAL="${OPT:2}"
				OPT="${OPT:0:2}"
				_shiftopt_next
			} ;; 

			# SPLIT mode: "-abc=0" -> ("-a=0" "-b=0" "-c=0")
			SPLIT) {
				OPT="-${OPT:$_ARGV_SUBINDEX:1}"
				((_ARGV_SUBINDEX++)) || true
				if [[ "$_ARGV_SUBINDEX" -gt "${#OPT}" ]]; then
					_shiftopt_next
				fi
			} ;;

			# ????? mode: Treat it as pass.
			*)
				printf "shiftopt: unknown SHIFTOPT_SHORT_OPTIONS mode '%s'" \
					"$SHIFTOPT_SHORT_OPTIONS" 1>&2
				_shiftopt_next
				;;
		esac
	else
		_shiftopt_next
	fi

	# Handle hooks.
	local hook
	for hook in "${SHIFTOPT_HOOKS[@]}"; do
		if "$hook"; then
			shiftopt
			return $?
		fi
	done

	return 0
}

# Gets the value for the current option.
#
# Variables:
#     OPT_VAL  -- The option value.
#
# Returns:
#     0       -- An option value was read.
#     EXIT 1  -- No option value was available.
shiftval() {
	# Skip if a value was already provided.
	if [[ -n "${OPT_VAL+x}" ]]; then
		return 0
	fi
	
	if [[ "$_ARGV_SUBINDEX" -gt 1 && "$SHIFTOPT_SHORT_OPTIONS" = "SPLIT" ]]; then
		# If it's a short group argument in SPLIT mode, we grab the next argument.
		OPT_VAL="${_ARGV[$((_ARGV_INDEX+1))]}"
	else
		# Otherwise, we can handle it normally.
		OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
		_shiftopt_next
	fi

	# Error if no value is provided.
	if [[ "$OPT_VAL" =~ -.* ]]; then
		printc "%{RED}%s: '%s' requires a value%{CLEAR}\n" "batwatch" "$ARG"
		exit 1
	fi
}

setargs "$@"
_ARGV_ORIGINAL=("$@")
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: opt_hook_color.sh ---

# Option parser hook: color support.
# This will accept --no-color or --color.
# It will also try to accept --color=never|always|auto.
#
# The variable OPT_COLOR will be set depending on whether or not a TTY is
# detected and whether or not --color/--no-color is specified.
hook_color() {
	SHIFTOPT_HOOKS+=("__shiftopt_hook__color")
	__shiftopt_hook__color() {
		case "$OPT" in

		--no-color) OPT_COLOR=false ;;
		--color) {
			case "$OPT_VAL" in
			"")            OPT_COLOR=true ;;
			always | true) OPT_COLOR=true  ;;
			never | false) OPT_COLOR=false ;;
			auto) return 0 ;;
			*)
				printc "%{RED}%s: '--color' expects value of 'auto', 'always', or 'never'%{CLEAR}\n" "batwatch"
				exit 1
				;;
			esac
		} ;;

		*) return 1 ;;
		esac

		printc_init "$OPT_COLOR"
		return 0
	}

	# Default color support.
	if [[ -z "$OPT_COLOR" ]]; then
		if [[ -t 1 ]]; then
			OPT_COLOR=true
		else
			OPT_COLOR=false
		fi
		printc_init "$OPT_COLOR"
	fi
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: opt_hook_help.sh ---

# Option parser hook: --help support.
# This will accept -h or --help, which prints the usage information and exits.
hook_help() {
	SHIFTOPT_HOOKS+=("__shiftopt_hook__help")
	if [[ "$1" == "--no-short" ]]; then
		__shiftopt_hook__help() {
			if [[ "$OPT" = "--help" ]]; then
				show_help
				exit 0
			fi
	
			return 1
		}
	else
		__shiftopt_hook__help() {
			if [[ "$OPT" = "--help" ]] || [[ "$OPT" = "-h" ]]; then
				show_help
				exit 0
			fi
	
			return 1
		}
	fi
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: opt_hook_version.sh ---

# Option parser hook: --version support.
# This will accept --version, which prints the version information and exits.
hook_version() {
	SHIFTOPT_HOOKS+=("__shiftopt_hook__version")
	__shiftopt_hook__version() {
		if [[ "$OPT" = "--version" ]]; then
			printf "%s %s\n\n%s\n%s\n" \
				"batwatch" \
				"2022.07.27" \
				"Copyright (C) 2019-2021 eth-p | MIT License" \
				"https://github.com/eth-p/bat-extras"
			exit 0
		fi

		return 1
	}
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: opt_hook_width.sh ---
	# --- BEGIN LIBRARY FILE: term.sh ---
	
	# Gets the width of the terminal.
	# This will return 80 unless stdin is attached to the terminal.
	#
	# Returns:
	#     The terminal width, or 80 if there's no TTY.
	#
	term_width() {
		# shellcheck disable=SC2155
		local width="$({ stty size 2>/dev/null || echo "22 80"; } | cut -d ' ' -f2)"
		if [[ "$width" -ne 0 ]]; then
			echo "$width"
		else
			echo "80"
		fi
		return 0
	}
	
	# Clears the terminal using the ANSI escape sequences for erase screen and cursor absolute positioning.
	term_clear() {
		printf "\x1B[3J\x1B[2J\x1B[H"
	}
	# --- END LIBRARY FILE ---

# Option parser hook: --terminal-width support.
# This will accept --terminal-width=number.
#
# The variable OPT_TERMINAL_WIDTH will be set.
hook_width() {
	SHIFTOPT_HOOKS+=("__shiftopt_hook__width")
	__shiftopt_hook__width() {
		case "$OPT" in

		--terminal-width) shiftval; OPT_TERMINAL_WIDTH="$OPT_VAL" ;;

		*) return 1 ;;
		esac
		return 0
	}

	# Default terminal width.
	OPT_TERMINAL_WIDTH="$(term_width)"
}
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: print.sh ---

# Printf, but with optional colors.
# This uses the same syntax and arguments as printf.
#
# Example:
#     printc "%{RED}This is red %s.%{CLEAR}\n" "text"
#
printc() {
	printf "$(sed "$_PRINTC_PATTERN" <<<"$1")" "${@:2}"
}

# Initializes the color tags for printc.
#
# Arguments:
#     true  -- Turns on color output.
#     false -- Turns off color output.
printc_init() {
	case "$1" in
	true)  _PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI" ;;
	false) _PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN" ;;

	"[DEFINE]") {
		_PRINTC_PATTERN_ANSI=""
		_PRINTC_PATTERN_PLAIN=""

		local name
		local ansi
		while read -r name ansi; do
			if [[ -z "${name}" && -z "${ansi}" ]] || [[ "${name:0:1}" = "#" ]]; then
				continue
			fi

			ansi="${ansi/\\/\\\\}"

			_PRINTC_PATTERN_PLAIN="${_PRINTC_PATTERN_PLAIN}s/%{${name}}//g;"
			_PRINTC_PATTERN_ANSI="${_PRINTC_PATTERN_ANSI}s/%{${name}}/${ansi}/g;"
		done

		if [[ -t 1 && -z "${NO_COLOR+x}" ]]; then
			_PRINTC_PATTERN="$_PRINTC_PATTERN_ANSI"
		else
			_PRINTC_PATTERN="$_PRINTC_PATTERN_PLAIN"
		fi
	} ;;
	esac
}

# Print a warning message to stderr.
# Arguments:
#     1   -- The printc formatting string.
#     ... -- The printc formatting arguments.
print_warning() {
	printc "%{YELLOW}[%s warning]%{CLEAR}: $1%{CLEAR}\n" "batwatch" "${@:2}" 1>&2
}

# Print an error message to stderr.
# Arguments:
#     1   -- The printc formatting string.
#     ... -- The printc formatting arguments.
print_error() {
	printc "%{RED}[%s error]%{CLEAR}: $1%{CLEAR}\n" "batwatch" "${@:2}" 1>&2
}

# Initialization:
printc_init "[DEFINE]" <<END
	CLEAR	\x1B[0m
	RED		\x1B[31m
	GREEN	\x1B[32m
	YELLOW	\x1B[33m
	BLUE	\x1B[34m
	MAGENTA	\x1B[35m
	CYAN	\x1B[36m

	DEFAULT \x1B[39m
	DIM		\x1B[2m
END
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: pager.sh ---

# Returns 0 (true) if the current pager is less, otherwise 1 (false).
is_pager_less() {
	[[ "$(pager_name)" = "less" ]]
	return $?
}

# Returns 0 (true) if the current pager is bat, otherwise 1 (false).
is_pager_bat() {
	[[ "$(pager_name)" = "bat" ]]
	return $?
}

# Returns 0 (true) if the current pager is disabled, otherwise 1 (false).
is_pager_disabled() {
	[[ -z "$(pager_name)" ]]
	return $?
}

# Prints the detected pager name.
pager_name() {
	_detect_pager 1>&2
	echo "$_SCRIPT_PAGER_NAME"
}

# Prints the detected pager version.
pager_version() {
	_detect_pager 1>&2
	echo "$_SCRIPT_PAGER_VERSION"
}

# Executes a command or function, and pipes its output to the pager (if it exists).
#
# Returns: The exit code of the command.
# Example:
#     pager_exec echo hi
pager_exec() {
	if [[ -n "$SCRIPT_PAGER_CMD" ]]; then
		"$@" | pager_display
		return $?
	else
		"$@"
		return $?
	fi
}

# Displays the output of a command or function inside the pager (if it exists).
#
# Example:
#     bat | pager_display
pager_display() {
	if [[ -n "$SCRIPT_PAGER_CMD" ]]; then
		if [[ -n "$SCRIPT_PAGER_ARGS" ]]; then
			"${SCRIPT_PAGER_CMD[@]}" "${SCRIPT_PAGER_ARGS[@]}"
			return $?
		else
			"${SCRIPT_PAGER_CMD[@]}"
			return $?
		fi
	else
		cat
		return $?
	fi
}


# Detect the pager information.
# shellcheck disable=SC2120
_detect_pager() {
	if [[ "$_SCRIPT_PAGER_DETECTED" = "true" ]]; then return; fi
	_SCRIPT_PAGER_DETECTED=true

	# If the pager command is empty, the pager is disabled.
	if [[ -z "${SCRIPT_PAGER_CMD[0]}" ]]; then
		_SCRIPT_PAGER_VERSION=0
		_SCRIPT_PAGER_NAME=""
		return;
	fi

	# Determine the pager name and version.
	local output
	local output1
	output="$("${SCRIPT_PAGER_CMD[0]}" --version 2>&1)"
	output1="$(head -n 1 <<<"$output")"

	if [[ "$output1" =~ ^less[[:blank:]]([[:digit:]]+) ]]; then
		_SCRIPT_PAGER_VERSION="${BASH_REMATCH[1]}"
		_SCRIPT_PAGER_NAME="less"
	elif [[ "$output1" =~ ^bat(cat)?[[:blank:]]([[:digit:]]+) ]]; then
		# shellcheck disable=SC2034
		__BAT_VERSION="${BASH_REMATCH[2]}"
		_SCRIPT_PAGER_VERSION="${BASH_REMATCH[2]}"
		_SCRIPT_PAGER_NAME="bat"
	else
		_SCRIPT_PAGER_VERSION=0
		_SCRIPT_PAGER_NAME="$(basename "${SCRIPT_PAGER_CMD[0]}")"
	fi
}

# Configure the script pager.
# This attempts to mimic how bat determines the pager and pager arguments.
#
# 1. Use BAT_PAGER
# 2. Use PAGER with special arguments for less
# 3. Use PAGER
_configure_pager() {
	# shellcheck disable=SC2206
	SCRIPT_PAGER_ARGS=()
	if [[ -n "${PAGER+x}" ]]; then
		SCRIPT_PAGER_CMD=($PAGER)
	else
		SCRIPT_PAGER_CMD=("less")
	fi

	# Prefer the BAT_PAGER environment variable.
	if [[ -n "${BAT_PAGER+x}" ]]; then
		# [note]: This is intentional.
		# shellcheck disable=SC2206
		SCRIPT_PAGER_CMD=($BAT_PAGER)
		SCRIPT_PAGER_ARGS=()
		return
	fi
	
	# If the pager is bat, use less instead.
	if is_pager_bat; then
		SCRIPT_PAGER_CMD=("less")
		SCRIPT_PAGER_ARGS=()
	fi

	# Add arguments for the less pager.
	if is_pager_less; then
		SCRIPT_PAGER_CMD=("${SCRIPT_PAGER_CMD[0]}" -R --quit-if-one-screen)
		if [[ "$(pager_version)" -lt 500 ]]; then
			SCRIPT_PAGER_CMD+=(--no-init)
		fi
	fi
}


if [[ -t 1 ]]; then
	# Detect and choose the arguments for the pager.
	_configure_pager
else
	# Prefer no pager if not a tty.
	SCRIPT_PAGER_CMD=()
	SCRIPT_PAGER_ARGS=()
fi
# --- END LIBRARY FILE ---
# --- BEGIN LIBRARY FILE: version.sh ---

# Gets the current bat version.
bat_version() {
	if [[ -z "${__BAT_VERSION}" ]]; then
		__BAT_VERSION="$(command "bat" --version | cut -d ' ' -f 2)"
	fi
	
	echo "${__BAT_VERSION}"
}

# Compares two version strings.
# Arguments:
#    1  -- The version to compare.
#    2  -- The comparison operator (same as []).
#    3  -- The version to compare with.
version_compare() {
	local version="$1"
	local compare="$3"

	if ! [[ "$version" =~ \.$ ]]; then
		version="${version}."
	fi

	if ! [[ "$compare" =~ \.$ ]]; then
		compare="${compare}."
	fi

	version_compare__recurse "$version" "$2" "$compare"
	return $?
}

version_compare__recurse() {
	local version="$1"
	local operator="$2"
	local compare="$3"

	# Extract the leading number.
	local v_major="${version%%.*}"
	local c_major="${compare%%.*}"

	# Extract the remaining numbers.
	local v_minor="${version#*.}"
	local c_minor="${compare#*.}"

	# Compare the versions specially if the final number has been reached.
	if [[ -z "$v_minor" && -z "$c_minor" ]]; then
		[ "$v_major" $operator "$c_major" ];
		return $?
	fi

	# Insert zeroes where there are missing numbers.
	if [[ -z "$v_minor" ]]; then
		v_minor="0."
	fi

	if [[ -z "$c_minor" ]]; then
		c_minor="0."
	fi

	# Compare the versions.
	# This is an early escape case.
	case "$operator" in
	-eq)       [[ "$v_major" -ne "$c_major" ]] && return 1 ;;
	-ne)       [[ "$v_major" -ne "$c_major" ]] && return 0 ;;
	-ge | -gt) [[ "$v_major" -lt "$c_major" ]] && return 1
	           [[ "$v_major" -gt "$c_major" ]] && return 0 ;;
	-le | -lt) [[ "$v_major" -gt "$c_major" ]] && return 1
	           [[ "$v_major" -lt "$c_major" ]] && return 0 ;;
	esac

	version_compare__recurse "$v_minor" "$operator" "$c_minor"
}
# --- END LIBRARY FILE ---
# -----------------------------------------------------------------------------
# Init:
# -----------------------------------------------------------------------------
hook_color
hook_version
hook_width
hook_help
# -----------------------------------------------------------------------------
# Help:
# -----------------------------------------------------------------------------
show_help() {
	echo 'Usage: batwatch --file [--watcher entr|poll][--[no-]clear] <file> [<file> ...]'
	echo '       batwatch --command [-n<interval>] <command> [<arg> ...]' 
}
# -----------------------------------------------------------------------------
# Watchers:
# -----------------------------------------------------------------------------

WATCHERS=("entr" "poll")

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

watcher_entr_watch() {
	ENTR_ARGS=()

	if [[ "$OPT_CLEAR" == "true" ]]; then
		ENTR_ARGS+=('-c')
	fi

	entr "${ENTR_ARGS[@]}" \
		"bat" "${BAT_ARGS[@]}" \
		--terminal-width="$OPT_TERMINAL_WIDTH" \
		--paging=never \
		"$@" \
		< <(printf "%s\n" "$@")
}

watcher_entr_supported() {
	command -v entr &> /dev/null
	return $?
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

POLL_STAT_VARIANT=''
POLL_STAT_COMMAND=()

determine_stat_variant() {
	if [[ -n "$POLL_STAT_VARIANT" ]]; then
		return 0
	fi

	local variant name flags ts
	for variant in "gnu -c %Z" "bsd -f %m"; do
		read    -r name flags <<< "$variant"

		# save the results of the stat command
		if read -r ts < <(stat ${flags} "$0" 2> /dev/null); then

			# verify that the value is an epoch timestamp
			# before proceeding
			if [[ "${ts}" =~ ^[0-9]+$ ]]; then
				POLL_STAT_COMMAND=(stat ${flags})
				POLL_STAT_VARIANT="$name"
				return 0
			fi
		fi
	done

	return 1
}

watcher_poll_watch() {
	determine_stat_variant

	local files=("$@")
	local times=()

	# Get the initial modified times.
	local file
	local time
	local modified=true
	for file in "${files[@]}"; do
		time="$("${POLL_STAT_COMMAND[@]}" "$file")"
		times+=("$time")
	done

	# Display files.
	while true; do
		if "$modified"; then
			modified=false
			clear
			"bat" "${BAT_ARGS[@]}" \
				--terminal-width="$OPT_TERMINAL_WIDTH" \
				--paging=never \
				"${files[@]}"

		fi

		# Check if the file has been modified.
		local i=0
		for file in "${files[@]}"; do
			time="$("${POLL_STAT_COMMAND[@]}" "$file")"

			if [[ "$time" -ne "${times[$i]}" ]]; then
				times[$i]="$time"
				modified=true
			fi

			((i++))
		done

		# Wait for "q" to exit, or check again after a few seconds.
		local input
		read -r -t "${OPT_INTERVAL}" input
		if [[ "$input" =~ [q|Q] ]]; then
			exit
		fi
	done

	"${POLL_STAT_COMMAND[@]}" "$@"
	local ts
}

watcher_poll_supported() {
	determine_stat_variant
	return $?
}

# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------

determine_watcher() {
	local watcher
	for watcher in "${WATCHERS[@]}"; do
		if "watcher_${watcher}_supported"; then
			OPT_WATCHER="$watcher"
			return 0
		fi
	done

	return 1
}

# -----------------------------------------------------------------------------
# Options:
# -----------------------------------------------------------------------------
BAT_ARGS=(--paging=never)
FILES=()
FILES_HAS_DIRECTORY=false
OPT_MODE=file
OPT_INTERVAL=3
OPT_CLEAR=true
OPT_WATCHER=""

# Set options based on tty.
if [[ -t 1 ]]; then
	OPT_COLOR=true
fi

# Parse arguments.
while shiftopt; do
	case "$OPT" in

		# Script options
		--watcher)     shiftval;   OPT_WATCHER="$OPT_VAL" ;;
		--interval|-n) shiftval;   OPT_INTERVAL="$OPT_VAL" ;;
		--file|-f)                 OPT_MODE=file ;;
		--command|-x)              OPT_MODE=command ;;
		--clear)                   OPT_CLEAR=true ;;
		--no-clear)                OPT_CLEAR=false ;;

		# bat/Pager options
		-*) BAT_ARGS+=("$OPT=$OPT_VAL") ;;

		# Files
		*) {
			FILES+=("$OPT")
			if [[ "$OPT_MODE" = "command" ]]; then
				getargs --append FILES
				break
			fi
		} ;;

	esac
done

# Validate that a file/command was provided.
if [[ ${#FILES[@]} -eq 0 ]]; then
	if [[ "$OPT_MODE" = "file" ]]; then
		print_error "no files provided"
	else
		print_error "no command provided"
	fi
	exit 1
fi
	
# Validate that the provided files exist.
if [[ "$OPT_MODE" = "file" ]]; then
	for file in "${FILES[@]}"; do
		if ! [[ -e "$file" ]]; then
			print_error "'%s' does not exist" "$file"
			exit 1
		fi
	
		if [[ -d "$file" ]]; then
			FILES_HAS_DIRECTORY=true
		fi
	done
fi

# Append bat arguments.
if "$OPT_COLOR"; then
	BAT_ARGS+=("--color=always")
else
	BAT_ARGS+=("--color=never")
fi

# Initialize clear command based on whether or not ANSI should be used.
if [[ "$OPT_CLEAR" == "true" ]]; then
	if "$OPT_COLOR"; then
		clear() {
			term_clear || return $?
		}
	fi
else
	clear() {
		:
	}
fi

# -----------------------------------------------------------------------------
# Main:
# -----------------------------------------------------------------------------
if [[ "$OPT_MODE" = "file" ]]; then
	# Determine the watcher.
	if [[ -z "$OPT_WATCHER" ]]; then
		if ! determine_watcher; then
			print_error "Your system does not have any supported watchers."
			printc "Please read the documentation at %{BLUE}%s%{CLEAR} for more details.\n" "https://github.com/eth-p/bat-extras" 1>&2
			exit 2
		fi
	else
		if ! type "watcher_${OPT_WATCHER}_supported" &> /dev/null; then
			print_error "Unknown watcher: '%s'" "$OPT_WATCHER"
			exit 1
		fi
	
		if ! "watcher_${OPT_WATCHER}_supported" &> /dev/null; then
			print_error "Unsupported watcher: '%s'" "$OPT_WATCHER"
			exit 1
		fi
	fi
	
	main() {
		"watcher_${OPT_WATCHER}_watch"  "${FILES[@]}"
		return  $?
	}
else
	
	# Set bat's header to show the command.
	BAT_VERSION="$(bat_version)"
	if version_compare "$BAT_VERSION" -ge "0.14"; then
		BAT_ARGS+=(--file-name="${FILES[*]}")
	fi

	main() {
		local last_rendered
		local rendered
		local term_width="$(term_width)"
		BAT_ARGS+=("--terminal-width=$term_width")

		while true; do
			IFS='' rendered="$("${FILES[@]}" 2>&1 | "bat" "${BAT_ARGS[@]}")"
			if [ "$rendered" != "$last_rendered" ]; then
				# Only clear and redraw if there's a change.
				# This reduces excessive flickering.
				last_rendered="$rendered"
				clear
				printf "%s\n" "$rendered"
				rendered=''
			fi

			sleep "${OPT_INTERVAL}" || exit 1
		done
	}
fi

# Run the main function.
main
exit $?
