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

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.world.BlockUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ShulkerBoxBlock;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.ShulkerBoxScreenHandler;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class AntiRegear extends Module {
   public static AntiRegear INSTANCE;
   public static boolean hideShulkerUI;
   private final SliderSetting range = this.add(new SliderSetting("Range", 4.0, 1.0, 8.0, 0.1));
   public final BooleanSetting rotate = this.add(new BooleanSetting("Rotate", true));
   public final BooleanSetting drop = this.add(new BooleanSetting("Drop", true));
   public final BooleanSetting IgnoreSelf = this.add(new BooleanSetting("IgnoreSelf", true));
   public final BooleanSetting NoUi = this.add(new BooleanSetting("NoUi", false));
   public final Set<BlockPos> safe = ConcurrentHashMap.newKeySet();
   private final Map<BlockPos, Long> safeAddTime = new ConcurrentHashMap<>();
   private static final long SAFE_GRACE_PERIOD_MS;
   private static final long OPEN_RESPONSE_TIMEOUT_MS;
   private static final long FAILED_OPEN_RETRY_DELAY_MS;
   private static final int MAX_FAILED_OPEN_ATTEMPTS;
   private static final long PLACEMENT_MODE_DURATION_MS;
   private static final long RECENT_SAFE_THRESHOLD_MS;
   private final Set<BlockPos> openedBoxes = ConcurrentHashMap.newKeySet();
   private final Set<BlockPos> autoOpenAttemptedBoxes = ConcurrentHashMap.newKeySet();
   private final Set<BlockPos> prePlacementSnapshot = ConcurrentHashMap.newKeySet();
   private final Set<BlockPos> inventoryDetectedPositions = ConcurrentHashMap.newKeySet();
   private final Timer openTimer = new Timer();
   private final Timer failedOpenRetryTimer = new Timer();
   private BlockPos targetBox;
   private BlockPos failedOpenPos;
   private BlockPos manualBoxPos;
   private BlockPos screenOpenCrosshairPos;
   private int failedOpenAttempts;
   private int previousShulkerCount = -1;
   private boolean processedThisBox;
   private boolean droppedThisBox;
   private boolean inPlacementMode;
   private long placementModeEndTime;

   public AntiRegear() {
      super("AntiRegear", Module.Category.Combat);
      this.setChinese("\u53cd\u8865\u7ed9");
      INSTANCE = this;
   }

   public void addSafe(BlockPos pos) {
      if (pos != null) {
         this.safe.add(pos);
         this.safeAddTime.put(pos, System.currentTimeMillis());
      }
   }

   public void addSafePos(BlockPos pos) {
      this.addSafe(pos);
   }

   public boolean isHandlingOpenedContainer() {
      if (this.isOn() && this.drop.getValue() && mc.player != null) {
         ScreenHandler var1 = mc.player.currentScreenHandler;
         return this.isTargetShulkerHandler(var1) && (this.targetBox != null || hideShulkerUI || var1 instanceof ShulkerBoxScreenHandler);
      } else {
         return false;
      }
   }

   @Override
   public void onEnable() {
      this.resetState();
   }

   @Override
   public void onDisable() {
      this.resetState();
   }

   @EventListener
   public void onUpdate(UpdateEvent event) {
      if (mc.player != null && mc.world != null) {
         this.checkInventoryAndHandlePlacement();
         this.cleanupSafePositions();
         if (this.isTargetShulkerHandler(mc.player.currentScreenHandler)) {
            this.captureScreenOpenCrosshair();
            this.resolveManualBoxPos();
            if (this.IgnoreSelf.getValue() && this.targetBox == null) {
               if (this.manualBoxPos == null || !this.safe.contains(this.manualBoxPos)) {
                  this.tryProtectNewShulkers();
               }

               if (this.manualBoxPos == null || this.safe.contains(this.manualBoxPos)) {
                  hideShulkerUI = false;
                  return;
               }
            }

            this.handleShulkerScreen();
         } else {
            hideShulkerUI = false;
            this.droppedThisBox = false;
            this.manualBoxPos = null;
            this.screenOpenCrosshairPos = null;
            if (this.processedThisBox) {
               this.processedThisBox = false;
            }

            if (this.targetBox != null && !this.openTimer.passedMs(250.0)) {
               hideShulkerUI = this.NoUi.getValue() && !this.safe.contains(this.targetBox);
            } else {
               if (this.failedOpenPos != null && !(mc.world.getBlockState(this.failedOpenPos).getBlock() instanceof ShulkerBoxBlock)) {
                  this.failedOpenPos = null;
                  this.failedOpenAttempts = 0;
               }

               if (this.targetBox != null) {
                  if (this.targetBox.equals(this.failedOpenPos)) {
                     this.failedOpenAttempts++;
                  } else {
                     this.failedOpenPos = this.targetBox;
                     this.failedOpenAttempts = 1;
                  }

                  this.failedOpenRetryTimer.reset();
                  if (this.failedOpenAttempts >= 3) {
                     this.openedBoxes.add(this.targetBox);
                     this.failedOpenPos = null;
                     this.failedOpenAttempts = 0;
                  }
               }

               this.targetBox = null;
               this.scanAndOpenShulker();
            }
         }
      }
   }

   private void resetState() {
      this.targetBox = null;
      this.processedThisBox = false;
      this.droppedThisBox = false;
      hideShulkerUI = false;
      this.openedBoxes.clear();
      this.autoOpenAttemptedBoxes.clear();
      this.failedOpenPos = null;
      this.failedOpenAttempts = 0;
      this.safeAddTime.clear();
      this.previousShulkerCount = -1;
      this.inPlacementMode = false;
      this.placementModeEndTime = 0L;
      this.prePlacementSnapshot.clear();
      this.inventoryDetectedPositions.clear();
      this.screenOpenCrosshairPos = null;
      this.manualBoxPos = null;
   }

   private void cleanupSafePositions() {
      long var1 = System.currentTimeMillis();
      if (!this.IgnoreSelf.getValue()) {
         this.safe.clear();
         this.safeAddTime.clear();
      } else {
         Iterator var3 = this.safe.iterator();

         while (var3.hasNext()) {
            BlockPos var4 = (BlockPos)var3.next();
            BlockState var5 = mc.world.getBlockState(var4);
            boolean var6 = var5.getBlock() instanceof ShulkerBoxBlock || var5.getBlock() == Blocks.ENDER_CHEST;
            Long var7 = this.safeAddTime.get(var4);
            boolean var8 = var7 != null && var1 - var7 < 2000L;
            if (!var6 && !var8) {
               var3.remove();
               this.safeAddTime.remove(var4);
            }
         }
      }

      this.openedBoxes.removeIf(pos -> !(mc.world.getBlockState(pos).getBlock() instanceof ShulkerBoxBlock));
      this.autoOpenAttemptedBoxes.removeIf(pos -> !(mc.world.getBlockState(pos).getBlock() instanceof ShulkerBoxBlock));
      Iterator var9 = this.inventoryDetectedPositions.iterator();

      while (var9.hasNext()) {
         BlockPos var10 = (BlockPos)var9.next();
         if (!(mc.world.getBlockState(var10).getBlock() instanceof ShulkerBoxBlock)) {
            var9.remove();
            this.safe.remove(var10);
            this.safeAddTime.remove(var10);
         }
      }
   }

   private void resolveManualBoxPos() {
      if (this.manualBoxPos == null) {
         if (this.targetBox != null) {
            this.manualBoxPos = this.targetBox;
         } else if (this.screenOpenCrosshairPos != null) {
            this.manualBoxPos = this.screenOpenCrosshairPos;
         } else {
            double var1 = Double.MAX_VALUE;

            for (BlockPos var4 : BlockUtil.getSphere(this.range.getValueFloat())) {
               if (mc.world.getBlockState(var4).getBlock() instanceof ShulkerBoxBlock) {
                  double var5 = mc.player.squaredDistanceTo((double)var4.getX() + 0.5, (double)var4.getY() + 0.5, (double)var4.getZ() + 0.5);
                  if (var5 < var1) {
                     var1 = var5;
                     this.manualBoxPos = var4;
                  }
               }
            }

            if (var1 > this.range.getValue() * this.range.getValue()) {
               this.manualBoxPos = null;
            }
         }
      }
   }

   private void handleShulkerScreen() {
      if (this.drop.getValue()) {
         ScreenHandler var1 = mc.player.currentScreenHandler;
         if (this.isTargetShulkerHandler(var1)) {
            if (this.IgnoreSelf.getValue() && this.targetBox == null) {
               this.resolveManualBoxPos();
               if (this.manualBoxPos == null || this.safe.contains(this.manualBoxPos)) {
                  hideShulkerUI = false;
                  return;
               }
            }

            hideShulkerUI = this.NoUi.getValue() && (this.manualBoxPos == null || !this.safe.contains(this.manualBoxPos));
            if (!this.droppedThisBox) {
               int var2 = Math.min(27, var1.slots.size());

               for (int var3 = 0; var3 < var2; var3++) {
                  if (!var1.getSlot(var3).getStack().isEmpty()) {
                     mc.interactionManager.clickSlot(var1.syncId, var3, 1, SlotActionType.THROW, mc.player);
                  }
               }

               this.droppedThisBox = true;
               this.processedThisBox = true;
            } else if (this.openTimer.passedMs(100.0)) {
               mc.player.closeHandledScreen();
            }
         }
      }
   }

   private boolean isTargetShulkerHandler(ScreenHandler handler) {
      if (handler instanceof ShulkerBoxScreenHandler) {
         return true;
      } else {
         if (this.targetBox != null && handler instanceof GenericContainerScreenHandler var2 && var2.getRows() == 3) {
            return true;
         }

         return false;
      }
   }

   private void scanAndOpenShulker() {
      if (this.targetBox == null) {
         double var1 = this.range.getValue() + 3.0;

         for (BlockPos var4 : BlockUtil.getSphere((float)var1)) {
            BlockState var5 = mc.world.getBlockState(var4);
            if (var5.getBlock() instanceof ShulkerBoxBlock
               && this.canOpenShulker(var4, var5)
               && !this.openedBoxes.contains(var4)
               && (!var4.equals(this.failedOpenPos) || this.failedOpenRetryTimer.passedMs(700.0))
               && (!this.IgnoreSelf.getValue() || !this.safe.contains(var4))) {
               double var6 = mc.player.squaredDistanceTo((double)var4.getX() + 0.5, (double)var4.getY() + 0.5, (double)var4.getZ() + 0.5);
               if (!(Math.sqrt(var6) > this.range.getValue())) {
                  this.targetBox = var4;
                  this.openedBoxes.add(var4);
                  this.autoOpenAttemptedBoxes.add(var4);
                  this.openShulker(var4);
                  return;
               }
            }
         }
      }
   }

   private boolean canOpenShulker(BlockPos pos, BlockState state) {
      if (!state.contains(ShulkerBoxBlock.FACING)) {
         return mc.world.isAir(pos.up());
      } else {
         Direction var3 = state.get(ShulkerBoxBlock.FACING);
         BlockPos var4 = pos.offset(var3);
         return mc.world.getBlockState(var4).getCollisionShape(mc.world, var4).isEmpty();
      }
   }

   private void checkInventoryAndHandlePlacement() {
      int var1 = this.countShulkersInInventory();
      if (this.previousShulkerCount == -1) {
         this.previousShulkerCount = var1;
      } else {
         if (var1 == this.previousShulkerCount - 1) {
            this.capturePlacedPositionFromCrosshair();
            this.startPlacementMode();
         }

         this.previousShulkerCount = var1;
         if (this.inPlacementMode) {
            this.handlePlacementModeScan();
         }
      }
   }

   private void startPlacementMode() {
      this.inPlacementMode = true;
      this.placementModeEndTime = System.currentTimeMillis() + 3000L;
      this.prePlacementSnapshot.clear();
      double var1 = this.range.getValue() + 3.0;

      for (BlockPos var4 : BlockUtil.getSphere((float)var1)) {
         if (mc.world.getBlockState(var4).getBlock() instanceof ShulkerBoxBlock) {
            this.prePlacementSnapshot.add(var4);
         }
      }
   }

   private void handlePlacementModeScan() {
      if (System.currentTimeMillis() >= this.placementModeEndTime) {
         this.inPlacementMode = false;
         this.prePlacementSnapshot.clear();
      } else {
         double var1 = this.range.getValue() + 3.0;

         for (BlockPos var4 : BlockUtil.getSphere((float)var1)) {
            if (mc.world.getBlockState(var4).getBlock() instanceof ShulkerBoxBlock && !this.prePlacementSnapshot.contains(var4) && !this.safe.contains(var4)) {
               this.addSafe(var4);
               this.inventoryDetectedPositions.add(var4);
            }
         }
      }
   }

   private int countShulkersInInventory() {
      int var1 = 0;

      for (int var2 = 0; var2 < 36; var2++) {
         ItemStack var3 = mc.player.getInventory().getStack(var2);
         if (!var3.isEmpty()) {
            Item var4 = var3.getItem();
            if (var4 instanceof BlockItem) {
               BlockItem var5 = (BlockItem)var4;
               if (var5.getBlock() instanceof ShulkerBoxBlock) {
                  var1 += var3.getCount();
               }
            }
         }
      }

      return var1;
   }

   private void capturePlacedPositionFromCrosshair() {
      if (mc.crosshairTarget instanceof BlockHitResult var2 && var2.getType() == Type.BLOCK) {
         BlockPos var4 = var2.getBlockPos().offset(var2.getSide());
         this.addSafe(var4);
         this.inventoryDetectedPositions.add(var4);
         BlockPos var3 = var2.getBlockPos();
         if (!var3.equals(var4) && mc.world.getBlockState(var3).getBlock() instanceof ShulkerBoxBlock) {
            this.addSafe(var3);
            this.inventoryDetectedPositions.add(var3);
         }

         return;
      }
   }

   private void captureScreenOpenCrosshair() {
      if (this.screenOpenCrosshairPos == null) {
         if (mc.crosshairTarget instanceof BlockHitResult var2 && var2.getType() == Type.BLOCK) {
            BlockPos var4 = var2.getBlockPos();
            if (mc.world.getBlockState(var4).getBlock() instanceof ShulkerBoxBlock) {
               this.screenOpenCrosshairPos = var4;
               return;
            }

            BlockPos var3 = var4.offset(var2.getSide());
            if (mc.world.getBlockState(var3).getBlock() instanceof ShulkerBoxBlock) {
               this.screenOpenCrosshairPos = var3;
            }

            return;
         }
      }
   }

   private void tryProtectNewShulkers() {
      long var1 = System.currentTimeMillis();

      for (Entry var4 : this.safeAddTime.entrySet()) {
         BlockPos var5 = (BlockPos)var4.getKey();
         long var6 = (Long)var4.getValue();
         if (var1 - var6 < 5000L && mc.world.getBlockState(var5).getBlock() instanceof ShulkerBoxBlock) {
            double var8 = Math.sqrt(mc.player.squaredDistanceTo((double)var5.getX() + 0.5, (double)var5.getY() + 0.5, (double)var5.getZ() + 0.5));
            if (var8 <= this.range.getValue()) {
               if (this.manualBoxPos == null || !this.manualBoxPos.equals(var5)) {
                  this.manualBoxPos = var5;
               }

               return;
            }
         }
      }
   }

   private void openShulker(BlockPos pos) {
      Direction var2 = BlockUtil.getClickSide(pos);
      Vec3d var3 = pos.toCenterPos().add(Vec3d.of(var2.getVector()).multiply(0.5));
      if (this.rotate.getValue()) {
         CrucifixBeta.ROTATION.lookAt(var3);
      }

      this.openTimer.reset();
      hideShulkerUI = this.NoUi.getValue() && !this.safe.contains(pos);
      BlockHitResult var4 = new BlockHitResult(var3, var2, pos, false);
      sendSequencedPacket(seq -> new PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, var4, seq));
   }

   static {

      RECENT_SAFE_THRESHOLD_MS = 5000L;
      PLACEMENT_MODE_DURATION_MS = 3000L;
      MAX_FAILED_OPEN_ATTEMPTS = 3;
      FAILED_OPEN_RETRY_DELAY_MS = 700L;
      OPEN_RESPONSE_TIMEOUT_MS = 250L;
      SAFE_GRACE_PERIOD_MS = 2000L;
      hideShulkerUI = false;
   }
}
