package com.tiedup.remake.entities.ai;

import com.tiedup.remake.util.KidnapperAIHelper;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Mob;
import org.jetbrains.annotations.Nullable;

/**
 * Reusable stuck detection for kidnapper AI goals.
 *
 * When the entity stops making progress:
 * 1. First tries random detours to navigate around obstacles
 * 2. After max detour attempts, signals that a teleport is needed
 *
 * Maintains a blacklist of positions where the entity got stuck.
 * Returning to a blacklisted position triggers immediate stuck detection.
 */
public class StuckDetector {

    private BlockPos lastProgressPos;
    private int stuckTicks;
    private int detourAttempts;
    private boolean inDetour;

    @Nullable
    private BlockPos detourTarget;

    /** Positions where the entity previously got stuck (FIFO, max MAX_AVOIDED) */
    private final List<BlockPos> avoidedPositions = new ArrayList<>();
    private static final int MAX_AVOIDED = 5;
    private static final int AVOID_RADIUS_SQ = 9; // 3 blocks squared

    private final int stuckThreshold;
    private final int maxDetours;
    private final int pathFailPenalty;

    /**
     * Create a StuckDetector with default settings.
     * Threshold: 60 ticks (3s), max detours: 3, path fail penalty: 20 ticks.
     */
    public StuckDetector() {
        this(60, 3);
    }

    /**
     * Create a StuckDetector with custom settings.
     *
     * @param stuckThreshold Ticks without progress before considering stuck
     * @param maxDetours     Number of detour attempts before signaling teleport
     */
    public StuckDetector(int stuckThreshold, int maxDetours) {
        this.stuckThreshold = stuckThreshold;
        this.maxDetours = maxDetours;
        this.pathFailPenalty = 20;
        this.stuckTicks = 0;
        this.detourAttempts = 0;
        this.inDetour = false;
        this.detourTarget = null;
    }

    /**
     * Full reset including blacklist. Call on goal start/stop.
     */
    public void reset() {
        this.lastProgressPos = null;
        this.stuckTicks = 0;
        this.detourAttempts = 0;
        this.inDetour = false;
        this.detourTarget = null;
        this.avoidedPositions.clear();
    }

    /**
     * Update stuck detection state. Call every tick.
     *
     * @param entity The mob being tracked
     */
    public void update(Mob entity) {
        BlockPos currentPos = entity.blockPosition();

        // If in detour, check progress towards detour target first
        if (this.inDetour) {
            // Check if reached detour target (within 3 blocks)
            if (
                this.detourTarget != null &&
                currentPos.distSqr(this.detourTarget) <= AVOID_RADIUS_SQ
            ) {
                this.inDetour = false;
                this.detourTarget = null;
                this.stuckTicks = 0;
                this.lastProgressPos = currentPos;
                return;
            }

            // Check if made progress during detour (moved >= 2 blocks)
            if (
                this.lastProgressPos == null ||
                currentPos.distManhattan(this.lastProgressPos) >= 2
            ) {
                this.lastProgressPos = currentPos;
                this.stuckTicks = 0;
                // Don't clear detour yet — keep going until reaching target
            } else {
                this.stuckTicks++;
            }
            // Skip blacklist check while in detour — entity needs time to walk away
            return;
        }

        // NOT in detour: check if current position is near a blacklisted position — instant stuck
        for (BlockPos avoided : avoidedPositions) {
            if (currentPos.distSqr(avoided) <= AVOID_RADIUS_SQ) {
                this.stuckTicks = this.stuckThreshold;
                return;
            }
        }

        // Check if entity made progress (moved >= 2 blocks manhattan)
        if (
            this.lastProgressPos == null ||
            currentPos.distManhattan(this.lastProgressPos) >= 2
        ) {
            this.lastProgressPos = currentPos;
            this.stuckTicks = 0;
        } else {
            this.stuckTicks++;
        }
    }

    /**
     * Add penalty ticks when pathfinding fails. Call when moveTo() returns false.
     */
    public void onPathFailed() {
        this.stuckTicks += this.pathFailPenalty;
    }

    /**
     * @return true if the entity has been stuck long enough to need intervention
     */
    public boolean isStuck() {
        return this.stuckTicks >= this.stuckThreshold;
    }

    /**
     * @return true if detour attempts are exhausted and a teleport is needed
     */
    public boolean shouldTeleport() {
        return isStuck() && this.detourAttempts >= this.maxDetours;
    }

    /**
     * @return true if currently navigating a detour route
     */
    public boolean isInDetour() {
        return this.inDetour;
    }

    /**
     * Try to find a detour position to navigate around the obstacle.
     * Blacklists the current position and finds a random ground position
     * that is not near any blacklisted position.
     *
     * @param level   The server level
     * @param current The entity's current block position
     * @param radius  Search radius for detour points
     * @return A detour position, or null if none found (should teleport)
     */
    @Nullable
    public BlockPos tryDetour(ServerLevel level, BlockPos current, int radius) {
        // Blacklist current position (FIFO eviction)
        if (avoidedPositions.size() >= MAX_AVOIDED) {
            avoidedPositions.remove(0);
        }
        avoidedPositions.add(current.immutable());

        // Try to find a detour point that is NOT near a blacklisted position
        for (int attempt = 0; attempt < 15; attempt++) {
            BlockPos candidate = KidnapperAIHelper.findRandomGroundPos(
                level,
                current,
                radius,
                level.getRandom(),
                1
            );
            if (candidate == null) continue;

            // Check candidate is not near any blacklisted position
            boolean tooClose = false;
            for (BlockPos avoided : avoidedPositions) {
                if (candidate.distSqr(avoided) <= AVOID_RADIUS_SQ) {
                    tooClose = true;
                    break;
                }
            }

            if (!tooClose) {
                this.detourTarget = candidate;
                this.inDetour = true;
                this.detourAttempts++;
                this.stuckTicks = 0;
                return candidate;
            }
        }

        // No valid detour found — caller should teleport
        this.detourAttempts++;
        this.stuckTicks = 0;
        return null;
    }
}
