/*
 *  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.navigation.pathfinder;

import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.Mth;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.PathNavigationRegion;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.pathfinder.*;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;

import java.util.Map;

public class NPCSwimNodeEvaluator extends NPCNodeEvaluator {
    private final boolean allowBreaching;
    private final Long2ObjectMap<BlockPathTypes> pathTypesByPosCache = new Long2ObjectOpenHashMap<>();

    public NPCSwimNodeEvaluator(boolean allowBreaching) {
        this.allowBreaching = allowBreaching;
    }

    public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
        super.prepare(level, npc);
        this.pathTypesByPosCache.clear();
    }

    public void done() {
        super.done();
        this.pathTypesByPosCache.clear();
    }

    public Node getStart() {
        return this.getNode(Mth.floor(this.npc.getBoundingBox().minX), Mth.floor(this.npc.getBoundingBox().minY + 0.5D), Mth.floor(this.npc.getBoundingBox().minZ));
    }

    public Target getGoal(double x, double y, double z) {
        return this.getTargetFromNode(this.getNode(Mth.floor(x), Mth.floor(y), Mth.floor(z)));
    }

    public int getNeighbors(Node[] outputArray, Node p_node) {
        int i = 0;
        Map<Direction, Node> map = Maps.newEnumMap(Direction.class);

        for(Direction direction : Direction.values()) {
            Node node = this.findAcceptedNode(p_node.x + direction.getStepX(), p_node.y + direction.getStepY(), p_node.z + direction.getStepZ());
            map.put(direction, node);
            if (this.isNodeValid(node)) {
                outputArray[i++] = node;
            }
        }

        for(Direction direction1 : Direction.Plane.HORIZONTAL) {
            Direction direction2 = direction1.getClockWise();
            Node node1 = this.findAcceptedNode(p_node.x + direction1.getStepX() + direction2.getStepX(), p_node.y, p_node.z + direction1.getStepZ() + direction2.getStepZ());
            if (this.isDiagonalNodeValid(node1, map.get(direction1), map.get(direction2))) {
                outputArray[i++] = node1;
            }
        }

        return i;
    }

    protected boolean isNodeValid(@Nullable Node node) {
        return node != null && !node.closed;
    }

    protected boolean isDiagonalNodeValid(@Nullable Node root, @Nullable Node horizontal, @Nullable Node clockwise) {
        return this.isNodeValid(root) && horizontal != null && horizontal.costMalus >= 0.0F && clockwise != null && clockwise.costMalus >= 0.0F;
    }

    @Nullable
    protected Node findAcceptedNode(int x, int y, int z) {
        Node node = null;
        BlockPathTypes blockpathtypes = this.getCachedBlockType(x, y, z);
        if (this.allowBreaching && blockpathtypes == BlockPathTypes.BREACH || blockpathtypes == BlockPathTypes.WATER) {
            float f = this.npc.getPathfindingMalus(blockpathtypes);
            if (f >= 0.0F) {
                node = this.getNode(x, y, z);
                node.type = blockpathtypes;
                node.costMalus = Math.max(node.costMalus, f);
                if (this.level.getFluidState(new BlockPos(x, y, z)).isEmpty()) {
                    node.costMalus += 8.0F;
                }
            }
        }

        return node;
    }

    protected BlockPathTypes getCachedBlockType(int x, int y, int z) {
        return this.pathTypesByPosCache.computeIfAbsent(BlockPos.asLong(x, y, z), (p_192957_) -> {
            return this.getBlockPathType(this.level, x, y, z);
        });
    }

    /**
     * Returns the node type at the specified postion taking the block below into account
     */
    public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z) {
        return this.getBlockPathType(level, x, y, z, this.npc);
    }

    public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, NPCServerPlayer mob) {
        BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

        for(int i = x; i < x + this.entityWidth; ++i) {
            for(int j = y; j < y + this.entityHeight; ++j) {
                for(int k = z; k < z + this.entityDepth; ++k) {
                    FluidState fluidstate = level.getFluidState(blockpos$mutableblockpos.set(i, j, k));
                    BlockState blockstate = level.getBlockState(blockpos$mutableblockpos.set(i, j, k));
                    if (fluidstate.isEmpty() && blockstate.isPathfindable(level, blockpos$mutableblockpos.below(), PathComputationType.WATER) && blockstate.isAir()) {
                        return BlockPathTypes.BREACH;
                    }

                    if (!fluidstate.is(FluidTags.WATER)) {
                        return BlockPathTypes.BLOCKED;
                    }
                }
            }
        }

        BlockState blockstate1 = level.getBlockState(blockpos$mutableblockpos);
        return blockstate1.isPathfindable(level, blockpos$mutableblockpos, PathComputationType.WATER) ? BlockPathTypes.WATER : BlockPathTypes.BLOCKED;
    }
}
