package com.tiedup.remake.entities.ai.kidnapper;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.StuckDetector;
import com.tiedup.remake.util.KidnapperAIHelper;
import java.util.EnumSet;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.level.Level;

/**
 * Abstract base class for kidnapper flee goals.
 *
 *
 * Subclasses:
 * - KidnapperFleeSafeGoal: Flee without slave
 * - KidnapperFleeWithCaptiveGoal: Flee with captive, then release
 */
public abstract class AbstractKidnapperFleeGoal extends Goal {

    protected final EntityKidnapper kidnapper;

    /** Current flee timer */
    protected int fleeTimer;

    /** Starting position for distance calculation */
    protected BlockPos startPos;

    /** Current flee target */
    protected BlockPos fleeTarget;

    /** Stuck detection — lower threshold (40 ticks = 2s) since being stuck while fleeing is very visible */
    protected final StuckDetector stuckDetector = new StuckDetector(40, 2);

    /**
     * Create a new flee goal.
     *
     * @param kidnapper The kidnapper entity
     */
    public AbstractKidnapperFleeGoal(EntityKidnapper kidnapper) {
        this.kidnapper = kidnapper;
        this.fleeTimer = 0;
        this.startPos = null;
        this.fleeTarget = null;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE));
    }

    // ==================== CONFIGURATION (Abstract) ====================

    /** Get the flee speed. */
    protected abstract double getFleeSpeed();

    /** Get the maximum flee duration in ticks. */
    protected abstract int getMaxFleeDuration();

    /** Get the maximum flee distance in blocks. */
    protected abstract double getMaxFleeDistance();

    /** Get the search range radius for flee targets. */
    protected abstract int getSearchRangeRadius();

    // ==================== LOGIC HOOKS (Abstract) ====================

    /**
     * Check if the captive condition is met.
     * @return true if the goal should activate based on captive state
     */
    protected abstract boolean checkCaptiveCondition();

    /**
     * Called when the flee is complete (distance or time reached).
     */
    protected abstract void onFleeComplete();

    // ==================== OPTIONAL HOOKS (Overrideable) ====================

    /**
     * Called at the start of fleeing.
     * Override for dialogue or logging.
     */
    protected void onStart() {
        // Default: do nothing
    }

    // ==================== GOAL IMPLEMENTATION ====================

    @Override
    public boolean canUse() {
        // Must be in "get out" state
        if (!this.kidnapper.isGetOutState()) {
            return false;
        }

        // Check captive condition (subclass-specific)
        if (!checkCaptiveCondition()) {
            return false;
        }

        // Must not be tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        return true;
    }

    @Override
    public boolean canContinueToUse() {
        // Stop if no longer in get out state
        if (!this.kidnapper.isGetOutState()) {
            return false;
        }

        // Check captive condition (subclass-specific)
        if (!checkCaptiveCondition()) {
            return false;
        }

        // Stop if tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        return true;
    }

    @Override
    public void start() {
        this.fleeTimer = 0;
        this.startPos = this.kidnapper.blockPosition();
        this.fleeTarget = null;
        this.stuckDetector.reset();

        // Hook for subclasses
        onStart();

        selectFleeTarget();
    }

    @Override
    public void stop() {
        this.kidnapper.getNavigation().stop();
        this.fleeTimer = 0;
        this.startPos = null;
        this.fleeTarget = null;
        this.stuckDetector.reset();
    }

    @Override
    public void tick() {
        this.fleeTimer++;

        // Check if reached safe distance or timeout
        double distanceFromStart = this.kidnapper.blockPosition().distSqr(
            this.startPos
        );
        double maxDist = getMaxFleeDistance();
        double maxDistSq = maxDist * maxDist;

        if (
            this.fleeTimer >= getMaxFleeDuration() ||
            distanceFromStart >= maxDistSq
        ) {
            onFleeComplete();
            return;
        }

        // Continue fleeing - recalculate path frequently
        if (this.fleeTarget == null || reachedFleeTarget()) {
            selectFleeTarget();
            this.stuckDetector.reset();
        }

        // Update stuck detection
        this.stuckDetector.update(this.kidnapper);

        if (this.stuckDetector.isStuck()) {
            if (this.stuckDetector.shouldTeleport()) {
                // Teleport to a safe position away from current location
                if (this.kidnapper.level() instanceof ServerLevel serverLevel) {
                    BlockPos safePos = KidnapperAIHelper.findSafePosition(
                        serverLevel,
                        this.kidnapper.blockPosition(),
                        getSearchRangeRadius(),
                        serverLevel.getRandom()
                    );
                    if (safePos != null) {
                        this.kidnapper.teleportTo(
                            safePos.getX() + 0.5,
                            safePos.getY(),
                            safePos.getZ() + 0.5
                        );
                        // Also teleport captive if leashed
                        com.tiedup.remake.state.IBondageState captive =
                            this.kidnapper.getCaptive();
                        if (
                            captive != null &&
                            captive.asLivingEntity().isAlive()
                        ) {
                            captive
                                .asLivingEntity()
                                .teleportTo(
                                    safePos.getX() + 0.5,
                                    safePos.getY(),
                                    safePos.getZ() + 0.5
                                );
                        }
                        TiedUpMod.LOGGER.debug(
                            "[AbstractKidnapperFleeGoal] {} teleported to {} to unblock flee",
                            this.kidnapper.getNpcName(),
                            safePos.toShortString()
                        );
                    }
                }
                this.stuckDetector.reset();
            } else if (
                this.kidnapper.level() instanceof ServerLevel serverLevel
            ) {
                BlockPos detour = this.stuckDetector.tryDetour(
                    serverLevel,
                    this.kidnapper.blockPosition(),
                    5
                );
                if (detour != null) {
                    this.kidnapper.getNavigation().moveTo(
                        detour.getX() + 0.5,
                        detour.getY(),
                        detour.getZ() + 0.5,
                        getFleeSpeed()
                    );
                }
            }
            return;
        }

        // Skip normal navigation if in detour
        if (this.stuckDetector.isInDetour()) {
            return;
        }

        // Move to flee target - recalculate every few ticks for smooth movement
        if (
            this.fleeTarget != null &&
            (this.fleeTimer % 3 == 0 ||
                !this.kidnapper.getNavigation().isInProgress())
        ) {
            boolean pathFound = this.kidnapper.getNavigation().moveTo(
                this.fleeTarget.getX() + 0.5,
                this.fleeTarget.getY(),
                this.fleeTarget.getZ() + 0.5,
                getFleeSpeed()
            );
            if (!pathFound) {
                this.stuckDetector.onPathFailed();
            }
        }
    }

    // ==================== HELPER METHODS ====================

    /**
     * Select a random flee target away from start position.
     */
    protected void selectFleeTarget() {
        Level level = this.kidnapper.level();
        int range = getSearchRangeRadius();

        // Try to find a valid flee point
        for (int attempts = 0; attempts < 10; attempts++) {
            // Random direction, biased away from start
            int offsetX = this.kidnapper.getRandom().nextInt(range * 2) - range;
            int offsetZ = this.kidnapper.getRandom().nextInt(range * 2) - range;

            // Bias away from start position
            if (this.startPos != null) {
                double dirX = this.kidnapper.getX() - this.startPos.getX();
                double dirZ = this.kidnapper.getZ() - this.startPos.getZ();
                double len = Math.sqrt(dirX * dirX + dirZ * dirZ);
                if (len > 0.1) {
                    offsetX += (int) ((dirX / len) * range);
                    offsetZ += (int) ((dirZ / len) * range);
                }
            }

            int targetX = (int) this.kidnapper.getX() + offsetX;
            int targetZ = (int) this.kidnapper.getZ() + offsetZ;
            int targetY = (int) this.kidnapper.getY();

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

            // Find ground level
            for (int yOffset = -5; yOffset <= 5; yOffset++) {
                BlockPos checkPos = testPos.offset(0, yOffset, 0);

                if (isValidFleePoint(level, checkPos)) {
                    this.fleeTarget = checkPos;
                    return;
                }
            }
        }
    }

    /**
     * Check if reached current flee target.
     */
    protected boolean reachedFleeTarget() {
        if (this.fleeTarget == null) return true;

        double distSq = this.kidnapper.distanceToSqr(
            this.fleeTarget.getX() + 0.5,
            this.fleeTarget.getY(),
            this.fleeTarget.getZ() + 0.5
        );

        return distSq < 4.0;
    }

    /**
     * Check if a position is valid for fleeing.
     * Delegates to KidnapperAIHelper for consistent ground position checks.
     */
    protected boolean isValidFleePoint(Level level, BlockPos pos) {
        return KidnapperAIHelper.isValidGroundPosition(level, pos);
    }
}
