#compdef s6

# boilerplate
local context state state_descr line curcontext="$curcontext"
typeset -A opt_args

local -a args cmds tools=(
	'help' 'version'
	'set[manage default bundle]'
	'process[manage individual running daemons]'
	'live[manage services, with dependencies]'
	'system[poweroff/reboot]'
)

_arguments -A '-*' -S -s : \
		':tool:->tool' \
		':tool-scmd:->subtool' \
		'*::args:->args' \
		'--color:pretty-print output:(auto yes no)' \
		'--storelist=-[colon-separated list of stores]:storelist:_path_files -/' \
		'--stmpdir=-[temporary files directory]:stmpdir:_path_files -/' \
	+ '(help)' \
		{-h,--help}'[help message]' \
		{-V,--version}'[show version]' \
	+ '(verbosity)' {-v,--verbosity=-}'[verbosity (numerical)]:verbosity:(0 1 2 3)' \
	+ '(repository)' {-r,--repodir=-}'[choose s6-rc repository folder]:repository:_path_files -/' \
	+ '(bootdir)' {-c,--bootdb=-}'[choose s6-rc boot-time database path]:bootdb:_path_files -/' \
	+ '(livedir)' {-l,--livedir=-}'[choose s6-rc runtime database path]:livedir:_path_files -/' \
	+ '(scandir)' {-s,--scandir=-}'[choose s6-svscan service directory]:scandir:_path_files -/' \

if [ "$state" = tool ]; then
	_values "s6-frontend-tool" $tools
elif [ "$state" = subtool ]; then
	case ${line[1]} in
		set)
			cmds=(
				'mask[services will not be included in the database, and will not be startable]'
				{disable,unmask}'[services included in the database, not started by default]'
				'enable[service started by default]'
				'make-essential[service must always be up]'
				'copy[make a copy of the current set]'
				'apply[commit and install a set in one step]'
				'delete[deletes given sets]'
				'list[list available sets]'
				'status[list services and the prescription they are in]'
				'check[check set for inconsistencies and possibly fix them]'
				'commit[compile database for the set (make active with s6 live install)]'
			)
			;;
		process)
			cmds=(
				'kill[send a signal to a service]'
				'status[describe daemon status]'
				'start[start a process (can conflict with s6 live start)]'
				'stop[stop a process (can conflict with s6 live stop)]'
				'restart[restart a process (does not respect s6 live dependencies)]'
			)
			;;
		live)

			cmds=(
				'start[start a service and its dependencies]'
				'stop[stop a service and those depending on it]'
				'restart[start+stop]'
				'start-everything[start all services in the default bundle]'
				'stop-everything[stop all non essential services (DANGEROUS)]'
				'install[ install the working set, generated by `s6 set commit`]'
			)
			;;
		system)
			cmds=(
				'boot[initialize boot-time s6-rc (if you see it you likely do not need it)]'
				'reboot' 'poweroff' 'halt'
			)
			;;
	esac
	_values command $cmds
fi

[ "$state" = args ] || return

shift words
((CURRENT--))

local var cont scandir livedir bootdb repodir
typeset -A conf confdefs
conf=(
	scandir s
	livedir l
	bootdb c
	repodir r

)
confdefs=(
	scandir /run/service
	repodir /var/lib/s6-rc/repository
)

for var in ${(k)conf}; do
	local kshort="$var--${conf[$var]}" klong="$var---$var"
	for cont in \
		${confdefs[$var]} \
		$(envfile -I "${S6_FRONTEND_CONF-/etc/s6-frontend.conf}" importas -i var "$var" printf %s '$var' 2>/dev/null) \
		${opt_args[$kshort]} \
		${opt_args[$klong]}
	do
		print -v "$var" -- "$cont" 
	done
done
set +x

args=()

case ${line[1]} in
	set)
		case ${line[2]} in
			copy)
				args+=(
					'(force)'{-f,--force}':overwrite existing set'
					':set:->set'
				)
				;;
			apply)
				args+=(
					+ '(block)' {-b,--block}'[wait if another service is locking the database instead of failing]'
					+ '(dbundle)' {-D,--default-bundle=-}'[Use this name for the configured bundle instead of "default"]:: '
					+ '(timeout)' {-t,--timeout=-}'[timeout in milliseconds waiting for all operations]:timeout:'
					+ '(set)' {-s,--set=-}'[set to apply]:set:->set'
				)
				;;
			delete) args+=( ':set:->set' ) ;;
			check)
				args+=(
					+ '(set)' {-s,--set=-}'[set to apply]:set:->set'
					+ '(fix)' {-F,--fix}'[fix errors checked for]'
					+ '(moreless)'
						'(fixup)'{-u,--up}'[fix inconsistencies by adding services]'
						'(fixdown)'{-d,--down}'[fix inconsistencies by removing services]'
					+ '(onessential)'
						'(noessential)'{-E,--no-force-essential}'[report error if service must become "essential"/always on (default)]'
						'(essential)'{-e,--force-essential}'[allow services to become essential despite not being so in the sources]'
				)
				;;
			status)
				args+=(
					'*:service:->service'
				)
				;&
			list|status)
				args+=(
					+ '(ls-essentials)'
						'(essential)'{-E,--with-essentials}'[print essential services]'
						'(noessential)'{-e,--without-essentials}'[skip listing essential services (default)]'
				)
				;;
			commit)
				args+=(
					+ '(dbundle)' {-D,--default-bundle=-}'[Use this name for the configured bundle instead of "default"]:: '
					+ '(fdhuser)' {-h,--fdhuser=-}'[(advanced) setuidgid fd holder daemon]:fdhuser:_users'
					+ '(keep)' {-K,--keep-old}'[print path of old database backup after commit]'
					+ '(force)' {-f,--force}'[commit set even if compile database is already up to date]'
				)
				;;
			*)
				# set change cmds
				args+=(
					'*:services:->service'
					+ '(dryrun)' {-n,--dry-run}'[check whether command would succeeded, but do not actually do anything]'
					+ '(set)' {-s,--set=-}'[set to apply]:set:->set'
					+ '(ondep)'
						'(faildep)'{-i,--fail-on-dependencies}'[fail if the service requires additional dependencies to be enabled]'
						'(depok)'{-I,--no-fail-on-dependencies}'[enable service even if it requires dependencies]'
						'(nopulldep)'{-P,--no-pull-dependencies}'[do not handle dependencies (might create a broken set with -I)]'
						'(pulldep)'{-p,--pull-dependencies}'[automatically enable dependencies if needed]'
				)
				;;
		esac
		;;
	process)
		# reusing these local variables
		var=finishes # what service will do
		cont=add # what do with down file
		args+=( '*:svc:->svc' )
		case ${line[2]} in
			start)
				var='is up'
				cont='remove'
				;&
			restart|start|stop|kill)
				args+=(
					+ '(timeout)' {-t,--timeout=-}'[timeout in milliseconds for waiting]:timeout:'
					+ '(do-wait)'
						'(wait)'{-w,--wait}"[wait until service ${var}]"
						'(no-wait)'{-W,--no-wait}"[do not wait until service ${var} (default)]"
				)
				;|
			kill)
				args+=( + '(sig)' {-s,--signal=-}'[signal to send]:signal:_signals -s' )
				;;
			start|stop)
				args+=(
					+ '(do-perma)'
						'(perma)'{-p,--permanent}"[${cont} the down file to change default supervisor state]"
						'(no-perma)'{-P,--no-permanent}'[do not touch the down file (default)]'
				)
				;;
			status)
				args+=(
					+ '(do-loggers)'
						'(no-log)'{-L,--no-log}'[do not print the status of nested logger service (default)]'
						'(log)'{-l,--log}'[print status of nested logger service too]'
				)
				;;
		esac
	;;
	live)
		case ${line[2]} in
			start*|stop*|restart)
				args+=(
					'*:service:->live'
					+ '(dryrun)' {-n,--dry-run}'[only print what would be done]'
					+ '(timeout)' {-t,--timeout=-}"[timeout in milliseconds for services to ${line[2]%-everything}]:timeout:"
				)
				;;
			status)
				args+=(
					+ '(ls-essentials)'
						'(essential)'{-E,--with-essentials}'[print essential services]'
						'(noessential)'{-e,--without-essentials}'[skip listing essential services (default)]'
				)
				;;
			install)
				args+=(
					'--init[update boot-time database, not the live one (DANGEROUS, do not use)]'
					+ '(compiled)' {-c,--bootdb=-}'[boot-time database path]:bootdb:_path_files -/'
					+ '(live)' {-l,--livedir=-}'[s6-rc runtime database path]:livedir:_path_files -/'
					+ '(convfile)' {-f,--conversion-file=-}'[s6-rc-update conversion file]:convfile:_path_files'
					+ '(block)' {-b,--block}'[wait if another service is locking the database instead of failing]'
					+ '(keep)' {-K,--keep-old}'[print path of old database backup after commit]'
					+ '(want-stop-essential)'
						'(noessential)'{-E,--no-force-essentials}'[do not stop essential services, even if needed to update]'
						'(essential)'{-e,--force-essentials}'[stop essential services if necessary (default)]'
				)
				;;
		esac
		;;
esac

curcontext="${curcontext%:*:*}:s6_$line[1]_$line[2]:"
_arguments -A '-*' -S -s : $args

case $state in
	svc)
		# TODO: any cursed zsh globs to avoid a subprocess?
		compadd -- ${(ps./.)"$(cd -- "$scandir" && printf '%s/' */)"}
		;;
	set)
		compadd -- ${(f)"$(s6-rc-repo-list -r ${repodir} 2>/dev/null)"}
		;;
	live)
		compadd -- ${(f)"$(s6-rc-db ${livedir:+-l$livedir} list all 2>/dev/null)"}
		;;
	service)
		compadd -- ${(f)"$(s6-rc-db -c ${repodir}/compiled/.ref list all)"}
		;;
esac
