package asm.mixins;

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.impl.HeldItemRendererEvent;
import dev.crucifixbeta.mod.features.SpearPort;
import dev.crucifixbeta.mod.modules.impl.render.AnimationsModule;
import dev.crucifixbeta.mod.modules.impl.render.SwingModule;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Arm;
import net.minecraft.util.Hand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Environment(EnvType.CLIENT)
@Mixin({HeldItemRenderer.class})
public abstract class MixinHeldItemRenderer {
   @Shadow
   private ItemStack mainHand;
   @Shadow
   private ItemStack offHand;
   @Shadow
   private float equipProgressMainHand;
   @Shadow
   private float equipProgressOffHand;
   @Shadow
   private float prevEquipProgressMainHand;
   @Shadow
   private float prevEquipProgressOffHand;

   @Shadow
   private void applyEquipOffset(MatrixStack matrices, Arm arm, float equipProgress) {
   }

   @Shadow
   public abstract void renderItem(
      LivingEntity livingEntity,
      ItemStack itemStack,
      ModelTransformationMode modelTransformationMode,
      boolean bl,
      MatrixStack matrixStack,
      VertexConsumerProvider vertexConsumerProvider,
      int i
   );

   @Inject(
      method = {"updateHeldItems"},
      at = {@At("HEAD")},
      cancellable = true,
      require = 0
   )
   private void animations$updateHeldItems(CallbackInfo ci) {
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldNoSwitchAnimation()) {
         MinecraftClient var2 = MinecraftClient.getInstance();
         if (var2.player != null) {
            ci.cancel();
            this.equipProgressMainHand = 1.0F;
            this.equipProgressOffHand = 1.0F;
            this.prevEquipProgressMainHand = 1.0F;
            this.prevEquipProgressOffHand = 1.0F;
            this.mainHand = var2.player.getMainHandStack();
            this.offHand = var2.player.getOffHandStack();
         }
      }
   }

   @ModifyArg(
      method = {"updateHeldItems"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/util/math/MathHelper;clamp(FFF)F",
         ordinal = 2
      ),
      index = 0,
      require = 0
   )
   private float animations$equipProgressMainhand(float value) {
      MinecraftClient var2 = MinecraftClient.getInstance();
      if (var2.player == null) {
         return value;
      } else {
         float var3 = var2.player.getAttackCooldownProgress(1.0F);
         float var4 = AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldOldSwingAnimation() ? 1.0F : var3 * var3 * var3;
         return (ItemStack.areEqual(this.mainHand, var2.player.getMainHandStack()) ? var4 : 0.0F) - this.equipProgressMainHand;
      }
   }

   @Inject(
      method = {"renderFirstPersonItem"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void renderPortedSpear(
      AbstractClientPlayerEntity player,
      float tickDelta,
      float pitch,
      Hand hand,
      float swingProgress,
      ItemStack item,
      float equipProgress,
      MatrixStack matrices,
      VertexConsumerProvider vertexConsumers,
      int light,
      CallbackInfo ci
   ) {
      if (SpearPort.isSpear(item)) {
         boolean var12 = hand == Hand.MAIN_HAND ? player.getMainArm() == Arm.RIGHT : player.getMainArm() != Arm.RIGHT;
         Arm var13 = var12 ? Arm.RIGHT : Arm.LEFT;
         int var14 = var12 ? 1 : -1;
         boolean var15 = player.isUsingItem() && player.getActiveHand() == hand && player.getItemUseTimeLeft() > 0;
         HeldItemRendererEvent var16 = HeldItemRendererEvent.get(hand, item, equipProgress, matrices);
         CrucifixBeta.EVENT_BUS.post(var16);
         matrices.push();
         if (var15) {
            matrices.translate((float)var14 * 0.56F, -0.52F, -0.72F);
            float var17 = (float)item.getMaxUseTime(player) - ((float)player.getItemUseTimeLeft() - tickDelta + 1.0F);
            float var18 = MathHelper.clamp(var17 / 10.0F, 0.0F, 1.0F);
            applyVanillaSpearUseTransform(var18, matrices, var17, var13);
         } else {
            this.applyEquipOffset(matrices, var13, equipProgress);
            applyVanillaSpearIdleTransform(swingProgress, matrices, var14, var13);
         }

         this.renderSpearItem(player, item, var12, matrices, vertexConsumers, light);
         matrices.pop();
         ci.cancel();
      }
   }

   private void renderSpearItem(
      AbstractClientPlayerEntity player, ItemStack item, boolean rightHand, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light
   ) {
      SpearPort.setInHandRender(true);

      try {
         this.renderItem(
            player,
            SpearPort.getVisualStack(item),
            rightHand ? ModelTransformationMode.FIRST_PERSON_RIGHT_HAND : ModelTransformationMode.FIRST_PERSON_LEFT_HAND,
            !rightHand,
            matrices,
            vertexConsumers,
            light
         );
      } finally {
         SpearPort.setInHandRender(false);
      }
   }

   private static void applyVanillaSpearIdleTransform(float swingProgress, MatrixStack matrices, int side, Arm arm) {
      float var4 = -0.4F * MathHelper.sin(MathHelper.sqrt(swingProgress) * (float) Math.PI);
      float var5 = 0.2F * MathHelper.sin(MathHelper.sqrt(swingProgress) * (float) (Math.PI * 2));
      float var6 = -0.2F * MathHelper.sin(swingProgress * (float) Math.PI);
      matrices.translate((float)side * var4, var5, var6);
      applyVanillaSwingOffset(matrices, arm, swingProgress);
   }

   private static void applyVanillaSwingOffset(MatrixStack matrices, Arm arm, float swingProgress) {
      int var3 = arm == Arm.RIGHT ? 1 : -1;
      float var4 = MathHelper.sin(swingProgress * swingProgress * (float) Math.PI);
      matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((float)var3 * (45.0F + var4 * -20.0F)));
      float var5 = MathHelper.sin(MathHelper.sqrt(swingProgress) * (float) Math.PI);
      matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((float)var3 * var5 * -20.0F));
      matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(var5 * -80.0F));
      matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((float)var3 * -45.0F));
   }

   private static void applyVanillaSpearUseTransform(float charge, MatrixStack matrices, float useTicks, Arm arm) {
      float var4 = clampCurve(useTicks, 0.0F, 10.0F);
      float var5 = clampCurve(var4, 0.0F, 0.5F);
      float var6 = clampCurve(var4, 0.5F, 0.8F);
      float var7 = clampCurve(var4, 0.8F, 1.0F);
      float var8 = clampCurve(useTicks, 4.0F, 10.0F);
      float var9 = smoothOut(smoothHold(clampCurve(useTicks - 6.0F, 0.0F, 8.0F)));
      float var10 = clampCurve(useTicks, 12.0F, 16.0F);
      float var11 = 2.0F * smoothIn(var8) - 2.0F * smoothOut(var10);
      float var12 = MathHelper.sin(useTicks * 19.0F * (float) (Math.PI / 180.0)) * var11;
      float var13 = MathHelper.sin(useTicks * 30.0F * (float) (Math.PI / 180.0)) * var11;
      int var14 = arm == Arm.RIGHT ? 1 : -1;
      matrices.translate(
         (double)((float)var14 * (var4 * 0.15F + var7 * -0.05F + var8 * -0.1F + var12 * 0.005F)),
         (double)(var4 * -0.075F + var6 * 0.075F + var13 * 0.01F),
         (double)(var5 * 0.05F + var7 * -0.05F + var12 * 0.005F)
      );
      matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-65.0F * smoothOut(var4) - 35.0F * var9 + 100.0F * var10 - 0.5F * var13));
      matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((float)var14 * (-90.0F * var4 * clampCurve(var4, 0.5F, 0.55F) + 90.0F * var8 + 2.0F * var12)));
      matrices.translate((float)var14 * 0.15F, -spearPullback(charge), 0.0F);
   }

   private static float spearPullback(float charge) {
      return smoothHold(clampCurve(charge, 0.4F, 3.0F)) - smoothIn(clampCurve(charge, 3.0F, 10.0F));
   }

   private static float clampCurve(float value, float start, float end) {
      return MathHelper.clamp((value - start) / (end - start), 0.0F, 1.0F);
   }

   private static float smoothIn(float value) {
      return value * value;
   }

   private static float smoothOut(float value) {
      float var1 = 1.0F - value;
      return 1.0F - var1 * var1;
   }

   private static float smoothHold(float value) {
      return (float)Math.sin((double)value * Math.PI * 0.5);
   }

   @Inject(
      method = {"renderFirstPersonItem"},
      at = {@At(
         value = "INVOKE",
         target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V"
      )}
   )
   private void onRenderItem(
      AbstractClientPlayerEntity player,
      float tickDelta,
      float pitch,
      Hand hand,
      float swingProgress,
      ItemStack item,
      float equipProgress,
      MatrixStack matrices,
      VertexConsumerProvider vertexConsumers,
      int light,
      CallbackInfo ci
   ) {
      HeldItemRendererEvent var12 = HeldItemRendererEvent.get(hand, item, equipProgress, matrices);
      CrucifixBeta.EVENT_BUS.post(var12);
   }

   @Inject(
      method = {"applyEatOrDrinkTransformation"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void hookApplyEatOrDrinkTransformation(MatrixStack matrices, float tickDelta, Arm arm, ItemStack stack, PlayerEntity player, CallbackInfo ci) {
      if (this.animations$shouldTransformEat()) {
         ci.cancel();
         MinecraftClient var7 = MinecraftClient.getInstance();
         float var8 = (float)var7.player.getItemUseTimeLeft() - tickDelta + 1.0F;
         float var9 = var8 / (float)stack.getMaxUseTime(var7.player);
         if (var9 < 0.8F) {
            float var10 = MathHelper.abs(MathHelper.cos(var8 / 4.0F * (float) Math.PI) * 0.1F);
            matrices.translate(0.0F, var10 * this.animations$getEatTransformFactor(), 0.0F);
         }

         float var12 = 1.0F - (float)Math.pow((double)var9, 27.0);
         int var11 = arm == Arm.RIGHT ? 1 : -1;
         matrices.translate(var12 * 0.6F * (float)var11, var12 * -0.5F, var12 * 0.0F);
         matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((float)var11 * var12 * 90.0F));
         matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(var12 * 10.0F));
         matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((float)var11 * var12 * 30.0F));
      }
   }

   private boolean animations$shouldTransformEat() {
      return AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldTransformEat()
         ? true
         : SwingModule.INSTANCE != null && SwingModule.INSTANCE.shouldTransformEat();
   }

   private float animations$getEatTransformFactor() {
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldTransformEat()) {
         return AnimationsModule.INSTANCE.getEatTransformFactor();
      } else {
         return SwingModule.INSTANCE != null ? SwingModule.INSTANCE.getEatTransformFactor() : 1.0F;
      }
   }
}
