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

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.InteractItemEvent;
import dev.crucifixbeta.api.events.impl.KeyboardInputEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.UpdateEvent;
import dev.crucifixbeta.api.utils.player.MovementUtil;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.EnumSetting;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
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.PositionAndOnGround;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;

@Environment(EnvType.CLIENT)
public class NoSlow extends Module {
   public static NoSlow INSTANCE;
   final Queue<ClickSlotC2SPacket> storedClicks = new LinkedList<>();
   final AtomicBoolean pause = new AtomicBoolean();
   private final EnumSetting<NoSlow.Mode> mode = this.add(new EnumSetting<>("Mode", NoSlow.Mode.Vanilla));
   private final BooleanSetting food = this.add(new BooleanSetting("Food", true));
   private final BooleanSetting bow = this.add(new BooleanSetting("Bow", true));
   private final BooleanSetting crossbow = this.add(new BooleanSetting("Crossbow", true));
   private final BooleanSetting soulSand = this.add(new BooleanSetting("SoulSand", true));
   private final BooleanSetting sneak = this.add(new BooleanSetting("Sneak", false));
   private final BooleanSetting climb = this.add(new BooleanSetting("Climb", false));
   private final BooleanSetting gui = this.add(new BooleanSetting("Gui", true));
   private final BooleanSetting allowSneak = this.add(new BooleanSetting("AllowSneak", false, this.gui::getValue));
   private final EnumSetting<NoSlow.Bypass> clickBypass = this.add(new EnumSetting<>("GuiMoveBypass", NoSlow.Bypass.None));
   boolean using = false;
   int delay = 0;
   private int onGroundTick = 0;
   private int grimSwapSlot = -1;
   private int grimSwapButton = -1;
   private boolean grimSwapSprinting;

   public NoSlow() {
      super("NoSlow", Module.Category.Movement);
      this.setChinese("\u65e0\u51cf\u901f");
      INSTANCE = this;
   }

   private static float getMovementMultiplier(boolean positive, boolean negative) {
      if (positive == negative) {
         return 0.0F;
      } else {
         return positive ? 1.0F : -1.0F;
      }
   }

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

   @EventListener
   public void onUpdate(UpdateEvent event) {
      this.using = mc.player.isUsingItem();
      this.delay--;
      if (this.using) {
         this.delay = 2;
      }

      if (this.using && !mc.player.isRiding() && !mc.player.isFallFlying()) {
         switch ((NoSlow.Mode)this.mode.getValue()) {
            case NCP:
               mc.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(mc.player.getInventory().selectedSlot));
               break;
            case Grim:
               if (mc.player.getActiveHand() == Hand.OFF_HAND) {
                  sendSequencedPacket(
                     id -> new PlayerInteractItemC2SPacket(Hand.MAIN_HAND, id, CrucifixBeta.ROTATION.getLastYaw(), CrucifixBeta.ROTATION.getLastPitch())
                  );
               } else {
                  sendSequencedPacket(
                     id -> new PlayerInteractItemC2SPacket(Hand.OFF_HAND, id, CrucifixBeta.ROTATION.getLastYaw(), CrucifixBeta.ROTATION.getLastPitch())
                  );
               }
               break;
            case GrimPacket:
               mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, 1, 0, SlotActionType.PICKUP, mc.player);
               if (mc.player.getActiveHand() == Hand.OFF_HAND) {
                  sendSequencedPacket(
                     id -> new PlayerInteractItemC2SPacket(Hand.MAIN_HAND, id, CrucifixBeta.ROTATION.getLastYaw(), CrucifixBeta.ROTATION.getLastPitch())
                  );
               } else {
                  sendSequencedPacket(
                     id -> new PlayerInteractItemC2SPacket(Hand.OFF_HAND, id, CrucifixBeta.ROTATION.getLastYaw(), CrucifixBeta.ROTATION.getLastPitch())
                  );
               }
         }
      }

      if (mc.player.isOnGround()) {
         this.onGroundTick++;
      } else {
         this.onGroundTick = 0;
      }

      if (this.using && this.shouldKeepSprintThisTick() && !mc.player.isSprinting()) {
         mc.player.setSprinting(true);
      }

      if (this.gui.getValue() && !(mc.currentScreen instanceof ChatScreen)) {
         for (KeyBinding var5 : new KeyBinding[]{mc.options.backKey, mc.options.leftKey, mc.options.rightKey}) {
            var5.setPressed(InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(var5.getBoundKeyTranslationKey()).getCode()));
         }

         mc.options
            .jumpKey
            .setPressed(
               ElytraFly.INSTANCE.isOn() && ElytraFly.INSTANCE.mode.is(ElytraFly.Mode.Bounce) && ElytraFly.INSTANCE.autoJump.getValue()
                  || InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.jumpKey.getBoundKeyTranslationKey()).getCode())
            );
         mc.options
            .forwardKey
            .setPressed(
               Strafe.INSTANCE.forward()
                  || InputUtil.isKeyPressed(
                     mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.forwardKey.getBoundKeyTranslationKey()).getCode()
                  )
            );
         mc.options
            .sprintKey
            .setPressed(
               Sprint.INSTANCE.isOn() && !Sprint.INSTANCE.inWater()
                  || InputUtil.isKeyPressed(
                     mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.sprintKey.getBoundKeyTranslationKey()).getCode()
                  )
            );
         if (this.allowSneak.getValue()) {
            mc.options
               .sneakKey
               .setPressed(
                  InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.sneakKey.getBoundKeyTranslationKey()).getCode())
               );
         }
      }
   }

   @EventListener(
      priority = 100
   )
   public void keyboard(KeyboardInputEvent event) {
      if (this.sneak.getValue()) {
         event.cancel();
      }

      if (this.gui.getValue() && !(mc.currentScreen instanceof ChatScreen)) {
         for (KeyBinding var5 : new KeyBinding[]{mc.options.backKey, mc.options.leftKey, mc.options.rightKey}) {
            var5.setPressed(InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(var5.getBoundKeyTranslationKey()).getCode()));
         }

         mc.options
            .jumpKey
            .setPressed(
               ElytraFly.INSTANCE.isOn() && ElytraFly.INSTANCE.mode.is(ElytraFly.Mode.Bounce) && ElytraFly.INSTANCE.autoJump.getValue()
                  || InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.jumpKey.getBoundKeyTranslationKey()).getCode())
            );
         mc.options
            .forwardKey
            .setPressed(
               Strafe.INSTANCE.forward()
                  || InputUtil.isKeyPressed(
                     mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.forwardKey.getBoundKeyTranslationKey()).getCode()
                  )
            );
         mc.options
            .sprintKey
            .setPressed(
               Sprint.INSTANCE.isOn() && !Sprint.INSTANCE.inWater()
                  || InputUtil.isKeyPressed(
                     mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.sprintKey.getBoundKeyTranslationKey()).getCode()
                  )
            );
         if (this.allowSneak.getValue()) {
            mc.options
               .sneakKey
               .setPressed(
                  InputUtil.isKeyPressed(mc.getWindow().getHandle(), InputUtil.fromTranslationKey(mc.options.sneakKey.getBoundKeyTranslationKey()).getCode())
               );
         }

         mc.player.input.pressingForward = mc.options.forwardKey.isPressed();
         mc.player.input.pressingBack = mc.options.backKey.isPressed();
         mc.player.input.pressingLeft = mc.options.leftKey.isPressed();
         mc.player.input.pressingRight = mc.options.rightKey.isPressed();
         mc.player.input.movementForward = getMovementMultiplier(mc.player.input.pressingForward, mc.player.input.pressingBack);
         mc.player.input.movementSideways = getMovementMultiplier(mc.player.input.pressingLeft, mc.player.input.pressingRight);
         mc.player.input.jumping = mc.options.jumpKey.isPressed();
         mc.player.input.sneaking = mc.options.sneakKey.isPressed();
      }

      if (this.mode.is(NoSlow.Mode.Jump)
         && !nullCheck()
         && mc.player.isOnGround()
         && mc.player.isUsingItem()
         && MovementUtil.isMoving()
         && this.isNoSlowAllowedForCurrentItem()) {
         mc.player.input.jumping = true;
      }
   }

   @EventListener
   public void onUse(InteractItemEvent event) {
      if (event.isPre()) {
         if (this.delay > 0) {
            mc.itemUseCooldown = 0;
            event.cancel();
         } else if (this.mode.is(NoSlow.Mode.GrimPacket)
            && mc.player != null
            && mc.player.getStackInHand(event.hand).getItem().getComponents().contains(DataComponentTypes.FOOD)) {
            mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, 1, 0, SlotActionType.PICKUP, mc.player);
         }
      }
   }

   @EventListener
   public void onPacketSend(PacketEvent.Send e) {
      if (!nullCheck()) {
         Packet var2;
         PlayerInteractItemC2SPacket var3;
         if (this.mode.is(NoSlow.Mode.Drop)
            && (var2 = e.getPacket()) instanceof PlayerInteractItemC2SPacket
            && (var3 = (PlayerInteractItemC2SPacket)var2).getHand() == Hand.MAIN_HAND
            && mc.player.getMainHandStack().getItem().getComponents().contains(DataComponentTypes.FOOD)) {
            mc.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(Action.DROP_ITEM, BlockPos.ORIGIN, Direction.DOWN));
         } else if (MovementUtil.isMoving() && !this.pause.get()) {
            var2 = e.getPacket();
            if (var2 instanceof PlayerMoveC2SPacket && this.shouldRunSlimefunUseBypass()) {
               this.preSlimefunUseBypassSwap();
            }

            if (var2 instanceof ClickSlotC2SPacket var4) {
               switch ((NoSlow.Bypass)this.clickBypass.getValue()) {
                  case NCP:
                     if (mc.player.isOnGround()
                        && !mc.world.getBlockCollisions(mc.player, mc.player.getBoundingBox().offset(0.0, 0.0656, 0.0)).iterator().hasNext()) {
                        if (mc.player.isSprinting()) {
                           mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.STOP_SPRINTING));
                        }

                        mc.getNetworkHandler().sendPacket(new PositionAndOnGround(mc.player.getX(), mc.player.getY() + 0.0656, mc.player.getZ(), false));
                     }
                     break;
                  case NCP2:
                     if (mc.player.isOnGround()
                        && !mc.world.getBlockCollisions(mc.player, mc.player.getBoundingBox().offset(0.0, 2.71875E-7, 0.0)).iterator().hasNext()) {
                        if (mc.player.isSprinting()) {
                           mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.STOP_SPRINTING));
                        }

                        mc.getNetworkHandler().sendPacket(new PositionAndOnGround(mc.player.getX(), mc.player.getY() + 2.71875E-7, mc.player.getZ(), false));
                     }
                     break;
                  case Grim:
                     if (var4.getActionType() != SlotActionType.PICKUP && var4.getActionType() != SlotActionType.PICKUP_ALL) {
                        mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(0));
                     }
                     break;
                  case Delay:
                     this.storedClicks.add(var4);
                     e.cancel();
               }
            }

            if (e.getPacket() instanceof CloseHandledScreenC2SPacket && this.clickBypass.is(NoSlow.Bypass.Delay)) {
               this.pause.set(true);

               while (!this.storedClicks.isEmpty()) {
                  mc.getNetworkHandler().sendPacket(this.storedClicks.poll());
               }

               this.pause.set(false);
            }
         }
      }
   }

   @EventListener
   public void onPacketSendPost(PacketEvent.Sent e) {
      if (e.getPacket() instanceof PlayerMoveC2SPacket) {
         this.postSlimefunUseBypassSwap();
      }

      if (e.getPacket() instanceof ClickSlotC2SPacket && mc.player.isSprinting() && this.clickBypass.is(NoSlow.Bypass.NCP)) {
         mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.START_SPRINTING));
      }
   }

   public boolean noSlow() {
      if (this.isOn() && !nullCheck() && mc.player.isUsingItem()) {
         NoSlow.Mode var1 = this.mode.getValue();
         if (var1 == NoSlow.Mode.None) {
            return false;
         } else if ((var1 == NoSlow.Mode.Drop || var1 == NoSlow.Mode.GrimPacket) && !this.using) {
            return false;
         } else if (!this.isNoSlowAllowedForCurrentItem()) {
            return false;
         } else {
            return switch (var1) {
               case GrimLazy -> this.shouldRunGrimLazy();
               case GrimTick -> this.shouldRunGrimTick();
               default -> true;
               case Heypixel2_3 -> this.getItemUseTimeLeft() % 3 != 0 && (!this.isFoodLimitedItem() || this.getItemUseTimeLeft() <= 30);
               case Grim50 -> this.getItemUseTimeLeft() % 2 == 0 && this.getItemUseTimeLeft() <= 30;
               case Grim1_3 -> this.getItemUseTimeLeft() % 3 == 0 && (!this.isFoodLimitedItem() || this.getItemUseTimeLeft() <= 30);
               case Jump -> this.onGroundTick == 1 && this.getItemUseTimeLeft() <= 30;
            };
         }
      } else {
         return false;
      }
   }

   @Override
   public void onEnable() {
      this.onGroundTick = 0;
      this.resetSlimefunSwap();
   }

   @Override
   public void onDisable() {
      this.onGroundTick = 0;
      this.resetSlimefunSwap();
   }

   public boolean soulSand() {
      return this.isOn() && this.soulSand.getValue();
   }

   public boolean climb() {
      return this.isOn() && this.climb.getValue();
   }

   private int getItemUseTimeLeft() {
      return mc.player.getItemUseTimeLeft();
   }

   private boolean isFoodLimitedItem() {
      ItemStack var1 = mc.player.getMainHandStack();
      ItemStack var2 = mc.player.getOffHandStack();
      return this.isFoodLimitedItem(var1) || this.isFoodLimitedItem(var2);
   }

   private boolean isFoodLimitedItem(ItemStack stack) {
      return stack.getItem() == Items.GOLDEN_APPLE || stack.getItem() == Items.ENCHANTED_GOLDEN_APPLE || stack.getItem() == Items.POTION;
   }

   private boolean isHolding(Item item) {
      return mc.player.getMainHandStack().getItem() == item || mc.player.getOffHandStack().getItem() == item;
   }

   private boolean isNoSlowAllowedForCurrentItem() {
      if (!this.food.getValue() && this.isFoodLimitedItem()) {
         return false;
      } else if (!this.bow.getValue() && this.isHolding(Items.BOW)) {
         return false;
      } else {
         return !this.crossbow.getValue() && this.isHolding(Items.CROSSBOW) ? false : !this.isFoodLimitedItem() || this.getItemUseTimeLeft() <= 30;
      }
   }

   private boolean shouldRunSlimefunUseBypass() {
      return switch ((NoSlow.Mode)this.mode.getValue()) {
         case GrimLazy -> this.shouldRunGrimLazy();
         case GrimTick -> this.shouldRunGrimTick();
         default -> false;
      };
   }

   private boolean shouldRunGrimLazy() {
      return this.isOn()
         && !nullCheck()
         && mc.player.isUsingItem()
         && !mc.player.isFallFlying()
         && MovementUtil.isMoving()
         && this.isNoSlowAllowedForCurrentItem()
         && this.isActiveItemSlowing();
   }

   private boolean shouldRunGrimTick() {
      return this.isOn() && !nullCheck() && mc.player.isUsingItem() && this.isNoSlowAllowedForCurrentItem();
   }

   private boolean isActiveItemSlowing() {
      ItemStack var1 = mc.player.getActiveItem();
      return var1 != null && !var1.isEmpty();
   }

   private void preSlimefunUseBypassSwap() {
      if (this.grimSwapSlot == -1 && mc.player.currentScreenHandler != null && mc.player.currentScreenHandler.getCursorStack().isEmpty()) {
         int var1 = mc.player.getActiveHand() == Hand.MAIN_HAND ? mc.player.getInventory().selectedSlot : 40;
         ItemStack var2 = mc.player.getActiveItem();
         int var3 = this.findEmptyPlayerScreenSlot(var1, var2);
         if (var3 == -1) {
            mc.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(mc.player.getInventory().selectedSlot));
         } else {
            this.grimSwapSprinting = mc.player.isSprinting();
            if (this.grimSwapSprinting) {
               mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.STOP_SPRINTING));
            }

            this.pause.set(true);

            try {
               mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, var3, var1, SlotActionType.SWAP, mc.player);
               this.grimSwapSlot = var3;
               this.grimSwapButton = var1;
            } finally {
               this.pause.set(false);
            }
         }
      }
   }

   private void postSlimefunUseBypassSwap() {
      if (this.grimSwapSlot != -1 && !nullCheck() && mc.player.currentScreenHandler != null) {
         this.pause.set(true);

         try {
            mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, this.grimSwapSlot, this.grimSwapButton, SlotActionType.SWAP, mc.player);
            if (this.grimSwapSprinting) {
               mc.getNetworkHandler().sendPacket(new ClientCommandC2SPacket(mc.player, ClientCommandC2SPacket.Mode.START_SPRINTING));
            }
         } finally {
            this.pause.set(false);
            this.resetSlimefunSwap();
         }
      } else {
         this.resetSlimefunSwap();
      }
   }

   private int findEmptyPlayerScreenSlot(int exceptInventoryIndex, ItemStack activeStack) {
      for (int var3 = mc.player.currentScreenHandler.slots.size() - 1; var3 >= 0; var3--) {
         Slot var4 = mc.player.currentScreenHandler.slots.get(var3);
         if (var4.inventory == mc.player.getInventory() && var4.getIndex() != exceptInventoryIndex && var4.getStack().isEmpty() && var4.canInsert(activeStack)) {
            return var3;
         }
      }

      return -1;
   }

   private void resetSlimefunSwap() {
      this.grimSwapSlot = -1;
      this.grimSwapButton = -1;
      this.grimSwapSprinting = false;
   }

   private boolean shouldKeepSprintThisTick() {
      if (this.isOn() && !nullCheck() && mc.player.isUsingItem()) {
         return switch ((NoSlow.Mode)this.mode.getValue()) {
            case GrimLazy, GrimTick, Heypixel2_3, Grim50, Grim1_3, Jump -> this.noSlow();
            default -> false;
         };
      } else {
         return false;
      }
   }
@Environment(EnvType.CLIENT)
   private static enum Bypass {
      None,
      NCP,
      NCP2,
      Grim,
      Delay;

}

   @Environment(EnvType.CLIENT)
   public static enum Mode {
      Vanilla,
      NCP,
      Grim,
      GrimPacket,
      GrimLazy,
      GrimTick,
      Drop,
      Heypixel2_3,
      Grim50,
      Grim1_3,
      Jump,
      None;

}
}
