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

import asm.accessors.IPlayerMoveC2SPacket;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.EntityVelocityUpdateEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.exploit.BowBomb;
import dev.crucifixbeta.mod.modules.impl.exploit.MaceSpoof;
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 net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.entity.EquipmentSlot;
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.network.packet.c2s.play.PlayerMoveC2SPacket.OnGroundOnly;
import net.minecraft.registry.Registries;

@Environment(EnvType.CLIENT)
public class NoFall extends Module {
   private static final double DELTA_Y;
   private static final int VELOCITY_PROTECT_TICKS;
   private final EnumSetting<NoFall.NoFallMode> mode = this.add(new EnumSetting<>("Mode", NoFall.NoFallMode.LAZY_MODE));
   private final SliderSetting safeDistanceModify = this.add(new SliderSetting("SafeDistance", 0.0, -3.0, 8.0, 0.1));
   private final BooleanSetting disableWithElytra = this.add(new BooleanSetting("DisableWithElytra", true));
   private final BooleanSetting equipmentBypass = this.add(new BooleanSetting("EquipmentBypass", true));
   private double lastOnGroundHeight = Double.NEGATIVE_INFINITY;
   private double lastServerY;
   private int ticks;
   private int lastNoFallTick = -1000;
   private boolean sendingNoFallPacket;
   private boolean grimJumpQueued;

   public NoFall() {
      super("NoFall", "Prevents fall damage.", Module.Category.Player);
      this.setChinese("\u6ca1\u6709\u6454\u843d\u4f24\u5bb3");
   }

   @Override
   public String getInfo() {
      return this.mode.getValue().displayName();
   }

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

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

   @EventListener
   public void onUpdate(UpdateEvent event) {
      if (!nullCheck()) {
         this.ticks++;
         this.updateFallReference();
         if (!this.isBypassDisabled()) {
            NoFall.NoFallMode var2 = this.normalizedMode();
            if (var2 == NoFall.NoFallMode.BYPASS_GRIM && this.isUnsafeFall() && !mc.player.isOnGround()) {
               this.sendPosition(mc.player.getY() + 1.0E-9, false);
               this.markHandledFall();
            } else {
               if (this.grimJumpQueued) {
                  this.grimJumpQueued = false;
                  mc.options.jumpKey.setPressed(false);
                  mc.player.setOnGround(false);
               }
            }
         }
      }
   }

   @EventListener
   public void onPacketSend(PacketEvent.Send event) {
      if (!nullCheck() && !this.sendingNoFallPacket) {
         if (event.getPacket() instanceof PlayerMoveC2SPacket var3) {
            if (var3.changesPosition()) {
               this.lastServerY = var3.getY(this.lastServerY);
            }

            if (!this.isBypassDisabled() && !BowBomb.send && this.isUnsafeFall()) {
               NoFall.NoFallMode var4 = this.normalizedMode();
               switch (var4) {
                  case NO_BYPASS:
                     ((IPlayerMoveC2SPacket)var3).setOnGround(true);
                     this.markHandledFall();
                     break;
                  case LAZY_MODE:
                     ((IPlayerMoveC2SPacket)var3).setOnGround(false);
                     this.sendPosition(this.lastServerY + 9.0E-8, false);
                     this.markHandledFall();
                     break;
                  case BYPASS_GRIM:
                     event.cancel();
                     mc.getNetworkHandler().sendPacket(new OnGroundOnly(true));
                     mc.player.setOnGround(true);
                     this.grimJumpQueued = true;
                     this.markHandledFall();
                     break;
                  case LAZY_BYPASS_GRIM:
                  case LAZY_GRIM_PLUS:
                  case LAZY_GRIM_PLUS_2:
                  case TEST:
                  case TEST2:
                     ((IPlayerMoveC2SPacket)var3).setOnGround(false);
                     this.sendPosition(mc.player.getY() + 9.0E-8, false);
                     this.markHandledFall();
               }
            }
         }
      }
   }

   @EventListener
   public void onVelocity(EntityVelocityUpdateEvent event) {
      if (!nullCheck() && event.getEntity() == mc.player) {
         NoFall.NoFallMode var2 = this.normalizedMode();
         if (var2.usesVelocityProtection()) {
            if (this.ticks <= this.lastNoFallTick + 10 && event.getY() < 0.0) {
               event.setY(0.0);
            }
         }
      }
   }

   private void updateFallReference() {
      if (!mc.player.isOnGround() && !mc.player.isTouchingWater() && !mc.player.isInLava()) {
         if (this.lastOnGroundHeight == Double.NEGATIVE_INFINITY || mc.player.getY() > this.lastOnGroundHeight) {
            this.lastOnGroundHeight = mc.player.getY();
         }
      } else {
         this.lastOnGroundHeight = mc.player.getY();
         this.lastServerY = mc.player.getY();
      }
   }

   private boolean isUnsafeFall() {
      if (this.lastOnGroundHeight == Double.NEGATIVE_INFINITY) {
         return false;
      } else {
         double var1 = Math.max(0.0, (double)mc.player.getSafeFallDistance() + this.safeDistanceModify.getValue());
         return mc.player.getY() <= this.lastOnGroundHeight - var1 || (double)mc.player.fallDistance >= var1;
      }
   }

   private boolean isBypassDisabled() {
      if (!MaceSpoof.shouldDisableNoFall() && !mc.player.getAbilities().invulnerable && !mc.player.isFallFlying()) {
         if (mc.player.getMainHandStack().isOf(Items.MACE)) {
            return true;
         } else {
            if (this.disableWithElytra.getValue()) {
               for (ItemStack var2 : mc.player.getArmorItems()) {
                  if (var2.isOf(Items.ELYTRA)) {
                     return true;
                  }
               }
            }

            return this.equipmentBypass.getValue() && this.hasInvulnerableEquipment();
         }
      } else {
         return true;
      }
   }

   private boolean hasInvulnerableEquipment() {
      for (EquipmentSlot var4 : new EquipmentSlot[]{EquipmentSlot.FEET, EquipmentSlot.LEGS, EquipmentSlot.CHEST, EquipmentSlot.HEAD}) {
         ItemStack var5 = mc.player.getEquippedStack(var4);
         if (!var5.isEmpty()) {
            String var6 = Registries.ITEM.getId(var5.getItem()).getPath().toUpperCase();
            if (var6.matches("^SLIME.*_BOOTS$")) {
               return true;
            }
         }
      }

      return false;
   }

   private NoFall.NoFallMode normalizedMode() {
      return switch ((NoFall.NoFallMode)this.mode.getValue()) {
         case Packet -> NoFall.NoFallMode.NO_BYPASS;
         case Grim -> NoFall.NoFallMode.BYPASS_GRIM;
         default -> (NoFall.NoFallMode)this.mode.getValue();
      };
   }

   private void sendPosition(double y, boolean onGround) {
      this.sendingNoFallPacket = true;

      try {
         mc.getNetworkHandler().sendPacket(new Full(mc.player.getX(), y, mc.player.getZ(), mc.player.getYaw(), mc.player.getPitch(), onGround));
      } finally {
         this.sendingNoFallPacket = false;
      }
   }

   private void markHandledFall() {
      this.lastNoFallTick = this.ticks;
      this.lastOnGroundHeight = this.lastServerY;
      mc.player.onLanding();
   }

   private void resetState() {
      this.lastOnGroundHeight = Double.NEGATIVE_INFINITY;
      this.lastServerY = 0.0;
      this.ticks = 0;
      this.lastNoFallTick = -1000;
      this.sendingNoFallPacket = false;
      this.grimJumpQueued = false;
   }

   static {

      VELOCITY_PROTECT_TICKS = 10;
      DELTA_Y = 9.0E-8;
   }
@Environment(EnvType.CLIENT)
   public static enum NoFallMode {
      NO_BYPASS("NO_BYPASS"),
      LAZY_MODE("LAZY_MODE"),
      BYPASS_GRIM("BYPASS_GRIM"),
      LAZY_BYPASS_GRIM("LAZY_BYPASS_GRIM"),
      LAZY_GRIM_PLUS("LAZY_GRIM_PLUS"),
      LAZY_GRIM_PLUS_2("LAZY_GRIM_PLUS_2"),
      TEST("TEST"),
      TEST2("TEST2"),
      Packet("Packet"),
      Grim("Grim");

      private final String displayName;

      private NoFallMode(String displayName) {
         this.displayName = displayName;
      }

      public String displayName() {
         return this.displayName;
      }

      public boolean usesVelocityProtection() {
         return this == LAZY_BYPASS_GRIM || this == LAZY_GRIM_PLUS || this == LAZY_GRIM_PLUS_2 || this == TEST || this == TEST2;
      }

}
}
