package asm.mixins;

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.impl.LerpToEvent;
import dev.crucifixbeta.api.events.impl.SprintEvent;
import dev.crucifixbeta.mod.modules.impl.movement.ElytraFly;
import dev.crucifixbeta.mod.modules.impl.movement.NoSlow;
import dev.crucifixbeta.mod.modules.impl.movement.Velocity;
import dev.crucifixbeta.mod.modules.impl.player.AntiEffects;
import dev.crucifixbeta.mod.modules.impl.render.AnimationsModule;
import dev.crucifixbeta.mod.modules.impl.render.SwingModule;
import dev.crucifixbeta.mod.modules.impl.render.ViewModel;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.Entity.RemovalReason;
import net.minecraft.entity.attribute.AttributeContainer;
import net.minecraft.entity.attribute.EntityAttribute;
import net.minecraft.entity.attribute.EntityAttributeInstance;
import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Environment(EnvType.CLIENT)
@Mixin({LivingEntity.class})
public abstract class MixinLivingEntity extends Entity {
   @Final
   @Shadow
   private static EntityAttributeModifier SPRINTING_SPEED_BOOST;
   @Unique
   private boolean previousElytra = false;
   @Unique
   private long lastLerp = 0L;

   public MixinLivingEntity(EntityType<?> type, World world) {
      super(type, world);
   }

   @Shadow
   @Nullable
   public EntityAttributeInstance getAttributeInstance(RegistryEntry<EntityAttribute> attribute) {
      return this.getAttributes().getCustomInstance(attribute);
   }

   @Shadow
   public AttributeContainer getAttributes() {
      return null;
   }

   @Shadow
   @Override
   public abstract void remove(RemovalReason removalReason);

   @Inject(
      method = {"getHandSwingDuration"},
      at = {@At("HEAD")},
      cancellable = true
   )
   private void getArmSwingAnimationEnd(CallbackInfoReturnable<Integer> info) {
      LivingEntity var2 = LivingEntity.class.cast(this);
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldModifySwingSpeed(var2)) {
         info.setReturnValue(AnimationsModule.INSTANCE.getSwingSpeed());
      } else if (SwingModule.INSTANCE == null
         || !SwingModule.INSTANCE.shouldModifySwingSpeed()
         || SwingModule.INSTANCE.isSelfOnly() && var2 != MinecraftClient.getInstance().player) {
         if (ViewModel.INSTANCE != null && ViewModel.INSTANCE.isOn() && ViewModel.INSTANCE.slowAnimation.getValue()) {
            info.setReturnValue(ViewModel.INSTANCE.slowAnimationVal.getValueInt());
         }
      } else {
         info.setReturnValue(SwingModule.INSTANCE.getSwingSpeed());
      }
   }

   @Inject(
      method = {"updateLimbs(F)V"},
      at = {@At("HEAD")},
      cancellable = true,
      require = 0
   )
   private void animations$updateLimbs(float posDelta, CallbackInfo ci) {
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldCancelLimbSwing()) {
         ci.cancel();
      }
   }

   @Inject(
      method = {"updateLimbs(Z)V"},
      at = {@At("HEAD")},
      cancellable = true,
      require = 0
   )
   private void animations$updateLimbs(boolean flutter, CallbackInfo ci) {
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldCancelLimbSwing()) {
         ci.cancel();
      }
   }

   @Inject(
      method = {"isFallFlying"},
      at = {@At("TAIL")},
      cancellable = true
   )
   public void recastOnLand(CallbackInfoReturnable<Boolean> cir) {
      boolean var2 = (Boolean)cir.getReturnValue();
      if (this.previousElytra && !var2 && ElytraFly.INSTANCE != null && ElytraFly.INSTANCE.isOn() && ElytraFly.INSTANCE.mode.is(ElytraFly.Mode.Bounce)) {
         cir.setReturnValue(ElytraFly.recastElytra(MinecraftClient.getInstance().player));
      }

      this.previousElytra = var2;
   }

   @Inject(
      method = {"getFallFlyingTicks"},
      at = {@At("HEAD")},
      cancellable = true
   )
   public void getFallFlyingTicksHook(CallbackInfoReturnable<Integer> cir) {
      LivingEntity var2 = LivingEntity.class.cast(this);
      if (var2 == MinecraftClient.getInstance().player && ElytraFly.INSTANCE != null && ElytraFly.INSTANCE.shouldVisualSpoof()) {
         cir.setReturnValue(0);
      }
   }

   @Inject(
      method = {"getDimensions"},
      at = {@At("HEAD")},
      cancellable = true
   )
   public void getDimensionsHook(EntityPose pose, CallbackInfoReturnable<EntityDimensions> cir) {
      LivingEntity var3 = LivingEntity.class.cast(this);
      if (var3 == MinecraftClient.getInstance().player
         && ElytraFly.INSTANCE != null
         && ElytraFly.INSTANCE.isOn()
         && ElytraFly.INSTANCE.noAnimation.getValue()
         && var3.isFallFlying()) {
         cir.setReturnValue(super.getDimensions(EntityPose.STANDING));
      }
   }

   @Redirect(
      method = {"travel"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/entity/LivingEntity;hasStatusEffect(Lnet/minecraft/registry/entry/RegistryEntry;)Z"
      ),
      require = 0
   )
   private boolean travelEffectHook(LivingEntity instance, RegistryEntry<StatusEffect> effect) {
      if (AntiEffects.INSTANCE.isOn()) {
         if (effect == StatusEffects.SLOW_FALLING && AntiEffects.INSTANCE.slowFalling.getValue()) {
            return false;
         }

         if (effect == StatusEffects.LEVITATION && AntiEffects.INSTANCE.levitation.getValue()) {
            return false;
         }
      }

      return instance.hasStatusEffect(effect);
   }

   @Redirect(
      method = {"applyMovementInput"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/entity/LivingEntity;isClimbing()Z"
      ),
      require = 0
   )
   public boolean climbingHook(LivingEntity instance) {
      return Velocity.INSTANCE.isOn() && Velocity.INSTANCE.noClimb.getValue() && LivingEntity.class.cast(this) == MinecraftClient.getInstance().player
         ? false
         : instance.isClimbing();
   }

   @Redirect(
      method = {"applyClimbingSpeed"},
      at = @At(
         value = "INVOKE",
         target = "Lnet/minecraft/entity/LivingEntity;isClimbing()Z"
      ),
      require = 0
   )
   public boolean climbingHook2(LivingEntity instance) {
      return NoSlow.INSTANCE.climb() && LivingEntity.class.cast(this) == MinecraftClient.getInstance().player ? false : instance.isClimbing();
   }

   @Inject(
      method = {"updateTrackedPositionAndAngles"},
      at = {@At("HEAD")}
   )
   private void lerpToHook(double x, double y, double z, float yRot, float xRot, int steps, CallbackInfo ci) {
      if (AnimationsModule.INSTANCE != null && AnimationsModule.INSTANCE.shouldNoInterpolation()) {
         this.setPosition(x, y, z);
         this.setYaw(yRot);
         this.setPitch(xRot);
      }

      CrucifixBeta.EVENT_BUS.post(LerpToEvent.get(LivingEntity.class.cast(this), x, y, z, yRot, xRot, this.lastLerp));
      this.lastLerp = System.currentTimeMillis();
   }

   @Inject(
      method = {"setSprinting"},
      at = {@At("HEAD")},
      cancellable = true
   )
   public void setSprintingHook(boolean sprinting, CallbackInfo ci) {
      if (LivingEntity.class.cast(this) == MinecraftClient.getInstance().player) {
         SprintEvent var3 = SprintEvent.get();
         CrucifixBeta.EVENT_BUS.post(var3);
         if (var3.isCancelled()) {
            ci.cancel();
            sprinting = var3.isSprint();
            super.setSprinting(sprinting);
            EntityAttributeInstance var4 = this.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED);
            var4.removeModifier(SPRINTING_SPEED_BOOST.id());
            if (sprinting) {
               var4.addTemporaryModifier(SPRINTING_SPEED_BOOST);
            }
         }
      }
   }
}
