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

import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.TickEvent;
import dev.crucifixbeta.api.utils.combat.CombatUtil;
import dev.crucifixbeta.api.utils.player.InventoryUtil;
import dev.crucifixbeta.api.utils.render.Render3DUtil;
import dev.crucifixbeta.mod.features.SpearPort;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.settings.impl.BindSetting;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.RaycastContext.FluidHandling;
import net.minecraft.world.RaycastContext.ShapeType;

@Environment(EnvType.CLIENT)
public class AutoSpear extends Module {
   public static AutoSpear INSTANCE;
   private static final double[] SEARCH_ORDER;
   private static final double PATH_SAMPLE_STEP;
   public final BindSetting actionKey = this.add(new BindSetting("ActionKey", -2));
   public final SliderSetting spearDistance = this.add(
      new SliderSetting("Distance", 50.0, 1.0, 100.0, 1.0).setSuffix("m")
   );
   public final SliderSetting spearMaxTp = this.add(
      new SliderSetting("MaxTp", 100.0, 1.0, 200.0, 1.0).setSuffix("m")
   );
   public final SliderSetting attackRange = this.add(new SliderSetting("AttackRange", 3.0, 1.0, 8.0, 0.1).setSuffix("m"));
   public final SliderSetting minRange = this.add(new SliderSetting("MinRange", 3.0, 0.0, 8.0, 0.1).setSuffix("m"));
   public final SliderSetting delay = this.add(new SliderSetting("Delay", 2, 0, 10).setSuffix("t"));
   public final SliderSetting predictTicks = this.add(new SliderSetting("PredictTicks", 1, 0, 5).setSuffix("t"));
   public final BooleanSetting autoSwitch = this.add(new BooleanSetting("AutoSwitch", true));
   public final BooleanSetting autoCharge = this.add(new BooleanSetting("AutoCharge", true));
   public final BooleanSetting autoAttack = this.add(new BooleanSetting("AutoAttack", true));
   public final BooleanSetting autoRestart = this.add(new BooleanSetting("AutoRestart", true));
   public final BooleanSetting playersOnly = this.add(new BooleanSetting("PlayersOnly", true));
   public final BooleanSetting renderTarget = this.add(new BooleanSetting("RenderTarget", false));
   public final BooleanSetting swing = this.add(new BooleanSetting("Swing", true));
   public final BooleanSetting elytraFireworkCompat = this.add(new BooleanSetting("\u517c\u5bb9\u9798\u7fc5\u98de\u884c", true));
   private int prevSlot = -1;
   private int fireworkPauseTicks;
   private int waitBackTicks;
   private boolean using;
   private boolean sendingSpearPacket;
   private boolean lastActionPressed;
   private Entity renderEntity;

   public AutoSpear() {
      super("AutoSpear", Module.Category.Combat);
      this.setChinese("\u81ea\u52a8\u957f\u77db");
      INSTANCE = this;
   }

   @Override
   public void onEnable() {
      this.prevSlot = -1;
      this.fireworkPauseTicks = 0;
      this.waitBackTicks = 0;
      this.using = false;
      this.sendingSpearPacket = false;
      this.lastActionPressed = false;
      this.renderEntity = null;
   }

   @Override
   public void onDisable() {
      this.pauseUsing();
      this.waitBackTicks = 0;
      this.sendingSpearPacket = false;
      this.lastActionPressed = false;
      this.renderEntity = null;
      this.releaseCombatTask();
   }

   @EventListener
   public void onTick(TickEvent event) {
      if (event.isPre()) {
         if (!nullCheck()) {
            if (mc.currentScreen == null && !this.shouldPauseForFirework()) {
               this.handleAutoRestart();
               Entity var2 = this.findTarget();
               this.renderEntity = var2;
               if (var2 != null && this.prepareSpear()) {
                  if (this.autoCharge.getValue()) {
                     this.startUsingSpear();
                  }

                  boolean var3 = this.actionKey.isPressed();
                  boolean var4 = this.autoAttack.getValue() || var3 && !this.lastActionPressed;
                  this.lastActionPressed = var3;
                  if (var4 && this.waitBackTicks == 0 && this.canSpearKineticAttack()) {
                     this.spearAttack(var2);
                  }
               } else {
                  this.pauseUsing();
               }
            } else {
               this.renderEntity = null;
               this.pauseUsing();
            }
         }
      }
   }

   @EventListener
   public void onPacketSend(PacketEvent.Send event) {
      if (!this.sendingSpearPacket && this.waitBackTicks > 0 && !nullCheck()) {
         if (event.getPacket() instanceof PlayerMoveC2SPacket) {
            this.waitBackTicks--;
            if (this.waitBackTicks > this.delay.getValueInt()) {
               this.waitBackTicks = this.delay.getValueInt();
            }

            if (this.waitBackTicks > 0) {
               mc.player.setOnGround(false);
               event.cancel();
            } else {
               this.releaseCombatTask();
            }
         }
      }
   }

   @Override
   public void onRender3D(MatrixStack matrixStack) {
      if (this.renderTarget.getValue() && this.renderEntity != null && this.renderEntity.isAlive() && this.canSpearKineticAttack()) {
         Box var2 = this.renderEntity.getBoundingBox();
         Render3DUtil.draw3DBox(matrixStack, var2, new Color(30, 220, 90, 70), new Color(30, 220, 90, 170), true, true);
      }
   }

   public static void pauseForFirework() {
      if (INSTANCE != null && INSTANCE.elytraFireworkCompat.getValue()) {
         INSTANCE.fireworkPauseTicks = 6;
      }
   }

   private boolean prepareSpear() {
      int var1 = SpearPort.findSpearHotbarSlot();
      if (var1 == -1) {
         return false;
      } else {
         int var2 = mc.player.getInventory().selectedSlot;
         if (!this.autoSwitch.getValue() && var2 != var1 && !SpearPort.isSpear(mc.player.getMainHandStack())) {
            return false;
         } else {
            if (this.autoSwitch.getValue() && var2 != var1 && !this.using) {
               this.prevSlot = var2;
               InventoryUtil.switchToSlot(var1);
            }

            return SpearPort.isSpear(mc.player.getMainHandStack());
         }
      }
   }

   private void startUsingSpear() {
      if (this.isUsingSpear()) {
         this.using = true;
         this.holdUsingItemPause();
      } else if (SpearPort.isSpear(mc.player.getMainHandStack())) {
         mc.options.useKey.setPressed(true);
         mc.interactionManager.interactItem(mc.player, Hand.MAIN_HAND);
         this.using = true;
         this.holdUsingItemPause();
      }
   }

   private void pauseUsing() {
      mc.options.useKey.setPressed(false);
      if (mc.player != null && mc.interactionManager != null) {
         if (this.using && this.isUsingSpear()) {
            mc.interactionManager.stopUsingItem(mc.player);
         }

         if (this.prevSlot >= 0) {
            InventoryUtil.switchToSlot(this.prevSlot);
            this.prevSlot = -1;
         }

         this.using = false;
         this.releaseUsingItemPause();
      } else {
         this.prevSlot = -1;
         this.using = false;
         this.releaseUsingItemPause();
      }
   }

   private boolean shouldPauseForFirework() {
      if (!this.elytraFireworkCompat.getValue()) {
         return false;
      } else if (this.fireworkPauseTicks > 0) {
         this.fireworkPauseTicks--;
         return true;
      } else {
         return mc.player.isFallFlying()
            && mc.options.useKey.isPressed()
            && (mc.player.getMainHandStack().isOf(Items.FIREWORK_ROCKET) || mc.player.getOffHandStack().isOf(Items.FIREWORK_ROCKET));
      }
   }

   private void handleAutoRestart() {
      if (this.autoRestart.getValue() && this.isUsingSpear()) {
         ItemStack var1 = mc.player.getActiveItem();
         int var2 = SpearPort.getMaxKineticUseTicks(var1);
         if (var2 > 0 && mc.player.getItemUseTime() > var2) {
            Hand var3 = mc.player.getActiveHand();
            mc.interactionManager.stopUsingItem(mc.player);
            mc.interactionManager.interactItem(mc.player, var3);
            this.using = true;
         }
      }
   }

   private boolean canSpearKineticAttack() {
      if (!this.isUsingSpear()) {
         return false;
      } else {
         ItemStack var1 = mc.player.getActiveItem();
         int var2 = mc.player.getItemUseTime();
         int var3 = SpearPort.getKineticDelayTicks(var1);
         int var4 = SpearPort.getMaxKineticUseTicks(var1);
         return var2 >= var3 && var4 > 0 && var2 < var4;
      }
   }

   private boolean isUsingSpear() {
      return mc.player != null && mc.player.isUsingItem() && SpearPort.isSpear(mc.player.getActiveItem());
   }

   private Entity findTarget() {
      double var1 = this.spearDistance.getValue() + this.attackRange.getValue();
      double var3 = var1 * var1;
      Entity var5 = null;
      double var6 = Double.MAX_VALUE;

      for (Entity var9 : mc.world.getEntities()) {
         if (var9 instanceof LivingEntity && (!this.playersOnly.getValue() || var9 instanceof PlayerEntity) && CombatUtil.isValid(var9)) {
            double var10 = mc.player.squaredDistanceTo(var9);
            if (!(var10 > var3) && !(var10 >= var6) && this.isSpearable(var9)) {
               var5 = var9;
               var6 = var10;
            }
         }
      }

      return var5;
   }

   private boolean isSpearable(Entity entity) {
      if (mc.player.getEyePos().squaredDistanceTo(entity.getEyePos()) <= square(this.minRange.getValue())) {
         return false;
      } else {
         BlockHitResult var2 = mc.world
            .raycast(new RaycastContext(mc.player.getEyePos(), entity.getEyePos(), ShapeType.COLLIDER, FluidHandling.NONE, mc.player));
         return var2.getType() == Type.MISS;
      }
   }

   private boolean spearAttack(Entity target) {
      if (this.spearDistance.getValue() < 0.0 || this.spearMaxTp.getValue() < this.spearDistance.getValue()) {
         return false;
      } else if (!this.tryUseCombatTask(350L)) {
         return false;
      } else {
         Vec3d var2 = mc.player.getPos();
         Vec3d var3 = this.getPredictedEyePos(target);
         Vec3d var4 = var3.subtract(mc.player.getEyePos());
         double var5 = var4.length();
         if (!(var5 <= this.minRange.getValue()) && !(var5 < 1.0E-4)) {
            var4 = var4.normalize();
            Vec3d var7 = var4.multiply(-1.0);
            Vec3d var8 = getExpandDirection(var4);
            List var9 = this.findValidTpPath(var2, var7, var8, var5);
            if (var9 != null && !var9.isEmpty()) {
               Vec2f var10 = rotationToPitchYaw(var4);
               this.sendSpearMoveSequence(var9, var2, var10);
               this.waitBackTicks = this.delay.getValueInt() + 1;
               if (this.swing.getValue()) {
                  mc.player.swingHand(Hand.MAIN_HAND);
               }

               return true;
            } else {
               this.releaseCombatTask();
               return false;
            }
         } else {
            this.releaseCombatTask();
            return false;
         }
      }
   }

   private Vec3d getPredictedEyePos(Entity target) {
      return target.getEyePos().add(target.getVelocity().multiply(this.predictTicks.getValue()));
   }

   private List<Vec3d> findValidTpPath(Vec3d playerPos, Vec3d tpDirection, Vec3d expandDirection, double targetDistance) {
      double var6 = square(this.spearMaxTp.getValue());

      for (double var8 = this.spearDistance.getValue(); var8 > targetDistance; var8 -= 2.0) {
         for (double var13 : SEARCH_ORDER) {
            Vec3d var15 = playerPos.add(tpDirection.multiply(var8)).add(expandDirection.multiply(var13));
            if (!(var15.squaredDistanceTo(playerPos) > var6) && this.isSafePlayerLocation(var15) && this.isPathClear(var15, playerPos)) {
               List var16 = this.generateTpSequence(playerPos, var15);
               if (!var16.isEmpty()) {
                  return var16;
               }
            }
         }
      }

      return null;
   }

   private List<Vec3d> generateTpSequence(Vec3d current, Vec3d target) {
      if (!this.isSafePlayerLocation(target)) {
         return List.of();
      } else if (this.isPathClear(current, target)) {
         return List.of(target);
      } else {
         for (double var3 = 1.0; var3 <= 6.0; var3++) {
            Vec3d var5 = current.add(0.0, var3, 0.0);
            Vec3d var6 = new Vec3d(target.x, var5.y, target.z);
            if (this.isSafePlayerLocation(var5)
               && this.isSafePlayerLocation(var6)
               && this.isPathClear(current, var5)
               && this.isPathClear(var5, var6)
               && this.isPathClear(var6, target)) {
               ArrayList var7 = new ArrayList();
               addIfMoved(var7, current, var5);
               addIfMoved(var7, var5, var6);
               addIfMoved(var7, var6, target);
               return var7;
            }
         }

         return List.of();
      }
   }

   private void sendSpearMoveSequence(List<Vec3d> path, Vec3d backPos, Vec2f rotation) {
      this.sendingSpearPacket = true;

      try {
         for (Vec3d var5 : path) {
            this.sendFullMove(var5, rotation, false);
         }

         this.sendFullMove(backPos, rotation, false);
      } finally {
         this.sendingSpearPacket = false;
      }
   }

   private void sendFullMove(Vec3d pos, Vec2f rotation, boolean onGround) {
      mc.getNetworkHandler().sendPacket(new Full(pos.x, pos.y, pos.z, rotation.y, rotation.x, onGround));
   }

   private boolean isSafePlayerLocation(Vec3d pos) {
      if (!(pos.y < (double)mc.world.getBottomY()) && !(pos.y > (double)(mc.world.getBottomY() + mc.world.getHeight()))) {
         Box var2 = mc.player.getBoundingBox().offset(pos.subtract(mc.player.getPos()));
         return mc.world.isSpaceEmpty(mc.player, var2);
      } else {
         return false;
      }
   }

   private boolean isPathClear(Vec3d from, Vec3d to) {
      double var3 = from.distanceTo(to);
      int var5 = Math.max(1, (int)Math.ceil(var3 / 1.0));

      for (int var6 = 1; var6 <= var5; var6++) {
         Vec3d var7 = from.lerp(to, (double)var6 / (double)var5);
         if (!this.isSafePlayerLocation(var7)) {
            return false;
         }
      }

      return true;
   }

   private static void addIfMoved(List<Vec3d> path, Vec3d from, Vec3d to) {
      if (from.squaredDistanceTo(to) > 1.0E-6) {
         path.add(to);
      }
   }

   private static Vec3d getExpandDirection(Vec3d direction) {
      Vec3d var1;
      if (Math.abs(direction.y) > 1.0E-5) {
         var1 = new Vec3d(direction.x, -(direction.x * direction.x + direction.z * direction.z) / direction.y, direction.z);
      } else {
         var1 = new Vec3d(0.0, 1.0, 0.0);
      }

      return var1.lengthSquared() < 1.0E-6 ? new Vec3d(0.0, 1.0, 0.0) : var1.normalize();
   }

   private static Vec2f rotationToPitchYaw(Vec3d direction) {
      double var1 = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
      float var3 = (float)MathHelper.wrapDegrees(Math.toDegrees(Math.atan2(direction.z, direction.x)) - 90.0);
      float var4 = (float)(-Math.toDegrees(Math.atan2(direction.y, var1)));
      return new Vec2f(var4, var3);
   }

   private static double square(double value) {
      return value * value;
   }

   private static double[] createSearchOrder() {
      double[] var0 = new double[19];
      var0[0] = 0.0;
      int var1 = 1;

      for (int var2 = 1; var2 < 10; var2++) {
         var0[var1++] = (double)var2;
         var0[var1++] = (double)(-var2);
      }

      return var0;
   }

   static {

      PATH_SAMPLE_STEP = 1.0;
      SEARCH_ORDER = createSearchOrder();
   }
}
