#!/usr/bin/env bash

VERSION="0.4.0"

# --- Globals ---

RED='\033[0;31m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
NC='\033[0m'

MODS_DIR=""
DB_FILE=""
MODPACKS_FOLDER=""
MODPACKS_DB_FILE=""

H2PATH="${HOME}/.config/h2mm/h2path"
LAST_CHECKED_UPDATE_FILE="${HOME}/.config/h2mm/last_update"
VERSION_URL="https://raw.githubusercontent.com/v4n00/h2mm-cli/refs/heads/master/version"

breaking_changes_patches=(
    ["2"]='sed -i "s/^\([0-9]\+\),/\1,ENABLED,/" "$1/mods.csv"'
    ["3"]='sed -i "1 i\\3" "$1/mods.csv"'
	["4"]='awk '\''BEGIN {FS=OFS=","} NR==1 {print 4; next} {print NR-1, $2, $3, $4, $5}'\'' "$1/mods.csv" | tee "$1/mods.csv" > /dev/null'
)

# --- Utility Functions ---

function get_version_major() {
	echo "$1" | awk -F. '{print $2}'
}

function get_filename_without_path() {
	echo "$1" | awk -F/ '{print $NF}'
}

function get_patch_number() {
	echo "$1" | grep -oP '(?<=patch_)\d+'
}

function get_basename() {
	get_filename_without_path "$1" | sed -E 's/\.+.*//'
}

function get_extension() {
	get_filename_without_path "$1" | sed -E 's/.*patch_[0-9]+//'
}

function get_files_by_entry_from_db() {
	echo "$1" | cut -d',' -f4- | tr ',' ' ' | head -1
}

function get_name_by_entry_from_db() {
	echo "$1" | awk -F, '{print $3}'
}

function disable_all_modpacks() {
	sed -i 's/ENABLED/DISABLED/' "$MODPACKS_DB_FILE"
}

function parse_help_no_arguments() {
	display_help="$1"
	[[ "$2" == "--help" || "$2" == "-h" ]] && { $display_help; exit 0; }
}

function parse_help_has_arguments() {
	display_help="$1"
	[[ $# -eq 1 || "$2" == "--help" || "$2" == "-h"  ]] && { $display_help; exit 0; }
}

function log() {
	local type="$1"
	shift
	case "$type" in
		INFO)
			[[ "$silent" == "true" ]] && return
			echo -e "$*" >&2
			;;
		ERROR)
			echo -e "${RED}[ERROR]${NC} $*" >&2
			;;
		PROMPT)
			echo -ne "$*" >&2
			;;
		*)
			echo -e "$*" >&2
			;;
	esac
}

# --- Functions ---

function remove_disabled_prefix() {
	local disabledFile="$1"
	while [[ "$disabledFile" == disabled_* ]]; do
		disabledFile=$(echo "$disabledFile" | sed 's/^disabled_//')
	done
	echo "$disabledFile"
}

function get_mod_name_and_index() {
	# if calling install multiple times in the same call of h2mm, the mod_index needs to be reset if it was -1
	[[ $mod_index -eq -1 ]] && unset mod_index

	if [[ -n "$mod_index" ]]; then # if mod index exists
		entry=$(grep "^${mod_index}," "$DB_FILE")
		mod_name=$(get_name_by_entry_from_db "$entry")
	elif [[ -n "$mod_name" ]]; then # if mod name exists
		entry=$(grep -F ",$mod_name," "$DB_FILE")
		mod_index=$(echo "$entry" | awk -F, '{print $1}' | head -1)
	fi

	if [[ -z "$entry" || -z "$mod_index" || -z "$mod_name" ]]; then
		if [[ "$1" == "--do-not-exit" ]]; then
			mod_index=-1
		else
			log ERROR "Mod not found."
			exit 1
		fi
	fi

	status=$(echo "$entry" | awk -F, '{print $2}')
}

function get_modpack_name_and_index() {
	if [[ -n "$modpack_index" ]]; then # if modpack index exists
		entry=$(grep "^${modpack_index}," "$MODPACKS_DB_FILE")
		modpack_name=$(get_name_by_entry_from_db "$entry")
	elif [[ -n "$modpack_name" ]]; then # if modpack name exists
		entry=$(grep ",$modpack_name$" "$MODPACKS_DB_FILE")
		modpack_index=$(echo "$entry" | awk -F, '{print $1}' | head -1)
	fi

	if [[ -z "$entry" || -z "$modpack_index" || -z "$modpack_name" ]]; then
		log ERROR "Modpack not found."
		exit 1
	fi
}

function find_game_directory() {
	local search_dir="${HOME}"
	local target_dir="Steam/steamapps/common/Helldivers\ 2/data"

	# check if path is saved
	if [[ -f "$H2PATH" ]]; then
		saved_dir=$(cat "$H2PATH")
		if [[ -d "$saved_dir" ]]; then
			echo "$saved_dir"
			return
		else
			log ERROR "Saved game directory is invalid. Proceeding to get a new directory."
		fi
	fi

	# first time setup, or directory is not valid anymore
	log INFO "Searching for the Helldivers 2 data directory... (20 seconds timeout)"
	game_dir=$(timeout 20 find "$search_dir" -type d -path "*/$target_dir" 2>/dev/null | head -n 1)

	if [[ -z "$game_dir" ]]; then
		log INFO "Could not find the Helldivers 2 data directory automatically."
		log PROMPT "Please enter the path to the Helldivers 2 data directory: "
		IFS= read -e game_dir
		game_dir="$(realpath "${game_dir/#\~/$HOME}")"

		[[ ! -d "$game_dir" ]] && { log ERROR "Provided path is not a valid directory."; exit 1; }
	fi

	# save path
	mkdir -p "$(dirname "$H2PATH")"
	echo "$game_dir" > "$H2PATH"

	[[ $? -ne 0 ]] && { log ERROR "Could not save game directory."; exit 1; }
	log INFO "Game directory ${GREEN}saved${NC}: $game_dir"

	# return the directory
	echo "$game_dir"
}

function initialize_directories() {
	MODS_DIR=$(find_game_directory)
	DB_FILE="$MODS_DIR/mods.csv"

	if [[ ! -f "$DB_FILE" ]]; then
		touch "$DB_FILE"

		[[ $? -ne 0 ]] && { log ERROR "Could not create database file."; exit 1; }

		echo "$VERSION" | awk -F. '{print $2}' > "$DB_FILE"
		log INFO "Database file ${GREEN}created${NC}: $DB_FILE"
	fi
}

function initialize_modpack_directories() {
	MODPACKS_FOLDER="$MODS_DIR/modpacks"
	MODPACKS_DB_FILE="$MODPACKS_FOLDER/modpacks.csv"

	if [[ ! -d "$MODPACKS_FOLDER" || ! -f "$MODPACKS_DB_FILE" ]]; then
		mkdir -p "$MODPACKS_FOLDER" && touch "$MODPACKS_DB_FILE"
		[[ $? -ne 0 ]] && { log ERROR "Could not create modpacks folder/file."; exit 1; }

		echo "$VERSION" | awk -F. '{print $2}' > "$MODPACKS_DB_FILE"
		log INFO "Modpacks folder and file ${GREEN}created${NC}: $MODPACKS_FOLDER"
	fi
}

# --- Help Functions ---

function display_help_main() {
	cat << EOF
Helldivers 2 Mod Manager v${VERSION}
Usage: h2mm [OPTION] COMMAND
Commands:
	i, install					Install a mod by the file provided (directory, zip, patch).
	u, uninstall					Uninstall a mod.
	l, list						List all installed mods.
	e, enable					Enable a mod.
	d, disable					Disable a mod.
	ex, export					Export installed mods to a zip file.
	im, import					Import mods from a zip file.
	o, order					Change load order of a mod.
	mc, modpack-create				Create a modpack from the currently installed mods.
	ms, modpack-switch				Switch to a modpack.
	ml, modpack-list				List all installed modpacks.
	mc, modpack-delete				Delete a modpack.
	mo, modpack-overwrite				Overwrite a modpack.
	mr, modpack-reset				Reset all installed modpacks.
	up, update					Update h2mm to the latest version.
	r, reset					Reset all installed mods.
	help						Display this help message.
For more information on usage, use h2mm <COMMAND> --help.
Usage:
	h2mm install /path/to/mod.zip
	h2mm uninstall -n "Example mod"
	h2mm list
EOF
}

function display_help_install() {
	cat << EOF
Usage: h2mm install [OPTIONS] <MOD_FILES|MOD_DIRECTORIES|MOD_ZIPS>
Install a mod with any combination of mod files, directories, and zip files.
Options:
	-n "MOD_NAME"			Name of the mod.
	MOD_ZIPS			Zip file(s) containing mod files.
	MOD_FILES			Mod file(s), accepts wildcards.
	MOD_DIRECTORIES			Directory/directories containing mod files.
Example:
	h2mm install /path/to/mod.zip
	h2mm install /path/to/mod/files
	h2mm install /path/to/mod.zip /path/to/mod2.zip /path/to/mod/files
	h2mm install mod.patch_0 mod.patch_0.stream -n "Example mod"
EOF
}

function display_help_uninstall() {
	cat << EOF
Usage: h2mm uninstall [OPTIONS] <"MOD_NAME"|MOD_INDEX>
Uninstall a mod by name or index.
Options:
	-n "MOD_NAME"		Name of the mod to uninstall.
	-i MOD_INDEX		Index of the mod to uninstall.
Usage:
	h2mm uninstall -n "Example mod"
	h2mm uninstall -i 3
EOF
}

function display_help_enable() {
	cat << EOF
Usage: h2mm enable [OPTIONS] <"MOD_NAME"|MOD_INDEX>
Enable a mod by name or index.
Options:
	-n "MOD_NAME"		Name of the mod to enable.
	-i MOD_INDEX		Index of the mod to enable.
Usage:
	h2mm enable -n "Example mod"
	h2mm enable -i 3
EOF
}

function display_help_disable() {
	cat << EOF
Usage: h2mm disable [OPTIONS] <"MOD_NAME"|MOD_INDEX>
Disable a mod by name or index.
Options:
	-n "MOD_NAME"		Name of the mod to disable.
	-i MOD_INDEX		Index of the mod to disable.
Usage:
	h2mm disable -n "Example mod"
	h2mm disable -i 3
EOF
}

function display_help_list() {
	cat << EOF
Usage: h2mm list
Database of mods is stored in Steam/steamapps/common/Helldivers\ 2/data/mods.csv
You can rename, delete, or edit this file to manage mods manually.
Options:
	-v		Verbose mode.
EOF
}

function display_help_reset() {
	cat << EOF
Usage: h2mm reset
Reset all installed mods.
Deletes all installed mods/modpacks and the database file.
Database of mods is stored in Steam/steamapps/common/Helldivers 2/data/mods.csv, along with the mods.
EOF
}

function display_help_export() {
	cat << EOF
Usage: h2mm export
Export installed mods, modpacks and database to a zip file (in h2mm format - archive with csv) in the current working directory.
EOF
}

function display_help_import() {
	cat << EOF
Usage: h2mm import <ARCHIVE_FILE>
Import mods, modpacks and database from an archive file (coming from h2mm).
EOF
}

function display_help_order() {
	cat << EOF
Usage: h2mm order [OPTIONS] <"MOD_NAME"|MOD_INDEX> <NEW_INDEX>
Change order of a mod by name or index.
Options:
	-i index		Index of the mod to order.
	-n "MOD_NAME"		Name of the mod to order.
Usage:
	h2mm order -n "Example mod" 6
	h2mm order -i 3 6
EOF
}

function display_help_modpack_list() {
	cat << EOF
Usage: h2mm modpack-list
List all installed modpacks.
Database of modpacks is stored in Steam/steamapps/common/Helldivers\ 2/data/modpacks/modpacks.csv
You can rename, delete, or edit this file to manage modpacks manually.
EOF
}

function display_help_modpack_create() {
	cat << EOF
Usage: h2mm modpack-create -n "MODPACK_NAME"
Create a modpack from a range of mods specified after command is called.
EOF
}

function display_help_modpack_switch() {
	cat << EOF
Usage: h2mm modpack-switch [OPTIONS] <"MODPACK_NAME"|MODPACK_INDEX>
Switch to a modpack by name or index.
Options:
	-n "MODPACK_NAME"	Name of the modpack to switch to.
	-i index		Index of the modpack to switch to.
EOF
}

function display_help_modpack_reset() {
	cat << EOF
Usage: h2mm modpack-reset
Reset all installed modpacks.
Deletes all installed modpacks and the database file.
Database of modpacks is stored in Steam/steamapps/common/Helldivers\ 2/data/modpacks/modpacks.csv, along with the modpacks.
EOF
}

function display_help_modpack_delete() {
	cat << EOF
Usage: h2mm modpack-delete [OPTIONS] <"MODPACK_NAME"|MODPACK_INDEX>
Delete a modpack by name or index.
Options:
	-n "MODPACK_NAME"	Name of the modpack to delete.
	-i index		Index of the modpack to delete.
EOF
}

function display_help_modpack_overwrite() {
	cat << EOF
Usage: h2mm modpack-overwrite [OPTIONS] <"MODPACK_NAME"|MODPACK_INDEX>
Overwrite a modpack (the mods that it uses) by name or index.
Options:
	-n "MODPACK_NAME"	Name of the modpack to overwrite.
	-i index		Index of the modpack to overwrite.
EOF
}

# --- Main Functions ---

# Check for updates

function check_for_updates() {
	if [[ -f "$LAST_CHECKED_UPDATE_FILE" ]]; then
		last_update=$(cat "$LAST_CHECKED_UPDATE_FILE")
		if [[ "$(date +%s)" -lt $(("$(date +%s -d "$last_update")" + 3600)) ]]; then
			return
		fi
	else
		echo "$(date +%Y-%m-%dT%H:%M:%S)" > "$LAST_CHECKED_UPDATE_FILE"
		exit 0
	fi

	latest_version=$(curl -sS "$VERSION_URL")
	if [[ $? -ne 0 ]]; then
		log ERROR "Could not check for updates."
		return
	fi

	if [[ "$latest_version" != "$VERSION" ]]; then
		log INFO "A new version of h2mm is available: $VERSION -> $latest_version"
		log INFO "Run \"h2mm update\" to update."
	fi

	echo "$(date +%Y-%m-%dT%H:%M:%S)" > "$LAST_CHECKED_UPDATE_FILE"
}

# Upgrade/downgrade logic

function downgrade_mods() {
	local files="$1"
	declare -A downgrades_versions
	declare -A downgrades_to_apply

	for file in $files; do
		# save the basename for the files that were deleted into a hash table, so we can downgrade mods with greater version number
		# also depending on how many patches the mod has, we need to downgrade with more versions
		current_version=$(get_patch_number "$file")
		base_name=$(get_basename "$file")

		downgrades_versions["$base_name"]=$current_version

		# count how many unique patches the mod has
		[[ -z "${downgrades_to_apply["$base_name"]+unset}" ]] && downgrades_to_apply["$base_name"]=$(echo $files | tr ' ' '\n' | grep "$base_name" | sed -E "s/(.*_[0-9]+).*/\1/" | sort -u | wc -l)
	done

	# downgrade any necessary mods - it takes ~20 minutes to re-understand this code so I'm writing a comment here
	# for each base name, find all files that have the same base name, and are greater than the current version, and downgrade them
	# the downgrades_versions hash table stores the current version, so we can compare it with the version of the files
	# the downgrades_to_apply hash table stores the number of patches the mod has, so we can downgrade with more versions
	# for example, if we have:
	# mod 1: AAA.patch_0 AAA.patch_0.stream AAA.patch_1 AAA.patch_1.stream
	# mod 2: AAA.patch_2 AAA.patch_2.stream AAA.patch_3 AAA.patch_3.stream
	# mod 3: AAA.patch_4 AAA.patch_4.stream AAA.patch_5 AAA.patch_5.stream
	# if we remove mod with index 2, we need to downgrade mod with index 3 (which has patch version 4 and 5 > 3 (biggest last patch removed)) by 2 versions,
	# the number 2 we get by counting the number of unique base names (without extensions like .stream, but with the .patch_[0-9]) in the files
	for base_name in "${!downgrades_to_apply[@]}"; do
		# find all files that have the same base name, and are greater than the current version, and downgrade them
		IFS=$'\n' mods_to_downgrade=($(ls "$MODS_DIR/$base_name"*.patch_* 2>/dev/null | sort -V)); unset IFS

		for mod in "${mods_to_downgrade[@]}"; do
			mod=$(get_filename_without_path "$mod")
			patch_version=$(get_patch_number "$mod")

			if [[ $patch_version -gt ${downgrades_versions[$base_name]} ]]; then
				new_version=$((patch_version - downgrades_to_apply["$base_name"]))
				extension=$(get_extension "$mod")

				new_patch="${base_name}.patch_${new_version}${extension}"
				mv "$MODS_DIR/$mod" "$MODS_DIR/$new_patch"

				[[ $? -ne 0 ]] && { log ERROR "Could not downgrade mod file $mod."; exit 1; }
				log INFO "Downgraded ${ORANGE}$mod${NC} to ${GREEN}\$MODS_DIR/$new_patch${NC}."

				# save changes in database as well
				sed -i "s/\(\b$mod\b\)/$new_patch/" "$DB_FILE"
			fi
		done
	done
}

function upgrade_mods() {
	local files="$1"
	declare -A upgrade_versions
	declare -A upgrades_to_apply

	# opposite of downgrade_mods
	for file in $files; do
		# remove disabled_ prefix if it exists
		file=$(remove_disabled_prefix "$file")

		current_version=$(get_patch_number "$file")
		base_name=$(get_basename "$file")

		# basically save the lowest number, by limiting the setting of the key to the first time we see it
		[[ -z "${upgrade_versions["$base_name"]+unset}" ]] && upgrade_versions["$base_name"]=$current_version

		[[ -z "${upgrades_to_apply["$base_name"]+unset}" ]] && upgrades_to_apply["$base_name"]=$(echo $files | tr ' ' '\n' | grep "$base_name" | sed -E "s/(.*_[0-9]+).*/\1/" | sort -u | wc -l)
	done

	for base_name in "${!upgrades_to_apply[@]}"; do
		IFS=$'\n' mods_to_upgrade=($(ls "$MODS_DIR/$base_name"*.patch_* 2>/dev/null | sort -rV)); unset IFS

		for mod in "${mods_to_upgrade[@]}"; do
			mod=$(get_filename_without_path "$mod")
			patch_version=$(get_patch_number "$mod")

			if [[ $patch_version -ge ${upgrade_versions[$base_name]} ]]; then
				new_version=$((patch_version + upgrades_to_apply["$base_name"]))
				extension=$(get_extension "$mod")

				new_patch="${base_name}.patch_${new_version}${extension}"
				mv "$MODS_DIR/$mod" "$MODS_DIR/$new_patch"

				[[ $? -ne 0 ]] && { log ERROR "Could not upgrade mod file $mod."; exit 1; }
				log INFO "Upgraded ${ORANGE}$mod${NC} to ${GREEN}\$MODS_DIR/$new_patch${NC}."

				sed -i "s/\(\b$mod\b\)/$new_patch/" "$DB_FILE"
			fi
		done
	done
}

# Mod management

function mod_disable() {
	parse_help_has_arguments display_help_disable "$@"
	local mod_name=""
	local mod_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid mod index."; exit 1; }
				mod_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
				mod_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	if [[ -z "$mod_name" && -z "$mod_index" ]]; then
		log ERROR "Mod name or index is required to disable."
		exit 1
	fi

	# find mod files
	get_mod_name_and_index

	if [[ "$status" == "DISABLED" ]]; then
		log ERROR "Mod $mod_name is already disabled."
		exit 1
	fi

	# disable each mod file by adding disabled_ to the start of the filename
	current_mod_files=$(get_files_by_entry_from_db "$entry")
	for file in $current_mod_files; do
		[[ ! -f "$MODS_DIR/$file" ]] && { log ERROR "Mod file $file does not exist."; exit 1; }

		disabled_file="disabled_$file"
		while [[ -f "$MODS_DIR/$disabled_file" ]]; do
			disabled_file="disabled_$disabled_file"
		done

		mv "$MODS_DIR/$file" "$MODS_DIR/$disabled_file"

		[[ $? -ne 0 ]] && { log ERROR "Could not disable mod file $file."; exit 1; }
		log INFO "Disabled ${ORANGE}$file${NC} (changed to ${GREEN}\$MODS_DIR/$disabled_file${NC})."

		# save change to db
		sed -i "/^$mod_index,/ s/\(\b$file\b\)/$disabled_file/" "$DB_FILE"
	done

	# downgrade mods with greater version number
	downgrade_mods "$current_mod_files"

	# update the database
	sed -i "/^$mod_index,/s/ENABLED/DISABLED/" "$DB_FILE"

	[[ $? -ne 0 ]] && { log ERROR "Could not disable mod."; exit 1; }
	log INFO "Mod ${GREEN}successfully${NC} disabled: $mod_name."

	disable_all_modpacks
}

function mod_enable() {
	parse_help_has_arguments display_help_enable "$@"
	local mod_name=""
	local mod_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid mod index."; exit 1; }
				mod_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
				mod_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	[[ -z "$mod_name" && -z "$mod_index" ]] && { log ERROR "Mod name or index is required to enable."; exit 1; }

	# find mod files
	get_mod_name_and_index

	[[ "$status" == "ENABLED" ]] && { log ERROR "Mod $mod_name is already enabled."; exit 1; }

	current_mod_files=$(get_files_by_entry_from_db "$entry")

	# upgrade mods with lower version number
	upgrade_mods "$current_mod_files"

	# enable each mod file by removing disabled_ from the start of the filename
	for file in $current_mod_files; do
		enabled_file=$(remove_disabled_prefix "$file")

		# check if the files exists
		[[ -f "$MODS_DIR/$file" ]] || { log ERROR "Mod file $file does not exist."; exit 1; }

		mv "$MODS_DIR/$file" "$MODS_DIR/$enabled_file"

		# check if the file was moved successfully
		[[ $? -ne 0 ]] && { log ERROR "Could not enable mod file $file."; exit 1; }
		log INFO "Enabled ${ORANGE}$file${NC} (changed to ${GREEN}\$MODS_DIR/$enabled_file${NC})."

		# save change to db
		sed -i "/^$mod_index,/ s/\(\b$file\b\)/$enabled_file/" "$DB_FILE"
	done

	# update the database
	sed -i "/^$mod_index,/s/DISABLED/ENABLED/" "$DB_FILE"

	[[ $? -ne 0 ]] && { log ERROR "Could not enable mod."; exit 1; }
	log INFO "Mod ${GREEN}successfully${NC} enabled: $mod_name."

	disable_all_modpacks
}

function mod_reset() {
	parse_help_no_arguments display_help_reset "$@"
	local no_path_reset=false; [[ "$1" == "--no-path-reset" ]] && no_path_reset=true

	log PROMPT "Are you sure you want to ${RED}reset${NC} all installed mods? (Y/n): "
	read confirm

	if [[ "$confirm" == "y" || "$confirm" == "Y" || "$confirm" = "" ]]; then
		rm -f "$MODS_DIR"/*.patch_*
		rm -f "$DB_FILE"
		[[ $no_path_reset == false ]] && rm -f "$H2PATH"
		log INFO "Mods ${GREEN}successfully${NC} reset."
	else
		log INFO "Reset cancelled."
		exit 0
	fi
}

function mod_install() {
	parse_help_has_arguments display_help_install "$@"
	local mod_name=""
	local mod_dir=()
	local mod_files=()
	local mod_zip=()

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
		"-n")
			[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
			mod_name="$2"; shift 2
			;;
		*)
			if [[ -f "$1" && "$1" == *.zip ]]; then
				mod_zip+=("$1")
			elif [[ -d "$1" ]]; then
				mod_dir+=("$1")
			else
				mod_files+=("$1")
			fi
			shift
			;;
		esac
	done

	# edge case when there is a combination of mod zips and directories, call function for all zips and dirs
	if [[ ${mod_zip} && ${mod_dir} ]]; then
		mod_install "${mod_zip[@]}"
		mod_install "${mod_dir[@]}"

		# reset arrays
		mod_zip=()
		mod_dir=()

		# if there are no more arguments, exit
		[[ ${#mod_files[@]} -eq 0 ]] && exit 0
	fi

	# if there's more than 1 zip, call recursively
	while [[ ${#mod_zip[@]} -gt 1 ]]; do
		mod_install "${mod_zip[0]}"
		mod_zip=("${mod_zip[@]:1}")
	done

	# if zip, extract the zip file and pass it to mod dirs
	if [[ -n "$mod_zip" ]]; then
		command -v unzip &> /dev/null || { log ERROR "unzip package is not installed."; exit 1; }

		[[ ! -f "$mod_zip" ]] && { log ERROR "Zip file $mod_zip does not exist."; exit 1; }

		# if the name is not specified, use the name of the directory, last sed for making nexusmods names not have numbers
		if [[ -z "$mod_name" ]]; then
			mod_name=$(basename "$mod_zip" | sed -E 's/\.zip//' | awk -F/ '{print $NF}' | sed -E 's/-[0-9]+-.*//')
		fi

		# mod_dir as a temporary directory
		mod_dir+=$(mktemp -d)
		unzip -qq "$mod_zip" -d "$mod_dir"
	fi

	# if there's more than 1 directory, call recursively
	while [[ ${#mod_dir[@]} -gt 1 ]]; do
		mod_install "${mod_dir[0]}"
		mod_dir=("${mod_dir[@]:1}")
	done

	# directory containing mod files
	if [[ -n "$mod_dir" ]]; then
		# verify directory exists
		[[ ! -d "$mod_dir" ]] && { log ERROR "Directory $mod_dir does not exist."; exit 1; }

		# read every file from the directory
		readarray -d '' mod_files < <(find "$mod_dir" -type f -name "*.patch_*" -print0)
		# if the name is not specified, use the name of the directory, last sed for making nexusmods names not have numbers
		if [[ -z "$mod_name" ]]; then
			mod_name=$(echo "$mod_dir" | sed 's:/*$::' | awk -F/ '{print $NF}' | sed -E 's/-[0-9]+-.*//')
		fi

		# check for mod variants and handle
		# if the mod directory contains more than 1 directory, it means there are multiple variants for the mod
		# prompt the user to choose which variant to install, or install multiple
		readarray -d '' all_dirs < <(find "$mod_dir" -mindepth 1 -type d -print0)

		# filter so that we only have dirs that have *.patch_* files inside them
		filtered_dirs=()
		for dir in "${all_dirs[@]}"; do
			if find "$dir" -maxdepth 1 -type f -name "*.patch_*" -print -quit | grep -q .; then
				filtered_dirs+=("$dir")
			fi
		done

		if [[ ${#filtered_dirs[@]} -gt 1 ]]; then
			log INFO "Multiple mod variants found for mod ${mod_name}."
			for i in "${!filtered_dirs[@]}"; do
				log INFO "$((i + 1)). $(basename "${filtered_dirs[$i]}")"
			done

			# prompt user to choose
			log PROMPT "Enter the number of the variant(s) to install (separated by space) or press Enter to install all: "
			read -a variant_indices
			if [[ -n "${variant_indices[0]}" ]]; then
				# clear mod_files
				mod_files=()

				# get the files from the chosen variant
				for index in "${variant_indices[@]}"; do
					[[ ! "$index" =~ ^[0-9]+$ ]] && { log ERROR "Invalid variant index."; exit 1; }
					[[ $index -lt 1 || $index -gt ${#filtered_dirs[@]} ]] && { log ERROR "Variant index out of range."; exit 1; }

					readarray -d '' variant_files < <(find "${filtered_dirs[$((index - 1))]}" -type f -name "*.patch_*" -print0)

					# update mod_name to contain the variant name
					mod_name="${mod_name} [$(basename "${filtered_dirs[$((index - 1))]}")]"

					# add the files to the mod_files array
					mod_files+=("${variant_files[@]}")
				done
			fi
		fi
	fi

	# verify minimum information required
	[[ -z "$mod_name" || ${#mod_files[@]} -eq 0 ]] && { log ERROR "Mod name and files are required."; exit 1; }

	# verify duplicate mod names
	get_mod_name_and_index --do-not-exit
	[[ $mod_index -ne -1 ]] && { log ERROR "The mod '$mod_name' is already installed."; exit 1; }

	# verify mod files exist
	for file in "${mod_files[@]}"; do
		[[ ! -f "$file" ]] && { log ERROR "Mod file $file does not exist."; exit 1; }
	done

	# store the target files so we can put them in the database later
	target_files=()
	# sort the mod files because with the below logic, the .stream and .gpu_resources files need to come after their respective patch files
	IFS=$'\n' mod_files=($(printf "%s\n" "${mod_files[@]}" | sort -t. -k1,1 -k2,2n)); unset IFS

	for file in "${mod_files[@]}"; do
		base_name=$(get_basename "$file")
		extension=$(get_extension "$file")


		# count already installed patches
		count=$(ls "$MODS_DIR/${base_name}.patch_"* 2>/dev/null | grep -E '([0-9]+$)' 2>/dev/null | wc -l)

		# if the file has an extension, look for the last patch number and subtract 1, otherwise, the count will be wrong
		target_file="${base_name}.patch_"
		if [[ -n "$extension" ]]; then
			target_file="${target_file}$(($count - 1))${extension}"
		else
			target_file="${target_file}${count}"
		fi

		target_files+=($target_file)
		cp "$file" "$MODS_DIR/$target_file"

		# verify installation worked
		[[ $? -ne 0 ]] && { log ERROR "Could not install mod file $file."; exit 1; }
		log INFO "Mod file ${ORANGE}$file${NC} installed at ${GREEN}\$MODS_DIR/$target_file${NC}."
	done

	# add entry to database
	next_id=$(awk -F, 'NR > 1 {last_id = $1} END {print last_id + 1}' "$DB_FILE")
	echo "$next_id,ENABLED,$mod_name,${target_files[*]}" >> "$DB_FILE"
	log INFO "Mod ${GREEN}successfully${NC} installed: $mod_name."

	# disable any modpack
	disable_all_modpacks
}

function mod_uninstall() {
	parse_help_has_arguments display_help_uninstall "$@"
	local mod_name=""
	local mod_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid mod index."; exit 1; }
				mod_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
				mod_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	[[ -z "$mod_name" && -z "$mod_index" ]] && { log ERROR "Mod name or index is required to uninstall."; exit 1; }

	# find mod files
	get_mod_name_and_index

	# delete mod files
	current_mod_files=$(get_files_by_entry_from_db "$entry")

	for file in $current_mod_files; do
		[[ ! -f "$MODS_DIR/$file" ]] && { log ERROR "Mod file $file does not exist."; exit 1; }

		log INFO "Removing ${ORANGE}\$MODS_DIR/$file${NC}."
		rm "$MODS_DIR/$file"

		[[ $? -ne 0 ]] && { log ERROR "Could not remove mod file $file."; exit 1; }
	done

	# downgrade mods with greater version number, only if the mod is enabled
	[[ "$status" == "ENABLED" ]] && downgrade_mods "$current_mod_files"

	# remove entry from database
    sed -i "/^$mod_index,/d" "$DB_FILE"

    # reindex the database
    mods_to_reindex=($(awk -F, -v idx=$mod_index 'NR > 1 && $1 > idx {print $1}' "$DB_FILE"))
    for idx in "${mods_to_reindex[@]}"; do
        sed -i "s/^$idx,/$(($idx - 1)),/" "$DB_FILE"
    done

    log INFO "Mod ${GREEN}successfully${NC} uninstalled: $mod_name."

	# disable any modpack
	disable_all_modpacks
}

function mod_list() {
	parse_help_no_arguments display_help_list "$@"
    local verbose=false

    # parse arguments
	[[ "$1" == "--verbose" || "$1" == "-v" ]] && verbose=true

	# quit if no mods are installed
    [[ $(wc -l < "$DB_FILE") -le 1 ]] && { log INFO "No mods installed."; exit 0; }

    log INFO "Installed mods:"

    awk -v GREEN="$GREEN" -v RED="$RED" -v NC="$NC" -v verbose="$verbose" -F, 'NR > 1 {
        color = ($2 == "DISABLED") ? RED : GREEN;
		if (verbose == "false") {
			$4 = "";
		} else {
			gsub(/ /,"\n   -> ", $4);
			$4 = "\n   -> " $4;
		}
        printf "%2s. [%s%s%s] %s %s\n", $1, color, $2, NC, $3, $4
    }' "$DB_FILE"
}

function mod_export() {
	parse_help_no_arguments display_help_export "$@"
	local save_dir=$(pwd)
	local archive_name="Helldivers_2_Mods_$(date +%Y-%m-%d_%H-%M-%S)"
	local modpack_export=false
	local force=false

	if [[ "$1" == "--modpack" ]]; then
		modpack_export=true
		save_dir="$2"
		archive_name="$3"
	fi

	[[ $(wc -l < "$DB_FILE") -le 1 ]] && { log ERROR "No mods installed."; exit 1; }

	if [[ $modpack_export == false ]]; then
		log PROMPT "Archive file will be saved to ${save_dir}/${archive_name}. Make? (Y/n): "
		read -r confirm
	fi
	if [[ $modpack_export == true || "$confirm" == "y" || "$confirm" == "Y" || "$confirm" = "" ]]; then
		# create a temporary directory to store the mods
		OUT_DIR=$(mktemp -d)
		MODS_EXPORT_DIR="$OUT_DIR/Helldivers 2 Mods"
		mkdir -p "$MODS_EXPORT_DIR"
		cp "$DB_FILE" "$MODS_EXPORT_DIR"

		# copy modpacks if modpack_export is false
		if [[ $modpack_export == false ]]; then
			mkdir -p "$MODS_EXPORT_DIR/modpacks"
			cp "$MODPACKS_FOLDER"/* "$MODS_EXPORT_DIR/modpacks"
		fi

		[[ $? -ne 0 ]] && { log ERROR "Could not copy mods to target directory."; exit 1; }

		# copy all mod files to the export directory
		for file in $(ls "$MODS_DIR/" 2>/dev/null | grep -E 'patch_.*'); do
			cp "$MODS_DIR/$file" "$MODS_EXPORT_DIR"
		done

		[[ $? -ne 0 ]] && { log ERROR "Could not export mods. Possibly because no mods are present."; exit 1; }

		# zip up the mods with the current date and time in the name
		[[ -f "$save_dir/${archive_name}.tar.gz" ]] && { log ERROR "File $save_dir/${archive_name}.tar.gz already exists."; exit 1; }
		tar -czf "$save_dir/${archive_name}.tar.gz" -C "$OUT_DIR" "Helldivers 2 Mods"

		[[ $? -ne 0 ]] && { log ERROR "Failed to export mods."; exit 1; }
		[[ "$modpack_export" == false ]] && log INFO "Mods ${GREEN}successfully${NC} exported."
	fi
}

function mod_import() {
	parse_help_has_arguments display_help_import "$@"
	local modpack=false; [[ "$1" == "--modpack" ]] && { modpack=true; shift 1; }

	# check if the file exists
	[[ ! -f "$1" ]] && { log ERROR "File $1 does not exist."; exit 1; }

	# reset mods before importing
	[[ $modpack == false ]] && log INFO "Importing mods will ${RED}reset${NC} your mods."
	mod_reset --no-path-reset

	# extract in temp directory
	OUT_DIR=$(mktemp -d)
	tar -xzf "$1" -C "$OUT_DIR"
	[[ $? -ne 0 ]] && { log ERROR "Could not import mods. Possibly because the archive is invalid."; exit 1; }

	# get the mods directory
	MODS_EXPORT_DIR="$OUT_DIR/Helldivers 2 Mods"
	[[ ! -d "$MODS_EXPORT_DIR" ]] && { log ERROR "Could not import mods. Possibly because the archive is invalid."; exit 1; }

	# fix breaking changes if the version number (from the first line) is different
	db_version=$(head -n 1 "$MODS_EXPORT_DIR/mods.csv")
	current_version=$(echo "$VERSION" | awk -F. '{print $2}')

	[[ -z "$db_version" || ! "$db_version" =~ ^[0-9]+$ ]] && { log ERROR "Invalid version number inside mods.csv from imported archive."; exit 1; }

	if [[ "$db_version" != "$current_version" ]]; then
		log INFO "Import detected version 0.$db_version.x, current version is 0.$current_version.x."

		# iterate from installed major number to latest major number
		for ((i = db_version + 1; i <= current_version; i++)); do
			if [[ -n "${breaking_changes_patches[$i]}" ]]; then
				# apply breaking changes patch
				eval $(echo "${breaking_changes_patches[$i]}" | sed "s:\$1:$MODS_EXPORT_DIR:g")
			else
				log INFO "No breaking changes for version $i."
				continue
			fi

			if [[ $? -ne 0 ]]; then
				log ERROR "Failed to apply breaking changes patch for version $i. Do you want to continue? (Y/n): "
				read -er response

				[[ "$response" != "y" && "$response" != "Y" && -n "$response" ]] && { log INFO "Exiting." ; exit 1; }
			else
				log INFO "Version upgrade fix ${GREEN}successfully${NC} applied for version $i."
			fi
		done
	fi

	# copy everything in
	cp -r "$MODS_EXPORT_DIR"/* "$MODS_DIR"

	[[ $? -ne 0 ]] && { log ERROR "Failed to import mods."; exit 1; }

	log INFO "Mods ${GREEN}successfully${NC} imported."
}

function mod_order {
	parse_help_has_arguments display_help_order "$@"
	local mod_name=""
	local mod_index=""
	local new_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid mod index."; exit 1; }
				mod_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Mod name is required."; exit 1; }
				mod_name="$2"; shift 2
				;;
			*)
				new_index="$1"; shift 1
				;;
		esac
	done

	[[ -z "$mod_name" && -z "$mod_index" ]] && { log ERROR "Mod name or index is required to change order."; exit 1; }

	# find mod files
	get_mod_name_and_index

	# get the number of mods
	mod_count=$(($(cat "$DB_FILE" | wc -l) - 1))

	# check if the mod is already at the desired index and if new index is valid
	[[ "$mod_index" == "$new_index" ]] && { log ERROR "Mod $mod_name is already at index $new_index."; exit 1; }
	[[ $new_index -lt 1 ]] && { log ERROR "Index can not be less than 1."; exit 1; }
	[[ $new_index -gt $mod_count ]] && { log ERROR "Index can not be more than mod count (${mod_count})."; exit 1; }

	# assert ascending or descending order
	ascending_order=true
	[[ $mod_index -gt $new_index ]] && ascending_order=false

	# get entries between the indexes
	if [[ $ascending_order == true ]]; then
		entries=$(awk -v mod_index="$mod_index" -v new_index="$new_index" -F, 'NR > 1 && $1 > mod_index && $1 <= new_index {print $0}' "$DB_FILE")
	else
		entries=$(awk -v mod_index="$mod_index" -v new_index="$new_index" -F, 'NR > 1 && $1 < mod_index && $1 >= new_index {print $0}' "$DB_FILE")
	fi

	# get files for the current mod
	IFS= current_mod_files=$(get_files_by_entry_from_db "$entry"); unset IFS

	# step 1 - count how many replaces need to be done in hash table
	declare -A replace_count
	for file in $current_mod_files; do
		extension=$(get_extension "$file")
		base_name=$(get_basename "$file")

		# if the file has no extension, add the basename to the hash table or increment the count
		if [[ -z "$extension" ]]; then
			replace_count["$base_name"]=$(( ${replace_count["$base_name"]:-0} + 1 ))
		fi
	done

	# step 2.1 - iterate over the basenames, find and store the files with the same basenames as the ones we want to upgrade/downgrade
	declare -A files_to_replace
	for base_name in "${!replace_count[@]}"; do
		IFS= files_to_replace["$base_name"]=$(echo "$entries" | awk -F, '{print $4}' | grep -o "$base_name\.patch_[^ ]*"); unset IFS
	done

	# step 2.2 - reverse sort the files_to_replace if we are in descending order
	[[ $ascending_order == false ]] && for base_name in "${!files_to_replace[@]}"; do
		files_to_replace["$base_name"]=$(echo "${files_to_replace["$base_name"]}" | sort -rV)
	done

	# move current mod files to "t_$file" so we can move the new files to the correct index
	for file in $current_mod_files; do
		mv "$MODS_DIR/$file" "$MODS_DIR/t_$file"
		[[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; }
	done

	# step 3 - for every basename in files_to_replace, add replace_count to each file's patch number and move
	for base_name in "${!files_to_replace[@]}"; do
		files=$(echo "${files_to_replace["$base_name"]}" | tr ' ' '\n')

		for file in $files; do
			base_name=$(get_basename "$file")
			patch_number=$(get_patch_number "$file")
			extension=$(get_extension "$file")

			# if ascending order, subtract replace_count to the patch number, otherwise add
			operation="-"; [[ $ascending_order == false ]] && operation="+"
			new_file="${base_name}.patch_$(($patch_number $operation ${replace_count["$base_name"]}))${extension}"

			mv "$MODS_DIR/$file" "$MODS_DIR/$new_file"
			[[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; }

			log INFO "Reindexing ${ORANGE}$file${NC} to ${GREEN}$new_file${NC}."

			# update db
			sed -i "s/\(\b$file\b\)/$new_file/" "$DB_FILE"
		done
	done

	# step 4 - for every file in current_mod_files, subtract the basename's array size of files_to_replace from the patch number
	for file in $current_mod_files; do
		base_name=$(get_basename "$file")
		patch_number=$(get_patch_number "$file")
		extension=$(get_extension "$file")

		# get size of only files without an extension
		size=$(echo "${files_to_replace["$base_name"]}" | grep -E "${base_name}.patch_[0-9]+$" | tr ' ' '\n' | wc -l)

		# in case size is 0, move t_file back to file later
		new_file=${file}

		if [[ $size -gt 0 ]]; then
			# if ascending order, add size from the patch number, otherwise subtract
			operation="+"; [[ $ascending_order == false ]] && operation="-"
			new_file="${base_name}.patch_$(($patch_number $operation $size))${extension}"

			log INFO "Reindexing ${ORANGE}$file${NC} to ${GREEN}$new_file${NC}."

			# dont forget current mod files are prefixed with t_
			mv "$MODS_DIR/t_$file" "$MODS_DIR/t_$new_file"
			[[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; }

			# update file names
			entry=$(echo "$entry" | sed "s/\(\b$file\b\)/$new_file/")
		fi

		# move back temp file
		mv "$MODS_DIR/t_$new_file" "$MODS_DIR/$new_file"

		# update index (will be replaced in db later)
		entry=$(echo "$entry" | sed "s/^$mod_index,/$new_index,/")
	done

	# step 5.1 - change current mod to t_index so we can move the other indexes
	sed -i "s/^$mod_index,/t_$mod_index,/" "$DB_FILE"

	# step 5.2 - reindex the rest of the mods
	idxs=$(echo "$entries" | awk -F, '{print $1}' | tr ' ' '\n')

	# step 5.3 - reverse sort the idxs if we are in descending order
	[[ $ascending_order == false ]] && idxs=$(echo "$idxs" | sort -r)

	for idx in $idxs; do
		# if ascending order, subtract 1 from the index, otherwise add 1
		operation="-"; [[ $ascending_order == false ]] && operation="+"

		sed -i "s/^$idx,/$(($idx $operation 1)),/" "$DB_FILE"
	done

	# step 6 - move the entry to the new index
	sed -i "/^t_$mod_index,/d" "$DB_FILE"

	# weird edge case where the new index is the last index
	if [[ $((new_index + 1)) -gt $(wc -l < "$DB_FILE") ]]; then
		echo "$entry" >> "$DB_FILE"
	else
		sed -i "$((new_index + 1))i $entry" "$DB_FILE"
	fi

	log INFO "Mod ${GREEN}successfully${NC} reindexed: \"$mod_name\" went from $mod_index to $new_index."
}

# --- Modpack management ---

function modpack_list() {
	parse_help_no_arguments display_help_modpack_list "$@"

	# quit if no modpacks are saved
	[[ $(wc -l < "$MODPACKS_DB_FILE") -le 1 ]] && { log INFO "No modpacks saved."; exit 0; }

	log INFO "Saved modpacks:"

	awk -v GREEN="$GREEN" -v RED="$RED" -v NC="$NC" -F, 'NR > 1{
	color = ($2 == "DISABLED") ? RED : GREEN;
	printf "%2s. [%s%s%s] %s\n", $1, color, $2, NC, $3}' "$MODPACKS_DB_FILE"
}

function modpack_create() {
	parse_help_no_arguments display_help_modpack_create "$@"
	local modpack_name=""

	# if no mods are installed, exit
	[[ $(wc -l < "$DB_FILE") -le 1 ]] && { log ERROR "No mods installed."; exit 1; }

	# if the same modpack name already exists, exit
	[[ -f "$MODPACKS_FOLDER/$1.tar.gz" ]] && { log ERROR "Modpack $1 already exists."; exit 1; }

	# let user select mods to save
	mod_list
	log PROMPT "Enter the mod indices to use for modpack (separated by space) or press Enter to select all: "
	read -a mod_indices
	if [[ -n "${mod_indices[0]}" ]]; then
		# get the mod entries from the indices
		mod_entries=()

		# grab all the entries from the database from the indices
		for index in "${mod_indices[@]}"; do
			[[ ! "$index" =~ ^[0-9]+$ ]] && { log ERROR "Invalid mod index."; exit 1; }
			[[ $index -lt 1 || $index -gt $(awk -F, 'END {print $1}' "$DB_FILE" ) ]] && { log ERROR "Mod index out of range."; exit 1; }

			IFS= mod_entries+=($(awk -v idx=$index -F, 'NR > 1 && $1 == idx {print $0}' "$DB_FILE")); unset IFS
		done

		# overwrite all paths to create a custom folder that will hold the mods specified, the mods will be "installed" there
		OLD_MODS_DIR="$MODS_DIR"
		MODS_DIR="$(mktemp -d)"
		DB_FILE="$MODS_DIR/mods.csv"
		echo "$VERSION" > "$DB_FILE"

		# install selected mods to temp directory
		for entry in "${mod_entries[@]}"; do
			mod_name=$(get_name_by_entry_from_db "$entry")
			IFS= _mod_files=$(get_files_by_entry_from_db "$entry"); unset IFS
			mod_files=()

			# add the OLD_MODS_DIR prefix to every entry from mod_files
			for file in $_mod_files; do
				mod_files+=("$OLD_MODS_DIR/$file")
			done

			silent=true
			mod_install -n "$mod_name" "${mod_files[@]}"
			silent=false
		done
	fi

	# use built-in export function
	modpack_name="$1"
	mod_export --modpack "$MODPACKS_FOLDER" "$modpack_name"

	log INFO "Modpack ${GREEN}successfully${NC} created: \$MODPACKS_FOLDER/$modpack_name.tar.gz"
	log INFO "Switch to the modpack with 'h2mm modpack-switch -n \"$modpack_name\"'."

	# warn user to create a modpack with his main mods
	[[ $(wc -l < "$MODPACKS_DB_FILE") -le 1 ]] && log INFO "If this is your first modpack, it is ${ORANGE}recommended${NC} to create a separate modpack with your main mods."

	# add entry to database
	next_id=$(awk -F, 'NR > 1 {last_id = $1} END {print last_id + 1}' "$MODPACKS_DB_FILE")
	echo "$next_id,DISABLED,$modpack_name" >> "$MODPACKS_DB_FILE"
}

function modpack_switch() {
	parse_help_has_arguments display_help_modpack_switch "$@"
	local modpack_name=""
	local modpack_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid modpack index."; exit 1; }
				modpack_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Modpack name is required."; exit 1; }
				modpack_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	[[ -z "$modpack_name" && -z "$modpack_index" ]] && { log ERROR "Modpack name or index is required to switch."; exit 1; }

	# find modpack files
	get_modpack_name_and_index "$modpack_name" "$modpack_index"

	log INFO "Switching modpacks mods will ${RED}reset${NC} your mods."
	silent=true
	mod_import --modpack "$MODPACKS_FOLDER/$modpack_name.tar.gz"
	silent=false

	log INFO "Modpack ${GREEN}successfully${NC} switched: $modpack_name."

	# save status to db
	sed -i "s/ENABLED/DISABLED/" "$MODPACKS_DB_FILE"
	sed -i "/^$modpack_index,/s/DISABLED/ENABLED/" "$MODPACKS_DB_FILE"
}

function modpack_reset() {
	parse_help_no_arguments display_help_modpack_reset "$@"

	log PROMPT "Are you sure you want to ${RED}reset${NC} all installed modpacks? (Y/n): "
	read confirm
	if [[ "$confirm" == "y" || "$confirm" == "Y" || "$confirm" = "" ]]; then
		rm -f "$MODPACKS_FOLDER"/*.tar.gz
		rm -f "$MODPACKS_DB_FILE"
		rmdir "$MODPACKS_FOLDER"
		log INFO "Modpacks ${GREEN}successfully${NC} reset."
	else
		log INFO "Reset cancelled."
		exit 1
	fi
}

function modpack_delete() {
	parse_help_has_arguments display_help_modpack_delete "$@"
	local modpack_name=""
	local modpack_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid modpack index."; exit 1; }
				modpack_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Modpack name is required."; exit 1; }
				modpack_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	[[ -z "$modpack_name" && -z "$modpack_index" ]] && { log ERROR "Modpack name or index is required to delete."; exit 1; }

	get_modpack_name_and_index "$modpack_name" "$modpack_index"

	rm -f "$MODPACKS_FOLDER/$modpack_name.tar.gz"

	[[ $? -ne 0 ]] && { log ERROR "Could not delete modpack."; exit 1; }
	log INFO "Modpack ${GREEN}successfully${NC} deleted: \$MODPACKS_FOLDER/$modpack_name.tar.gz"

	# remove entry from database
	sed -i "/^$modpack_index,/d" "$MODPACKS_DB_FILE"
}

function modpack_overwrite() {
	parse_help_has_arguments display_help_modpack_overwrite "$@"
	local modpack_name=""
	local modpack_index=""

	# parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			"-i")
				[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { log ERROR "Invalid modpack index."; exit 1; }
				modpack_index="$2"; shift 2
				;;
			"-n")
				[[ -z "$2" ]] && { log ERROR "Modpack name is required."; exit 1; }
				modpack_name="$2"; shift 2
				;;
			*)
				$display_help; exit 0
				;;
		esac
	done

	[[ -z "$modpack_name" && -z "$modpack_index" ]] && { log ERROR "Modpack name or index is required to overwrite."; exit 1; }

	get_modpack_name_and_index "$modpack_name" "$modpack_index"

	# if the modpack doesn't exist, exit
	[[ ! -f "$MODPACKS_FOLDER/$modpack_name.tar.gz" ]] && { log ERROR "Modpack $modpack_name does not exist."; exit 1; }

	rm -f "$MODPACKS_FOLDER/$modpack_name.tar.gz"
	[[ $? -ne 0 ]] && { log ERROR "Could not delete modpack."; exit 1; }

	# use built-in export function
	mod_export --modpack "$MODPACKS_FOLDER" "$modpack_name"

	log INFO "Modpack ${GREEN}successfully${NC} overwritten: \$MODPACKS_FOLDER/$modpack_name.tar.gz"
	sed -i "/^$modpack_index,/s/DISABLED/ENABLED/" "$MODPACKS_DB_FILE"
}

function self_update() {
	latest_version=$(curl -sS "$VERSION_URL")

	if [[ "$latest_version" == "$VERSION" ]]; then
		log INFO "Mod manager is already up-to-date."
		exit 0
	fi

	log INFO "Starting update script..."

	# run the installer for the latest version
	bash -c "$(curl -fsSL https://raw.githubusercontent.com/v4n00/h2mm-cli/refs/heads/master/install.sh)"
	exit 0
}

# --- Main ---

function main() {
	parse_help_has_arguments display_help_main "$@"

	command="$1"; shift

	initialize_directories
	initialize_modpack_directories
	check_for_updates

	case "$command" in
		"install"|"i")
			mod_install "$@"
			;;
		"list"|"l")
			mod_list "$@"
			;;
		"uninstall"|"u")
			mod_uninstall "$@"
			;;
		"enable"|"e")
			mod_enable "$@"
			;;
		"disable"|"d")
			mod_disable "$@"
			;;
		"export"|"ex")
			mod_export "$@"
			;;
		"import"|"im")
			mod_import "$@"
			;;
		"order"|"o")
			mod_order "$@"
			;;
		"modpack-list"|"ml")
			modpack_list "$@"
			;;
		"modpack-create"|"mc")
			modpack_create "$@"
			;;
		"modpack-delete"|"md")
			modpack_delete "$@"
			;;
		"modpack-overwrite"|"mo")
			modpack_overwrite "$@"
			;;
		"modpack-switch"|"ms")
			modpack_switch "$@"
			;;
		"modpack-reset"|"mr")
			modpack_reset "$@"
			;;
		"reset"|"r")
			mod_reset "$@"
			;;
		"version"|"v"|"-v"|"--version")
			echo "$VERSION"
			;;
		"update"|"up")
			self_update
			;;
		*)
			$display_help
			;;
	esac
}

main "$@"
