package dev.crucifixbeta.mod.modules.impl.player;

import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.ClickBlockEvent;
import dev.crucifixbeta.api.events.impl.ClientTickEvent;
import dev.crucifixbeta.api.events.impl.EntitySpawnEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.math.DamageUtils;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.player.EntityUtil;
import dev.crucifixbeta.api.utils.player.InventoryUtil;
import dev.crucifixbeta.api.utils.render.Render3DUtil;
import dev.crucifixbeta.api.utils.world.BlockUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.settings.enums.SwingSide;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.ColorSetting;
import dev.crucifixbeta.mod.modules.settings.impl.EnumSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.item.AxeItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.MiningToolItem;
import net.minecraft.item.PickaxeItem;
import net.minecraft.item.ShearsItem;
import net.minecraft.item.ShovelItem;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;

@Environment(EnvType.CLIENT)
public class SpeedMineGrimV3 extends Module {
   public static SpeedMineGrimV3 INSTANCE;
   private final EnumSetting<SpeedMineGrimV3.Page> page = this.add(new EnumSetting<>("Page", SpeedMineGrimV3.Page.General));
   public final EnumSetting<SpeedMineGrimV3.PauseMode> pause = this.add(
      new EnumSetting<>("Pause", SpeedMineGrimV3.PauseMode.None, () -> this.page.is(SpeedMineGrimV3.Page.General))
   );
   public final SliderSetting range = this.add(
      new SliderSetting("Range", 5.5, 1.0, 8.0, 0.1, () -> this.page.is(SpeedMineGrimV3.Page.General))
   );
   public final SliderSetting speed = this.add(
      new SliderSetting("Speed", 0.8, 0.5, 1.0, 0.01, () -> this.page.is(SpeedMineGrimV3.Page.General))
   );
   public final BooleanSetting doubleBreak = this.add(
      new BooleanSetting("DoubleBreak", false, () -> this.page.is(SpeedMineGrimV3.Page.General)).setParent()
   );
   public final SliderSetting firstWait = this.add(
      new SliderSetting("FirstWait", 5, 0, 20, () -> this.page.is(SpeedMineGrimV3.Page.General) && this.doubleBreak.isOpen())
   );
   public final BooleanSetting mineDown = this.add(
      new BooleanSetting("MineDown", false, () -> this.page.is(SpeedMineGrimV3.Page.General) && this.doubleBreak.isOpen())
   );
   public final SliderSetting mineDownDepth = this.add(
      new SliderSetting(
         "MineDownDepth", 2, 1, 8, () -> this.page.is(SpeedMineGrimV3.Page.General) && this.doubleBreak.isOpen() && this.mineDown.getValue()
      )
   );
   public final SliderSetting mineDownInterval = this.add(
      new SliderSetting(
         "MineDownInterval", 0, 0, 20, () -> this.page.is(SpeedMineGrimV3.Page.General) && this.doubleBreak.isOpen() && this.mineDown.getValue()
      )
   );
   public final BooleanSetting rebreak = this.add(
      new BooleanSetting("Rebreak", true, () -> this.page.is(SpeedMineGrimV3.Page.General)).setParent()
   );
   public final SliderSetting rebreakDelay = this.add(
      new SliderSetting("RebreakDelay", 0, 0, 10, () -> this.page.is(SpeedMineGrimV3.Page.General))
   );
   public final BooleanSetting swapBack = this.add(
      new BooleanSetting("SwapBack", false, () -> this.page.is(SpeedMineGrimV3.Page.General) && this.rebreak.isOpen())
   );
   public final BooleanSetting bypassGround = this.add(new BooleanSetting("BypassGround", true, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting cancelClick = this.add(new BooleanSetting("CancelClick", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting clickReset = this.add(new BooleanSetting("ClickReset", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting clickSwing = this.add(new BooleanSetting("ClickSwing", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final EnumSetting<SpeedMineGrimV3.Swap> swapMode = this.add(
      new EnumSetting<>("AutoSwap", SpeedMineGrimV3.Swap.Silent, () -> this.page.is(SpeedMineGrimV3.Page.Misc))
   );
   public final BooleanSetting silentSwitch = this.add(
      new BooleanSetting("SilentSwitch", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.isSilentSwap())
   );
   public final BooleanSetting ghostHand = this.add(
      new BooleanSetting("GhostHand", true, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.isSilentSwap())
   );
   public final BooleanSetting swapLock = this.add(
      new BooleanSetting("SwapLock", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.isSilentSwap() && this.ghostHand.getValue())
   );
   public final EnumSetting<SwingSide> swingSide = this.add(
      new EnumSetting<>("SwingSide", SwingSide.Server, () -> this.page.is(SpeedMineGrimV3.Page.Misc))
   );
   public final SliderSetting waitTime = this.add(
      new SliderSetting(
         "WaitTime", 0.0, 0.0, 1.0, 0.01, () -> this.page.is(SpeedMineGrimV3.Page.General) || this.page.is(SpeedMineGrimV3.Page.Misc)
      )
   );
   public final BooleanSetting airEnd = this.add(new BooleanSetting("AirEnd", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting resetMine = this.add(
      new BooleanSetting("ResetMine", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)).setParent()
   );
   public final SliderSetting rmDelay = this.add(
      new SliderSetting("RMDelay", 0, 0, 20, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.resetMine.isOpen())
   );
   public final SliderSetting stopTimes = this.add(
      new SliderSetting("HStopTimes", 3, 1, 20, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.resetMine.isOpen())
   );
   public final BooleanSetting retryAfterStop = this.add(new BooleanSetting("RetryAfterStop", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final SliderSetting retryTicks = this.add(
      new SliderSetting("RetryTicks", 5, 0, 40, () -> this.page.is(SpeedMineGrimV3.Page.Misc) && this.retryAfterStop.getValue())
   );
   public final BooleanSetting noGSpeed = this.add(new BooleanSetting("NOGSpeed", false, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting grimV3 = this.add(new BooleanSetting("GrimV3", true, () -> this.page.is(SpeedMineGrimV3.Page.Misc)));
   public final BooleanSetting renderEnable = this.add(new BooleanSetting("RenderEnable", true, () -> this.page.is(SpeedMineGrimV3.Page.Render)));
   public final EnumSetting<SpeedMineGrimV3.MioRenderMode> renderMode = this.add(
      new EnumSetting<>("RenderMode", SpeedMineGrimV3.MioRenderMode.Out, () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final EnumSetting<SpeedMineGrimV3.MioColorMode> colorMode = this.add(
      new EnumSetting<>("ColorMode", SpeedMineGrimV3.MioColorMode.Progress, () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final BooleanSetting air = this.add(new BooleanSetting("Air", false, () -> this.page.is(SpeedMineGrimV3.Page.Render)));
   public final BooleanSetting fade = this.add(new BooleanSetting("Fade", true, () -> this.page.is(SpeedMineGrimV3.Page.Render)));
   public final ColorSetting fillColor = this.add(
      new ColorSetting("Fill", new Color(255, 0, 0, 80), () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final ColorSetting outlineColor = this.add(
      new ColorSetting("Outline", new Color(255, 0, 0), () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final ColorSetting secondFillColor = this.add(
      new ColorSetting("SecondFill", new Color(255, 0, 0, 80), () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final ColorSetting secondOutlineColor = this.add(
      new ColorSetting("SecondOutline", new Color(255, 0, 0), () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public final SliderSetting lineWidth = this.add(
      new SliderSetting("LineWidth", 2.0, 0.5, 10.0, 0.1, () -> this.page.is(SpeedMineGrimV3.Page.Render))
   );
   public double lastMineSpeed = -1.0;
   public int lastSlot = -1;
   public SpeedMineGrimV3.BlockDate breakBlockDate;
   public SpeedMineGrimV3.BlockDate firstBreakBlockDate;
   public static SpeedMineGrimV3.BlockDate rebreakBlockDate;
   public static final List<Block> godBlocks;
   public static final List<Block> hardBlocks;
   private final HashMap<BlockPos, Timer> donePos = new HashMap<>();
   private final Timer rebreakTimer = new Timer();
   private final Timer breakTimer = new Timer();
   private final Timer mineDownTimer = new Timer();
   private boolean hideSwapPending;
   private SpeedMineGrimV3.MineData stopRetryData;
   private int stopRetryTicksLeft;
   private BlockPos pendingMineDownBasePos;
   private BlockPos pendingMineDownSecondPos;
   private Direction pendingMineDownSecondDirection;
   private SpeedMineGrimV3.MiningGoal pendingMineDownGoal;
   public boolean shouldPlaceEnderChest = false;
   public boolean shouldMineDown = false;
   private boolean manualOverride;
   private boolean autoMineClick;
   private BlockPos clickPos;

   public SpeedMineGrimV3() {
      super("SpeedMine Grim V3", Module.Category.Player);
      this.setChinese("\u901f\u6316 Grim V3");
      INSTANCE = this;
   }

   @Override
   public void onEnable() {
      this.lastSlot = -1;
      this.breakBlockDate = null;
      rebreakBlockDate = null;
      this.firstBreakBlockDate = null;
      this.donePos.clear();
      this.rebreakTimer.reset();
      this.lastMineSpeed = -1.0;
      this.shouldPlaceEnderChest = false;
      this.shouldMineDown = false;
      this.manualOverride = false;
      this.autoMineClick = false;
      this.clickPos = null;
      this.hideSwapPending = false;
      this.clearMineDownPending();
      this.clearStopRetry();
   }

   @Override
   public void onDisable() {
      this.reset();
      this.lastSlot = -1;
      this.breakBlockDate = null;
      rebreakBlockDate = null;
      this.donePos.clear();
      this.rebreakTimer.reset();
      this.lastMineSpeed = -1.0;
      this.shouldPlaceEnderChest = false;
      this.shouldMineDown = false;
      this.manualOverride = false;
      this.autoMineClick = false;
      this.clickPos = null;
      this.hideSwapPending = false;
      this.clearMineDownPending();
      this.clearStopRetry();
   }

   @EventListener
   public void onTick(ClientTickEvent event) {
      if (!nullCheck() && !event.isPost()) {
         if (this.manualOverride && !this.hasManualMine()) {
            this.manualOverride = false;
         }

         this.check();
         this.handleStopRetry();
         if (this.lastSlot != -1 && this.isSilentSwap()) {
            this.restoreSilentSwap();
            this.lastSlot = -1;
         }

         if (this.hideSwapPending) {
            this.restoreHideSwap();
         }

         if (!this.isPause(this.pause.getValue())) {
            this.handleMineDown();
            this.handleFirstBreak();
            this.handleCurrentBreak();
            this.handleRebreak();
         }
      }
   }

   private void handleFirstBreak() {
      if (this.firstBreakBlockDate != null) {
         if (!this.firstBreakBlockDate.done) {
            this.firstBreakBlockDate.freshProgress();
         } else if (mc.world.getBlockState(this.firstBreakBlockDate.pos).getBlock() != Blocks.AIR
            && !this.firstBreakBlockDate.finishTimer.passed((long)this.firstWait.getValueInt() * 100L)) {
            int var1 = this.getToolSlot(mc.world.getBlockState(this.firstBreakBlockDate.pos));
            this.prepareToolSwap(var1);
            EntityUtil.swingHand(Hand.MAIN_HAND, this.swingSide.getValue());
            if (!this.grimV3.getValue()) {
               this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.firstBreakBlockDate.pos, this.clickSide(this.firstBreakBlockDate.pos)));
            }
         } else {
            this.firstBreakBlockDate = null;
         }
      }
   }

   private void handleCurrentBreak() {
      if (this.breakBlockDate != null) {
         Block var1 = mc.world.getBlockState(this.breakBlockDate.pos).getBlock();
         if (var1 != Blocks.AIR && var1 != Blocks.WATER && var1 != Blocks.LAVA) {
            if (!this.breakBlockDate.isMining) {
               if (this.breakTimer.passed((long)(this.breakBlockDate.waitTime * 1000.0F))) {
                  this.breakBlockDate.isMining = true;
                  int var2 = this.getToolSlot(mc.world.getBlockState(this.breakBlockDate.pos));
                  this.prepareToolSwap(var2);
                  EntityUtil.swingHand(Hand.MAIN_HAND, this.swingSide.getValue());
                  this.spoofGroundIfNeeded();
                  if (!this.grimV3.getValue()) {
                     if (this.doubleBreak.getValue() && !mc.player.isInFluid()) {
                        this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                        this.sendPacket(new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                        this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                     }

                     if (mc.player.isInFluid() || !this.doubleBreak.getValue()) {
                        this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                        this.sendPacket(new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                        this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
                     }
                  } else {
                     this.sendPacket(new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
                  }

                  this.breakBlockDate.freshProgress();
               }
            } else {
               if (!this.breakBlockDate.done) {
                  this.breakBlockDate.freshProgress();
               } else {
                  this.finishCurrentMine();
               }
            }
         } else {
            if (this.fade.getValue()) {
               this.donePos.put(this.breakBlockDate.pos, new Timer());
            }

            rebreakBlockDate = new SpeedMineGrimV3.BlockDate(this.breakBlockDate.pos, this.breakBlockDate.direction, this.breakBlockDate.goal);
            this.breakBlockDate = null;
            this.breakTimer.reset();
            this.clickPos = null;
         }
      }
   }

   private void handleRebreak() {
      if (rebreakBlockDate != null && this.breakBlockDate == null && this.rebreak.getValue()) {
         Block var1 = mc.world.getBlockState(rebreakBlockDate.pos).getBlock();
         if (var1 != Blocks.AIR && var1 != Blocks.WATER && var1 != Blocks.LAVA) {
            if (this.rebreakTimer.passedMs((double)this.rebreakDelay.getValueInt() * 200.0)) {
               int var2 = mc.player.getInventory().selectedSlot;
               this.rebreakTimer.reset();
               int var3 = this.getToolSlot(mc.world.getBlockState(rebreakBlockDate.pos));
               this.prepareToolSwap(var3);
               if (!rebreakBlockDate.subed && this.grimV3.getValue()) {
                  rebreakBlockDate.subed = true;
                  this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, rebreakBlockDate.pos, this.clickSide(rebreakBlockDate.pos)));
               } else {
                  this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, rebreakBlockDate.pos, this.clickSide(rebreakBlockDate.pos)));
                  this.rebreakTimer.reset();
               }

               if (this.swapBack.getValue() && var3 != var2) {
                  if (this.isHideSwap()) {
                     this.restoreHideSwap();
                  } else {
                     InventoryUtil.switchToSlot(var2);
                  }
               }
            }
         }
      }
   }

   private void finishCurrentMine() {
      if (this.breakBlockDate != null) {
         SpeedMineGrimV3.MineData var1 = this.breakBlockDate.toMineData();
         if (this.fade.getValue() && !this.airEnd.getValue()) {
            this.donePos.put(this.breakBlockDate.pos, new Timer());
         }

         int var2 = this.getToolSlot(mc.world.getBlockState(this.breakBlockDate.pos));
         this.prepareToolSwap(var2);
         EntityUtil.swingHand(Hand.MAIN_HAND, this.swingSide.getValue());
         if (hardBlocks.contains(mc.world.getBlockState(this.breakBlockDate.pos).getBlock()) && this.grimV3.getValue()) {
            this.spoofGroundIfNeeded();
            if (this.doubleBreak.getValue()) {
               this.sendBypassStarts(6);
            }

            this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
            this.sendPacket(new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
            this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
         } else if (this.resetMine.getValue()) {
            if (this.breakBlockDate.stopNums < this.stopTimes.getValueInt()) {
               this.spoofGroundIfNeeded();
               if (this.doubleBreak.getValue() && this.grimV3.getValue()) {
                  this.sendBypassStarts(3);
               }

               this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               this.breakBlockDate.stopNums++;
               this.breakBlockDate.stoped = true;
            }
         } else {
            this.spoofGroundIfNeeded();
            if (this.doubleBreak.getValue() && this.grimV3.getValue()) {
               this.sendBypassStarts(3);
            }

            this.breakBlockDate.stoped = true;
            this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
         }

         this.armStopRetry(var1);
         if (!this.airEnd.getValue()) {
            rebreakBlockDate = new SpeedMineGrimV3.BlockDate(this.breakBlockDate.pos, this.breakBlockDate.direction, this.breakBlockDate.goal);
            this.breakBlockDate = null;
            this.clickPos = null;
         } else if (this.resetMine.getValue()
            && this.breakBlockDate.finishTimer != null
            && this.breakBlockDate.finishTimer.passed((long)this.rmDelay.getValueInt() * 100L)) {
            this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.breakBlockDate.direction));
            this.setBreakBlockDate(this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos), this.breakBlockDate.goal);
         }

         this.breakTimer.reset();
      }
   }

   @EventListener
   public void onClickBlock(ClickBlockEvent event) {
      if (!nullCheck()) {
         if (mc.player.isCreative()) {
            this.reset();
         } else if (!this.isPause(this.pause.getValue())) {
            if (this.cancelClick.getValue()) {
               event.setCancelled(true);
            }

            BlockPos var2 = event.getPos();
            Direction var3 = event.getDirection();
            if (this.shouldRejectDuplicateDoubleBreakClick(var2)) {
               event.setCancelled(true);
            } else {
               if (this.clickSwing.getValue()) {
                  EntityUtil.swingHand(Hand.MAIN_HAND, this.swingSide.getValue());
               }

               if (!this.autoMineClick && this.hasAutoMine()) {
                  this.manualOverride = true;
               }

               SpeedMineGrimV3.MiningGoal var4 = this.autoMineClick ? SpeedMineGrimV3.MiningGoal.MINING_ENEMY : SpeedMineGrimV3.MiningGoal.MANUAL;
               if (this.tryStartMineDown(var2, var3, var4)) {
                  if (this.autoMineClick) {
                     this.manualOverride = false;
                  }
               } else if (this.firstBreakBlockDate == null || !this.firstBreakBlockDate.pos.equals(var2)) {
                  if (this.breakBlockDate == null || !this.breakBlockDate.pos.equals(var2)) {
                     if (mc.world.getBlockState(var2).getBlock() == Blocks.NETHERRACK
                        && var3 == Direction.UP
                        && var2.getX() == mc.player.getBlockX()
                        && var2.getZ() == mc.player.getBlockZ()
                        && this.grimV3.getValue()) {
                        int var5 = this.getToolSlot(mc.world.getBlockState(var2));
                        if (var5 != -1) {
                           if (!this.isHideSwap() && (!this.isSilentSwap() || !this.silentSwitch.getValue())) {
                              if (var5 != mc.player.getInventory().selectedSlot) {
                                 InventoryUtil.switchToSlot(var5);
                              }
                           } else {
                              this.prepareToolSwap(var5);
                           }
                        }

                        if (this.breakBlockDate != null && this.breakBlockDate.isMining) {
                           this.sendPacket(
                              new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos))
                           );
                        }
                     } else if (mc.world.getBlockState(var2).getBlock() != Blocks.NETHERRACK
                        || this.breakBlockDate == null
                        || mc.world.getBlockState(this.breakBlockDate.pos).getBlock() != Blocks.NETHERRACK
                        || !this.grimV3.getValue()) {
                        if (!godBlocks.contains(mc.world.getBlockState(var2).getBlock())) {
                           if (this.breakBlockDate == null || !this.breakBlockDate.pos.equals(var2)) {
                              if (rebreakBlockDate == null || !rebreakBlockDate.pos.equals(var2) || this.clickReset.getValue()) {
                                 if (!(var2.toCenterPos().distanceTo(mc.player.getPos()) > this.range.getValue())) {
                                    if (this.clickPos == null || !this.clickPos.equals(var2)) {
                                       this.clickPos = var2;
                                    }

                                    this.setBreakBlockDate(var2, var3, var4);
                                    if (this.autoMineClick) {
                                       this.manualOverride = false;
                                    }
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   @EventListener
   public void onEntityAdd(EntitySpawnEvent event) {
      if (!nullCheck() && event.getEntity() instanceof ItemEntity var3) {
         if (this.breakBlockDate != null && this.distanceTo(var3) < this.range.getValue() && var3.getBoundingBox().intersects(new Box(this.breakBlockDate.pos))
            )
          {
            if (this.fade.getValue()) {
               this.donePos.put(this.breakBlockDate.pos, new Timer());
            }

            rebreakBlockDate = new SpeedMineGrimV3.BlockDate(this.breakBlockDate.pos, this.breakBlockDate.direction, this.breakBlockDate.goal);
            this.breakBlockDate = null;
            this.breakTimer.reset();
            this.clickPos = null;
         }

         if (this.firstBreakBlockDate != null
            && this.distanceTo(var3) < this.range.getValue()
            && var3.getBoundingBox().intersects(new Box(this.firstBreakBlockDate.pos))) {
            if (this.fade.getValue()) {
               this.donePos.put(this.firstBreakBlockDate.pos, new Timer());
            }

            this.firstBreakBlockDate = null;
         }
      }
   }

   @EventListener
   public void onPacketSend(PacketEvent.Send event) {
      if (!nullCheck()) {
         if (event.getPacket() instanceof PlayerActionC2SPacket var3
            && !this.grimV3.getValue()
            && (!this.doubleBreak.getValue() || mc.player.isInFluid())
            && var3.getAction() == Action.START_DESTROY_BLOCK
            && this.breakBlockDate != null
            && this.breakBlockDate.pos.equals(var3.getPos())) {
            this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
         }
      }
   }

   @Override
   public void onRender3D(MatrixStack matrixStack) {
      if (this.renderEnable.getValue() && !nullCheck()) {
         this.renderDonePos(matrixStack);
         this.renderBlockDate(
            matrixStack,
            this.firstBreakBlockDate,
            this.fillColor.getValue(),
            this.outlineColor.getValue(),
            this.grimV3.getValue() ? this.speed.getValue() : 1.0
         );
         this.renderBlockDate(matrixStack, this.breakBlockDate, this.secondFillColor.getValue(), this.secondOutlineColor.getValue(), this.speed.getValue());
         if (this.rebreak.getValue() && rebreakBlockDate != null && this.breakBlockDate == null) {
            this.renderMioBox(matrixStack, rebreakBlockDate.pos, 1.0F, this.secondFillColor.getValue(), this.secondOutlineColor.getValue(), true);
         }
      }
   }

   private void renderDonePos(MatrixStack matrixStack) {
      if (!this.donePos.isEmpty() && this.fade.getValue()) {
         Set<BlockPos> var2 = this.donePos.keySet();

         for (BlockPos var4 : new ArrayList<>(var2)) {
            Timer var5 = this.donePos.get(var4);
            if (var5.passed(500L)) {
               this.donePos.remove(var4);
            } else {
               float var6 = var5.getMs() < 150L ? 1.0F : 1.0F - (float)(var5.getMs() - 150L) / 350.0F;
               Color var7 = this.withAlpha(this.secondFillColor.getValue(), (int)((float)this.secondFillColor.getValue().getAlpha() * var6));
               Color var8 = this.withAlpha(this.secondOutlineColor.getValue(), (int)((float)this.secondOutlineColor.getValue().getAlpha() * var6));
               this.renderMioBox(matrixStack, var4, 1.0F, var7, var8, true);
            }
         }
      }
   }

   private void renderBlockDate(MatrixStack matrixStack, SpeedMineGrimV3.BlockDate data, Color fill, Color outline, double maxProgress) {
      if (data != null) {
         float var7 = mc.getRenderTickCounter().getTickDelta(true);
         float var8 = (float)Math.max(0.001, maxProgress);
         float var9 = MathHelper.clamp((float)MathHelper.lerp((double)var7, data.prevProgress, data.progress) / var8, 0.0F, 1.0F);
         this.renderMioBox(matrixStack, data.pos, var9, fill, outline, this.rebreak.getValue());
      }
   }

   private void renderMioBox(MatrixStack matrixStack, BlockPos pos, float progress, Color fill, Color outline, boolean allowAirRender) {
      BlockState var7 = mc.world.getBlockState(pos);
      VoxelShape var8 = var7.getOutlineShape(mc.world, pos);
      boolean var9 = var8.isEmpty();
      if (var9 || !this.canMine(var7)) {
         if (!allowAirRender) {
            return;
         }

         if (!this.air.getValue() && !this.fade.getValue()) {
            return;
         }
      }

      Box var10 = var9 ? new Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0) : var8.getBoundingBox();
      Box var11 = this.applyMioRenderMode(var10, progress).offset(pos);
      if (var7.isAir()) {
         var11 = new Box(pos);
      }

      Color[] var12 = this.getMioColors(fill, outline, progress);
      Render3DUtil.draw3DBox(matrixStack, var11, var12[0], var12[1], true, true, this.lineWidth.getValueFloat());
   }

   private Box applyMioRenderMode(Box box, float progress) {
      return switch ((SpeedMineGrimV3.MioRenderMode)this.renderMode.getValue()) {
         case Plain -> box;
         case In -> this.scaleBox(box, 1.0F - progress);
         case Out -> this.scaleBox(box, progress);
      };
   }

   private Box scaleBox(Box box, float scale) {
      double var3 = Math.abs(box.getLengthX());
      double var5 = Math.abs(box.getLengthY());
      double var7 = Math.abs(box.getLengthZ());
      Vec3d var9 = box.getCenter();
      return new Box(
         var9.subtract((double)scale * var3 / 2.0, (double)scale * var5 / 2.0, (double)scale * var7 / 2.0),
         var9.add((double)scale * var3 / 2.0, (double)scale * var5 / 2.0, (double)scale * var7 / 2.0)
      );
   }

   private Color[] getMioColors(Color fill, Color outline, float progress) {
      if (this.colorMode.getValue() == SpeedMineGrimV3.MioColorMode.Custom) {
         return new Color[]{fill, outline};
      } else {
         Color var4 = this.lerpColor(new Color(255, 0, 0), new Color(0, 255, 0), progress);
         return new Color[]{this.withAlpha(var4, fill.getAlpha()), this.withAlpha(var4, outline.getAlpha())};
      }
   }

   private Color lerpColor(Color from, Color to, float progress) {
      float var4 = MathHelper.clamp(progress, 0.0F, 1.0F);
      return new Color(
         MathHelper.lerp(var4, from.getRed(), to.getRed()),
         MathHelper.lerp(var4, from.getGreen(), to.getGreen()),
         MathHelper.lerp(var4, from.getBlue(), to.getBlue())
      );
   }

   private Color withAlpha(Color color, int alpha) {
      return new Color(color.getRed(), color.getGreen(), color.getBlue(), MathHelper.clamp(alpha, 0, 255));
   }

   public void check() {
      if (this.firstBreakBlockDate != null && this.distanceTo(this.firstBreakBlockDate.pos) > this.range.getValue()) {
         if (this.firstBreakBlockDate.isMining) {
            this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.firstBreakBlockDate.pos, this.clickSide(this.firstBreakBlockDate.pos)));
         }

         this.firstBreakBlockDate = null;
      }

      if (this.breakBlockDate != null && this.distanceTo(this.breakBlockDate.pos) > this.range.getValue()) {
         if (this.breakBlockDate.isMining) {
            this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
         }

         this.breakBlockDate = null;
      }

      if (rebreakBlockDate != null && this.distanceTo(rebreakBlockDate.pos) > this.range.getValue()) {
         rebreakBlockDate = null;
      }
   }

   public void setBreakBlockDate(BlockPos pos, Direction direction) {
      this.setBreakBlockDate(pos, direction, SpeedMineGrimV3.MiningGoal.MANUAL);
   }

   public void setBreakBlockDate(BlockPos pos, Direction direction, SpeedMineGrimV3.MiningGoal goal) {
      if (!this.doubleBreak.getValue() || !this.matchesMine(this.firstBreakBlockDate, pos)) {
         if (this.breakBlockDate != null) {
            if (this.breakBlockDate.pos.equals(this.clickPos)) {
               if (this.resetMine.getValue()
                  && this.breakBlockDate.finishTimer != null
                  && this.breakBlockDate.finishTimer.passed((long)this.rmDelay.getValueInt() * 100L)) {
                  this.breakBlockDate = new SpeedMineGrimV3.BlockDate(pos, direction, goal);
               }

               return;
            }

            if (this.breakBlockDate.pos.equals(pos)) {
               if (this.breakBlockDate.done) {
                  this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
                  this.breakBlockDate = new SpeedMineGrimV3.BlockDate(pos, direction, goal);
               }

               return;
            }

            if (this.breakBlockDate.isMining) {
               if (this.doubleBreak.getValue()
                  && (this.shouldMineDown || mc.world.getBlockState(this.breakBlockDate.pos).getBlock() != Blocks.NETHERRACK || !this.grimV3.getValue())
                  && (this.grimV3.getValue() || !mc.player.isInFluid())) {
                  if (this.firstBreakBlockDate != null && this.firstBreakBlockDate.pos.equals(pos)) {
                     return;
                  }

                  if (this.breakBlockDate.stoped) {
                     return;
                  }

                  this.breakBlockDate.stoped = true;
                  if (this.grimV3.getValue()) {
                     this.sendBypassStarts(6);
                  }

                  if (this.firstBreakBlockDate == null) {
                     this.firstBreakBlockDate = this.breakBlockDate;
                  }

                  this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               } else if (this.grimV3.getValue()) {
                  this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               }
            } else if (this.doubleBreak.getValue()
               && this.firstBreakBlockDate == null
               && (this.shouldMineDown || mc.world.getBlockState(this.breakBlockDate.pos).getBlock() != Blocks.NETHERRACK || !this.grimV3.getValue())
               && !this.breakBlockDate.stoped
               && (this.grimV3.getValue() || !mc.player.isInFluid())) {
               this.breakBlockDate.stoped = true;
               if (!this.grimV3.getValue()) {
                  this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               }

               this.sendPacket(new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               if (this.grimV3.getValue()) {
                  int var4 = hardBlocks.contains(mc.world.getBlockState(this.breakBlockDate.pos).getBlock()) ? 6 : 3;
                  this.sendBypassStarts(var4);
               }

               this.sendPacket(new PlayerActionC2SPacket(Action.STOP_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
               this.firstBreakBlockDate = this.breakBlockDate;
            }
         }

         if (rebreakBlockDate != null) {
            if (mc.world.getBlockState(rebreakBlockDate.pos).getBlock() != Blocks.AIR) {
               this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, rebreakBlockDate.pos, this.clickSide(rebreakBlockDate.pos)));
            }

            rebreakBlockDate = null;
         }

         this.breakBlockDate = new SpeedMineGrimV3.BlockDate(pos, direction, goal);
         this.breakTimer.reset();
      }
   }

   public void reset() {
      this.clearStopRetry();
      if (this.breakBlockDate != null) {
         if (this.breakBlockDate.isMining) {
            this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.breakBlockDate.pos, this.clickSide(this.breakBlockDate.pos)));
         }

         this.breakBlockDate = null;
      }

      if (rebreakBlockDate != null) {
         this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, rebreakBlockDate.pos, this.clickSide(rebreakBlockDate.pos)));
         rebreakBlockDate = null;
      }

      if (this.firstBreakBlockDate != null && this.firstBreakBlockDate.isMining) {
         this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, this.firstBreakBlockDate.pos, this.clickSide(this.firstBreakBlockDate.pos)));
      }

      this.firstBreakBlockDate = null;
      this.manualOverride = false;
      this.clickPos = null;
      if (this.hideSwapPending) {
         this.restoreHideSwap();
      }
   }

   public boolean canMine(BlockState state) {
      return state != null && !state.isAir() && !state.getCollisionShape(mc.world, BlockPos.ORIGIN).isEmpty();
   }

   public boolean isMining(BlockPos pos) {
      return this.matchesMine(this.breakBlockDate, pos)
         || this.matchesMine(this.firstBreakBlockDate, pos)
         || this.matchesMine(rebreakBlockDate, pos)
         || this.matchesMine(this.stopRetryData, pos);
   }

   public boolean isManualOverride() {
      return this.manualOverride || this.hasManualMine();
   }

   public boolean hasAutoMine() {
      return this.hasGoal(this.breakBlockDate, SpeedMineGrimV3.MiningGoal.MINING_ENEMY, SpeedMineGrimV3.MiningGoal.PREVENT_CRAWL)
         || this.hasGoal(this.firstBreakBlockDate, SpeedMineGrimV3.MiningGoal.MINING_ENEMY, SpeedMineGrimV3.MiningGoal.PREVENT_CRAWL)
         || this.hasGoal(rebreakBlockDate, SpeedMineGrimV3.MiningGoal.MINING_ENEMY, SpeedMineGrimV3.MiningGoal.PREVENT_CRAWL);
   }

   public boolean isCurrentMineComplete() {
      if (this.breakBlockDate == null) {
         return true;
      } else {
         BlockState var1 = mc.world.getBlockState(this.breakBlockDate.pos);
         return this.breakBlockDate.done && (var1.isAir() || !this.canMine(var1));
      }
   }

   public SpeedMineGrimV3.MineData getCurrentMineData() {
      return this.breakBlockDate == null ? null : this.breakBlockDate.toMineData();
   }

   public SpeedMineGrimV3.MineData getSecondaryMineData() {
      return this.firstBreakBlockDate == null ? null : this.firstBreakBlockDate.toMineData();
   }

   public SpeedMineGrimV3.MineData getRebreakMineData() {
      return rebreakBlockDate == null ? null : rebreakBlockDate.toMineData();
   }

   public boolean hasGoalMine(SpeedMineGrimV3.MiningGoal goal) {
      return this.hasGoal(this.breakBlockDate, goal) || this.hasGoal(this.firstBreakBlockDate, goal) || this.hasGoal(rebreakBlockDate, goal);
   }

   public int getGoalMineCount(SpeedMineGrimV3.MiningGoal goal) {
      int var2 = 0;
      if (this.hasGoal(this.breakBlockDate, goal)) {
         var2++;
      }

      if (this.hasGoal(this.firstBreakBlockDate, goal)) {
         var2++;
      }

      if (this.hasGoal(rebreakBlockDate, goal)) {
         var2++;
      }

      return var2;
   }

   public boolean isPausedForAutoMine() {
      return this.isPause(this.pause.getValue());
   }

   public boolean startAutoMine(SpeedMineGrimV3.MineData data) {
      if (!this.canStartAutoMine(data)) {
         return false;
      } else {
         this.clickPos = data.getPos();
         this.setBreakBlockDate(data.getPos(), data.getDirection(), data.getGoal());
         this.manualOverride = false;
         return true;
      }
   }

   public boolean attackAutoMineBlock(SpeedMineGrimV3.MineData data) {
      if (this.canStartAutoMine(data) && mc.interactionManager != null) {
         this.clickPos = data.getPos();
         this.autoMineClick = true;

         boolean var2;
         try {
            var2 = mc.interactionManager.attackBlock(data.getPos(), data.getDirection());
         } finally {
            this.autoMineClick = false;
         }

         return var2;
      } else {
         return false;
      }
   }

   public boolean canStartAutoMine(SpeedMineGrimV3.MineData data) {
      if (nullCheck() || data == null || this.isManualOverride() || this.isPause(this.pause.getValue())) {
         return false;
      } else if (!this.canMine(data.getState()) || this.isMining(data.getPos())) {
         return false;
      } else {
         return this.breakBlockDate == null
            ? true
            : this.breakBlockDate.goal != SpeedMineGrimV3.MiningGoal.MANUAL && this.doubleBreak.getValue() && this.firstBreakBlockDate == null;
      }
   }

   public void abortMine(SpeedMineGrimV3.MineData data) {
      if (data != null) {
         if (this.matchesMine(this.breakBlockDate, data.getPos(), data.getGoal())) {
            this.abortBlockDate(this.breakBlockDate);
            this.breakBlockDate = null;
            this.clickPos = null;
         }

         if (this.matchesMine(this.firstBreakBlockDate, data.getPos(), data.getGoal())) {
            this.abortBlockDate(this.firstBreakBlockDate);
            this.firstBreakBlockDate = null;
         }

         if (this.matchesMine(rebreakBlockDate, data.getPos(), data.getGoal())) {
            this.abortBlockDate(rebreakBlockDate);
            rebreakBlockDate = null;
         }
      }
   }

   public void abortGoal(SpeedMineGrimV3.MiningGoal goal) {
      if (goal != null) {
         if (this.breakBlockDate != null && this.breakBlockDate.goal == goal) {
            this.abortBlockDate(this.breakBlockDate);
            this.breakBlockDate = null;
            this.clickPos = null;
         }

         if (this.firstBreakBlockDate != null && this.firstBreakBlockDate.goal == goal) {
            this.abortBlockDate(this.firstBreakBlockDate);
            this.firstBreakBlockDate = null;
         }

         if (rebreakBlockDate != null && rebreakBlockDate.goal == goal) {
            this.abortBlockDate(rebreakBlockDate);
            rebreakBlockDate = null;
         }
      }
   }

   private void abortBlockDate(SpeedMineGrimV3.BlockDate data) {
      if (data != null) {
         this.sendPacket(new PlayerActionC2SPacket(Action.ABORT_DESTROY_BLOCK, data.pos, this.clickSide(data.pos)));
      }
   }

   private boolean hasGoal(SpeedMineGrimV3.BlockDate data, SpeedMineGrimV3.MiningGoal... goals) {
      if (data == null) {
         return false;
      } else {
         for (SpeedMineGrimV3.MiningGoal var6 : goals) {
            if (data.goal == var6) {
               return true;
            }
         }

         return false;
      }
   }

   private boolean hasManualMine() {
      return this.hasGoal(this.breakBlockDate, SpeedMineGrimV3.MiningGoal.MANUAL)
         || this.hasGoal(this.firstBreakBlockDate, SpeedMineGrimV3.MiningGoal.MANUAL)
         || this.hasGoal(rebreakBlockDate, SpeedMineGrimV3.MiningGoal.MANUAL);
   }

   private boolean matchesMine(SpeedMineGrimV3.BlockDate data, BlockPos pos) {
      return data != null && data.pos.equals(pos);
   }

   private boolean matchesMine(SpeedMineGrimV3.BlockDate data, BlockPos pos, SpeedMineGrimV3.MiningGoal goal) {
      return data != null && data.pos.equals(pos) && data.goal == goal;
   }

   private boolean matchesMine(SpeedMineGrimV3.MineData data, BlockPos pos) {
      return data != null && data.getPos().equals(pos);
   }

   private boolean tryStartMineDown(BlockPos pos, Direction direction, SpeedMineGrimV3.MiningGoal goal) {
      if (!this.shouldStartMineDown(pos)) {
         return false;
      } else {
         BlockPos var4 = this.getMineDownSecondPos(pos);
         return !this.isMineDownTargetValid(var4) ? false : this.startMineDownPair(pos, direction, var4, goal);
      }
   }

   private void handleMineDown() {
      if (!this.isMineDownActive()) {
         this.clearMineDownPending();
      } else {
         BlockPos var1 = mc.player.getBlockPos().down();
         BlockPos var2 = this.getMineDownSecondPos(var1);
         this.clearInvalidMineDownWork(var1, var2);
         if (this.pendingMineDownBasePos != null && !this.pendingMineDownBasePos.equals(var1)) {
            this.clearMineDownPending();
         }

         if (this.isPendingMineDownSecond(var1, var2)) {
            this.tryQueuePendingMineDownSecond();
         } else if (mc.options.attackKey.isPressed()) {
            if (!this.hasMineDownSecondWork(var2)) {
               if (this.hasMineDownBaseWork(var1)) {
                  if (this.isMineDownTargetValid(var2)) {
                     this.scheduleMineDownSecond(var1, var2, this.getMineDownGoal(var1));
                  }
               } else if (!this.hasMineDownWork(var1, var2)) {
                  if (this.isMineDownBaseValid(var1) && this.isMineDownTargetValid(var2)) {
                     this.startMineDownPair(var1, Direction.UP, var2, SpeedMineGrimV3.MiningGoal.MANUAL);
                  }
               }
            }
         }
      }
   }

   private boolean startMineDownPair(BlockPos pos, Direction direction, BlockPos secondPos, SpeedMineGrimV3.MiningGoal goal) {
      this.reset();
      this.shouldMineDown = true;

      try {
         this.clickPos = pos;
         this.setBreakBlockDate(pos, direction, goal);
      } finally {
         this.shouldMineDown = false;
      }

      if (this.mineDownInterval.getValueInt() <= 0) {
         this.queueMineDownSecond(secondPos, Direction.UP, goal);
      } else {
         this.pendingMineDownBasePos = pos;
         this.pendingMineDownSecondPos = secondPos;
         this.pendingMineDownSecondDirection = Direction.UP;
         this.pendingMineDownGoal = goal;
         this.mineDownTimer.reset();
      }

      return true;
   }

   private boolean shouldStartMineDown(BlockPos pos) {
      return this.isMineDownActive() && pos.equals(mc.player.getBlockPos().down());
   }

   private boolean isMineDownActive() {
      return this.mineDown.getValue()
         && this.doubleBreak.getValue()
         && mc.player != null
         && mc.world != null
         && mc.options.sneakKey.isPressed()
         && mc.player.getPitch() >= 60.0F;
   }

   private BlockPos getMineDownSecondPos(BlockPos pos) {
      return pos.down(Math.max(1, this.mineDownDepth.getValueInt()));
   }

   private boolean isMineDownBaseValid(BlockPos pos) {
      if (pos != null && !(this.distanceTo(pos) > this.range.getValue())) {
         BlockState var2 = mc.world.getBlockState(pos);
         return this.canMine(var2) && !godBlocks.contains(var2.getBlock()) && !this.isMining(pos);
      } else {
         return false;
      }
   }

   private boolean isMineDownTargetValid(BlockPos pos) {
      if (pos != null && !(this.distanceTo(pos) > this.range.getValue())) {
         BlockState var2 = mc.world.getBlockState(pos);
         return this.canMine(var2) && !godBlocks.contains(var2.getBlock()) && !this.isMining(pos);
      } else {
         return false;
      }
   }

   private boolean hasMineDownWork(BlockPos basePos, BlockPos secondPos) {
      return this.isMining(basePos) || this.isMining(secondPos);
   }

   private boolean hasMineDownBaseWork(BlockPos basePos) {
      return this.matchesMine(this.breakBlockDate, basePos)
         || this.matchesMine(this.firstBreakBlockDate, basePos)
         || this.matchesMine(rebreakBlockDate, basePos)
         || this.matchesMine(this.stopRetryData, basePos);
   }

   private boolean hasMineDownSecondWork(BlockPos secondPos) {
      return this.matchesMine(this.breakBlockDate, secondPos)
         || this.matchesMine(this.firstBreakBlockDate, secondPos)
         || this.matchesMine(rebreakBlockDate, secondPos)
         || this.matchesMine(this.stopRetryData, secondPos);
   }

   private SpeedMineGrimV3.MiningGoal getMineDownGoal(BlockPos basePos) {
      if (this.matchesMine(this.breakBlockDate, basePos)) {
         return this.breakBlockDate.goal;
      } else if (this.matchesMine(this.firstBreakBlockDate, basePos)) {
         return this.firstBreakBlockDate.goal;
      } else if (this.matchesMine(rebreakBlockDate, basePos)) {
         return rebreakBlockDate.goal;
      } else {
         return this.matchesMine(this.stopRetryData, basePos) ? this.stopRetryData.getGoal() : SpeedMineGrimV3.MiningGoal.MANUAL;
      }
   }

   private boolean isPendingMineDownSecond(BlockPos basePos, BlockPos secondPos) {
      return this.pendingMineDownBasePos != null
         && this.pendingMineDownSecondPos != null
         && this.pendingMineDownBasePos.equals(basePos)
         && this.pendingMineDownSecondPos.equals(secondPos);
   }

   private void tryQueuePendingMineDownSecond() {
      if (this.mineDownTimer.passed((long)this.mineDownInterval.getValueInt() * 50L)) {
         if (this.pendingMineDownSecondPos != null) {
            BlockPos var1 = this.pendingMineDownSecondPos;
            Direction var2 = this.pendingMineDownSecondDirection == null ? Direction.UP : this.pendingMineDownSecondDirection;
            SpeedMineGrimV3.MiningGoal var3 = this.pendingMineDownGoal == null ? SpeedMineGrimV3.MiningGoal.MANUAL : this.pendingMineDownGoal;
            if (!this.matchesMine(this.breakBlockDate, var1)
               && !this.matchesMine(this.firstBreakBlockDate, var1)
               && !this.matchesMine(rebreakBlockDate, var1)
               && !this.isMineDownTargetValid(var1)) {
               this.clearMineDownPending();
            } else {
               if (!this.matchesMine(this.breakBlockDate, var1)
                  && !this.matchesMine(this.firstBreakBlockDate, var1)
                  && !this.matchesMine(rebreakBlockDate, var1)) {
                  this.queueMineDownSecond(var1, var2, var3);
               }

               this.clearMineDownPending();
            }
         }
      }
   }

   private void scheduleMineDownSecond(BlockPos basePos, BlockPos secondPos, SpeedMineGrimV3.MiningGoal goal) {
      if (this.mineDownInterval.getValueInt() <= 0) {
         this.queueMineDownSecond(secondPos, Direction.UP, goal);
      } else {
         this.pendingMineDownBasePos = basePos;
         this.pendingMineDownSecondPos = secondPos;
         this.pendingMineDownSecondDirection = Direction.UP;
         this.pendingMineDownGoal = goal;
         this.mineDownTimer.reset();
      }
   }

   private void queueMineDownSecond(BlockPos pos, Direction direction, SpeedMineGrimV3.MiningGoal goal) {
      this.shouldMineDown = true;

      try {
         this.clickPos = pos;
         this.setBreakBlockDate(pos, direction, goal);
      } finally {
         this.shouldMineDown = false;
      }
   }

   private void clearMineDownPending() {
      this.pendingMineDownBasePos = null;
      this.pendingMineDownSecondPos = null;
      this.pendingMineDownSecondDirection = null;
      this.pendingMineDownGoal = null;
   }

   private void clearInvalidMineDownWork(BlockPos basePos, BlockPos secondPos) {
      if (this.isInvalidMineDownMine(this.firstBreakBlockDate, basePos, secondPos)) {
         this.firstBreakBlockDate = null;
      }

      if (this.isInvalidMineDownMine(rebreakBlockDate, basePos, secondPos)) {
         rebreakBlockDate = null;
      }

      if (this.matchesMine(this.stopRetryData, basePos) || this.matchesMine(this.stopRetryData, secondPos)) {
         BlockState var3 = this.stopRetryData.getState();
         if (!this.canMine(var3) || godBlocks.contains(var3.getBlock())) {
            this.clearStopRetry();
         }
      }
   }

   private boolean isInvalidMineDownMine(SpeedMineGrimV3.BlockDate data, BlockPos basePos, BlockPos secondPos) {
      if (data != null && (data.pos.equals(basePos) || data.pos.equals(secondPos))) {
         BlockState var4 = mc.world.getBlockState(data.pos);
         return !this.canMine(var4) || godBlocks.contains(var4.getBlock());
      } else {
         return false;
      }
   }

   private boolean shouldRejectDuplicateDoubleBreakClick(BlockPos pos) {
      if (!this.doubleBreak.getValue()) {
         return false;
      } else {
         return !this.matchesMine(this.breakBlockDate, pos) && !this.matchesMine(this.firstBreakBlockDate, pos)
            ? !this.clickReset.getValue() && this.matchesMine(rebreakBlockDate, pos)
            : true;
      }
   }

   private boolean armStopRetry(SpeedMineGrimV3.MineData data) {
      if (this.retryAfterStop.getValue() && data != null && mc.world != null) {
         this.stopRetryData = data;
         this.stopRetryTicksLeft = this.retryTicks.getValueInt();
         return true;
      } else {
         return false;
      }
   }

   private void handleStopRetry() {
      if (this.stopRetryData != null) {
         if (this.retryAfterStop.getValue() && !this.isPause(this.pause.getValue())) {
            if (this.distanceTo(this.stopRetryData.getPos()) > this.range.getValue()) {
               this.clearStopRetry();
            } else if (this.breakBlockDate == null && this.firstBreakBlockDate == null) {
               if (this.stopRetryTicksLeft > 0) {
                  this.stopRetryTicksLeft--;
               } else {
                  SpeedMineGrimV3.MineData var1 = this.stopRetryData;
                  this.clearStopRetry();
                  if (this.canMine(var1.getState())) {
                     if (this.matchesMine(rebreakBlockDate, var1.getPos(), var1.getGoal())) {
                        rebreakBlockDate = null;
                     }

                     this.clickPos = var1.getPos();
                     this.setBreakBlockDate(var1.getPos(), var1.getDirection(), var1.getGoal());
                     this.manualOverride = false;
                  }
               }
            }
         } else {
            this.clearStopRetry();
         }
      }
   }

   private void clearStopRetry() {
      this.stopRetryData = null;
      this.stopRetryTicksLeft = 0;
   }

   private boolean isSilentSwap() {
      return this.swapMode.getValue() == SpeedMineGrimV3.Swap.Silent || this.swapMode.getValue() == SpeedMineGrimV3.Swap._1_21;
   }

   private boolean isHideSwap() {
      return this.swapMode.getValue() == SpeedMineGrimV3.Swap.Hide || this.swapMode.getValue() == SpeedMineGrimV3.Swap.Inventory;
   }

   private void prepareToolSwap(int slot) {
      if (slot != -1 && mc.player != null && this.swapMode.getValue() != SpeedMineGrimV3.Swap.Off) {
         int var2 = mc.player.getInventory().selectedSlot;
         if (slot != var2) {
            if (this.isHideSwap()) {
               this.hideSwapPending = true;
               InventoryUtil.inventorySwap(slot + 36, var2);
            } else {
               if (this.isSilentSwap() && this.lastSlot == -1) {
                  this.lastSlot = var2;
               }

               if (this.isSilentSwap() && this.silentSwitch.getValue()) {
                  this.sendPacket(new UpdateSelectedSlotC2SPacket(slot));
               } else {
                  InventoryUtil.switchToSlot(slot);
               }
            }
         }
      }
   }

   private void restoreSilentSwap() {
      if (this.silentSwitch.getValue()) {
         this.sendPacket(new UpdateSelectedSlotC2SPacket(this.lastSlot));
      } else {
         InventoryUtil.switchToSlot(this.lastSlot);
      }
   }

   private void restoreHideSwap() {
      if (mc.player != null) {
         InventoryUtil.inventorySwap(mc.player.getInventory().selectedSlot + 36, mc.player.getInventory().selectedSlot);
      }

      this.hideSwapPending = false;
   }

   public int getToolSlot(BlockState blockState) {
      double var2 = -1.0;
      int var4 = -1;

      for (int var5 = 0; var5 < 9; var5++) {
         double var6 = (double)mc.player.getInventory().getStack(var5).getMiningSpeedMultiplier(blockState);
         if (var6 > var2) {
            var2 = var6;
            var4 = var5;
         }
      }

      if (var4 < 0) {
         return mc.player.getInventory().selectedSlot;
      } else {
         Item var8 = mc.player.getInventory().getStack(var4).getItem();
         if (!(var8 instanceof PickaxeItem)
            && !(var8 instanceof AxeItem)
            && !(var8 instanceof ShovelItem)
            && !(var8 instanceof ShearsItem)
            && !(var8 instanceof MiningToolItem)) {
            var4 = -1;
         }

         return var4 == -1 ? mc.player.getInventory().selectedSlot : var4;
      }
   }

   public float getWaitTime(BlockPos pos) {
      int var2 = this.getToolSlot(mc.world.getBlockState(pos));
      double var3 = (double)this.getBlockBreakingSpeed(mc.world.getBlockState(pos), var2);
      if (var2 == -1) {
         return this.waitTime.getValueFloat();
      } else {
         if (this.lastMineSpeed == -1.0) {
            this.lastMineSpeed = var3;
         } else {
            if (Math.abs(this.lastMineSpeed - var3) > 1.5) {
               this.lastMineSpeed = var3;
               return this.waitTime.getValueFloat();
            }

            this.lastMineSpeed = var3;
         }

         return mc.world.getBlockState(pos).getBlock() == Blocks.NETHERRACK
            ? 0.0F
            : (float)Math.min(Math.max(0.18F, (1.0 - var3) * 0.3F), (double)this.waitTime.getValueFloat());
      }
   }

   public double getBreakDelta(int slot, BlockState state, BlockPos pos) {
      float var4 = state.getHardness(mc.world, pos);
      if (var4 == -1.0F) {
         return 0.0;
      } else {
         ItemStack var5 = mc.player.getInventory().getStack(slot);
         return (double)(this.getBlockBreakingSpeed(state, slot) / var4 / (float)(state.isToolRequired() && !var5.isSuitableFor(state) ? 100 : 30));
      }
   }

   private float getBlockBreakingSpeed(BlockState state, int slot) {
      if (slot == -1) {
         slot = mc.player.getInventory().selectedSlot;
      }

      ItemStack var3 = mc.player.getInventory().getStack(slot >= 36 ? slot - 36 : slot);
      float var4 = var3.getMiningSpeedMultiplier(state);
      if (var4 > 1.0F) {
         int var5 = DamageUtils.getEnchantmentLevel(var3, Enchantments.EFFICIENCY);
         if (var5 > 0 && !var3.isEmpty()) {
            var4 += (float)(var5 * var5 + 1);
         }
      }

      if (mc.player.hasStatusEffect(StatusEffects.HASTE)) {
         var4 *= 1.0F + (float)(mc.player.getStatusEffect(StatusEffects.HASTE).getAmplifier() + 1) * 0.2F;
      }

      if (mc.player.hasStatusEffect(StatusEffects.MINING_FATIGUE)) {
         float var6 = switch (mc.player.getStatusEffect(StatusEffects.MINING_FATIGUE).getAmplifier()) {
            case 0 -> 0.3F;
            case 1 -> 0.09F;
            case 2 -> 0.0027F;
            default -> 8.1E-4F;
         };
         var4 *= var6;
      }

      if (!mc.player.isOnGround() && !this.noGSpeed.getValue()) {
         var4 /= 5.0F;
      }

      return var4;
   }

   private Direction clickSide(BlockPos pos) {
      Direction var2 = BlockUtil.getClickSide(pos);
      return var2 == null ? Direction.UP : var2;
   }

   private void spoofGroundIfNeeded() {
      if (this.bypassGround.getValue()) {
         mc.getNetworkHandler()
            .sendPacket(new Full(mc.player.getX(), mc.player.getY() + 1.0E-9, mc.player.getZ(), mc.player.getYaw(), mc.player.getPitch(), true));
         mc.player.onLanding();
      }
   }

   private void sendBypassStarts(int times) {
      for (int var2 = 0; var2 < times; var2++) {
         sendSequencedPacket(
            seq -> new PlayerActionC2SPacket(Action.START_DESTROY_BLOCK, BlockPos.ofFloored(mc.player.getPos()).withY(11451), Direction.DOWN, seq)
         );
      }
   }

   private void sendPacket(Packet<?> packet) {
      if (mc.getNetworkHandler() != null) {
         mc.getNetworkHandler().sendPacket(packet);
      }
   }

   private double distanceTo(BlockPos pos) {
      return mc.player.getPos().distanceTo(pos.toCenterPos());
   }

   private double distanceTo(ItemEntity entity) {
      return mc.player.getPos().distanceTo(entity.getPos());
   }

   private boolean isPause(SpeedMineGrimV3.PauseMode mode) {
      return switch (mode) {
         case None -> false;
         case Use -> mc.player.isUsingItem();
         case MainHand -> mc.player.isUsingItem() && mc.player.getActiveHand() == Hand.MAIN_HAND;
      };
   }

   static {

      godBlocks = Arrays.asList(
         Blocks.COMMAND_BLOCK,
         Blocks.LAVA,
         Blocks.WATER,
         Blocks.BEDROCK,
         Blocks.BARRIER,
         Blocks.END_PORTAL,
         Blocks.NETHER_PORTAL,
         Blocks.END_PORTAL_FRAME,
         Blocks.AIR
      );
      hardBlocks = Arrays.asList(Blocks.OBSIDIAN, Blocks.CRYING_OBSIDIAN, Blocks.ENDER_CHEST);
   }
@Environment(EnvType.CLIENT)
   public class BlockDate {
      public BlockPos pos;
      public Direction direction;
      public final SpeedMineGrimV3.MiningGoal goal;
      public boolean done = false;
      public double progress;
      public double prevProgress;
      public BlockState blockState;
      public boolean isMining = false;
      public boolean stoped = false;
      public Timer finishTimer;
      public boolean subed = false;
      public int stopNums = 0;
      private final float waitTime;

      public BlockDate(BlockPos pos, Direction direction) {
         this(pos, direction, SpeedMineGrimV3.MiningGoal.MANUAL);
      }

      public BlockDate(BlockPos pos, Direction direction, SpeedMineGrimV3.MiningGoal goal) {
         this.pos = pos;
         this.direction = direction;
         this.goal = goal;
         this.progress = 0.0;
         this.blockState = Wrapper.mc.world.getBlockState(pos);
         this.waitTime = SpeedMineGrimV3.this.getWaitTime(pos);
      }

      public SpeedMineGrimV3.MineData toMineData() {
         return new SpeedMineGrimV3.MineData(this.pos, this.direction, this.goal);
      }

      public void freshProgress() {
         int var1 = SpeedMineGrimV3.this.getToolSlot(this.blockState);
         double var2 = !SpeedMineGrimV3.this.grimV3.getValue() && this.equals(SpeedMineGrimV3.this.firstBreakBlockDate)
            ? 1.0
            : (double)SpeedMineGrimV3.this.speed.getValueFloat();
         if (this.progress <= var2) {
            this.prevProgress = this.progress;
            this.progress = this.progress + SpeedMineGrimV3.this.getBreakDelta(var1, this.blockState, this.pos);
         } else {
            SpeedMineGrimV3.this.prepareToolSwap(var1);
            this.done = true;
            this.finishTimer = new Timer();
            this.progress = 1.0;
         }
      }
   }

   @Environment(EnvType.CLIENT)
   public static class MineData implements Comparable<SpeedMineGrimV3.MineData> {
      private final BlockPos pos;
      private final Direction direction;
      private final SpeedMineGrimV3.MiningGoal goal;

      public MineData(BlockPos pos, Direction direction) {
         this(pos, direction, SpeedMineGrimV3.MiningGoal.MINING_ENEMY);
      }

      public MineData(BlockPos pos, Direction direction, SpeedMineGrimV3.MiningGoal goal) {
         this.pos = pos;
         this.direction = direction;
         this.goal = goal;
      }

      public int compareTo(SpeedMineGrimV3.MineData other) {
         return Double.compare(this.getPriority(), other.getPriority());
      }

      private double getPriority() {
         return 10.0;
      }

      @Override
      public boolean equals(Object obj) {
         if (obj instanceof SpeedMineGrimV3.MineData var2 && var2.getPos().equals(this.pos)) {
            return true;
         }

         return false;
      }

      @Override
      public int hashCode() {
         return this.pos.hashCode();
      }

      public BlockPos getPos() {
         return this.pos;
      }

      public Direction getDirection() {
         return this.direction;
      }

      public SpeedMineGrimV3.MiningGoal getGoal() {
         return this.goal;
      }

      public BlockState getState() {
         return Wrapper.mc.world.getBlockState(this.pos);
      }
   }

   @Environment(EnvType.CLIENT)
   public static enum MiningGoal {
      MANUAL,
      MINING_ENEMY,
      PREVENT_CRAWL;

}

   @Environment(EnvType.CLIENT)
   public static enum MioColorMode {
      Progress,
      Custom;

}

   @Environment(EnvType.CLIENT)
   public static enum MioRenderMode {
      Plain,
      In,
      Out;

}

   @Environment(EnvType.CLIENT)
   public static enum Page {
      General,
      Misc,
      Render;

}

   @Environment(EnvType.CLIENT)
   public static enum PauseMode {
      None,
      Use,
      MainHand;

}

   @Environment(EnvType.CLIENT)
   public static enum Swap {
      Normal,
      Silent,
      Hide,
      Inventory,
      _1_21,
      Off;

      @Override
      public String toString() {
         return this == _1_21 ? "1.21+" : this.name();
      }

}
}
