package net.minecraft.client.gui.components;

import net.minecraft.client.resources.language.I18n;
import net.minecraft.client.gui.chat.NarratorChatListener;
import com.mojang.brigadier.Message;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.world.phys.Vec2;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.client.gui.GuiComponent;
import com.mojang.brigadier.context.ParsedArgument;
import javax.annotation.Nullable;
import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.Commands;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Map;
import java.util.regex.Matcher;
import com.google.common.base.Strings;
import java.util.Collection;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.StringReader;
import java.util.Iterator;
import com.mojang.brigadier.suggestion.Suggestion;
import net.minecraft.util.Mth;
import com.google.common.collect.Lists;
import com.mojang.brigadier.suggestion.Suggestions;
import java.util.concurrent.CompletableFuture;
import net.minecraft.commands.SharedSuggestionProvider;
import com.mojang.brigadier.ParseResults;
import java.util.List;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.Minecraft;
import java.util.regex.Pattern;

public class CommandSuggestions {
    private static final Pattern WHITESPACE_PATTERN;
    private final Minecraft minecraft;
    private final Screen screen;
    private final EditBox input;
    private final Font font;
    private final boolean commandsOnly;
    private final boolean onlyShowIfCursorPastError;
    private final int lineStartOffset;
    private final int suggestionLineLimit;
    private final boolean anchorToBottom;
    private final int fillColor;
    private final List<String> commandUsage;
    private int commandUsagePosition;
    private int commandUsageWidth;
    private ParseResults<SharedSuggestionProvider> currentParse;
    private CompletableFuture<Suggestions> pendingSuggestions;
    private SuggestionsList suggestions;
    private boolean allowSuggestions;
    private boolean keepSuggestions;
    
    public CommandSuggestions(final Minecraft dbn, final Screen dgb, final EditBox ddc, final Font dch, final boolean boolean5, final boolean boolean6, final int integer7, final int integer8, final boolean boolean9, final int integer10) {
        this.commandUsage = Lists.newArrayList();
        this.minecraft = dbn;
        this.screen = dgb;
        this.input = ddc;
        this.font = dch;
        this.commandsOnly = boolean5;
        this.onlyShowIfCursorPastError = boolean6;
        this.lineStartOffset = integer7;
        this.suggestionLineLimit = integer8;
        this.anchorToBottom = boolean9;
        this.fillColor = integer10;
        ddc.setFormatter(this::formatChat);
    }
    
    public void setAllowSuggestions(final boolean boolean1) {
        if (!(this.allowSuggestions = boolean1)) {
            this.suggestions = null;
        }
    }
    
    public boolean keyPressed(final int integer1, final int integer2, final int integer3) {
        if (this.suggestions != null && this.suggestions.keyPressed(integer1, integer2, integer3)) {
            return true;
        }
        if (this.screen.getFocused() == this.input && integer1 == 258) {
            this.showSuggestions(true);
            return true;
        }
        return false;
    }
    
    public boolean mouseScrolled(final double double1) {
        return this.suggestions != null && this.suggestions.mouseScrolled(Mth.clamp(double1, -1.0, 1.0));
    }
    
    public boolean mouseClicked(final double double1, final double double2, final int integer) {
        return this.suggestions != null && this.suggestions.mouseClicked((int)double1, (int)double2, integer);
    }
    
    public void showSuggestions(final boolean boolean1) {
        if (this.pendingSuggestions != null && this.pendingSuggestions.isDone()) {
            final Suggestions suggestions3 = this.pendingSuggestions.join();
            if (!suggestions3.isEmpty()) {
                int integer4 = 0;
                for (final Suggestion suggestion6 : suggestions3.getList()) {
                    integer4 = Math.max(integer4, this.font.width(suggestion6.getText()));
                }
                final int integer5 = Mth.clamp(this.input.getScreenX(suggestions3.getRange().getStart()), 0, this.input.getScreenX(0) + this.input.getInnerWidth() - integer4);
                final int integer6 = this.anchorToBottom ? (this.screen.height - 12) : 72;
                this.suggestions = new SuggestionsList(integer5, integer6, integer4, suggestions3, boolean1);
            }
        }
    }
    
    public void updateCommandInfo() {
        final String string2 = this.input.getValue();
        if (this.currentParse != null && !this.currentParse.getReader().getString().equals(string2)) {
            this.currentParse = null;
        }
        if (!this.keepSuggestions) {
            this.input.setSuggestion(null);
            this.suggestions = null;
        }
        this.commandUsage.clear();
        final StringReader stringReader3 = new StringReader(string2);
        final boolean boolean4 = stringReader3.canRead() && stringReader3.peek() == '/';
        if (boolean4) {
            stringReader3.skip();
        }
        final boolean boolean5 = this.commandsOnly || boolean4;
        final int integer6 = this.input.getCursorPosition();
        if (boolean5) {
            final CommandDispatcher<SharedSuggestionProvider> commandDispatcher7 = this.minecraft.player.connection.getCommands();
            if (this.currentParse == null) {
                this.currentParse = (ParseResults<SharedSuggestionProvider>)commandDispatcher7.parse(stringReader3, this.minecraft.player.connection.getSuggestionsProvider());
            }
            final int integer7 = this.onlyShowIfCursorPastError ? stringReader3.getCursor() : 1;
            if (integer6 >= integer7 && (this.suggestions == null || !this.keepSuggestions)) {
                (this.pendingSuggestions = (CompletableFuture<Suggestions>)commandDispatcher7.getCompletionSuggestions((ParseResults)this.currentParse, integer6)).thenRun(() -> {
                    if (!(!this.pendingSuggestions.isDone())) {
                        this.updateUsageInfo();
                    }
                });
            }
        }
        else {
            final String string3 = string2.substring(0, integer6);
            final int integer7 = getLastWordIndex(string3);
            final Collection<String> collection9 = this.minecraft.player.connection.getSuggestionsProvider().getOnlinePlayerNames();
            this.pendingSuggestions = SharedSuggestionProvider.suggest(collection9, new SuggestionsBuilder(string3, integer7));
        }
    }
    
    private static int getLastWordIndex(final String string) {
        if (Strings.isNullOrEmpty(string)) {
            return 0;
        }
        int integer2 = 0;
        final Matcher matcher3 = CommandSuggestions.WHITESPACE_PATTERN.matcher(string);
        while (matcher3.find()) {
            integer2 = matcher3.end();
        }
        return integer2;
    }
    
    public void updateUsageInfo() {
        if (this.input.getCursorPosition() == this.input.getValue().length()) {
            if (this.pendingSuggestions.join().isEmpty() && !this.currentParse.getExceptions().isEmpty()) {
                int integer2 = 0;
                for (final Map.Entry<CommandNode<SharedSuggestionProvider>, CommandSyntaxException> entry4 : this.currentParse.getExceptions().entrySet()) {
                    final CommandSyntaxException commandSyntaxException5 = entry4.getValue();
                    if (commandSyntaxException5.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect()) {
                        ++integer2;
                    }
                    else {
                        this.commandUsage.add(commandSyntaxException5.getMessage());
                    }
                }
                if (integer2 > 0) {
                    this.commandUsage.add(CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().create().getMessage());
                }
            }
            else if (this.currentParse.getReader().canRead()) {
                this.commandUsage.add(Commands.<SharedSuggestionProvider>getParseException(this.currentParse).getMessage());
            }
        }
        this.commandUsagePosition = 0;
        this.commandUsageWidth = this.screen.width;
        if (this.commandUsage.isEmpty()) {
            this.fillNodeUsage(ChatFormatting.GRAY);
        }
        this.suggestions = null;
        if (this.allowSuggestions && this.minecraft.options.autoSuggestions) {
            this.showSuggestions(false);
        }
    }
    
    private void fillNodeUsage(final ChatFormatting g) {
        final CommandContextBuilder<SharedSuggestionProvider> commandContextBuilder3 = (CommandContextBuilder<SharedSuggestionProvider>)this.currentParse.getContext();
        final SuggestionContext<SharedSuggestionProvider> suggestionContext4 = (SuggestionContext<SharedSuggestionProvider>)commandContextBuilder3.findSuggestionContext(this.input.getCursorPosition());
        final Map<CommandNode<SharedSuggestionProvider>, String> map5 = (Map<CommandNode<SharedSuggestionProvider>, String>)this.minecraft.player.connection.getCommands().getSmartUsage(suggestionContext4.parent, this.minecraft.player.connection.getSuggestionsProvider());
        final List<String> list6 = Lists.newArrayList();
        int integer7 = 0;
        for (final Map.Entry<CommandNode<SharedSuggestionProvider>, String> entry9 : map5.entrySet()) {
            if (!(entry9.getKey() instanceof LiteralCommandNode)) {
                list6.add(g + entry9.getValue());
                integer7 = Math.max(integer7, this.font.width(entry9.getValue()));
            }
        }
        if (!list6.isEmpty()) {
            this.commandUsage.addAll(list6);
            this.commandUsagePosition = Mth.clamp(this.input.getScreenX(suggestionContext4.startPos), 0, this.input.getScreenX(0) + this.input.getInnerWidth() - integer7);
            this.commandUsageWidth = integer7;
        }
    }
    
    private String formatChat(final String string, final int integer) {
        if (this.currentParse != null) {
            return formatText(this.currentParse, string, integer);
        }
        return string;
    }
    
    @Nullable
    private static String calculateSuggestionSuffix(final String string1, final String string2) {
        if (string2.startsWith(string1)) {
            return string2.substring(string1.length());
        }
        return null;
    }
    
    public static String formatText(final ParseResults<SharedSuggestionProvider> parseResults, final String string, final int integer) {
        final ChatFormatting[] arr4 = { ChatFormatting.AQUA, ChatFormatting.YELLOW, ChatFormatting.GREEN, ChatFormatting.LIGHT_PURPLE, ChatFormatting.GOLD };
        final String string2 = ChatFormatting.GRAY.toString();
        final StringBuilder stringBuilder6 = new StringBuilder(string2);
        int integer2 = 0;
        int integer3 = -1;
        final CommandContextBuilder<SharedSuggestionProvider> commandContextBuilder9 = (CommandContextBuilder<SharedSuggestionProvider>)parseResults.getContext().getLastChild();
        for (final ParsedArgument<SharedSuggestionProvider, ?> parsedArgument11 : commandContextBuilder9.getArguments().values()) {
            if (++integer3 >= arr4.length) {
                integer3 = 0;
            }
            final int integer4 = Math.max(parsedArgument11.getRange().getStart() - integer, 0);
            if (integer4 >= string.length()) {
                break;
            }
            final int integer5 = Math.min(parsedArgument11.getRange().getEnd() - integer, string.length());
            if (integer5 <= 0) {
                continue;
            }
            stringBuilder6.append(string, integer2, integer4);
            stringBuilder6.append(arr4[integer3]);
            stringBuilder6.append(string, integer4, integer5);
            stringBuilder6.append(string2);
            integer2 = integer5;
        }
        if (parseResults.getReader().canRead()) {
            final int integer6 = Math.max(parseResults.getReader().getCursor() - integer, 0);
            if (integer6 < string.length()) {
                final int integer7 = Math.min(integer6 + parseResults.getReader().getRemainingLength(), string.length());
                stringBuilder6.append(string, integer2, integer6);
                stringBuilder6.append(ChatFormatting.RED);
                stringBuilder6.append(string, integer6, integer7);
                integer2 = integer7;
            }
        }
        stringBuilder6.append(string, integer2, string.length());
        return stringBuilder6.toString();
    }
    
    public void render(final int integer1, final int integer2) {
        if (this.suggestions != null) {
            this.suggestions.render(integer1, integer2);
        }
        else {
            int integer3 = 0;
            for (final String string6 : this.commandUsage) {
                final int integer4 = this.anchorToBottom ? (this.screen.height - 14 - 13 - 12 * integer3) : (72 + 12 * integer3);
                GuiComponent.fill(this.commandUsagePosition - 1, integer4, this.commandUsagePosition + this.commandUsageWidth + 1, integer4 + 12, this.fillColor);
                this.font.drawShadow(string6, (float)this.commandUsagePosition, (float)(integer4 + 2), -1);
                ++integer3;
            }
        }
    }
    
    public String getNarrationMessage() {
        if (this.suggestions != null) {
            return "\n" + this.suggestions.getNarrationMessage();
        }
        return "";
    }
    
    static {
        WHITESPACE_PATTERN = Pattern.compile("(\\s+)");
    }
    
    public class SuggestionsList {
        private final Rect2i rect;
        private final Suggestions suggestions;
        private final String originalContents;
        private int offset;
        private int current;
        private Vec2 lastMouse;
        private boolean tabCycles;
        private int lastNarratedEntry;
        
        private SuggestionsList(final int integer2, final int integer3, final int integer4, final Suggestions suggestions, final boolean boolean6) {
            this.lastMouse = Vec2.ZERO;
            final int integer5 = integer2 - 1;
            final int integer6 = CommandSuggestions.this.anchorToBottom ? (integer3 - 3 - Math.min(suggestions.getList().size(), CommandSuggestions.this.suggestionLineLimit) * 12) : integer3;
            this.rect = new Rect2i(integer5, integer6, integer4 + 1, Math.min(suggestions.getList().size(), CommandSuggestions.this.suggestionLineLimit) * 12);
            this.suggestions = suggestions;
            this.originalContents = CommandSuggestions.this.input.getValue();
            this.lastNarratedEntry = (boolean6 ? -1 : 0);
            this.select(0);
        }
        
        public void render(final int integer1, final int integer2) {
            final int integer3 = Math.min(this.suggestions.getList().size(), CommandSuggestions.this.suggestionLineLimit);
            final int integer4 = -5592406;
            final boolean boolean6 = this.offset > 0;
            final boolean boolean7 = this.suggestions.getList().size() > this.offset + integer3;
            final boolean boolean8 = boolean6 || boolean7;
            final boolean boolean9 = this.lastMouse.x != integer1 || this.lastMouse.y != integer2;
            if (boolean9) {
                this.lastMouse = new Vec2((float)integer1, (float)integer2);
            }
            if (boolean8) {
                GuiComponent.fill(this.rect.getX(), this.rect.getY() - 1, this.rect.getX() + this.rect.getWidth(), this.rect.getY(), CommandSuggestions.this.fillColor);
                GuiComponent.fill(this.rect.getX(), this.rect.getY() + this.rect.getHeight(), this.rect.getX() + this.rect.getWidth(), this.rect.getY() + this.rect.getHeight() + 1, CommandSuggestions.this.fillColor);
                if (boolean6) {
                    for (int integer5 = 0; integer5 < this.rect.getWidth(); ++integer5) {
                        if (integer5 % 2 == 0) {
                            GuiComponent.fill(this.rect.getX() + integer5, this.rect.getY() - 1, this.rect.getX() + integer5 + 1, this.rect.getY(), -1);
                        }
                    }
                }
                if (boolean7) {
                    for (int integer5 = 0; integer5 < this.rect.getWidth(); ++integer5) {
                        if (integer5 % 2 == 0) {
                            GuiComponent.fill(this.rect.getX() + integer5, this.rect.getY() + this.rect.getHeight(), this.rect.getX() + integer5 + 1, this.rect.getY() + this.rect.getHeight() + 1, -1);
                        }
                    }
                }
            }
            boolean boolean10 = false;
            for (int integer6 = 0; integer6 < integer3; ++integer6) {
                final Suggestion suggestion12 = this.suggestions.getList().get(integer6 + this.offset);
                GuiComponent.fill(this.rect.getX(), this.rect.getY() + 12 * integer6, this.rect.getX() + this.rect.getWidth(), this.rect.getY() + 12 * integer6 + 12, CommandSuggestions.this.fillColor);
                if (integer1 > this.rect.getX() && integer1 < this.rect.getX() + this.rect.getWidth() && integer2 > this.rect.getY() + 12 * integer6 && integer2 < this.rect.getY() + 12 * integer6 + 12) {
                    if (boolean9) {
                        this.select(integer6 + this.offset);
                    }
                    boolean10 = true;
                }
                CommandSuggestions.this.font.drawShadow(suggestion12.getText(), (float)(this.rect.getX() + 1), (float)(this.rect.getY() + 2 + 12 * integer6), (integer6 + this.offset == this.current) ? -256 : -5592406);
            }
            if (boolean10) {
                final Message message11 = this.suggestions.getList().get(this.current).getTooltip();
                if (message11 != null) {
                    CommandSuggestions.this.screen.renderTooltip(ComponentUtils.fromMessage(message11).getColoredString(), integer1, integer2);
                }
            }
        }
        
        public boolean mouseClicked(final int integer1, final int integer2, final int integer3) {
            if (!this.rect.contains(integer1, integer2)) {
                return false;
            }
            final int integer4 = (integer2 - this.rect.getY()) / 12 + this.offset;
            if (integer4 >= 0 && integer4 < this.suggestions.getList().size()) {
                this.select(integer4);
                this.useSuggestion();
            }
            return true;
        }
        
        public boolean mouseScrolled(final double double1) {
            final int integer4 = (int)(CommandSuggestions.this.minecraft.mouseHandler.xpos() * CommandSuggestions.this.minecraft.getWindow().getGuiScaledWidth() / CommandSuggestions.this.minecraft.getWindow().getScreenWidth());
            final int integer5 = (int)(CommandSuggestions.this.minecraft.mouseHandler.ypos() * CommandSuggestions.this.minecraft.getWindow().getGuiScaledHeight() / CommandSuggestions.this.minecraft.getWindow().getScreenHeight());
            if (this.rect.contains(integer4, integer5)) {
                this.offset = Mth.clamp((int)(this.offset - double1), 0, Math.max(this.suggestions.getList().size() - CommandSuggestions.this.suggestionLineLimit, 0));
                return true;
            }
            return false;
        }
        
        public boolean keyPressed(final int integer1, final int integer2, final int integer3) {
            if (integer1 == 265) {
                this.cycle(-1);
                this.tabCycles = false;
                return true;
            }
            if (integer1 == 264) {
                this.cycle(1);
                this.tabCycles = false;
                return true;
            }
            if (integer1 == 258) {
                if (this.tabCycles) {
                    this.cycle(Screen.hasShiftDown() ? -1 : 1);
                }
                this.useSuggestion();
                return true;
            }
            if (integer1 == 256) {
                this.hide();
                return true;
            }
            return false;
        }
        
        public void cycle(final int integer) {
            this.select(this.current + integer);
            final int integer2 = this.offset;
            final int integer3 = this.offset + CommandSuggestions.this.suggestionLineLimit - 1;
            if (this.current < integer2) {
                this.offset = Mth.clamp(this.current, 0, Math.max(this.suggestions.getList().size() - CommandSuggestions.this.suggestionLineLimit, 0));
            }
            else if (this.current > integer3) {
                this.offset = Mth.clamp(this.current + CommandSuggestions.this.lineStartOffset - CommandSuggestions.this.suggestionLineLimit, 0, Math.max(this.suggestions.getList().size() - CommandSuggestions.this.suggestionLineLimit, 0));
            }
        }
        
        public void select(final int integer) {
            this.current = integer;
            if (this.current < 0) {
                this.current += this.suggestions.getList().size();
            }
            if (this.current >= this.suggestions.getList().size()) {
                this.current -= this.suggestions.getList().size();
            }
            final Suggestion suggestion3 = this.suggestions.getList().get(this.current);
            CommandSuggestions.this.input.setSuggestion(calculateSuggestionSuffix(CommandSuggestions.this.input.getValue(), suggestion3.apply(this.originalContents)));
            if (NarratorChatListener.INSTANCE.isActive() && this.lastNarratedEntry != this.current) {
                NarratorChatListener.INSTANCE.sayNow(this.getNarrationMessage());
            }
        }
        
        public void useSuggestion() {
            final Suggestion suggestion2 = this.suggestions.getList().get(this.current);
            CommandSuggestions.this.keepSuggestions = true;
            CommandSuggestions.this.input.setValue(suggestion2.apply(this.originalContents));
            final int integer3 = suggestion2.getRange().getStart() + suggestion2.getText().length();
            CommandSuggestions.this.input.setCursorPosition(integer3);
            CommandSuggestions.this.input.setHighlightPos(integer3);
            this.select(this.current);
            CommandSuggestions.this.keepSuggestions = false;
            this.tabCycles = true;
        }
        
        private String getNarrationMessage() {
            this.lastNarratedEntry = this.current;
            final List<Suggestion> list2 = (List<Suggestion>)this.suggestions.getList();
            final Suggestion suggestion3 = list2.get(this.current);
            final Message message4 = suggestion3.getTooltip();
            if (message4 != null) {
                return I18n.get("narration.suggestion.tooltip", this.current + 1, list2.size(), suggestion3.getText(), message4.getString());
            }
            return I18n.get("narration.suggestion", this.current + 1, list2.size(), suggestion3.getText());
        }
        
        public void hide() {
            CommandSuggestions.this.suggestions = null;
        }
    }
}
