#!/usr/bin/env bash
#
#    dl-distro - Bash script for downloading and verifying OS images.
#
#    Copyright (C) 2023-2025 bashuser30 <bashuser30@mailbox.org>
#
#    dl-distro is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    dl-distro is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program. If not, see <https://www.gnu.org/licenses/>.
#

set -euo pipefail

version="v2.4.4"
data_dir="${XDG_DATA_HOME:-$HOME/.local/share}/dl-distro"
conf_dir="${XDG_CONFIG_HOME:-$HOME/.config}/dl-distro"
json_dir="$data_dir/json"
gpg_dir="$data_dir/gpg"
minisign_dir="$data_dir/minisign"
signify_dir="$data_dir/signify"
json_file="$json_dir/data.json"
user_json_file="$conf_dir/data.json"
conf_file="$conf_dir/dl-distro.conf"
default_download_dir="$PWD"
auto_update_enabled="true"
verification_enabled="true"
spider_mode_enabled="false"
manual_download_enabled="false"
gpg_keyserver="hkps://keyserver.ubuntu.com"
json_url="https://codeberg.org/bashuser30/dl-distro/raw/branch/master/data.json"
green=$'\033[32m'
red=$'\033[31m'
yellow=$'\033[33m'
bold=$'\033[1m'
und=$'\033[4m'
nc=$'\033[39m'
reset=$'\033[0m'
PS3="${bold}>${reset} "

# shellcheck source=/dev/null
[[ -f "$conf_file" ]] && source "$conf_file"
[[ -f "$user_json_file" ]] && json_file="$user_json_file"

declare -A supported_distros=(
	["AlmaLinux OS"]="alma"
	["Alpine"]="alpine"
	["Arch"]="arch"
	["CachyOS"]="cachy"
	["Debian"]="debian"
	["Dragora"]="dragora"
	["Fedora"]="fedora"
	["GhostBSD"]="ghostbsd"
	["GParted Live"]="gparted"
	["Guix System"]="guix"
	["Hyperbola"]="hyperbola"
	["Kali"]="kali"
	["Mint"]="mint"
	["MX Linux"]="mxlinux"
	["NixOS"]="nix"
	["Nobara"]="nobara"
	["OpenBSD"]="openbsd"
	["openSUSE"]="opensuse"
	["Parabola"]="parabola"
	["Parch"]="parch"
	["Parrot OS"]="parrot"
	["Pop!_OS"]="pop"
	["PureOS"]="pure"
	["Qubes OS"]="qubes"
	["Rocky"]="rocky"
	["Slackware"]="slackware"
	["Solus"]="solus"
	["Tails"]="tails"
	["Tiny Core"]="tinycore"
	["Trisquel"]="trisquel"
	["Ubuntu"]="ubuntu"
	["Void"]="void"
	["Whonix"]="whonix"
	["Zorin OS"]="zorin"
)

msg()
{
	printf "%b%s%b\n" "$bold" "$1" "$reset"
}

warn()
{
	printf >&2 "%b%bWARNING:%b %s%b\n" "$bold" "$yellow" "$nc" "$1" "$reset"
}

error()
{
	printf >&2 "%b%bERROR:%b %s%b\n" "$bold" "$red" "$nc" "$1" "$reset"
}

die()
{
	error "$1"
	exit 1
}

dependency_check()
{
	missing_deps=()

	for cmd in "$@"; do
		command -v "$cmd" &>/dev/null || missing_deps+=("$cmd")
	done

	if [[ "${#missing_deps[@]}" -gt 0 ]]; then
		die "Please install: ${red}${missing_deps[*]}"
	fi
}

wget_file()
{
	local url

	for url in "$@"; do
		if ! wget -qc --show-progress "$url"; then
			die "Failed to download: ${red}$url"
		fi
	done
}

wget_spider()
{
	[[ "$spider_mode_enabled" == "false" ]] && return 0

	local url

	for url in "$@"; do
		[[ "$(basename "$url")" == "null" ]] && continue

		if ! wget -nvc --spider "$url"; then
			error "Spider failed for: ${red}$url"
		fi
	done

	exit 0
}

fetch_gpg_key()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	dependency_check "gpg"

	gpg_key="$(jq -r ".gpg_keys.$1" "$json_file")"

	if ! gpg --homedir "$gpg_dir" --list-keys "$gpg_key" &>/dev/null; then
		msg "Fetching GPG key..."

		if ! gpg --homedir "$gpg_dir" --keyserver "$gpg_keyserver" --recv-keys "$gpg_key" &>/dev/null; then
			die "Failed to fetch GPG key: ${red}$gpg_key"
		fi
	fi
}

verify_gpg_signature()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	if gpg --homedir "$gpg_dir" --verify "$@" &>/dev/null; then
		msg "GPG verification: ${green}SUCCESS"
	else
		rm -f "$iso_file"
		die "GPG verification: ${red}FAILED"
	fi
}

fetch_minisign_key()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	dependency_check "minisign"

	minisign_key_url="$(jq -r ".minisign_keys.$distro" "$json_file")"
	minisign_key="$(basename "$minisign_key_url")"

	if [[ ! -f "$minisign_dir/$minisign_key" ]]; then
		msg "Fetching Minisign key..."

		if ! wget -P "$minisign_dir" -q "$minisign_key_url"; then
			die "Failed to fetch Minisign key: ${red}$minisign_key_url"
		fi
	fi
}

verify_minisign_signature()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	if minisign -Vp "$minisign_dir/$minisign_key" -x "$sig_file" -m "$sum_file" &>/dev/null; then
		msg "Minisign verification: ${green}SUCCESS"
	else
		die "Minisign verification: ${red}FAILED"
	fi
}

get_signify_name()
{
	local signify_name

	if apt list signify-openbsd 2>/dev/null | grep -q "signify-openbsd"; then
		signify_name="signify-openbsd"
	else
		signify_name="signify"
	fi

	printf "%s" "$signify_name"
}

fetch_signify_key()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	signify_name="$(get_signify_name)"

	dependency_check "$signify_name"

	signify_key_url="$(jq -r ".signify_keys.$distro" "$json_file")"
	signify_key="$(basename "$signify_key_url")"

	if [[ ! -f "$signify_dir/$signify_key" ]]; then
		msg "Fetching Signify key..."

		if ! wget -P "$signify_dir" -q "$signify_key_url"; then
			die "Failed to fetch Signify key: ${red}$signify_key_url"
		fi
	fi
}

verify_signify_signature()
{
	[[ "$verification_enabled" == "false" ]] && return 0

	if "$signify_name" -Vp "$signify_dir/$signify_key" -x "$sig_file" -m "$sum_file" &>/dev/null; then
		msg "Signify verification: ${green}SUCCESS"
	else
		die "Signify verification: ${red}FAILED"
	fi
}

verify_checksum()
{
	if "$sum_algo" -c --ignore-missing "$sum_file" &>/dev/null; then
		msg "$sum_algo verification: ${green}SUCCESS"
	else
		rm -f "$iso_file"
		die "$sum_algo verification: ${red}FAILED"
	fi
}

manual_download()
{
	[[ "$manual_download_enabled" != "true" ]] && return 0

	printf "\e[2J\e[H"

	msg "${green}Download files$reset"
	msg ""

	msg "Image:     $url/$iso_file"
	[[ "$sum_file" != "null" ]] && msg "Checksum:  $url/$sum_file"
	[[ "$sig_file" != "null" ]] && msg "Signature: $url/$sig_file"

	[[ "$sum_file" != "null" ]] && {
		msg ""
		msg "${green}Validate Checksum$reset"
		msg ""
		msg "Navigate to the download directory and run:"
		msg ""
		msg "    $sum_algo -c --ignore-missing $sum_file"
		msg ""
		msg "${yellow}If you see \"FAILED\", the file may be corrupt or tampered with!$reset"
	}

	[[ "$sig_file" != "null" && "$distro" != "void" && "$distro" != "openbsd" ]] && {
		msg ""
		msg "${green}Validate Signature$reset"
		msg ""
		msg "Import the GPG key and validate the signature:"
		msg ""
		msg "    gpg --keyserver $gpg_keyserver --recv-keys $(jq -r ".gpg_keys.$distro" "$json_file")"
		msg "    gpg --verify $sig_file"
		msg ""
		msg "${yellow}If you see \"BAD signature\", the file may be corrupt or tampered with!$reset"
		msg ""
		msg "Now you can delete the GPG key: "
		msg ""
		msg "gpg --yes --batch --delete-keys $(jq -r ".gpg_keys.$distro" "$json_file")"
		exit 0
	}

	[[ "$distro" == "void" ]] && {
		msg ""
		msg "${green}Validate Signature$reset"
		msg ""

		minisign_key_url="$(jq -r ".minisign_keys.$distro" "$json_file")"
		minisign_key="$(basename "$minisign_key_url")"

		msg "Download the Minisign key:"
		msg ""
		msg "    $minisign_key_url"
		msg ""
		msg "Validate the signature:"
		msg ""
		msg "    minisign -Vp $minisign_key -x $sig_file -m $sum_file"
		msg ""
		msg "${yellow}If you see \"Signature verification failed\", the file may be corrupt or tampered with!$reset"
		exit 0
	}

	[[ "$distro" == "openbsd" ]] && {
		msg ""
		msg "${green}Validate Signature$reset"
		msg ""

		signify_key_url="$(jq -r ".signify_keys.$distro" "$json_file")"
		signify_key="$(basename "$signify_key_url")"
		signify_name="$(get_signify_name)"

		msg "Download the Signify key:"
		msg ""
		msg "    $signify_key_url"
		msg ""
		msg "Validate the signature:"
		msg ""
		msg "    $signify_name -Vp $signify_key -x $sig_file -m $sum_file"
		msg ""
		msg "${yellow}If you see \"Signature verification failed\", the file may be corrupt or tampered with!$reset"
		exit 0
	}

	exit 0
}

download_alma()
{

	[[ "$traverse_path" =~ ${distro}_([0-9]+) ]] && distro="${distro}_${BASH_REMATCH[1]}"

	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"

	if [[ "$traverse_path" == *"live"* ]]; then
		wget_file "$url"/{"$sig_file","$sum_file"}
		verify_gpg_signature "$sig_file" "$sum_file"
	else
		wget_file "$url/$sig_file"
		verify_gpg_signature "$sig_file"
	fi

	wget_file "$url/$iso_file"
	verify_checksum
}

download_alpine()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_arch()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_cachy()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_debian()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}

	[[ "$traverse_path" == *"ports.gnu_hurd.latest"* ]] && {
		wget_file "$url/$iso_file"
		msg "Debian does not provide checksum or signature files for this image."
		return 0
	}

	[[ "$traverse_path" == *"ports.gnu_hurd"* ]] && {
		wget_file "$url"/{"$sum_file","$iso_file"}
		verify_checksum
		msg "Debian does not provide signature files for this image."
		return 0
	}

	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_dragora()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	wget_file "$url/$sum_file"
	verify_checksum
}

download_fedora()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url/$sig_file"
	verify_gpg_signature "$sig_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_ghostbsd()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "GhostBSD does not provide signature files."
}

download_gparted()
{
	manual_download
	sig_sum_url="$(jq -r ".oddballs.gparted.sig_sum_url" "$json_file")"
	wget_spider "$url/$iso_file" "$sig_sum_url"/{"$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$sig_sum_url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_guix()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}

	[[ "$traverse_path" == *"latest"* ]] && {
		msg "These images are development snapshots and don't have stability."
		wget_file "$url/$iso_file"
		msg "Guix System does not provide checksum or signature files for this image."

		if [[ "$traverse_path" == *"binary"* ]]; then
			mv "$iso_file" "x86_64-latest-$(cut -d'+' -f4 <<<"$iso_file")"
		else
			mv "$iso_file" "x86_64-latest-guix-$(cut -d'+' -f4 <<<"$iso_file")"
		fi

		return 0
	}

	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	msg "Guix System does not provide checksum files."
}

download_hyperbola()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_kali()
{
	if [[ "$traverse_path" == *"weekly"* ]]; then
		html_content="$(wget -qO- "$url/")"
		current_week_num="$(grep -o 'W[0-9]\+' <<<"$html_content" | sort -r | head -n 1 | cut -c2-)"
		iso_file="${iso_file//\{current_week_num\}/$current_week_num}"

		if ! grep -qF "$iso_file" <<<"$html_content"; then
			warn "No new ISO for week: ${yellow}$current_week_num"
			warn "Downloading previous week."
			((prev_week_num = current_week_num - 1))
			iso_file="${iso_file//$current_week_num/$prev_week_num}"
		fi
	fi

	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_mint()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_mxlinux()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sum_file"}
	wget_file "$url"/"$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_nix()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	printf "\n" >>"$sum_file"
	read -r line <"$sum_file"
	new_iso_file="${line##* }"
	mv "$iso_file" "$new_iso_file"
	iso_file="$new_iso_file"
	verify_checksum
	msg "NixOS does not provide signature files."
}

download_nobara()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "Nobara does not provide signature files."
}

download_openbsd()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_signify_key
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_signify_signature
	wget_file "$url/$iso_file"
	verify_checksum
	IFS="." read -ra path_parts <<<"$traverse_path"
	architecture="${path_parts[3]}"
	base_name="${iso_file%.*}"
	ext="${iso_file##*.}"
	mv "$iso_file" "${base_name}-${architecture}.${ext}"
}

download_opensuse()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	read -r line <"$sum_file"
	iso_file="${line##* }"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_parabola()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	wget_file "$url/$sum_file"
	verify_checksum
}

download_parch()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "Parch GNU/Linux does not provide signature files."
}

download_parrot()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url/$sig_file"
	verify_gpg_signature "$sig_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_pop()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_pure()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "PureOS does not provide signature files."
}

download_qubes()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url/$sum_file"
	verify_gpg_signature "$sum_file"
	wget_file "$url"/{"$sig_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_rocky()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "Rocky Linux does not provide signature files."
}

download_slackware()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_solus()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_tails()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	msg "Tails does not provide checksum files."
}

download_tinycore()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url"/{"$sum_file","$iso_file"}
	verify_checksum
	msg "Tiny Core does not provide signature files."
}

download_trisquel()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_ubuntu()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	[[ "$traverse_path" == *"v14_04"* ]] && fetch_gpg_key "${distro}_v14_04"
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_gpg_signature "$sig_file" "$sum_file"
	wget_file "$url/$iso_file"
	verify_checksum
}

download_void()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_minisign_key
	wget_file "$url"/{"$sig_file","$sum_file"}
	verify_minisign_signature
	wget_file "$url/$iso_file"
	verify_checksum
}

download_whonix()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	fetch_gpg_key "$distro"
	wget_file "$url"/{"$sig_file","$sum_file","$iso_file"}
	verify_gpg_signature "$sig_file" "$iso_file"
	verify_checksum
}

download_zorin()
{
	manual_download
	wget_spider "$url"/{"$iso_file","$sig_file","$sum_file"}
	wget_file "$url/$iso_file"
	msg "Zorin OS does not provide checksum or signature files."
}

toggle_auto_update()
{
	if [[ ! -f "$conf_file" ]]; then
		error "Config file not found."
		msg "Generating a new one..."
		generate_conf_file
	fi

	if [[ "$auto_update_enabled" == "true" ]]; then
		sed -i 's/auto_update_enabled=\(\"true\"\|true\)/auto_update_enabled="false"/' "$conf_file"
		msg "JSON auto-updating: ${red}DISABLED"
	else
		sed -i 's/auto_update_enabled=\(\"false\"\|false\)/auto_update_enabled="true"/' "$conf_file"
		msg "JSON auto-updating: ${green}ENABLED"
	fi
}

generate_conf_file()
{
	if [[ -f "$conf_file" ]]; then
		warn "This will overwrite the config file with default values."

		select gen_conf_choice in "Yes" "No"; do
			case "$gen_conf_choice" in
				"Yes")
					break
					;;
				"No")
					msg "Canceled."
					return 0
					;;
				*)
					error "Invalid option: ${red}$REPLY"
					;;
			esac
		done
	fi

	{
		# shellcheck disable=SC2016
		printf 'default_download_dir="$PWD"\n'
		printf 'auto_update_enabled="true"\n'
		printf 'verification_enabled="true"\n'
		printf 'spider_mode_enabled="false"\n'
		printf 'manual_download_enabled="false"\n'
		printf 'gpg_keyserver="hkps://keyserver.ubuntu.com"\n'
	} >"$conf_file"

	msg "Made config file at: ${green}$conf_file"
}

delete_gpg_key()
{
	dependency_check "gpg"

	gpg_key_id="${1:-}"

	if [[ -z "$gpg_key_id" ]]; then
		die "A GPG key ID is required."
	fi

	if gpg --homedir "$gpg_dir" --yes --batch --delete-keys "$gpg_key_id" &>/dev/null; then
		msg "GPG key deletion: ${green}SUCCESS"
	else
		die "GPG key deletion: ${red}FAILED"
	fi
}

import_gpg_key()
{
	dependency_check "gpg"

	gpg_key_file="${1:-}"

	if [[ -z "$gpg_key_file" ]]; then
		die "A path to a GPG key file is required."
	fi

	if gpg --homedir "$gpg_dir" --import "$gpg_key_file" &>/dev/null; then
		msg "GPG key import: ${green}SUCCESS"
	else
		die "GPG key import: ${red}FAILED"
	fi
}

list_gpg_keys()
{
	dependency_check "gpg"

	list_keys_output="$(gpg --homedir "$gpg_dir" --list-keys 2>/dev/null)"

	if [[ -z "$list_keys_output" ]]; then
		msg "No GPG keys saved."
	else
		printf "%s\n" "$list_keys_output"
	fi
}

purge_all_data()
{
	warn "This will remove ALL stored data at: ${yellow}$data_dir"

	select purge_choice in "Yes" "No"; do
		case "$purge_choice" in
			"Yes")
				rm -rf "$data_dir"
				msg "Data purge: ${green}SUCCESS"
				break
				;;
			"No")
				msg "Canceled."
				break
				;;
			*)
				error "Invalid option: ${red}$REPLY"
				;;
		esac
	done
}

update_json_file()
{
	temp_json_file="$json_dir/.temp_data.json"

	if wget -O "$temp_json_file" -q "$json_url"; then
		mv "$temp_json_file" "$json_dir/data.json"
		msg "JSON file update: ${green}SUCCESS"
	else
		rm -f "$temp_json_file"
		die "JSON file update: ${red}FAILED"
	fi
}

list_distros()
{
	printf "SUPPORTED DISTRIBUTIONS:\n"
	printf "%s\n" "$(printf "  %s\n" "${supported_distros[@]}" | sort)"
}

usage()
{
	cat <<EOF
USAGE:
  dl-distro [OPTIONS]
  dl-distro -d <DISTRO|JSON_QUERY> [OPTIONS]

OPTIONS:
  -a, --auto-update                          Toggle auto-updating of the local JSON file.
  -c, --conf-file                            Generate a config file with default values.
  -d, --distro <DISTRO|JSON_QUERY>           Specify a distro by name or a JSON query.
  -D, --delete-key <KEY_ID>                  Delete a stored GPG key using its key ID.
  -h, --help                                 Display this help message.
  -i, --import-key <KEY_FILE>                Import a GPG key from a key file.
  -l, --list-keys                            List the stored GPG keys.
  -L, --list-distros                         List the supported distributions.
  -m, --manual-download <DISTRO|JSON_QUERY>  Show manual download steps
  -n, --no-verify                            Skip key fetching and verification.
  -p, --path <PATH>                          Download image to specified directory.
  -P, --purge-data                           Purge ALL the stored data.
  -s, --spider                               Return HTTP status code for image's URLs.
  -u, --update-json                          Update the locally stored JSON file.
  -V, --version                              Print dl-distro's current version.

EXAMPLES:
  dl-distro
  dl-distro -d debian
  dl-distro -d debian.netinst.amd64 -p ~/Downloads
  dl-distro -m
  dl-distro -m debian.netinst.amd64
EOF
}

cleanup()
{
	[[ "$spider_mode_enabled" == "true" ]] && return 0
	[[ "$sig_file" != "null" ]] && rm -f "$sig_file"
	[[ "$sum_file" != "null" ]] && rm -f "$sum_file"
	return 0
}

start_download()
{
	mapfile -t json_values < <(jq -r "$traverse_path | .url, .iso_file, .sig_file, .sum_file" "$json_file")

	url="${json_values[0]}"
	iso_file="${json_values[1]}"
	sig_file="${json_values[2]}"
	sum_file="${json_values[3]}"
	sum_algo="$(jq -r ".sum_algos.$distro" "$json_file")"

	[[ "$manual_download_enabled" != "true" ]] && {
		trap cleanup EXIT INT TERM
		msg "Downloading at: ${green}$default_download_dir"
	}

	download_"$distro"

	exit 0
}

traverse_json_file()
{
	traverse_path="$1"

	if jq -e "$traverse_path.url" "$json_file" &>/dev/null; then
		start_download
	fi

	if [[ -n "${json_query:-}" ]]; then
		die "Invalid JSON query: ${red}${traverse_path#.}"
	fi

	mapfile -t json_keys < <(jq -r "$traverse_path | keys | .[]" "$json_file")

	json_options=("${json_keys[@]}" "back")

	if [[ "${#json_options[@]}" -le 1 ]]; then
		error "JSON parsing error."
		die "Try updating the JSON file, else report on Codeberg."
	fi

	printf "\e[2J\e[H"

	msg "${und}$distro_pretty_name"

	select json_choice in "${json_options[@]}"; do
		case "$json_choice" in
			"back")
				if [[ "$traverse_path" == ".$distro" ]]; then
					main_menu
				else
					traverse_json_file "${traverse_path%.*}"
				fi
				;;
			*)
				if [[ -z "$json_choice" ]]; then
					error "Invalid option: ${red}$REPLY"
					continue
				else
					traverse_json_file "$traverse_path.$json_choice"
				fi
				;;
		esac
	done
}

main_menu()
{
	mapfile -t sorted_distros < <(printf "%s\n" "${!supported_distros[@]}" | sort)

	printf "\e[2J\e[H"

	msg "${und}Main Menu"

	select main_menu_choice in "${sorted_distros[@]}"; do
		if [[ -z "$main_menu_choice" ]]; then
			error "Invalid option: ${red}$REPLY"
			continue
		fi

		distro="${supported_distros["$main_menu_choice"]}"
		distro_pretty_name="$main_menu_choice"

		wait

		traverse_json_file ".$distro"
	done
}

cli_mode()
{
	if [[ "$d_option_arg" == *.* ]]; then
		json_query="$d_option_arg"
		distro="${d_option_arg%%.*}"
	else
		distro="$d_option_arg"
	fi

	for key in "${!supported_distros[@]}"; do
		if [[ "${supported_distros[$key]}" == "$distro" ]]; then
			distro_pretty_name="$key"
			break
		fi
	done

	if type download_"$distro" &>/dev/null; then
		wait

		traverse_json_file ".${json_query:-$distro}"
	else
		die "Invalid distro: ${red}$distro"
	fi
}

parse_opts()
{
	cli_mode=0

	while (($#)); do
		case "$1" in
			-a | --auto-update)
				toggle_auto_update
				exit 0
				;;
			-c | --conf-file)
				generate_conf_file
				exit 0
				;;
			-d | --distro)
				if [[ -n "${2:-}" ]]; then
					d_option_arg="$2"
					cli_mode=1
					shift 2
				else
					die "$1 requires a distro name or JSON query."
				fi
				;;
			-D | --delete-key)
				delete_gpg_key "${2:-}"
				exit 0
				;;
			-h | --help)
				usage
				exit 0
				;;
			-i | --import-key)
				import_gpg_key "${2:-}"
				exit 0
				;;
			-l | --list-keys)
				list_gpg_keys
				exit 0
				;;
			-L | --list-distros)
				list_distros
				exit 0
				;;
			-m | --manual-download)
				manual_download_enabled="true"
				if [[ -n "${2:-}" ]]; then
					d_option_arg="$2"
					cli_mode=1
					shift 2
				else
					shift 1
				fi
				;;
			-n | --no-verify)
				verification_enabled="false"
				shift 1
				;;
			-p | --path)
				if [[ -n "${2:-}" ]]; then
					default_download_dir="$2"
					shift 2
				else
					die "$1 requires a directory path."
				fi
				;;
			-P | --purge-data)
				purge_all_data
				exit 0
				;;
			-s | --spider)
				spider_mode_enabled="true"
				shift 1
				;;
			-u | --update-json)
				update_json_file
				shift 1
				(($# == 0)) && exit 0
				;;
			-V | --version)
				printf "%s\n" "$version"
				exit 0
				;;
			*)
				die "Invalid option: ${red}$1"
				;;
		esac
	done
}

main()
{
	((EUID == 0)) && die "Do not run as root."

	dependency_check "jq" "wget"

	mkdir -p "$conf_dir" "$json_dir" "$gpg_dir" "$minisign_dir" "$signify_dir"
	chmod 700 "$gpg_dir"

	parse_opts "$@"

	if [[ ! -f "$json_file" || "$auto_update_enabled" == "true" ]]; then
		update_json_file >/dev/null &
	fi

	mkdir -p "$default_download_dir"
	cd "$default_download_dir"

	if [[ "$cli_mode" = 0 ]]; then
		main_menu
	else
		cli_mode
	fi
}

main "$@"
