package com.tiedup.remake.util;

import com.tiedup.remake.entities.EntityKidnapper;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import org.jetbrains.annotations.Nullable;

/**
 * Helper methods for Kidnapper AI goals.
 *
 * Phase: Code Quality Refactoring
 *
 * Consolidates duplicated logic from multiple AI goals:
 * - Ground position finding (AbstractKidnapperFleeGoal, KidnapperPatrolGoal)
 * - Common "can use" preconditions
 */
public final class KidnapperAIHelper {

    private KidnapperAIHelper() {
        // Utility class - prevent instantiation
    }

    // ==================== GROUND POSITION FINDING ====================

    /**
     * Check if a position is valid for walking (solid ground below, air at feet and head).
     *
     * @param level The world level
     * @param pos   The position to check (feet position)
     * @return true if the position is walkable
     */
    public static boolean isValidGroundPosition(Level level, BlockPos pos) {
        // Need solid ground below
        BlockPos below = pos.below();
        if (!level.getBlockState(below).isSolid()) {
            return false;
        }

        // Need air at feet and head level
        if (!level.getBlockState(pos).isAir()) {
            return false;
        }
        if (!level.getBlockState(pos.above()).isAir()) {
            return false;
        }

        return true;
    }

    /**
     * Find a valid ground position near a target position.
     * Searches in a vertical range of -5 to +5 blocks.
     *
     * @param level     The world level
     * @param targetPos The target position to search around
     * @return A valid ground position, or null if none found
     */
    @Nullable
    public static BlockPos findGroundPos(Level level, BlockPos targetPos) {
        return findGroundPos(level, targetPos, 5);
    }

    /**
     * Find a valid ground position near a target position.
     *
     * @param level       The world level
     * @param targetPos   The target position to search around
     * @param ySearchRange Vertical search range (up and down)
     * @return A valid ground position, or null if none found
     */
    @Nullable
    public static BlockPos findGroundPos(
        Level level,
        BlockPos targetPos,
        int ySearchRange
    ) {
        for (int yOffset = -ySearchRange; yOffset <= ySearchRange; yOffset++) {
            BlockPos checkPos = targetPos.offset(0, yOffset, 0);
            if (isValidGroundPosition(level, checkPos)) {
                return checkPos;
            }
        }
        return null;
    }

    /**
     * Find a random ground position within a radius.
     *
     * @param level   The world level
     * @param center  The center position
     * @param radius  The search radius
     * @param random  Random source
     * @param attempts Maximum number of attempts
     * @return A valid ground position, or null if none found
     */
    @Nullable
    public static BlockPos findRandomGroundPos(
        Level level,
        BlockPos center,
        int radius,
        RandomSource random,
        int attempts
    ) {
        for (int i = 0; i < attempts; i++) {
            int offsetX = random.nextInt(radius * 2 + 1) - radius;
            int offsetZ = random.nextInt(radius * 2 + 1) - radius;

            BlockPos targetPos = center.offset(offsetX, 0, offsetZ);
            BlockPos groundPos = findGroundPos(level, targetPos);

            if (groundPos != null) {
                return groundPos;
            }
        }
        return null;
    }

    // ==================== SAFE TELEPORT POSITION FINDING ====================

    /**
     * Find a safe position for teleportation within a radius.
     * Uses polar coordinates with bias towards farther positions.
     *
     * <p>Consolidated from EntityKidnapper, AbstractKidnapperFleeGoal, MaidExtractPrisonerGoal.</p>
     *
     * @param level     The server level
     * @param center    The center position to search from
     * @param radius    Maximum distance from center
     * @param random    Random source
     * @return Safe block position, or null if none found after 20 attempts
     */
    @Nullable
    public static BlockPos findSafePosition(
        ServerLevel level,
        BlockPos center,
        int radius,
        RandomSource random
    ) {
        // Try up to 20 random positions
        for (int attempt = 0; attempt < 20; attempt++) {
            // Random angle
            double angle = random.nextDouble() * Math.PI * 2;
            // Random distance (bias towards farther)
            double distance = radius * 0.7 + random.nextDouble() * radius * 0.3;

            int targetX = (int) (center.getX() + Math.cos(angle) * distance);
            int targetZ = (int) (center.getZ() + Math.sin(angle) * distance);

            // Find ground level using heightmap
            int targetY = level.getHeight(
                Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
                targetX,
                targetZ
            );

            BlockPos candidate = new BlockPos(targetX, targetY, targetZ);

            // Check if position is safe
            if (isSafeForTeleport(level, candidate)) {
                return candidate;
            }
        }

        return null;
    }

    /**
     * Find a safe position with a fallback if none found.
     *
     * @param level     The server level
     * @param center    The center position to search from
     * @param radius    Maximum distance from center
     * @param random    Random source
     * @return Safe block position, or fallback position if none found
     */
    public static BlockPos findSafePositionOrFallback(
        ServerLevel level,
        BlockPos center,
        int radius,
        RandomSource random
    ) {
        BlockPos safe = findSafePosition(level, center, radius, random);
        if (safe != null) {
            return safe;
        }
        // Fallback: offset from center
        return center.offset(radius, 0, 0);
    }

    /**
     * Check if a position is safe for teleporting an entity.
     *
     * @param level The level to check in
     * @param pos   The position to check (feet level)
     * @return true if position is safe for teleportation
     */
    public static boolean isSafeForTeleport(Level level, BlockPos pos) {
        BlockState feetBlock = level.getBlockState(pos);
        BlockState headBlock = level.getBlockState(pos.above());
        BlockState groundBlock = level.getBlockState(pos.below());

        // Ground must be solid
        if (!groundBlock.isSolid()) {
            return false;
        }

        // Feet and head must be passable (not solid)
        if (feetBlock.isSolid() || headBlock.isSolid()) {
            return false;
        }

        // Check for dangerous blocks
        var feetFluid = feetBlock.getFluidState();
        if (!feetFluid.isEmpty()) {
            return false; // Water or lava
        }

        // Check for fire, cactus, etc.
        if (
            feetBlock.getBlock() instanceof
                net.minecraft.world.level.block.FireBlock ||
            feetBlock.getBlock() instanceof
                net.minecraft.world.level.block.CactusBlock ||
            feetBlock.getBlock() instanceof
                net.minecraft.world.level.block.SweetBerryBushBlock ||
            feetBlock.getBlock() instanceof
                net.minecraft.world.level.block.MagmaBlock
        ) {
            return false;
        }

        return true;
    }

    // ==================== COMMON PRECONDITIONS ====================

    /**
     * Check common preconditions for kidnapper goals that require an active kidnapper.
     * Checks: not tied up.
     *
     * @param kidnapper The kidnapper entity
     * @return true if the kidnapper can act
     */
    public static boolean canKidnapperAct(EntityKidnapper kidnapper) {
        return !kidnapper.isTiedUp();
    }

    /**
     * Check common preconditions for kidnapper goals that require no captive.
     * Checks: not tied up, no captive, no target.
     *
     * @param kidnapper The kidnapper entity
     * @return true if the kidnapper can search for targets
     */
    public static boolean canKidnapperSearch(EntityKidnapper kidnapper) {
        if (kidnapper.isTiedUp()) return false;
        if (kidnapper.hasCaptives()) return false;
        if (kidnapper.getTarget() != null) return false;
        return true;
    }

    /**
     * Check common preconditions for kidnapper goals that handle captives.
     * Checks: not tied up, has captive, not in get-out state.
     *
     * @param kidnapper The kidnapper entity
     * @return true if the kidnapper can handle their captive
     */
    public static boolean canKidnapperHandleCaptive(EntityKidnapper kidnapper) {
        if (kidnapper.isTiedUp()) return false;
        if (!kidnapper.hasCaptives()) return false;
        if (kidnapper.isGetOutState()) return false;
        return true;
    }

    /**
     * Check common preconditions for kidnapper goals during captive transport.
     * Checks: not tied up, has captive, not for sale, not waiting for job.
     *
     * @param kidnapper The kidnapper entity
     * @return true if the kidnapper can transport their captive
     */
    public static boolean canKidnapperTransport(EntityKidnapper kidnapper) {
        if (!canKidnapperHandleCaptive(kidnapper)) return false;

        var captive = kidnapper.getCaptive();
        if (captive != null && captive.isForSell()) return false;
        if (kidnapper.isWaitingForJobToBeCompleted()) return false;

        return true;
    }

    // ==================== CELL NAVIGATION ====================

    /**
     * Get the best position to deliver/extract a prisoner at a cell.
     * Prefers DELIVERY marker, falls back to spawn point.
     *
     * <p>Consolidated from KidnapperBringToCellGoal, KidnapperWalkPrisonerGoal,
     * MaidExtractPrisonerGoal, MaidReturnPrisonerGoal.</p>
     *
     * @param cell  The cell data
     * @param level The world level
     * @return Best standing position for the cell
     */
    public static BlockPos getDeliveryOrSpawnPoint(
        com.tiedup.remake.cells.CellDataV2 cell,
        Level level
    ) {
        BlockPos deliveryPoint = cell.getDeliveryPoint();
        if (deliveryPoint != null) {
            return findStandablePosition(deliveryPoint, level);
        }
        return cell.getSpawnPoint() != null
            ? cell.getSpawnPoint()
            : cell.getCorePos().above();
    }

    /**
     * Find a standable position near a marker.
     * Handles both floor-placed markers (stand above) and air-placed markers (stand at marker).
     *
     * @param marker The marker position
     * @param level  The world level
     * @return A valid standing position
     */
    public static BlockPos findStandablePosition(BlockPos marker, Level level) {
        // 1. Check if marker itself is standable (air/passable block, solid below)
        if (isStandable(level, marker)) {
            return marker;
        }

        // 2. Check above - classic case where marker is the floor block
        if (isStandable(level, marker.above())) {
            return marker.above();
        }

        // 3. Search below - case where marker is floating/placed in air above the floor
        for (int y = 1; y <= 5; y++) {
            BlockPos below = marker.below(y);
            if (isStandable(level, below)) {
                return below;
            }
        }

        // Fallback: above the marker
        return marker.above();
    }

    /**
     * Check if a position is standable (entity can stand there).
     * More permissive than isSafeForTeleport - uses blocksMotion() instead of isSolid().
     *
     * @param level The world level
     * @param pos   The position to check (feet level)
     * @return true if an entity can stand at this position
     */
    public static boolean isStandable(Level level, BlockPos pos) {
        net.minecraft.world.level.block.state.BlockState below =
            level.getBlockState(pos.below());
        net.minecraft.world.level.block.state.BlockState atPos =
            level.getBlockState(pos);
        net.minecraft.world.level.block.state.BlockState above =
            level.getBlockState(pos.above());

        return (
            below.isSolid() && !atPos.blocksMotion() && !above.blocksMotion()
        );
    }
}
