/*
 *  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 it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
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.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.level.pathfinder.Node;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.level.pathfinder.Target;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCServerPlayer;

import java.util.EnumSet;

public class NPCWalkNodeEvaluator extends NPCNodeEvaluator {
    public static final double SPACE_BETWEEN_WALL_POSTS = 0.5D;
    private static final double DEFAULT_npc_JUMP_HEIGHT = 1.125D;
    private final Long2ObjectMap<BlockPathTypes> pathTypesByPosCache = new Long2ObjectOpenHashMap<>();
    private final Object2BooleanMap<AABB> collisionCache = new Object2BooleanOpenHashMap<>();

    public void prepare(PathNavigationRegion level, NPCServerPlayer npc) {
        super.prepare(level, npc);
        npc.onPathfindingStart();
    }

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

    public Node getStart() {
        BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
        int i = this.npc.getBlockY();
        BlockState blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
        if (!this.npc.canStandOnFluid(blockstate.getFluidState())) {
            if (this.canFloat() && this.npc.isInWater()) {
                while(true) {
                    if (!blockstate.is(Blocks.WATER) && blockstate.getFluidState() != Fluids.WATER.getSource(false)) {
                        --i;
                        break;
                    }

                    ++i;
                    blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
                }
            } else if (this.npc.onGround()) {
                i = Mth.floor(this.npc.getY() + 0.5D);
            } else {
                BlockPos blockpos;
                for(blockpos = this.npc.blockPosition(); (this.level.getBlockState(blockpos).isAir() || this.level.getBlockState(blockpos).isPathfindable(this.level, blockpos, PathComputationType.LAND)) && blockpos.getY() > this.npc.level().getMinBuildHeight(); blockpos = blockpos.below()) {
                }

                i = blockpos.above().getY();
            }
        } else {
            while(this.npc.canStandOnFluid(blockstate.getFluidState())) {
                ++i;
                blockstate = this.level.getBlockState(blockpos$mutableblockpos.set(this.npc.getX(), i, this.npc.getZ()));
            }

            --i;
        }

        BlockPos blockpos1 = this.npc.blockPosition();
        if (!this.canStartAt(blockpos$mutableblockpos.set(blockpos1.getX(), i, blockpos1.getZ()))) {
            AABB aabb = this.npc.getBoundingBox();
            if (this.canStartAt(blockpos$mutableblockpos.set(aabb.minX, i, aabb.minZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.minX, i, aabb.maxZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.maxX, i, aabb.minZ)) || this.canStartAt(blockpos$mutableblockpos.set(aabb.maxX, i, aabb.maxZ))) {
                return this.getStartNode(blockpos$mutableblockpos);
            }
        }

        return this.getStartNode(new BlockPos(blockpos1.getX(), i, blockpos1.getZ()));
    }

    protected Node getStartNode(BlockPos pos) {
        Node node = this.getNode(pos);
        node.type = this.getBlockPathType(this.npc, node.asBlockPos());
        node.costMalus = this.npc.getPathfindingMalus(node.type);
        return node;
    }

    protected boolean canStartAt(BlockPos pos) {
        BlockPathTypes blockpathtypes = this.getBlockPathType(this.npc, pos);
        return blockpathtypes != BlockPathTypes.OPEN && this.npc.getPathfindingMalus(blockpathtypes) >= 0.0F;
    }

    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;
        int j = 0;
        BlockPathTypes blockpathtypes = this.getCachedBlockType(this.npc, p_node.x, p_node.y + 1, p_node.z);
        BlockPathTypes blockpathtypes1 = this.getCachedBlockType(this.npc, p_node.x, p_node.y, p_node.z);
        if (this.npc.getPathfindingMalus(blockpathtypes) >= 0.0F && blockpathtypes1 != BlockPathTypes.STICKY_HONEY) {
            j = Mth.floor(Math.max(1.0F, this.npc.getStepHeight()));
        }

        double d0 = this.getFloorLevel(new BlockPos(p_node.x, p_node.y, p_node.z));
        Node node = this.findAcceptedNode(p_node.x, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
        if (this.isNeighborValid(node, p_node)) {
            outputArray[i++] = node;
        }

        Node node1 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z, j, d0, Direction.WEST, blockpathtypes1);
        if (this.isNeighborValid(node1, p_node)) {
            outputArray[i++] = node1;
        }

        Node node2 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z, j, d0, Direction.EAST, blockpathtypes1);
        if (this.isNeighborValid(node2, p_node)) {
            outputArray[i++] = node2;
        }

        Node node3 = this.findAcceptedNode(p_node.x, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
        if (this.isNeighborValid(node3, p_node)) {
            outputArray[i++] = node3;
        }

        Node node4 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
        if (this.isDiagonalValid(p_node, node1, node3, node4)) {
            outputArray[i++] = node4;
        }

        Node node5 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z - 1, j, d0, Direction.NORTH, blockpathtypes1);
        if (this.isDiagonalValid(p_node, node2, node3, node5)) {
            outputArray[i++] = node5;
        }

        Node node6 = this.findAcceptedNode(p_node.x - 1, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
        if (this.isDiagonalValid(p_node, node1, node, node6)) {
            outputArray[i++] = node6;
        }

        Node node7 = this.findAcceptedNode(p_node.x + 1, p_node.y, p_node.z + 1, j, d0, Direction.SOUTH, blockpathtypes1);
        if (this.isDiagonalValid(p_node, node2, node, node7)) {
            outputArray[i++] = node7;
        }

        return i;
    }

    protected boolean isNeighborValid(@Nullable Node neighbor, Node node) {
        return neighbor != null && !neighbor.closed && (neighbor.costMalus >= 0.0F || node.costMalus < 0.0F);
    }

    protected boolean isDiagonalValid(Node root, @Nullable Node xNode, @Nullable Node zNode, @Nullable Node diagonal) {
        if (diagonal != null && zNode != null && xNode != null) {
            if (diagonal.closed) {
                return false;
            } else if (zNode.y <= root.y && xNode.y <= root.y) {
                if (xNode.type != BlockPathTypes.WALKABLE_DOOR && zNode.type != BlockPathTypes.WALKABLE_DOOR && diagonal.type != BlockPathTypes.WALKABLE_DOOR) {
                    boolean flag = zNode.type == BlockPathTypes.FENCE && xNode.type == BlockPathTypes.FENCE && (double)this.npc.getBbWidth() < 0.5D;
                    return diagonal.costMalus >= 0.0F && (zNode.y < root.y || zNode.costMalus >= 0.0F || flag) && (xNode.y < root.y || xNode.costMalus >= 0.0F || flag);
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private static boolean doesBlockHavePartialCollision(BlockPathTypes blockPathType) {
        return blockPathType == BlockPathTypes.FENCE || blockPathType == BlockPathTypes.DOOR_WOOD_CLOSED || blockPathType == BlockPathTypes.DOOR_IRON_CLOSED;
    }

    private boolean canReachWithoutCollision(Node node) {
        AABB aabb = this.npc.getBoundingBox();
        Vec3 vec3 = new Vec3((double)node.x - this.npc.getX() + aabb.getXsize() / 2.0D, (double)node.y - this.npc.getY() + aabb.getYsize() / 2.0D, (double)node.z - this.npc.getZ() + aabb.getZsize() / 2.0D);
        int i = Mth.ceil(vec3.length() / aabb.getSize());
        vec3 = vec3.scale(1.0F / (float)i);

        for(int j = 1; j <= i; ++j) {
            aabb = aabb.move(vec3);
            if (this.hasCollisions(aabb)) {
                return false;
            }
        }

        return true;
    }

    protected double getFloorLevel(BlockPos pos) {
        return (this.canFloat() || this.isAmphibious()) && this.level.getFluidState(pos).is(FluidTags.WATER) ? (double)pos.getY() + 0.5D : getFloorLevel(this.level, pos);
    }

    public static double getFloorLevel(BlockGetter level, BlockPos pos) {
        BlockPos blockpos = pos.below();
        VoxelShape voxelshape = level.getBlockState(blockpos).getCollisionShape(level, blockpos);
        return (double)blockpos.getY() + (voxelshape.isEmpty() ? 0.0D : voxelshape.max(Direction.Axis.Y));
    }

    protected boolean isAmphibious() {
        return false;
    }

    @Nullable
    protected Node findAcceptedNode(int x, int y, int z, int verticalDeltaLimit, double nodeFloorLevel, Direction direction, BlockPathTypes pathType) {
        Node node = null;
        BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
        double d0 = this.getFloorLevel(blockpos$mutableblockpos.set(x, y, z));
        if (d0 - nodeFloorLevel > this.getnpcJumpHeight()) {
            return null;
        } else {
            BlockPathTypes blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
            float f = this.npc.getPathfindingMalus(blockpathtypes);
            double d1 = (double)this.npc.getBbWidth() / 2.0D;
            if (f >= 0.0F) {
                node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, f);
            }

            if (doesBlockHavePartialCollision(pathType) && node != null && node.costMalus >= 0.0F && !this.canReachWithoutCollision(node)) {
                node = null;
            }

            if (blockpathtypes != BlockPathTypes.WALKABLE && (!this.isAmphibious() || blockpathtypes != BlockPathTypes.WATER)) {
                if ((node == null || node.costMalus < 0.0F) && verticalDeltaLimit > 0 && (blockpathtypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && blockpathtypes != BlockPathTypes.UNPASSABLE_RAIL && blockpathtypes != BlockPathTypes.TRAPDOOR && blockpathtypes != BlockPathTypes.POWDER_SNOW) {
                    node = this.findAcceptedNode(x, y + 1, z, verticalDeltaLimit - 1, nodeFloorLevel, direction, pathType);
                    if (node != null && (node.type == BlockPathTypes.OPEN || node.type == BlockPathTypes.WALKABLE) && this.npc.getBbWidth() < 1.0F) {
                        double d2 = (double)(x - direction.getStepX()) + 0.5D;
                        double d3 = (double)(z - direction.getStepZ()) + 0.5D;
                        AABB aabb = new AABB(d2 - d1, this.getFloorLevel(blockpos$mutableblockpos.set(d2, y + 1, d3)) + 0.001D, d3 - d1, d2 + d1, (double)this.npc.getBbHeight() + this.getFloorLevel(blockpos$mutableblockpos.set(node.x, node.y, (double)node.z)) - 0.002D, d3 + d1);
                        if (this.hasCollisions(aabb)) {
                            node = null;
                        }
                    }
                }

                if (!this.isAmphibious() && blockpathtypes == BlockPathTypes.WATER && !this.canFloat()) {
                    if (this.getCachedBlockType(this.npc, x, y - 1, z) != BlockPathTypes.WATER) {
                        return node;
                    }

                    while(y > this.npc.level().getMinBuildHeight()) {
                        --y;
                        blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
                        if (blockpathtypes != BlockPathTypes.WATER) {
                            return node;
                        }

                        node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, this.npc.getPathfindingMalus(blockpathtypes));
                    }
                }

                if (blockpathtypes == BlockPathTypes.OPEN) {
                    int j = 0;
                    int i = y;

                    while(blockpathtypes == BlockPathTypes.OPEN) {
                        --y;
                        if (y < this.npc.level().getMinBuildHeight()) {
                            return this.getBlockedNode(x, i, z);
                        }

                        if (j++ >= this.npc.getMaxFallDistance()) {
                            return this.getBlockedNode(x, y, z);
                        }

                        blockpathtypes = this.getCachedBlockType(this.npc, x, y, z);
                        f = this.npc.getPathfindingMalus(blockpathtypes);
                        if (blockpathtypes != BlockPathTypes.OPEN && f >= 0.0F) {
                            node = this.getNodeAndUpdateCostToMax(x, y, z, blockpathtypes, f);
                            break;
                        }

                        if (f < 0.0F) {
                            return this.getBlockedNode(x, y, z);
                        }
                    }
                }

                if (doesBlockHavePartialCollision(blockpathtypes) && node == null) {
                    node = this.getNode(x, y, z);
                    node.closed = true;
                    node.type = blockpathtypes;
                    node.costMalus = blockpathtypes.getMalus();
                }

                return node;
            } else {
                return node;
            }
        }
    }

    private double getnpcJumpHeight() {
        return Math.max(1.125D, this.npc.getStepHeight());
    }

    private Node getNodeAndUpdateCostToMax(int x, int y, int z, BlockPathTypes type, float costMalus) {
        Node node = this.getNode(x, y, z);
        node.type = type;
        node.costMalus = Math.max(node.costMalus, costMalus);
        return node;
    }

    private Node getBlockedNode(int x, int y, int z) {
        Node node = this.getNode(x, y, z);
        node.type = BlockPathTypes.BLOCKED;
        node.costMalus = -1.0F;
        return node;
    }

    private boolean hasCollisions(AABB boundingBox) {
        return this.collisionCache.computeIfAbsent(boundingBox, (p_192973_) -> {
            return !this.level.noCollision(this.npc, boundingBox);
        });
    }

    public BlockPathTypes getBlockPathType(BlockGetter level, int x, int y, int z, @NotNull NPCServerPlayer npc) {
        EnumSet<BlockPathTypes> enumset = EnumSet.noneOf(BlockPathTypes.class);
        BlockPathTypes blockpathtypes = BlockPathTypes.BLOCKED;
        blockpathtypes = this.getBlockPathTypes(level, x, y, z, enumset, blockpathtypes, npc.blockPosition());
        if (enumset.contains(BlockPathTypes.FENCE)) {
            return BlockPathTypes.FENCE;
        } else if (enumset.contains(BlockPathTypes.UNPASSABLE_RAIL)) {
            return BlockPathTypes.UNPASSABLE_RAIL;
        } else {
            BlockPathTypes blockpathtypes1 = BlockPathTypes.BLOCKED;

            for(BlockPathTypes blockpathtypes2 : enumset) {
                if (npc.getPathfindingMalus(blockpathtypes2) < 0.0F) {
                    return blockpathtypes2;
                }

                if (npc.getPathfindingMalus(blockpathtypes2) >= npc.getPathfindingMalus(blockpathtypes1)) {
                    blockpathtypes1 = blockpathtypes2;
                }
            }

            return blockpathtypes == BlockPathTypes.OPEN && npc.getPathfindingMalus(blockpathtypes1) == 0.0F && this.entityWidth <= 1 ? BlockPathTypes.OPEN : blockpathtypes1;
        }
    }

    public BlockPathTypes getBlockPathTypes(BlockGetter level, int xOffset, int yOffset, int zOffset, EnumSet<BlockPathTypes> output, BlockPathTypes fallbackPathType, BlockPos pos) {
        for(int i = 0; i < this.entityWidth; ++i) {
            for(int j = 0; j < this.entityHeight; ++j) {
                for(int k = 0; k < this.entityDepth; ++k) {
                    int l = i + xOffset;
                    int i1 = j + yOffset;
                    int j1 = k + zOffset;
                    BlockPathTypes blockpathtypes = this.getBlockPathType(level, l, i1, j1);
                    blockpathtypes = this.evaluateBlockPathType(level, pos, blockpathtypes);
                    if (i == 0 && j == 0 && k == 0) {
                        fallbackPathType = blockpathtypes;
                    }

                    output.add(blockpathtypes);
                }
            }
        }

        return fallbackPathType;
    }

    protected BlockPathTypes evaluateBlockPathType(BlockGetter level, BlockPos pos, BlockPathTypes pathTypes) {
        boolean flag = this.canPassDoors();
        if (pathTypes == BlockPathTypes.DOOR_WOOD_CLOSED && this.canOpenDoors() && flag) {
            pathTypes = BlockPathTypes.WALKABLE_DOOR;
        }

        if (pathTypes == BlockPathTypes.DOOR_OPEN && !flag) {
            pathTypes = BlockPathTypes.BLOCKED;
        }

        if (pathTypes == BlockPathTypes.RAIL && !(level.getBlockState(pos).getBlock() instanceof BaseRailBlock) && !(level.getBlockState(pos.below()).getBlock() instanceof BaseRailBlock)) {
            pathTypes = BlockPathTypes.UNPASSABLE_RAIL;
        }

        return pathTypes;
    }

    /**
     * Returns a significant cached path node type for specified position or calculates it
     */
    protected BlockPathTypes getBlockPathType(NPCServerPlayer entityliving, @NotNull BlockPos pos) {
        return this.getCachedBlockType(entityliving, pos.getX(), pos.getY(), pos.getZ());
    }

    /**
     * Returns a cached path node type for specified position or calculates it
     */
    protected BlockPathTypes getCachedBlockType(NPCServerPlayer entity, int x, int y, int z) {
        return this.pathTypesByPosCache.computeIfAbsent(BlockPos.asLong(x, y, z), (l) -> this.getBlockPathType(this.level, x, y, z, entity));
    }

    /**
     * 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 getBlockPathTypeStatic(level, new BlockPos.MutableBlockPos(x, y, z));
    }

    /**
     * Returns the node type at the specified postion taking the block below into account
     */
    public static BlockPathTypes getBlockPathTypeStatic(BlockGetter level, BlockPos.@NotNull MutableBlockPos pos) {
        int i = pos.getX();
        int j = pos.getY();
        int k = pos.getZ();
        BlockPathTypes blockpathtypes = getBlockPathTypeRaw(level, pos);
        if (blockpathtypes == BlockPathTypes.OPEN && j >= level.getMinBuildHeight() + 1) {
            BlockPathTypes blockpathtypes1 = getBlockPathTypeRaw(level, pos.set(i, j - 1, k));
            blockpathtypes = blockpathtypes1 != BlockPathTypes.WALKABLE && blockpathtypes1 != BlockPathTypes.OPEN && blockpathtypes1 != BlockPathTypes.WATER && blockpathtypes1 != BlockPathTypes.LAVA ? BlockPathTypes.WALKABLE : BlockPathTypes.OPEN;
            if (blockpathtypes1 == BlockPathTypes.DAMAGE_FIRE) {
                blockpathtypes = BlockPathTypes.DAMAGE_FIRE;
            }

            if (blockpathtypes1 == BlockPathTypes.DAMAGE_OTHER) {
                blockpathtypes = BlockPathTypes.DAMAGE_OTHER;
            }

            if (blockpathtypes1 == BlockPathTypes.STICKY_HONEY) {
                blockpathtypes = BlockPathTypes.STICKY_HONEY;
            }

            if (blockpathtypes1 == BlockPathTypes.POWDER_SNOW) {
                blockpathtypes = BlockPathTypes.DANGER_POWDER_SNOW;
            }

            if (blockpathtypes1 == BlockPathTypes.DAMAGE_CAUTIOUS) {
                blockpathtypes = BlockPathTypes.DAMAGE_CAUTIOUS;
            }
        }

        if (blockpathtypes == BlockPathTypes.WALKABLE) {
            blockpathtypes = checkNeighbourBlocks(level, pos.set(i, j, k), blockpathtypes);
        }

        return blockpathtypes;
    }

    /**
     * Returns possible dangers in a 3x3 cube, otherwise nodeType
     */
    public static BlockPathTypes checkNeighbourBlocks(BlockGetter level, BlockPos.@NotNull MutableBlockPos centerPos, BlockPathTypes nodeType) {
        int i = centerPos.getX();
        int j = centerPos.getY();
        int k = centerPos.getZ();

        for(int l = -1; l <= 1; ++l) {
            for(int i1 = -1; i1 <= 1; ++i1) {
                for(int j1 = -1; j1 <= 1; ++j1) {
                    if (l != 0 || j1 != 0) {
                        centerPos.set(i + l, j + i1, k + j1);
                        BlockState blockstate = level.getBlockState(centerPos);
                        BlockPathTypes blockPathType = blockstate.getAdjacentBlockPathType(level, centerPos, null, nodeType);
                        if (blockPathType != null) return blockPathType;
                        FluidState fluidState = blockstate.getFluidState();
                        BlockPathTypes fluidPathType = fluidState.getAdjacentBlockPathType(level, centerPos, null, nodeType);
                        if (fluidPathType != null) return fluidPathType;
                        if (blockstate.is(Blocks.CACTUS) || blockstate.is(Blocks.SWEET_BERRY_BUSH)) {
                            return BlockPathTypes.DANGER_OTHER;
                        }

                        if (isBurningBlock(blockstate)) {
                            return BlockPathTypes.DANGER_FIRE;
                        }

                        if (level.getFluidState(centerPos).is(FluidTags.WATER)) {
                            return BlockPathTypes.WATER_BORDER;
                        }

                        if (blockstate.is(Blocks.WITHER_ROSE) || blockstate.is(Blocks.POINTED_DRIPSTONE)) {
                            return BlockPathTypes.DAMAGE_CAUTIOUS;
                        }
                    }
                }
            }
        }

        return nodeType;
    }

    protected static BlockPathTypes getBlockPathTypeRaw(@NotNull BlockGetter level, BlockPos pos) {
        BlockState blockstate = level.getBlockState(pos);
        BlockPathTypes type = blockstate.getBlockPathType(level, pos, null);
        if (type != null) return type;
        Block block = blockstate.getBlock();
        if (blockstate.isAir()) {
            return BlockPathTypes.OPEN;
        } else if (!blockstate.is(BlockTags.TRAPDOORS) && !blockstate.is(Blocks.LILY_PAD) && !blockstate.is(Blocks.BIG_DRIPLEAF)) {
            if (blockstate.is(Blocks.POWDER_SNOW)) {
                return BlockPathTypes.POWDER_SNOW;
            } else if (!blockstate.is(Blocks.CACTUS) && !blockstate.is(Blocks.SWEET_BERRY_BUSH)) {
                if (blockstate.is(Blocks.HONEY_BLOCK)) {
                    return BlockPathTypes.STICKY_HONEY;
                } else if (blockstate.is(Blocks.COCOA)) {
                    return BlockPathTypes.COCOA;
                } else if (!blockstate.is(Blocks.WITHER_ROSE) && !blockstate.is(Blocks.POINTED_DRIPSTONE)) {
                    FluidState fluidstate = level.getFluidState(pos);
                    BlockPathTypes nonLoggableFluidPathType = fluidstate.getBlockPathType(level, pos, null, false);
                    if (nonLoggableFluidPathType != null) return nonLoggableFluidPathType;
                    if (fluidstate.is(FluidTags.LAVA)) {
                        return BlockPathTypes.LAVA;
                    } else if (isBurningBlock(blockstate)) {
                        return BlockPathTypes.DAMAGE_FIRE;
                    } else if (block instanceof DoorBlock doorblock) {
                        if (blockstate.getValue(DoorBlock.OPEN)) {
                            return BlockPathTypes.DOOR_OPEN;
                        } else {
                            return doorblock.type().canOpenByHand() ? BlockPathTypes.DOOR_WOOD_CLOSED : BlockPathTypes.DOOR_IRON_CLOSED;
                        }
                    } else if (block instanceof BaseRailBlock) {
                        return BlockPathTypes.RAIL;
                    } else if (block instanceof LeavesBlock) {
                        return BlockPathTypes.LEAVES;
                    } else if (!blockstate.is(BlockTags.FENCES) && !blockstate.is(BlockTags.WALLS) && (!(block instanceof FenceGateBlock) || blockstate.getValue(FenceGateBlock.OPEN))) {
                        if (!blockstate.isPathfindable(level, pos, PathComputationType.LAND)) {
                            return BlockPathTypes.BLOCKED;
                        } else {
                            BlockPathTypes loggableFluidPathType = fluidstate.getBlockPathType(level, pos, null, true);
                            if (loggableFluidPathType != null) return loggableFluidPathType;
                            return fluidstate.is(FluidTags.WATER) ? BlockPathTypes.WATER : BlockPathTypes.OPEN;
                        }
                    } else {
                        return BlockPathTypes.FENCE;
                    }
                } else {
                    return BlockPathTypes.DAMAGE_CAUTIOUS;
                }
            } else {
                return BlockPathTypes.DAMAGE_OTHER;
            }
        } else {
            return BlockPathTypes.TRAPDOOR;
        }
    }

    /**
     * Checks whether the specified block state can cause burn damage
     */
    public static boolean isBurningBlock(@NotNull BlockState state) {
        return state.is(BlockTags.FIRE) || state.is(Blocks.LAVA) || state.is(Blocks.MAGMA_BLOCK) || CampfireBlock.isLitCampfire(state) || state.is(Blocks.LAVA_CAULDRON);
    }
}