/*
 *  Copyright 2025-2026 R3944Realms
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package top.r3944realms.eroticdungeongame.content.entity.npc.ai.control;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.Control;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.level.pathfinder.NodeEvaluator;
import net.minecraft.world.phys.shapes.VoxelShape;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.NPCPathNavigation;
import top.r3944realms.eroticdungeongame.content.entity.npc.ai.navigation.pathfinder.NPCNodeEvaluator;

public class NPCMoveControl implements Control {
    public static final float MIN_SPEED = 5.0E-4F;
    public static final float MIN_SPEED_SQR = 2.5000003E-7F;
    protected static final int MAX_TURN = 90;
    protected final NPCServerPlayer npc;
    protected double wantedX;
    protected double wantedY;
    protected double wantedZ;
    protected double speedModifier;
    protected float strafeForwards;
    protected float strafeRight;
    protected Operation operation = Operation.WAIT;

    public NPCMoveControl(NPCServerPlayer npc) {
        this.npc = npc;
    }

    public boolean hasWanted() {
        return this.operation == Operation.MOVE_TO;
    }

    public double getSpeedModifier() {
        return this.speedModifier;
    }

    /**
     * Sets the speed and location to move to
     */
    public void setWantedPosition(double x, double y, double z, double speed) {
        this.wantedX = x;
        this.wantedY = y;
        this.wantedZ = z;
        this.speedModifier = speed;
        if (this.operation != Operation.JUMPING) {
            this.operation = Operation.MOVE_TO;
        }

    }

    public void strafe(float forward, float strafe) {
        this.operation = Operation.STRAFE;
        this.strafeForwards = forward;
        this.strafeRight = strafe;
        this.speedModifier = 0.25D;
    }

    public void tick() {
        if (this.operation == Operation.STRAFE) {
            float f = (float)this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED);
            float f1 = (float)this.speedModifier * f;
            float f2 = this.strafeForwards;
            float f3 = this.strafeRight;
            float f4 = Mth.sqrt(f2 * f2 + f3 * f3);
            if (f4 < 1.0F) {
                f4 = 1.0F;
            }

            f4 = f1 / f4;
            f2 *= f4;
            f3 *= f4;
            float f5 = Mth.sin(this.npc.getYRot() * ((float)Math.PI / 180F));
            float f6 = Mth.cos(this.npc.getYRot() * ((float)Math.PI / 180F));
            float f7 = f2 * f6 - f3 * f5;
            float f8 = f3 * f6 + f2 * f5;
            if (!this.isWalkable(f7, f8)) {
                this.strafeForwards = 1.0F;
                this.strafeRight = 0.0F;
            }

            this.npc.setSpeed(f1);
            this.npc.setZza(this.strafeForwards);
            this.npc.setXxa(this.strafeRight);
            this.operation = Operation.WAIT;
        } else if (this.operation == Operation.MOVE_TO) {
            this.operation = Operation.WAIT;
            double d0 = this.wantedX - this.npc.getX();
            double d1 = this.wantedZ - this.npc.getZ();
            double d2 = this.wantedY - this.npc.getY();
            double d3 = d0 * d0 + d2 * d2 + d1 * d1;
            if (d3 < (double)2.5000003E-7F) {
                this.npc.setZza(0.0F);
                return;
            }

            float f9 = (float)(Mth.atan2(d1, d0) * (double)(180F / (float)Math.PI)) - 90.0F;
            this.npc.setYRot(this.rotlerp(this.npc.getYRot(), f9, 90.0F));
            this.npc.setSpeed((float)(this.speedModifier * this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED)));
            BlockPos blockpos = this.npc.blockPosition();
            BlockState blockstate = this.npc.level().getBlockState(blockpos);
            VoxelShape voxelshape = blockstate.getCollisionShape(this.npc.level(), blockpos);
            if (d2 > (double)this.npc.getStepHeight() && d0 * d0 + d1 * d1 < (double)Math.max(1.0F, this.npc.getBbWidth()) || !voxelshape.isEmpty() && this.npc.getY() < voxelshape.max(Direction.Axis.Y) + (double)blockpos.getY() && !blockstate.is(BlockTags.DOORS) && !blockstate.is(BlockTags.FENCES)) {
                this.npc.getJumpControl().jump();
                this.operation = Operation.JUMPING;
            }
        } else if (this.operation == Operation.JUMPING) {
            this.npc.setSpeed((float)(this.speedModifier * this.npc.getAttributeValue(Attributes.MOVEMENT_SPEED)));
            if (this.npc.onGround()) {
                this.operation = Operation.WAIT;
            }
        } else {
            this.npc.setZza(0.0F);
        }

    }

    /**
     * @return true if the mob can walk successfully to a given X and Z
     */
    private boolean isWalkable(float relativeX, float relativeZ) {
        NPCPathNavigation pathnavigation = this.npc.getNavigation();
        if (pathnavigation != null) {
            NPCNodeEvaluator nodeevaluator = pathnavigation.getNodeEvaluator();
            return nodeevaluator == null || nodeevaluator.getBlockPathType(this.npc.level(), Mth.floor(this.npc.getX() + (double) relativeX), this.npc.getBlockY(), Mth.floor(this.npc.getZ() + (double) relativeZ)) == BlockPathTypes.WALKABLE;
        }

        return true;
    }

    /**
     * Attempt to rotate the first angle to become the second angle, but only allow overall direction change to at max be third parameter
     */
    protected float rotlerp(float sourceAngle, float targetAngle, float maximumChange) {
        float f = Mth.wrapDegrees(targetAngle - sourceAngle);
        if (f > maximumChange) {
            f = maximumChange;
        }

        if (f < -maximumChange) {
            f = -maximumChange;
        }

        float f1 = sourceAngle + f;
        if (f1 < 0.0F) {
            f1 += 360.0F;
        } else if (f1 > 360.0F) {
            f1 -= 360.0F;
        }

        return f1;
    }

    public double getWantedX() {
        return this.wantedX;
    }

    public double getWantedY() {
        return this.wantedY;
    }

    public double getWantedZ() {
        return this.wantedZ;
    }

    protected enum Operation {
        WAIT,
        MOVE_TO,
        STRAFE,
        JUMPING;
    }
}
