#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2013-2026 Igor Pecovnik, igor@armbian.com
#
# This file is a part of the Armbian Build Framework
# https://github.com/armbian/build/

function cli_entrypoint() {
	# array, readonly, global, for future reference, "exported" to shutup shellcheck
	declare -rg -x -a ARMBIAN_ORIGINAL_ARGV=("${@}")

	if [[ "${ARMBIAN_ENABLE_CALL_TRACING}" == "yes" ]]; then
		set -T # inherit return/debug traps
		mkdir -p "${SRC}"/output/call-traces
		echo -n "" > "${SRC}"/output/call-traces/calls.txt
		# See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html
		trap 'echo "${FUNCNAME[*]}|${BASH_LINENO[*]}|${BASH_SOURCE[*]}|${LINENO}" >> ${SRC}/output/call-traces/calls.txt ;' RETURN
	fi

	# Capture the real terminal width once, here, before any logging redirects
	# fd 1, so the patch-summary tables can match the user's terminal. Empty when
	# stdout is not a tty (piped / CI), so those runs fall back to a fixed width.
	declare -g -x ARMBIAN_TTY_COLUMNS="" # "exported" to shutup shellcheck; read by the patching wrappers
	if [[ -t 1 ]]; then
		ARMBIAN_TTY_COLUMNS="$(tput cols 2> /dev/null || echo "")"
	fi

	# @TODO: allow for a super-early userpatches/config-000.custom.conf.sh to be loaded, before anything else.
	# This would allow for custom commands and interceptors.

	# Decide what we're gonna do. We've a few hardcoded, 1st-argument "commands".
	declare -g -A ARMBIAN_COMMANDS_TO_HANDLERS_DICT ARMBIAN_COMMANDS_TO_VARS_DICT
	armbian_register_commands # this defines the above two dictionaries

	# Process the command line, separating params (XX=YY) from non-params arguments.
	# That way they can be set in any order.
	declare -A -g ARMBIAN_PARSED_CMDLINE_PARAMS=() # A dict of PARAM=VALUE
	declare -a -g ARMBIAN_NON_PARAM_ARGS=()        # An array of all non-param arguments
	parse_cmdline_params "${@}"                    # which fills the above vars.

	# Now load the key=value pairs from cmdline into environment, before loading config or executing commands.
	# This will be done _again_ later, to make sure cmdline params override config et al.
	apply_cmdline_params_to_env "early" # which uses ARMBIAN_PARSED_CMDLINE_PARAMS
	# From here on, no more ${1} or stuff. We've parsed it all into ARMBIAN_PARSED_CMDLINE_PARAMS or ARMBIAN_NON_PARAM_ARGS and ARMBIAN_COMMAND.

	# Normalize renamed switches now, while the command line is all we have.
	# The pre_run loop and the PREFER_DOCKER / DOCKER_NICE checks below read
	# ARMBIAN_PARSED_CMDLINE_PARAMS directly, so an alias that only reached the
	# environment would be invisible to them. Config files are handled by the
	# second pass further down, once they have been sourced.
	apply_deprecated_switch_aliases

	# Re-initialize logging, to take into account the new environment after parsing cmdline params.
	logging_init

	declare -a -g ARMBIAN_CONFIG_FILES=()                                            # fully validated, complete paths to config files.
	declare -g ARMBIAN_COMMAND_HANDLER="" ARMBIAN_COMMAND="" ARMBIAN_COMMAND_VARS="" # only valid command and handler will ever be set here.
	declare -g ARMBIAN_HAS_UNKNOWN_ARG="no"                                          # if any unknown params, bomb.
	for argument in "${ARMBIAN_NON_PARAM_ARGS[@]}"; do                               # loop over all non-param arguments, find commands and configs.
		parse_each_cmdline_arg_as_command_param_or_config "${argument}"                 # sets all the vars above
	done

	# More sanity checks.
	# If unknowns, bail.
	if [[ "${ARMBIAN_HAS_UNKNOWN_ARG}" == "yes" ]]; then
		exit_with_error "Unknown arguments found. Please check the output above and fix them."
	fi

	# @TODO: Have a config that is always included? "${SRC}/userpatches/config-default.conf" ?

	# If we don't have a command decided yet, use the undecided command.
	if [[ "${ARMBIAN_COMMAND}" == "" ]]; then
		display_alert "No command found, using default" "undecided" "debug"
		ARMBIAN_COMMAND="undecided"
	fi

	# If we don't have a command at this stage, we should default either to 'build' or 'docker', depending on OS.
	# Give the chosen command a chance to refuse running, or, even, change the final command to run.
	# This allows for example the 'build' command to auto-launch under docker, even without specifying it.
	# Also allows for launchers to keep themselves when re-launched, yet do something diferent. (eg: docker under docker does build).
	# Or: build under Darwin does docker...
	# each _pre_run can change the command and vars to run too, so do it in a loop until it stops changing.
	declare -g ARMBIAN_CHANGE_COMMAND_TO="${ARMBIAN_COMMAND}"
	while [[ "${ARMBIAN_CHANGE_COMMAND_TO}" != "" ]]; do
		display_alert "Still a command to pre-run, this time:" "${ARMBIAN_CHANGE_COMMAND_TO}" "debug"

		declare -g ARMBIAN_COMMAND_REQUIRE_BASIC_DEPS="no" # reset this before every pre_run, so only the last one wins.
		ARMBIAN_COMMAND="${ARMBIAN_CHANGE_COMMAND_TO}"
		armbian_prepare_cli_command_to_run "${ARMBIAN_COMMAND}"

		ARMBIAN_CHANGE_COMMAND_TO=""
		armbian_cli_pre_run_command
	done

	declare -g DOCKER_NICE
	if [[ "$ARMBIAN_COMMAND" == "docker" ]] ||
		[[ -n "${ARMBIAN_PARSED_CMDLINE_PARAMS["PREFER_DOCKER"]}" && "${ARMBIAN_PARSED_CMDLINE_PARAMS["PREFER_DOCKER"]}" == "yes" ]] ||
		[[ -n "${ARMBIAN_PARSED_CMDLINE_PARAMS["DOCKER_NICE"]}" ]]; then

		CURRENT_NICE=$(($(ps -p $$ -o 'nice=') + 0))
		# by default, docker sets up a separate environment that inherits next to nothing.
		# this detects the current process nice value and attempts to propagate it.
		if [[ -z "${ARMBIAN_PARSED_CMDLINE_PARAMS["DOCKER_NICE"]}" ]]; then
			# since it's not been passed to us in our invocation, use our current nice value
			# this becomes a propagated cmdline parameter in cli-docker.sh
			DOCKER_NICE=$CURRENT_NICE
			display_alert "Niceness parameter (DOCKER_NICE)" "$DOCKER_NICE" "debug"
		else
			# initialize from passed cmdline arg
			DOCKER_NICE="${ARMBIAN_PARSED_CMDLINE_PARAMS["DOCKER_NICE"]}"
			# we cast DOCKER_NICE to integer in case we were handed garbage.
			DOCKER_NICE=$(("$DOCKER_NICE" + 0))
		fi

		if [[ $CURRENT_NICE -ne $DOCKER_NICE ]]; then
			# enforce the niceness
			if [[ $UID -eq 0 ]]; then # don't bother if we're not root
				# Given we run as root in docker, we shouldn't worry about lacking permissions.
				# if it's an invalid integer value, then we can feel secure in letting it fail.
				renice -n $DOCKER_NICE -p $$ &&
					display_alert "enforced nice value (DOCKER_NICE)" "$DOCKER_NICE" "debug" ||
					display_alert "renice failed" "FAILED" "warn"
			fi
		fi
	fi

	# IMPORTANT!!!: it is INVALID to relaunch compile.sh from here. It will cause logging mistakes.
	# So the last possible moment to relaunch is in xxxxx_pre_run!
	# Also form here, UUID will be generated, output created, logging enabled, etc.

	# Init basic dirs.
	declare -g -r DEST="${SRC}/output" USERPATCHES_PATH="${SRC}"/userpatches # DEST is the main output dir, and USERPATCHES_PATH is the userpatches dir. read-only.
	mkdir -p "${DEST}" "${USERPATCHES_PATH}"                                 # Create output and userpatches directory if not already there
	display_alert "Output directory created! DEST:" "${DEST}" "debug"

	# set unique mounting directory for this execution.
	# basic deps, which include "uuidgen", will be installed _after_ this, so we gotta tolerate it not being there yet.
	declare -g ARMBIAN_BUILD_UUID
	if [[ "${ARMBIAN_BUILD_UUID}" != "" ]]; then
		display_alert "Using passed-in ARMBIAN_BUILD_UUID" "${ARMBIAN_BUILD_UUID}" "debug"
	else
		if command -v uuidgen 1> /dev/null; then
			ARMBIAN_BUILD_UUID="$(uuidgen)"
		else
			display_alert "uuidgen not found" "uuidgen not installed yet" "info"
			ARMBIAN_BUILD_UUID="no-uuidgen-yet-${RANDOM}-$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))$((1 + $RANDOM % 10))"
		fi
		display_alert "Generated ARMBIAN_BUILD_UUID" "${ARMBIAN_BUILD_UUID}" "debug"
	fi
	declare -g -r ARMBIAN_BUILD_UUID="${ARMBIAN_BUILD_UUID}" # Make read-only
	display_alert "Build UUID:" "${ARMBIAN_BUILD_UUID}" "debug"

	# Super-global variables, used everywhere. The directories are NOT _created_ here, since this very early stage. They are all readonly, for sanity.
	declare -g -r WORKDIR_BASE_TMP="${SRC}/.tmp" # a.k.a. ".tmp" dir. it is a shared base dir for all builds, but each build gets its own WORKDIR/TMPDIR.

	declare -g -r WORKDIR="${WORKDIR_BASE_TMP}/work-${ARMBIAN_BUILD_UUID}"                         # WORKDIR at this stage. It will become TMPDIR later. It has special significance to `mktemp` and others!
	declare -g -r LOGDIR="${WORKDIR_BASE_TMP}/logs-${ARMBIAN_BUILD_UUID}"                          # Will be initialized very soon, literally, below.
	declare -g -r EXTENSION_MANAGER_TMP_DIR="${WORKDIR_BASE_TMP}/extensions-${ARMBIAN_BUILD_UUID}" # EXTENSION_MANAGER_TMP_DIR used to store extension-composed functions

	# @TODO: These are used only by rootfs/image actual build, move there...
	declare -g -r SDCARD="${WORKDIR_BASE_TMP}/rootfs-${ARMBIAN_BUILD_UUID}" # SDCARD (which is NOT an sdcard, but will be, maybe, one day) is where we work the rootfs before final imaging. "rootfs" stage.
	declare -g -r MOUNT="${WORKDIR_BASE_TMP}/mount-${ARMBIAN_BUILD_UUID}"   # MOUNT ("mounted on the loop") is the mounted root on final image (via loop). "image" stage
	declare -g -r DESTIMG="${WORKDIR_BASE_TMP}/image-${ARMBIAN_BUILD_UUID}" # DESTIMG is where the backing image (raw, huge, sparse file) is kept (not the final destination)

	# Make sure ARMBIAN_LOG_CLI_ID is set, and unique, and readonly.
	# Pre-runs might change it before this, but if not set, default to ARMBIAN_COMMAND.
	declare -r -g ARMBIAN_LOG_CLI_ID="${ARMBIAN_LOG_CLI_ID:-${ARMBIAN_COMMAND}}"

	# If we're on Linux & root, mount tmpfs on LOGDIR. This has it's own cleanup handler.
	# It also _creates_ the LOGDIR, and the cleanup handler will delete.
	prepare_tmpfs_for "LOGDIR" "${LOGDIR}"

	LOG_SECTION="entrypoint" start_logging_section      # This will create LOGDIR if it does not exist. @TODO: also maybe causes a spurious group to be created in the log file
	add_cleanup_handler trap_handler_cleanup_logging    # cleanup handler for logs; it rolls it up from LOGDIR into DEST/logs
	add_cleanup_handler trap_handler_reset_output_owner # make sure output folder is owned by pre-sudo/pre-Docker user if that's the case

	# @TODO: So gigantic contention point here about logging the basic deps installation.
	if [[ "${ARMBIAN_COMMAND_REQUIRE_BASIC_DEPS}" == "yes" ]]; then
		if [[ "${OFFLINE_WORK}" == "yes" ]]; then
			display_alert "* " "You are working offline!"
			display_alert "* " "Sources, time and host will not be checked"
		else
			# check and install the basic utilities;
			LOG_SECTION="prepare_host_basic" do_with_logging prepare_host_basic # This includes the 'docker' case.
		fi
	fi

	# Legacy. We used to source the extension manager here, but now it's included in the library.
	# @TODO: a quick check on the globals in extensions.sh would get rid of this.
	extension_manager_declare_globals

	# Loop over the ARMBIAN_CONFIG_FILES array and source each. The order is important.
	for config_file in "${ARMBIAN_CONFIG_FILES[@]}"; do
		local config_filename="${config_file##*/}" config_dir="${config_file%/*}"
		display_alert "Sourcing config file" "${config_filename}" "debug"

		# use pushd/popd to change directory to the config file's directory, so that relative paths in the config file work.
		pushd "${config_dir}" > /dev/null || exit_with_error "Failed to pushd to ${config_dir}"

		# shellcheck source=/dev/null
		LOG_SECTION="userpatches_config:${config_filename}" do_with_logging source "${config_file}"

		# reset completely after sourcing config file
		set -e
		#set -o pipefail  # trace ERR through pipes - will be enabled "soon"
		#set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable - one day will be enabled
		set -o errtrace # trace ERR through - enabled
		set -o errexit  ## set -e : exit the script if any statement returns a non-true return value - enabled

		popd > /dev/null || exit_with_error "Failed to popd from ${config_dir}"

		# Apply the params received from the command line _again_ after running the config.
		# This ensures that params take precedence over stuff possibly defined in the config.
		apply_cmdline_params_to_env "after config '${config_filename}'" # which uses ARMBIAN_PARSED_CMDLINE_PARAMS
	done

	# Early check for deprecations
	apply_deprecated_switch_aliases # forward renamed switches to their new names, with a warning (backward compat)
	error_if_lib_tag_set # make sure users are not thrown off by using old parameter which does nothing anymore; explain

	display_alert "Executing final CLI command" "${ARMBIAN_COMMAND}" "debug"
	armbian_cli_run_command
	display_alert "Done Executing final CLI command" "${ARMBIAN_COMMAND}" "debug"

	# Build done, run the cleanup handlers explicitly.
	# This zeroes out the list of cleanups, so it"s not done again when the main script exits normally and trap = 0 runs.
	run_cleanup_handlers
}