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

import com.mojang.authlib.GameProfile;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.ClientTickEvent;
import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.combat.CombatUtil;
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.world.BlockPosX;
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.EnumSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.TrapdoorBlock;
import net.minecraft.entity.MovementType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;

@Environment(EnvType.CLIENT)
public class AntiBurrow extends Module {
   public static AntiBurrow INSTANCE;
   private final EnumSetting<AntiBurrow.Mode> mode = this.add(new EnumSetting<>("Mode", AntiBurrow.Mode.LADDER));
   private final SliderSetting range = this.add(new SliderSetting("Range", 6.0, 0.0, 10.0, 0.1));
   private final SliderSetting delay = this.add(new SliderSetting("Delay", 1.0, 0.0, 20.0, 1.0));
   private final BooleanSetting rotate = this.add(new BooleanSetting("Rotate", true));
   private final BooleanSetting up = this.add(new BooleanSetting("Up", true));
   private final BooleanSetting bottom = this.add(new BooleanSetting("Bottom", true));
   private final BooleanSetting usingPause = this.add(new BooleanSetting("UsingPause", true));
   private final BooleanSetting fast = this.add(new BooleanSetting("Fast", false));
   private final BooleanSetting inventorySwap = this.add(new BooleanSetting("InventorySwap", true));
   private final SliderSetting predictTicks = this.add(new SliderSetting("PredictTicks", 4.0, 0.0, 20.0, 1.0));
   private final BooleanSetting openTrapdoor = this.add(new BooleanSetting("OpenTrapdoor", true, () -> this.mode.is(AntiBurrow.Mode.TRAPDOOR)));
   private final BooleanSetting spamTrapdoor = this.add(
      new BooleanSetting("SpamTrapdoor", false, () -> this.mode.is(AntiBurrow.Mode.TRAPDOOR)).setParent()
   );
   private final SliderSetting spamTrapdoorDelay = this.add(
      new SliderSetting("SpamTrapdoorDelay", 50.0, 0.0, 1000.0, 10.0, () -> this.spamTrapdoor.isOpen() && this.mode.is(AntiBurrow.Mode.TRAPDOOR))
   );
   private final Timer delayTimer = new Timer();
   private final Timer spamTrapdoorTimer = new Timer();
   private AntiBurrow.PlayerAndPredict target;
   private int selectedSlot = -1;
   private int lastSlot = -1;

   public AntiBurrow() {
      super("AntiBurrow", Module.Category.Combat);
      this.setChinese("AntiBurrow");
      INSTANCE = this;
   }

   @Override
   public void onEnable() {
      this.target = null;
      this.lastSlot = -1;
      this.selectedSlot = -1;
   }

   @EventListener
   public void onTick(ClientTickEvent event) {
      if (!this.usingPause.getValue() || !this.shouldPauseForUsingItem()) {
         Block var2 = this.getBlock();
         int var3 = InventoryUtil.findBlockInventorySlot(var2);
         if (var3 != -1) {
            PlayerEntity var4 = CombatUtil.getClosestEnemy(this.range.getValue());
            if (var4 != null) {
               this.target = new AntiBurrow.PlayerAndPredict(var4);
               List<BlockPos> var5 = Arrays.asList(this.getBurrowPos(this.target.predict));
               if (this.mode.is(AntiBurrow.Mode.TRAPDOOR)) {
                  List<BlockPos> var6 = new ArrayList<>(BlockUtil.getSphere((float)this.range.getValueInt()))
                     .stream()
                     .filter(
                        blockPos -> BlockUtil.getBlock(blockPos) instanceof TrapdoorBlock
                              && var5.stream().anyMatch(burrowPos -> burrowPos.equals(blockPos) || burrowPos.up().equals(blockPos))
                     )
                     .toList();
                  if (this.openTrapdoor.getValue() && this.spamTrapdoorTimer.passed((long)this.spamTrapdoorDelay.getValue())) {
                     var6.stream()
                        .filter(blockPos -> !Wrapper.mc.world.getBlockState(blockPos).get(TrapdoorBlock.OPEN))
                        .min(Comparator.comparingDouble(pos -> pos.getSquaredDistance(Wrapper.mc.player.getPos())))
                        .ifPresent(this::clickTrapdoor);
                  }

                  if (this.spamTrapdoor.getValue() && this.spamTrapdoorTimer.passed((long)this.spamTrapdoorDelay.getValue())) {
                     this.spamTrapdoorTimer.reset();
                     var6.stream()
                        .filter(blockPos -> Wrapper.mc.world.getBlockState(blockPos).get(TrapdoorBlock.OPEN))
                        .min(Comparator.comparingDouble(pos -> pos.getSquaredDistance(Wrapper.mc.player.getPos())))
                        .ifPresent(this::clickTrapdoor);
                  }
               }

               if (this.delayTimer.passed((long)this.delay.getValue())) {
                  if (this.bottom.getValue()) {
                     for (BlockPos var7 : var5) {
                        if (this.placeCheck(var7)) {
                           if (this.swapBlock(var2)) {
                              return;
                           }

                           this.place(var7, this.rotate.getValue());
                           this.swapBack();
                           this.delayTimer.reset();
                           return;
                        }
                     }
                  }

                  if (this.up.getValue()) {
                     for (BlockPos var10 : var5) {
                        if (this.placeCheck(var10.up())) {
                           if (this.swapBlock(var2)) {
                              return;
                           }

                           this.place(var10.up(), this.rotate.getValue());
                           this.swapBack();
                           this.delayTimer.reset();
                           return;
                        }
                     }
                  }
               }
            }
         }
      }
   }

   private void clickTrapdoor(BlockPos pos) {
      Direction var2 = BlockUtil.getClickSideStrict(pos);
      if (var2 != null) {
         Vec3d var3 = new Vec3d(
            (double)pos.getX() + 0.5 + (double)var2.getVector().getX() * 0.5,
            (double)pos.getY() + 0.5 + (double)var2.getVector().getY() * 0.5,
            (double)pos.getZ() + 0.5 + (double)var2.getVector().getZ() * 0.5
         );
         if (this.rotate.getValue()) {
            CrucifixBeta.ROTATION.lookAt(var3);
         }

         BlockHitResult var4 = new BlockHitResult(var3, var2, pos, false);
         sendSequencedPacket(id -> new PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, var4, id));
      }
   }

   private boolean placeCheck(BlockPos pos) {
      return BlockUtil.canReplace(pos) && this.canPlace(pos);
   }

   private boolean canPlace(BlockPos pos) {
      if (pos == null) {
         return false;
      } else {
         return this.mode.getValue() == AntiBurrow.Mode.LADDER
            ? this.canPlace2(pos, 0.1)
            : (
               this.mode.getValue() == AntiBurrow.Mode.TRAPDOOR
                  ? this.canPlace2(pos, 0.4) || this.getTrapdoorDirection(pos) != null
                  : (this.fast.getValue() ? BlockUtil.getPlaceSide(pos) != null : BlockUtil.getPlaceSide(pos) != null)
            );
      }
   }

   private boolean canPlace2(BlockPos pos, double thickness) {
      return pos != null && BlockUtil.getPlaceSide(pos) != null && this.getDirection(pos, thickness) != null;
   }

   private Direction getTrapdoorDirection(BlockPos pos) {
      Direction var2 = this.getDirection(pos, 0.4);
      if (var2 != null) {
         return var2;
      } else if (this.target != null && this.target.player != null) {
         if (pos.getY() != this.target.player.getBlockY() + 1) {
            return null;
         } else {
            Direction[] var3 = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP};

            for (Direction var7 : var3) {
               if (BlockUtil.getBlock(pos.offset(var7)) != Blocks.AIR && BlockUtil.canClick(pos.offset(var7)) && this.isCollied(pos, var7, 0.4)) {
                  return var7;
               }
            }

            return null;
         }
      } else {
         return null;
      }
   }

   private void place(BlockPos pos, boolean rotate) {
      switch ((AntiBurrow.Mode)this.mode.getValue()) {
         case LADDER:
            this.placeLadder(pos, rotate);
            break;
         case COBWEB:
         case BUTTON:
         default:
            this.placeBlock(pos, rotate);
            break;
         case TRAPDOOR:
            this.placeTrapdoor(pos, rotate);
      }
   }

   private void placeTrapdoor(BlockPos pos, boolean rotate) {
      Direction var3 = this.getTrapdoorDirection(pos);
      if (var3 != null) {
         BlockPos var4 = pos.offset(var3);
         Direction var5 = var3.getOpposite();
         double var6 = (double)var4.getX() + 0.5 + (double)var5.getVector().getX() * 0.5;
         double var8 = (double)var4.getY() + 0.5 + (double)var5.getVector().getY() * 0.5;
         double var10 = (double)var4.getZ() + 0.5 + (double)var5.getVector().getZ() * 0.5;
         Vec3d var12 = new Vec3d(var6, var8, var10);
         if (rotate) {
            CrucifixBeta.ROTATION.lookAt(var12);
         }

         EntityUtil.swingHand(Hand.MAIN_HAND, SwingSide.All);
         BlockHitResult var13 = new BlockHitResult(var12, var5, var4, false);
         sendSequencedPacket(id -> new PlayerInteractBlockC2SPacket(Hand.MAIN_HAND, var13, id));
         if (!BlockUtil.placedPos.contains(var4)) {
            BlockUtil.placedPos.add(var4);
         }
      }
   }

   private void placeLadder(BlockPos pos, boolean rotate) {
      Direction var3 = this.getDirection(pos, 0.1);
      if (var3 != null) {
         BlockUtil.clickBlock(pos.offset(var3), var3.getOpposite(), rotate);
         if (!BlockUtil.placedPos.contains(pos)) {
            BlockUtil.placedPos.add(pos);
         }
      }
   }

   private void placeBlock(BlockPos pos, boolean rotate) {
      if (!this.fast.getValue()) {
         BlockUtil.placeBlock(pos, rotate, true);
      } else {
         Direction var3 = BlockUtil.getPlaceSide(pos);
         if (var3 != null) {
            BlockUtil.clickBlock(pos.offset(var3), var3.getOpposite(), rotate, Hand.MAIN_HAND, true);
            if (!BlockUtil.placedPos.contains(pos)) {
               BlockUtil.placedPos.add(pos);
            }
         }
      }
   }

   private boolean swapBlock(Block block) {
      this.selectedSlot = Wrapper.mc.player.getInventory().selectedSlot;
      if (this.inventorySwap.getValue()) {
         this.lastSlot = InventoryUtil.findBlockInventorySlot(block);
         if (this.lastSlot == -1) {
            return true;
         } else {
            InventoryUtil.inventorySwap(this.selectedSlot, this.lastSlot);
            return false;
         }
      } else {
         this.lastSlot = InventoryUtil.findBlock(block);
         if (this.lastSlot == -1) {
            return true;
         } else {
            InventoryUtil.switchToSlot(this.lastSlot);
            return false;
         }
      }
   }

   private void swapBack() {
      if (this.lastSlot != -1) {
         if (this.inventorySwap.getValue()) {
            InventoryUtil.inventorySwap(this.selectedSlot, this.lastSlot);
         } else {
            InventoryUtil.switchToSlot(this.selectedSlot);
         }
      }
   }

   private Block getBlock() {
      switch ((AntiBurrow.Mode)this.mode.getValue()) {
         case LADDER:
            return Blocks.LADDER;
         case COBWEB:
            return Blocks.COBWEB;
         case BUTTON:
            return Blocks.STONE_BUTTON;
         case TRAPDOOR:
            return Blocks.OAK_TRAPDOOR;
         default:
            return Blocks.LADDER;
      }
   }

   private Direction getDirection(BlockPos pos, double thickness) {
      Direction var4 = null;

      for (Direction var8 : Direction.values()) {
         if (!Wrapper.mc.world.getBlockState(pos.offset(var8)).isAir() && !this.isCollied(pos, var8, thickness)) {
            var4 = var8;
            break;
         }
      }

      return var4;
   }

   private boolean isCollied(BlockPos pos, Direction dir, double thickness) {
      Vec3d var5 = pos.toCenterPos().add(Vec3d.of(dir.getVector()).multiply(0.5));
      Vec3d var6 = var5.add(Vec3d.of(dir.rotateYCounterclockwise().getVector()).multiply(0.5)).add(Vec3d.of(Direction.DOWN.getVector()).multiply(0.5));
      Vec3d var7 = var5.add(Vec3d.of(dir.rotateYClockwise().getVector()).multiply(0.5))
         .add(Vec3d.of(Direction.UP.getVector()).multiply(0.5))
         .add(Vec3d.of(dir.getOpposite().getVector()).multiply(thickness));
      return !BlockUtil.getEntities(new Box(var6, var7)).isEmpty();
   }

   public boolean isLadderBlock(BlockPos pos) {
      BlockPos var2 = null;

      for (Direction var6 : Direction.values()) {
         if (BlockUtil.getBlock(pos.offset(var6)) == Blocks.LADDER) {
            var2 = pos.offset(var6);
         }
      }

      if (var2 == null) {
         return false;
      } else {
         try {
            return var2.offset(Wrapper.mc.world.getBlockState(var2).get(Properties.FACING).getOpposite()).equals(pos);
         } catch (IllegalArgumentException var7) {
            return false;
         }
      }
   }

   private BlockPos[] getBurrowPos(PlayerEntity player) {
      BlockPosX var2 = new BlockPosX(player.getPos());
      return new BlockPos[]{var2, var2.up()};
   }
@Environment(EnvType.CLIENT)
   private static enum Mode {
      LADDER,
      COBWEB,
      BUTTON,
      TRAPDOOR;

}

   @Environment(EnvType.CLIENT)
   private class PlayerAndPredict {
      final PlayerEntity player;
      final PlayerEntity predict;

      private PlayerAndPredict(PlayerEntity player) {
         this.player = player;
         if (AntiBurrow.this.predictTicks.getValueFloat() > 0.0F) {
            this.predict = new PlayerEntity(
               Wrapper.mc.world,
               player.getBlockPos(),
               player.getYaw(),
               new GameProfile(UUID.fromString("66123666-1234-5432-6666-667563866600"), "PredictEntity339")
            ) {
               @Override
               public boolean isPlayer() {
                  return true;
               }

               @Override
               public boolean isCreative() {
                  return false;
               }

               @Override
               public boolean isSpectator() {
                  return false;
               }
            };
            this.predict.setVelocity(Vec3d.ZERO);

            for (int var3 = 0; var3 < AntiBurrow.this.predictTicks.getValueInt(); var3++) {
               float var4 = Wrapper.mc.player.input.movementForward;
               float var5 = Wrapper.mc.player.input.movementSideways;
               Vec3d var6 = new Vec3d((double)var5, 0.0, (double)var4).normalize().rotateY((float)Math.toRadians((double)this.predict.getYaw()));
               this.predict.updateVelocity(0.0F, var6);
               this.predict.move(MovementType.PLAYER, this.predict.getVelocity());
               if (this.predict.getY() < -10.0) {
                  this.predict.setPosition(player.getX(), player.getY(), player.getZ());
                  this.predict.setVelocity(Vec3d.ZERO);
               }
            }
         } else {
            this.predict = player;
         }
      }
}
}
