--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -1,7 +1,6 @@
 package net.minecraft.commands;
 
 import com.google.common.collect.Maps;
-import com.mojang.brigadier.CommandDispatcher;
 import com.mojang.brigadier.ParseResults;
 import com.mojang.brigadier.StringReader;
 import com.mojang.brigadier.arguments.ArgumentType;
@@ -14,6 +13,7 @@
 import com.mojang.brigadier.tree.CommandNode;
 import com.mojang.brigadier.tree.RootCommandNode;
 import com.mojang.logging.LogUtils;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -135,17 +135,27 @@
 import net.minecraft.world.level.GameRules;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import com.google.common.base.Joiner;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import org.bukkit.event.player.PlayerCommandSendEvent;
+import org.bukkit.event.server.ServerCommandEvent;
+// CraftBukkit end
+
 public class Commands {
-    private static final ThreadLocal<ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal<>();
+
+    private static final ThreadLocal<ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal();
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final int LEVEL_ALL = 0;
     public static final int LEVEL_MODERATORS = 1;
     public static final int LEVEL_GAMEMASTERS = 2;
     public static final int LEVEL_ADMINS = 3;
     public static final int LEVEL_OWNERS = 4;
-    private final CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();
+    private final com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = new com.mojang.brigadier.CommandDispatcher();
 
     public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) {
+        // Paper
         AdvancementCommands.register(this.dispatcher);
         AttributeCommand.register(this.dispatcher, commandRegistryAccess);
         ExecuteCommand.register(this.dispatcher, commandRegistryAccess);
@@ -246,86 +256,172 @@
             PublishCommand.register(this.dispatcher);
         }
 
+        // Paper start - Vanilla command permission fixes
+        for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
+            if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
+                node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node));
+            }
+        }
+        // Paper end - Vanilla command permission fixes
+        // Paper start - Brigadier Command API
+        // Create legacy minecraft namespace commands
+        for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
+            // The brigadier dispatcher is not able to resolve nested redirects.
+            // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
+            // Instead, target the first none redirecting node.
+            CommandNode<CommandSourceStack> flattenedAliasTarget = node;
+            while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
+
+            this.dispatcher.register(
+                com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
+                    .executes(flattenedAliasTarget.getCommand())
+                    .requires(flattenedAliasTarget.getRequirement())
+                    .redirect(flattenedAliasTarget)
+            );
+        }
+        // Paper end - Brigadier Command API
+        // Paper - remove public constructor, no longer needed
         this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
     }
 
     public static <S> ParseResults<S> mapSource(ParseResults<S> parseResults, UnaryOperator<S> sourceMapper) {
-        CommandContextBuilder<S> commandContextBuilder = parseResults.getContext();
-        CommandContextBuilder<S> commandContextBuilder2 = commandContextBuilder.withSource(sourceMapper.apply(commandContextBuilder.getSource()));
-        return new ParseResults<>(commandContextBuilder2, parseResults.getReader(), parseResults.getExceptions());
+        CommandContextBuilder<S> commandcontextbuilder = parseResults.getContext();
+        CommandContextBuilder<S> commandcontextbuilder1 = commandcontextbuilder.withSource(sourceMapper.apply(commandcontextbuilder.getSource()));
+
+        return new ParseResults(commandcontextbuilder1, parseResults.getReader(), parseResults.getExceptions());
     }
 
+    // CraftBukkit start
+    public void dispatchServerCommand(CommandSourceStack sender, String command) {
+        Joiner joiner = Joiner.on(" ");
+        if (command.startsWith("/")) {
+            command = command.substring(1);
+        }
+
+        ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command);
+        org.bukkit.Bukkit.getPluginManager().callEvent(event);
+        if (event.isCancelled()) {
+            return;
+        }
+        command = event.getCommand();
+
+        String[] args = command.split(" ");
+        if (args.length == 0) return; // Paper - empty commands shall not be dispatched
+
+        // Paper - Fix permission levels for command blocks
+
+        // Handle vanilla commands;
+        if (sender.getLevel().getCraftServer().getCommandBlockOverride(args[0])) {
+            args[0] = "minecraft:" + args[0];
+        }
+
+        String newCommand = joiner.join(args);
+        this.performPrefixedCommand(sender, newCommand, newCommand);
+    }
+    // CraftBukkit end
+
     public void performPrefixedCommand(CommandSourceStack source, String command) {
-        command = command.startsWith("/") ? command.substring(1) : command;
-        this.performCommand(this.dispatcher.parse(command, source), command);
+        // CraftBukkit start
+        this.performPrefixedCommand(source, command, command);
+    }
+
+    public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s, String label) {
+        s = s.startsWith("/") ? s.substring(1) : s;
+        this.performCommand(this.dispatcher.parse(s, commandlistenerwrapper), s, label);
+        // CraftBukkit end
     }
 
     public void performCommand(ParseResults<CommandSourceStack> parseResults, String command) {
-        CommandSourceStack commandSourceStack = parseResults.getContext().getSource();
-        commandSourceStack.getServer().getProfiler().push(() -> "/" + command);
-        ContextChain<CommandSourceStack> contextChain = finishParsing(parseResults, command, commandSourceStack);
+        this.performCommand(parseResults, command, command);
+    }
+
+    public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
+        // Paper start
+        this.performCommand(parseresults, s, label, false);
+    }
+    public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label, boolean throwCommandError) {
+        // Paper end
+        CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
+
+        commandlistenerwrapper.getServer().getProfiler().push(() -> {
+            return "/" + s;
+        });
+        ContextChain contextchain = this.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit // Paper - Add UnknownCommandEvent
 
         try {
-            if (contextChain != null) {
-                executeCommandInContext(
-                    commandSourceStack,
-                    context -> ExecutionContext.queueInitialCommandExecution(context, command, contextChain, commandSourceStack, CommandResultCallback.EMPTY)
-                );
+            if (contextchain != null) {
+                Commands.executeCommandInContext(commandlistenerwrapper, (executioncontext) -> {
+                    ExecutionContext.queueInitialCommandExecution(executioncontext, s, contextchain, commandlistenerwrapper, CommandResultCallback.EMPTY);
+                });
             }
-        } catch (Exception var12) {
-            MutableComponent mutableComponent = Component.literal(var12.getMessage() == null ? var12.getClass().getName() : var12.getMessage());
-            if (LOGGER.isDebugEnabled()) {
-                LOGGER.error("Command exception: /{}", command, var12);
-                StackTraceElement[] stackTraceElements = var12.getStackTrace();
+        } catch (Exception exception) {
+            if (throwCommandError) throw exception;
+            MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage());
 
-                for (int i = 0; i < Math.min(stackTraceElements.length, 3); i++) {
-                    mutableComponent.append("\n\n")
-                        .append(stackTraceElements[i].getMethodName())
-                        .append("\n ")
-                        .append(stackTraceElements[i].getFileName())
-                        .append(":")
-                        .append(String.valueOf(stackTraceElements[i].getLineNumber()));
+            Commands.LOGGER.error("Command exception: /{}", s, exception); // Paper - always show execution exception in console log
+            if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
+                StackTraceElement[] astacktraceelement = exception.getStackTrace();
+
+                for (int i = 0; i < Math.min(astacktraceelement.length, 3); ++i) {
+                    ichatmutablecomponent.append("\n\n").append(astacktraceelement[i].getMethodName()).append("\n ").append(astacktraceelement[i].getFileName()).append(":").append(String.valueOf(astacktraceelement[i].getLineNumber()));
                 }
             }
 
-            commandSourceStack.sendFailure(
-                Component.translatable("command.failed")
-                    .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, mutableComponent)))
-            );
+            commandlistenerwrapper.sendFailure(Component.translatable("command.failed").withStyle((chatmodifier) -> {
+                return chatmodifier.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, ichatmutablecomponent));
+            }));
             if (SharedConstants.IS_RUNNING_IN_IDE) {
-                commandSourceStack.sendFailure(Component.literal(Util.describeError(var12)));
-                LOGGER.error("'/{}' threw an exception", command, var12);
+                commandlistenerwrapper.sendFailure(Component.literal(Util.describeError(exception)));
+                Commands.LOGGER.error("'/{}' threw an exception", s, exception);
             }
         } finally {
-            commandSourceStack.getServer().getProfiler().pop();
+            commandlistenerwrapper.getServer().getProfiler().pop();
         }
+
     }
 
     @Nullable
-    private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseResults, String command, CommandSourceStack source) {
+    private ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent
         try {
-            validateParseResults(parseResults);
-            return ContextChain.tryFlatten(parseResults.getContext().build(command))
-                .orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseResults.getReader()));
-        } catch (CommandSyntaxException var7) {
-            source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage()));
-            if (var7.getInput() != null && var7.getCursor() >= 0) {
-                int i = Math.min(var7.getInput().length(), var7.getCursor());
-                MutableComponent mutableComponent = Component.empty()
-                    .withStyle(ChatFormatting.GRAY)
-                    .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command)));
+            Commands.validateParseResults(parseresults);
+            return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> {
+                return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader());
+            });
+        } catch (CommandSyntaxException commandsyntaxexception) {
+            // Paper start - Add UnknownCommandEvent
+            final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
+            // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage()));
+            builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage()));
+            // Paper end - Add UnknownCommandEvent
+            if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) {
+                int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor());
+                MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> {
+                    return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper
+                });
+
                 if (i > 10) {
-                    mutableComponent.append(CommonComponents.ELLIPSIS);
+                    ichatmutablecomponent.append(CommonComponents.ELLIPSIS);
                 }
 
-                mutableComponent.append(var7.getInput().substring(Math.max(0, i - 10), i));
-                if (i < var7.getInput().length()) {
-                    Component component = Component.literal(var7.getInput().substring(i)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE);
-                    mutableComponent.append(component);
+                ichatmutablecomponent.append(commandsyntaxexception.getInput().substring(Math.max(0, i - 10), i));
+                if (i < commandsyntaxexception.getInput().length()) {
+                    MutableComponent ichatmutablecomponent1 = Component.literal(commandsyntaxexception.getInput().substring(i)).withStyle(ChatFormatting.RED, ChatFormatting.UNDERLINE);
+
+                    ichatmutablecomponent.append((Component) ichatmutablecomponent1);
                 }
 
-                mutableComponent.append(Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
-                source.sendFailure(mutableComponent);
+                ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
+                // Paper start - Add UnknownCommandEvent
+                // commandlistenerwrapper.sendFailure(ichatmutablecomponent);
+                builder
+                    .append(net.kyori.adventure.text.Component.newline())
+                    .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent));
+            }
+            org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build());
+            org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event);
+            if (event.message() != null) {
+                commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
+                // Paper end - Add UnknownCommandEvent
             }
 
             return null;
@@ -333,66 +429,198 @@
     }
 
     public static void executeCommandInContext(CommandSourceStack commandSource, Consumer<ExecutionContext<CommandSourceStack>> callback) {
-        MinecraftServer minecraftServer = commandSource.getServer();
-        ExecutionContext<CommandSourceStack> executionContext = CURRENT_EXECUTION_CONTEXT.get();
-        boolean bl = executionContext == null;
-        if (bl) {
-            int i = Math.max(1, minecraftServer.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_CHAIN_LENGTH));
-            int j = minecraftServer.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_FORK_COUNT);
+        MinecraftServer minecraftserver = commandSource.getServer();
+        ExecutionContext<CommandSourceStack> executioncontext = (ExecutionContext) Commands.CURRENT_EXECUTION_CONTEXT.get();
+        boolean flag = executioncontext == null;
 
-            try (ExecutionContext<CommandSourceStack> executionContext2 = new ExecutionContext<>(i, j, minecraftServer.getProfiler())) {
-                CURRENT_EXECUTION_CONTEXT.set(executionContext2);
-                callback.accept(executionContext2);
-                executionContext2.runCommandQueue();
+        if (flag) {
+            int i = Math.max(1, minecraftserver.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_CHAIN_LENGTH));
+            int j = minecraftserver.getGameRules().getInt(GameRules.RULE_MAX_COMMAND_FORK_COUNT);
+
+            try {
+                ExecutionContext<CommandSourceStack> executioncontext1 = new ExecutionContext<>(i, j, minecraftserver.getProfiler());
+
+                try {
+                    Commands.CURRENT_EXECUTION_CONTEXT.set(executioncontext1);
+                    callback.accept(executioncontext1);
+                    executioncontext1.runCommandQueue();
+                } catch (Throwable throwable) {
+                    try {
+                        executioncontext1.close();
+                    } catch (Throwable throwable1) {
+                        throwable.addSuppressed(throwable1);
+                    }
+
+                    throw throwable;
+                }
+
+                executioncontext1.close();
             } finally {
-                CURRENT_EXECUTION_CONTEXT.set(null);
+                Commands.CURRENT_EXECUTION_CONTEXT.set(null); // CraftBukkit - decompile error
             }
         } else {
-            callback.accept(executionContext);
+            callback.accept(executioncontext);
         }
+
     }
 
     public void sendCommands(ServerPlayer player) {
-        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
-        RootCommandNode<SharedSuggestionProvider> rootCommandNode = new RootCommandNode<>();
-        map.put(this.dispatcher.getRoot(), rootCommandNode);
-        this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
-        player.connection.send(new ClientboundCommandsPacket(rootCommandNode));
+        // Paper start - Send empty commands if tab completion is disabled
+        if (org.spigotmc.SpigotConfig.tabComplete < 0) {
+            player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>()));
+            return;
+        }
+        // Paper end - Send empty commands if tab completion is disabled
+        // CraftBukkit start
+        // Register Vanilla commands into builtRoot as before
+        // Paper start - Perf: Async command map building
+        COMMAND_SENDING_POOL.execute(() -> {
+                this.sendAsync(player);
+        });
     }
 
-    private void fillUsableCommands(
-        CommandNode<CommandSourceStack> tree,
-        CommandNode<SharedSuggestionProvider> result,
-        CommandSourceStack source,
-        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes
-    ) {
-        for (CommandNode<CommandSourceStack> commandNode : tree.getChildren()) {
-            if (commandNode.canUse(source)) {
-                ArgumentBuilder<SharedSuggestionProvider, ?> argumentBuilder = commandNode.createBuilder();
-                argumentBuilder.requires(sourcex -> true);
-                if (argumentBuilder.getCommand() != null) {
-                    argumentBuilder.executes(context -> 0);
+    public static final java.util.concurrent.ThreadPoolExecutor COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor(
+        0, 2, 60L, java.util.concurrent.TimeUnit.SECONDS,
+        new java.util.concurrent.LinkedBlockingQueue<>(),
+        new com.google.common.util.concurrent.ThreadFactoryBuilder()
+            .setNameFormat("Paper Async Command Builder Thread Pool - %1$d")
+            .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER))
+            .build(),
+        new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy()
+    );
+
+    private void sendAsync(ServerPlayer player) {
+        // Paper end - Perf: Async command map building
+        Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
+        RootCommandNode vanillaRoot = new RootCommandNode();
+
+        RootCommandNode<CommandSourceStack> vanilla = player.server.getCommands().getDispatcher().getRoot(); // Paper
+        map.put(vanilla, vanillaRoot);
+        this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map);
+
+        // Now build the global commands in a second pass
+        RootCommandNode<SharedSuggestionProvider> rootcommandnode = new RootCommandNode();
+
+        map.put(this.dispatcher.getRoot(), rootcommandnode);
+        this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, player.createCommandSourceStack(), map);
+
+        Collection<String> bukkit = new LinkedHashSet<>();
+        for (CommandNode node : rootcommandnode.getChildren()) {
+            bukkit.add(node.getName());
+        }
+        // Paper start - Perf: Async command map building
+        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API
+        net.minecraft.server.MinecraftServer.getServer().execute(() -> {
+           runSync(player, bukkit, rootcommandnode);
+        });
+    }
+
+    private void runSync(ServerPlayer player, Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootcommandnode) {
+        // Paper end - Perf: Async command map building
+        new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API
+        PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
+        event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+        // Remove labels that were removed during the event
+        for (String orig : bukkit) {
+            if (!event.getCommands().contains(orig)) {
+                rootcommandnode.removeCommand(orig);
+            }
+        }
+        // CraftBukkit end
+        player.connection.send(new ClientboundCommandsPacket(rootcommandnode));
+    }
+
+    private void fillUsableCommands(CommandNode<CommandSourceStack> tree, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
+        resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
+        Iterator iterator = tree.getChildren().iterator();
+
+        boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments
+        while (iterator.hasNext()) {
+            CommandNode<CommandSourceStack> commandnode2 = (CommandNode) iterator.next();
+            // Paper start - Brigadier API
+            if (commandnode2.clientNode != null) {
+                commandnode2 = commandnode2.clientNode;
+            }
+            // Paper end - Brigadier API
+            if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot
+
+            if (commandnode2.canUse(source)) {
+                ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); // CraftBukkit - decompile error
+                // Paper start
+                /*
+                Because of how commands can be yeeted right left and center due to bad bukkit practices
+                we need to be able to ensure that ALL commands are registered (even redirects).
+
+                What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
+                all the children from the dead redirect to the node.
+
+                So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
+
+                The only way to fix this is to either:
+                - Send EVERYTHING flattened, don't use redirects
+                - Don't allow command nodes to be deleted
+                - Do this :)
+                 */
+
+                // Is there an invalid command redirect?
+                if (argumentbuilder.getRedirect() != null && (CommandNode) resultNodes.get(argumentbuilder.getRedirect()) == null) {
+                    // Create the argument builder with the same values as the specified node, but with a different literal and populated children
+
+                    CommandNode<CommandSourceStack> redirect = argumentbuilder.getRedirect();
+                    // Diff copied from LiteralCommand#createBuilder
+                    final com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandnode2.getName());
+                    builder.requires(redirect.getRequirement());
+                    // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
+                    if (redirect.getCommand() != null) {
+                        builder.executes(redirect.getCommand());
+                    }
+                    // Diff copied from LiteralCommand#createBuilder
+                    for (CommandNode<CommandSourceStack> child : redirect.getChildren()) {
+                        builder.then(child);
+                    }
+
+                    argumentbuilder = builder;
                 }
+                // Paper end
 
-                if (argumentBuilder instanceof RequiredArgumentBuilder) {
-                    RequiredArgumentBuilder<SharedSuggestionProvider, ?> requiredArgumentBuilder = (RequiredArgumentBuilder<SharedSuggestionProvider, ?>)argumentBuilder;
-                    if (requiredArgumentBuilder.getSuggestionsProvider() != null) {
-                        requiredArgumentBuilder.suggests(SuggestionProviders.safelySwap(requiredArgumentBuilder.getSuggestionsProvider()));
+                argumentbuilder.requires((icompletionprovider) -> {
+                    return true;
+                });
+                if (argumentbuilder.getCommand() != null) {
+                    argumentbuilder.executes((commandcontext) -> {
+                        return 0;
+                    });
+                }
+
+                if (argumentbuilder instanceof RequiredArgumentBuilder) {
+                    RequiredArgumentBuilder<SharedSuggestionProvider, ?> requiredargumentbuilder = (RequiredArgumentBuilder) argumentbuilder;
+
+                    if (requiredargumentbuilder.getSuggestionsProvider() != null) {
+                        requiredargumentbuilder.suggests(SuggestionProviders.safelySwap(requiredargumentbuilder.getSuggestionsProvider()));
+                        // Paper start - tell clients to ask server for suggestions for EntityArguments
+                        registeredAskServerSuggestionsForTree = requiredargumentbuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER;
+                    } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredargumentbuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) {
+                        requiredargumentbuilder.suggests(requiredargumentbuilder.getType()::listSuggestions);
+                        registeredAskServerSuggestionsForTree = true; // You can only
+                        // Paper end - tell clients to ask server for suggestions for EntityArguments
                     }
                 }
 
-                if (argumentBuilder.getRedirect() != null) {
-                    argumentBuilder.redirect(resultNodes.get(argumentBuilder.getRedirect()));
+                if (argumentbuilder.getRedirect() != null) {
+                    argumentbuilder.redirect((CommandNode) resultNodes.get(argumentbuilder.getRedirect()));
                 }
 
-                CommandNode<SharedSuggestionProvider> commandNode2 = argumentBuilder.build();
-                resultNodes.put(commandNode, commandNode2);
-                result.addChild(commandNode2);
-                if (!commandNode.getChildren().isEmpty()) {
-                    this.fillUsableCommands(commandNode, commandNode2, source, resultNodes);
+                CommandNode commandnode3 = argumentbuilder.build(); // CraftBukkit - decompile error
+
+                resultNodes.put(commandnode2, commandnode3);
+                result.addChild(commandnode3);
+                if (!commandnode2.getChildren().isEmpty()) {
+                    this.fillUsableCommands(commandnode2, commandnode3, source, resultNodes);
                 }
             }
         }
+
     }
 
     public static LiteralArgumentBuilder<CommandSourceStack> literal(String literal) {
@@ -404,41 +632,34 @@
     }
 
     public static Predicate<String> createValidator(Commands.ParseFunction parser) {
-        return string -> {
+        return (s) -> {
             try {
-                parser.parse(new StringReader(string));
+                parser.parse(new StringReader(s));
                 return true;
-            } catch (CommandSyntaxException var3) {
+            } catch (CommandSyntaxException commandsyntaxexception) {
                 return false;
             }
         };
     }
 
-    public CommandDispatcher<CommandSourceStack> getDispatcher() {
+    public com.mojang.brigadier.CommandDispatcher<CommandSourceStack> getDispatcher() {
         return this.dispatcher;
     }
 
     public static <S> void validateParseResults(ParseResults<S> parse) throws CommandSyntaxException {
-        CommandSyntaxException commandSyntaxException = getParseException(parse);
-        if (commandSyntaxException != null) {
-            throw commandSyntaxException;
+        CommandSyntaxException commandsyntaxexception = Commands.getParseException(parse);
+
+        if (commandsyntaxexception != null) {
+            throw commandsyntaxexception;
         }
     }
 
     @Nullable
     public static <S> CommandSyntaxException getParseException(ParseResults<S> parse) {
-        if (!parse.getReader().canRead()) {
-            return null;
-        } else if (parse.getExceptions().size() == 1) {
-            return parse.getExceptions().values().iterator().next();
-        } else {
-            return parse.getContext().getRange().isEmpty()
-                ? CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader())
-                : CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(parse.getReader());
-        }
+        return !parse.getReader().canRead() ? null : (parse.getExceptions().size() == 1 ? (CommandSyntaxException) parse.getExceptions().values().iterator().next() : (parse.getContext().getRange().isEmpty() ? CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader()) : CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(parse.getReader())));
     }
 
-    public static CommandBuildContext createValidationContext(HolderLookup.Provider registryLookup) {
+    public static CommandBuildContext createValidationContext(final HolderLookup.Provider registryLookup) {
         return new CommandBuildContext() {
             @Override
             public Stream<ResourceKey<? extends Registry<?>>> listRegistries() {
@@ -450,8 +671,8 @@
                 return registryLookup.lookup(registryRef).map(this::createLookup);
             }
 
-            private <T> HolderLookup.RegistryLookup.Delegate<T> createLookup(HolderLookup.RegistryLookup<T> original) {
-                return new HolderLookup.RegistryLookup.Delegate<T>() {
+            private <T> HolderLookup.RegistryLookup.Delegate<T> createLookup(final HolderLookup.RegistryLookup<T> original) {
+                return new HolderLookup.RegistryLookup.Delegate<T>() { // CraftBukkit - decompile error
                     @Override
                     public HolderLookup.RegistryLookup<T> parent() {
                         return original;
@@ -465,7 +686,10 @@
                     @Override
                     public HolderSet.Named<T> getOrThrow(TagKey<T> tag) {
                         Optional<HolderSet.Named<T>> optional = this.parent().get(tag);
-                        return optional.orElseGet(() -> HolderSet.emptyNamed(this.parent(), tag));
+
+                        return (HolderSet.Named) optional.orElseGet(() -> {
+                            return HolderSet.emptyNamed(this.parent(), tag);
+                        });
                     }
                 };
             }
@@ -473,38 +697,42 @@
     }
 
     public static void validate() {
-        CommandBuildContext commandBuildContext = createValidationContext(VanillaRegistries.createLookup());
-        CommandDispatcher<CommandSourceStack> commandDispatcher = new Commands(Commands.CommandSelection.ALL, commandBuildContext).getDispatcher();
-        RootCommandNode<CommandSourceStack> rootCommandNode = commandDispatcher.getRoot();
-        commandDispatcher.findAmbiguities(
-            (parent, child, sibling, inputs) -> LOGGER.warn(
-                    "Ambiguity between arguments {} and {} with inputs: {}", commandDispatcher.getPath(child), commandDispatcher.getPath(sibling), inputs
-                )
-        );
-        Set<ArgumentType<?>> set = ArgumentUtils.findUsedArgumentTypes(rootCommandNode);
-        Set<ArgumentType<?>> set2 = set.stream().filter(type -> !ArgumentTypeInfos.isClassRecognized(type.getClass())).collect(Collectors.toSet());
-        if (!set2.isEmpty()) {
-            LOGGER.warn("Missing type registration for following arguments:\n {}", set2.stream().map(type -> "\t" + type).collect(Collectors.joining(",\n")));
+        CommandBuildContext commandbuildcontext = Commands.createValidationContext(VanillaRegistries.createLookup());
+        com.mojang.brigadier.CommandDispatcher<CommandSourceStack> com_mojang_brigadier_commanddispatcher = (new Commands(Commands.CommandSelection.ALL, commandbuildcontext)).getDispatcher();
+        RootCommandNode<CommandSourceStack> rootcommandnode = com_mojang_brigadier_commanddispatcher.getRoot();
+
+        com_mojang_brigadier_commanddispatcher.findAmbiguities((commandnode, commandnode1, commandnode2, collection) -> {
+            Commands.LOGGER.warn("Ambiguity between arguments {} and {} with inputs: {}", new Object[]{com_mojang_brigadier_commanddispatcher.getPath(commandnode1), com_mojang_brigadier_commanddispatcher.getPath(commandnode2), collection});
+        });
+        Set<ArgumentType<?>> set = ArgumentUtils.findUsedArgumentTypes(rootcommandnode);
+        Set<ArgumentType<?>> set1 = (Set) set.stream().filter((argumenttype) -> {
+            return !ArgumentTypeInfos.isClassRecognized(argumenttype.getClass());
+        }).collect(Collectors.toSet());
+
+        if (!set1.isEmpty()) {
+            Commands.LOGGER.warn("Missing type registration for following arguments:\n {}", set1.stream().map((argumenttype) -> {
+                return "\t" + String.valueOf(argumenttype);
+            }).collect(Collectors.joining(",\n")));
             throw new IllegalStateException("Unregistered argument types");
         }
     }
 
     public static enum CommandSelection {
-        ALL(true, true),
-        DEDICATED(false, true),
-        INTEGRATED(true, false);
+
+        ALL(true, true), DEDICATED(false, true), INTEGRATED(true, false);
 
         final boolean includeIntegrated;
         final boolean includeDedicated;
 
-        private CommandSelection(final boolean integrated, final boolean dedicated) {
-            this.includeIntegrated = integrated;
-            this.includeDedicated = dedicated;
+        private CommandSelection(final boolean flag, final boolean flag1) {
+            this.includeIntegrated = flag;
+            this.includeDedicated = flag1;
         }
     }
 
     @FunctionalInterface
     public interface ParseFunction {
+
         void parse(StringReader reader) throws CommandSyntaxException;
     }
 }
