@SuppressWarnings("unchecked")
    private @NonNull CompletableFuture<SuggestionContext<C, ?>> addSuggestionsForDynamicArgument(
            final @NonNull SuggestionContext<C, ?> context,
            final @NonNull CommandInput commandInput,
            final @NonNull CommandNode<C> child,
            final @NonNull Executor executor,
            final boolean inFlag
    ) {
        final CommandComponent<C> component = child.component();
        if (component == null) {
            return CompletableFuture.completedFuture(context);
        }

        if (!inFlag && component.parser() instanceof CommandFlagParser) {
            // Use the flag argument parser to deduce what flag is being suggested right now
            // If empty, then no flag value is being typed, and the different flag options should
            // be suggested instead.
            final CommandFlagParser<C> parser = (CommandFlagParser<C>) component.parser();

            return parser.parseCurrentFlag(context.commandContext(), commandInput, executor).thenCompose(lastFlag -> {
                if (lastFlag.isPresent()) {
                    context.commandContext().store(CommandFlagParser.FLAG_META_KEY, lastFlag.get());
                } else {
                    context.commandContext().remove(CommandFlagParser.FLAG_META_KEY);
                }
                return this.addSuggestionsForDynamicArgument(context, commandInput, child, executor, true);
            });
        }

        if (commandInput.isEmpty() || commandInput.remainingTokens() == 1
                || (child.isLeaf() && child.component().parser() instanceof AggregateParser)
                || (child.isLeaf() && child.component().parser() instanceof CommandFlagParser)) {
            return this.addArgumentSuggestions(context, child, commandInput, executor);
        }

        // Store original input command queue before the parsers below modify it
        final CommandInput commandInputOriginal = commandInput.copy();

        // START: Preprocessing
        final ArgumentParseResult<Boolean> preParseResult = component.preprocess(
                context.commandContext(),
                commandInput
        );
        final boolean preParseSuccess = !preParseResult.failure().isPresent()
                && preParseResult.parsedValue().orElse(false);
        // END: Preprocessing

        final CompletableFuture<SuggestionContext<C, ?>> parsingFuture;
        if (!preParseSuccess) {
            parsingFuture = CompletableFuture.completedFuture(null);
        } else {
            // START: Parsing
            final ParsingContext<C> parsingContext = context.commandContext().createParsingContext(child.component());
            parsingContext.markStart();
            final CommandInput preParseInput = commandInput.copy();

            parsingFuture = child.component()
                    .parser()
                    .parseFuture(context.commandContext(), commandInput)
                    .thenComposeAsync(result -> {
                        final Optional<?> parsedValue = result.parsedValue();
                        final boolean parseSuccess = parsedValue.isPresent();

                        if (result.failure().isPresent()) {
                            commandInput.cursor(preParseInput.cursor());
                            return this.addArgumentSuggestions(context, child, commandInput, executor);
                        }

                        if (child.isLeaf()) {
                            if (!commandInput.isEmpty()) {
                                return CompletableFuture.completedFuture(context);
                            }

                            // Greedy parser took all the input, we can restore and just ask for suggestions
                            commandInput.cursor(commandInputOriginal.cursor());
                            this.addArgumentSuggestions(context, child, commandInput, executor);
                        }

                        if (parseSuccess && (!commandInput.isEmpty() || commandInput.input().endsWith(" "))) {
                            if (commandInput.isEmpty()) {
                                commandInput.moveCursor(-1);
                            }
                            // the current argument at the position is parsable and there are more arguments following
                            context.commandContext().store(child.component().name(), parsedValue.get());
                            parsingContext.success(true);
                            return this.getSuggestions(context, commandInput, child, executor);
                        } else if (!parseSuccess && commandInputOriginal.remainingTokens() > 1) {
                            // at this point there should normally be no need to reset the command queue as we expect
                            // users to only take out an argument if the parse succeeded. Just to be sure we reset anyway
                            commandInput.cursor(commandInputOriginal.cursor());

                            // there are more arguments following but the current argument isn't matching - there
                            // is no need to collect any further suggestions
                            return CompletableFuture.completedFuture(context);
                        }
                        return CompletableFuture.completedFuture(null);
                    }, executor);
        }

        return parsingFuture.thenCompose(previousResult -> {
            if (previousResult != null) {
                return CompletableFuture.completedFuture(previousResult);
            }

            // Restore original command input queue
            commandInput.cursor(commandInputOriginal.cursor());

            if (!preParseSuccess && commandInput.remainingTokens() > 1) {
                // The preprocessor denied the argument, and there are more arguments following the current one
                // Therefore we shouldn't list the suggestions of the current argument, as clearly the suggestions of
                // one of the following arguments is requested
                return CompletableFuture.completedFuture(context);
            }

            return this.addArgumentSuggestions(context, child, commandInput, executor);
        });
    }
Adds the suggestions for a static argument if they match the given {@code input}

@param context the suggestion context
@param node    the node containing the static argument
@param input   the current input
@return future that completes with the context
cloud-core/src/main/java/org/incendo/cloud/CommandTree.java