package zenith.zov.client.modules.impl.combat;

import com.darkmagician6.eventapi.EventTarget;
import java.util.List;
import lombok.Generated;
import net.minecraft.client.util.InputUtil;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.Pair;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import zenith.zov.Zenith;
import zenith.zov.base.events.impl.player.EventMoveInput;
import zenith.zov.base.events.impl.player.EventRotate;
import zenith.zov.base.player.AttackUtil;
import zenith.zov.base.rotation.RotationTarget;
import zenith.zov.client.modules.api.Category;
import zenith.zov.client.modules.api.Module;
import zenith.zov.client.modules.api.ModuleAnnotation;
import zenith.zov.client.modules.api.setting.impl.BooleanSetting;
import zenith.zov.client.modules.api.setting.impl.ModeSetting;
import zenith.zov.client.modules.api.setting.impl.MultiBooleanSetting;
import zenith.zov.client.modules.api.setting.impl.NumberSetting;
import zenith.zov.utility.game.player.MovingUtil;
import zenith.zov.utility.game.player.PointFinder;
import zenith.zov.utility.game.player.RaytracingUtil;
import zenith.zov.utility.game.player.SimulatedPlayer;
import zenith.zov.utility.game.player.TargetSelector;
import zenith.zov.utility.game.player.rotation.Rotation;
import zenith.zov.utility.game.player.rotation.RotationUtil;

@ModuleAnnotation(
    name = "Aura",
    category = Category.COMBAT,
    description = "Hits target"
)
public final class Aura extends Module {
    public static final Aura INSTANCE = new Aura();
    private final ModeSetting rotationMode = new ModeSetting("Rotation");
    private final ModeSetting.Value hvh = new ModeSetting.Value(this.rotationMode, "HVH");
    private final ModeSetting.Value hollyworld = new ModeSetting.Value(this.rotationMode, "HollyWorld").select();
    private final ModeSetting sprintMode = new ModeSetting("Sprint");
    private final ModeSetting.Value sprintHvh = new ModeSetting.Value(this.sprintMode, "HVH");
    private final ModeSetting.Value sprintNormal = new ModeSetting.Value(this.sprintMode, "Normal").select();
    private final ModeSetting.Value sprintLegit = new ModeSetting.Value(this.sprintMode, "Legit");
    private final ModeSetting.Value sprintNone = new ModeSetting.Value(this.sprintMode, "None");
    private final ModeSetting correction = new ModeSetting("Correction");
    private final ModeSetting.Value correctionFocus = new ModeSetting.Value(this.correction, "Focus");
    private final ModeSetting.Value correctionGood = new ModeSetting.Value(this.correction, "Free").select();
    private final ModeSetting.Value correctionNone = new ModeSetting.Value(this.correction, "None");
    private final NumberSetting distance = new NumberSetting("Distance", 3.0F, 0.5F, 6.0F, 0.1F, "Attack distance");
    private final NumberSetting distanceRotation = new NumberSetting("Pre-distance", 0.1F, 0.0F, 6.0F, 0.1F);
    private final MultiBooleanSetting settings = new MultiBooleanSetting("Settings");
    private final MultiBooleanSetting.Value shieldBreak = new MultiBooleanSetting.Value(this.settings, "Break Shield", true);
    private final MultiBooleanSetting.Value shielRealese = new MultiBooleanSetting.Value(this.settings, "Release Shield", true);
    private final MultiBooleanSetting.Value eatUseAttack = new MultiBooleanSetting.Value(this.settings, "Hit and Eat", true);
    private final MultiBooleanSetting.Value attackIgnoreWals = new MultiBooleanSetting.Value(this.settings, "Hit Through Walls", true);
    private final MultiBooleanSetting targetTypeSetting = MultiBooleanSetting.create("Attack", List.of("Players", "Hostile", "Peaceful"));
    private final BooleanSetting onlyCrit = new BooleanSetting("Only Crits", true);
    private final BooleanSetting smartCrit = new BooleanSetting("Smart Crits", "Hits with crits if jump button is held", false, this.onlyCrit::isEnabled);
    private final TargetSelector targetSelector = new TargetSelector();
    private final PointFinder pointFinder = new PointFinder();
    private LivingEntity target = null;
    private boolean legitBackStop = false;
    private boolean preAttack = false;
    private boolean isCanAttack = false;

    private Aura() {
    }

    @EventTarget
    public void eventRotate(EventRotate e) {
        if (this.legitBackStop) {
            this.legitBackStop = false;
            mc.options
                .forwardKey
                .setPressed(InputUtil.isKeyPressed(mc.getWindow().getHandle(), mc.options.forwardKey.getDefaultKey().getCode()));
        }

        this.target = this.updateTarget();
        this.preAttack = false;
        this.isCanAttack = false;
        if (this.target != null) {
            Pair<Vec3d, Box> point = this.pointFinder
                .computeVector(
                    this.target, this.distance.getCurrent(), rotationManager.getCurrentRotation(), new Vec3d(0.0, 0.0, 0.0), this.attackIgnoreWals.isEnabled()
                );
            Vec3d eyes = SimulatedPlayer.simulateLocalPlayer(1)
                .pos
                .add(0.0, (double)mc.player.getDimensions(mc.player.getPose()).eyeHeight(), 0.0);
            Rotation angle = RotationUtil.fromVec3d(((Vec3d)point.getLeft()).subtract(eyes));
            Box box = (Box)point.getRight();
            this.preAttack = this.updatePreAttack();
            this.isCanAttack = this.isAttack();
            if (RaytracingUtil.rayTrace(rotationManager.getCurrentRotation().toVector(), (double)this.distance.getCurrent(), box)
                && this.isCanAttack
                && (
                    !Zenith.getInstance().getServerHandler().isServerSprint()
                        || mc.player.isGliding()
                        || AttackUtil.hasMovementRestrictions()
                        || this.sprintHvh.isSelected()
                        || this.sprintNone.isSelected()
                )) {
                if (this.sprintHvh.isSelected()) {
                    mc.player.setSprinting(false);
                    mc.player.sendSprintingPacket();
                }

                AttackUtil.attackEntity(this.target);
                mc.options.sprintKey.setPressed(true);
            }

            this.preAttack = this.updatePreAttack();
            this.isCanAttack = this.isAttack();
            if (this.hvh.isSelected()) {
                rotationManager.setRotation(
                    new RotationTarget(angle, () -> aimManager.rotate(aimManager.getInstantSetup(), angle), aimManager.getInstantSetup()), 3, this
                );
            }

            if (this.hollyworld.isSelected() && (this.preAttack || this.isCanAttack)) {
                rotationManager.setRotation(
                    new RotationTarget(angle, () -> aimManager.rotate(aimManager.getInstantSetup(), angle), aimManager.getAiSetup()), 3, this
                );
            }

            if (this.preAttack || this.isCanAttack) {
                this.updateSprint();
            }
        }
    }

    private boolean updatePreAttack() {
        SimulatedPlayer simulatedPlayer = SimulatedPlayer.simulateLocalPlayer(1);
        if (mc.player.isUsingItem() && !this.eatUseAttack.isEnabled()) {
            return false;
        } else if ((double)mc.player.getAttackCooldownProgress(1.0F) < 0.9) {
            return false;
        } else {
            return this.onlyCrit.isEnabled() && !AttackUtil.hasPreMovementRestrictions(simulatedPlayer)
                ? AttackUtil.isPrePlayerInCriticalState(simulatedPlayer) || this.smartCrit.isEnabled() && !mc.options.jumpKey.isPressed()
                : true;
        }
    }

    private boolean isAttack() {
        if (mc.player.isUsingItem() && !this.eatUseAttack.isEnabled()) {
            return false;
        } else if ((double)mc.player.getAttackCooldownProgress(1.0F) < 0.9) {
            return false;
        } else {
            return this.onlyCrit.isEnabled() && !AttackUtil.hasMovementRestrictions()
                ? AttackUtil.isPlayerInCriticalState() || this.smartCrit.isEnabled() && !mc.options.jumpKey.isPressed()
                : true;
        }
    }

    public void updateSprint() {
        if (this.hasStopSprint()) {
            boolean sprint = mc.options.sprintKey.isPressed();
            boolean forward = mc.options.forwardKey.isPressed();
            if (this.sprintLegit.isSelected()) {
                sprint = false;
                if (mc.player.isSprinting()) {
                    forward = false;
                    this.legitBackStop = true;
                }
            }

            if (this.sprintNormal.isSelected()) {
                if (mc.player.isSprinting()) {
                    mc.player.setSprinting(false);
                }

                sprint = false;
            }

            mc.options.sprintKey.setPressed(sprint);
            mc.options.forwardKey.setPressed(forward);
        }
    }

    public boolean hasStopSprint() {
        return !this.sprintNone.isSelected() && !AttackUtil.hasMovementRestrictions();
    }

    private LivingEntity updateTarget() {
        TargetSelector.EntityFilter filter = new TargetSelector.EntityFilter(this.targetTypeSetting.getSelectedNames());
        this.targetSelector
            .searchTargets(mc.world.getEntities(), this.distance.getCurrent() + this.distanceRotation.getCurrent(), this.attackIgnoreWals.isEnabled());
        this.targetSelector.validateTarget(filter::isValid);
        return this.targetSelector.getCurrentTarget();
    }

    @EventTarget
    private void setCorrection(EventMoveInput eventMoveInput) {
        if (!this.correctionNone.isSelected()) {
            if (this.correctionFocus.isSelected()) {
                Rotation angle = RotationUtil.fromVec3d(this.target.getBoundingBox().getCenter().subtract(mc.player.getBoundingBox().getCenter()));
                MovingUtil.fixMovement(eventMoveInput, rotationManager.getCurrentRotation().getYaw(), angle.getYaw());
            } else {
                MovingUtil.fixMovement(eventMoveInput, rotationManager.getCurrentRotation().getYaw(), mc.player.getYaw());
            }
        }
    }

    public LivingEntity getTarget() {
        return this.isEnabled() ? this.target : null;
    }

    @Generated
    public boolean isPreAttack() {
        return this.preAttack;
    }

    @Generated
    public boolean isCanAttack() {
        return this.isCanAttack;
    }
}
