package com.tiedup.remake.entities.damsel.components;

import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.entities.ai.damsel.*;
import com.tiedup.remake.entities.ai.personality.*;
import com.tiedup.remake.state.ICaptor;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.OpenDoorGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.target.TargetGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

/**
 * Manages all AI-related systems for EntityDamsel:
 * - AI goals registration (25+ goals in priority order)
 * - Call for help system (captive calling for rescue)
 * - Leash traction system (prevents stuck while leashed)
 *
 */
public class DamselAIController {

    private final EntityDamsel entity;
    private final IAIHost host;

    // CALL FOR HELP SYSTEM

    /** Cooldown between call for help messages */
    private int callForHelpCooldown = 0;

    /** Radius to search for players to call for help (blocks) */
    private static final int CALL_FOR_HELP_RADIUS = 15;

    /** Minimum cooldown for call for help (5 seconds) */
    private static final int CALL_FOR_HELP_COOLDOWN_MIN = 100;

    /** Maximum cooldown for call for help (10 seconds) */
    private static final int CALL_FOR_HELP_COOLDOWN_MAX = 200;

    // LEASH TRACTION SYSTEM

    /** Counter for how long NPC has been stuck while leashed */
    private int leashStuckCounter = 0;

    /** Previous X position for stuck detection */
    private double leashPrevX;

    /** Previous Z position for stuck detection */
    private double leashPrevZ;

    /** Distance from holder at which pulling starts (blocks) */
    private static final double LEASH_PULL_START_DISTANCE = 2.5;

    /** Distance at which stuck teleport begins (blocks) */
    private static final double LEASH_TELEPORT_DISTANCE = 5.0;

    /** Maximum distance before forced teleport (blocks) */
    private static final double LEASH_MAX_DISTANCE = 8.0;

    /** Ticks stuck before teleport (40 ticks = 2 seconds) */
    private static final int LEASH_STUCK_THRESHOLD = 40;

    /** FIX: Max traction force - reduced for smoother movement */
    private static final double LEASH_MAX_FORCE = 0.12;

    /** FIX: Force ramp factor - gradual increase based on distance */
    private static final double LEASH_FORCE_RAMP = 0.05;

    // CONSTRUCTOR

    public DamselAIController(EntityDamsel entity, IAIHost host) {
        this.entity = entity;
        this.host = host;
    }

    // AI GOALS REGISTRATION

    /**
     * Register all AI goals for this entity.
     * Must be called from EntityDamsel.registerGoals() with goalSelector and targetSelector.
     *
     * @param goalSelector The goal selector from Mob
     * @param targetSelector The target selector from Mob
     */
    public void registerGoals(
        net.minecraft.world.entity.ai.goal.GoalSelector goalSelector,
        net.minecraft.world.entity.ai.goal.GoalSelector targetSelector
    ) {
        // Priority 0: Always swim (FloatGoal)
        goalSelector.addGoal(0, new FloatGoal(entity));

        // Priority 1: Personality command goals (take precedence when active)
        goalSelector.addGoal(1, new NpcFollowCommandGoal(entity, 1.0));
        // NpcHeelCommandGoal removed - HEEL is now a FollowDistance mode in NpcFollowCommandGoal
        goalSelector.addGoal(1, new NpcStayCommandGoal(entity));
        goalSelector.addGoal(1, new NpcIdleCommandGoal(entity));
        goalSelector.addGoal(1, new NpcComeCommandGoal(entity, 1.2));
        goalSelector.addGoal(1, new NpcGoHomeGoal(entity));
        goalSelector.addGoal(1, new NpcSitCommandGoal(entity));
        goalSelector.addGoal(1, new NpcKneelCommandGoal(entity));
        goalSelector.addGoal(1, new NpcPatrolCommandGoal(entity, 0.9));
        goalSelector.addGoal(1, new NpcGuardCommandGoal(entity));
        goalSelector.addGoal(1, new NpcFetchCommandGoal(entity, 1.1));
        goalSelector.addGoal(1, new NpcCollectCommandGoal(entity, 1.0));
        // NOTE: Combat goals (Defend, Attack, Capture) removed.
        // Combat behavior is now handled by NpcFollowCommandGoal based on main hand item.
        // Work commands (FARM, COOK, STORE) - chest-hub system
        goalSelector.addGoal(1, new NpcFarmCommandGoal(entity, 0.9));
        goalSelector.addGoal(1, new NpcCookCommandGoal(entity, 1.0));
        goalSelector.addGoal(1, new NpcTransferCommandGoal(entity, 1.0));
        goalSelector.addGoal(1, new NpcShearCommandGoal(entity, 1.0));
        goalSelector.addGoal(1, new NpcMineCommandGoal(entity, 0.9));
        goalSelector.addGoal(1, new NpcBreedCommandGoal(entity, 1.0));
        goalSelector.addGoal(1, new NpcFishCommandGoal(entity, 0.9));
        goalSelector.addGoal(1, new NpcSortCommandGoal(entity, 1.0));

        // Priority 2: Struggle goal (passive, runs alongside other goals - no flags)
        goalSelector.addGoal(2, new NpcStruggleGoal(entity));

        // Priority 2: Auto-eat goal (passive, eats from inventory when hungry)
        goalSelector.addGoal(2, new NpcAutoEatGoal(entity));

        // Priority 2: Auto-rest goal (passive, goes home to rest when tired)
        goalSelector.addGoal(2, new NpcAutoRestGoal(entity));

        // Priority 2-5: Custom conditional AI goals
        goalSelector.addGoal(
            2,
            new DamselFleeFromPlayerGoal(entity, 10.0f, 1.1, 1.3)
        );
        goalSelector.addGoal(3, new DamselPanicGoal(entity, 1.0));
        goalSelector.addGoal(4, new DamselWanderGoal(entity, 1.2));
        goalSelector.addGoal(
            5,
            new DamselWatchPlayerGoal(entity, Player.class, 6.0f)
        );

        // Priority 6-7: Basic vanilla goals
        goalSelector.addGoal(6, new RandomLookAroundGoal(entity));
        goalSelector.addGoal(7, new OpenDoorGoal(entity, false));
    }

    // COMMAND GOALS UTILITY

    /**
     * Register all NPC command goals on a goal selector.
     * Used by subclasses (EntityMaid, EntitySlaveTrader, EntityMaster) that override
     * registerGoals() but still need command functionality.
     *
     * @param goalSelector The goal selector to register on
     * @param entity The entity (must extend EntityDamsel)
     * @param priority The priority level for command goals
     */
    public static void registerCommandGoals(
        net.minecraft.world.entity.ai.goal.GoalSelector goalSelector,
        EntityDamsel entity,
        int priority
    ) {
        goalSelector.addGoal(priority, new NpcFollowCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcStayCommandGoal(entity));
        goalSelector.addGoal(priority, new NpcIdleCommandGoal(entity));
        goalSelector.addGoal(priority, new NpcComeCommandGoal(entity, 1.2));
        goalSelector.addGoal(priority, new NpcGoHomeGoal(entity));
        goalSelector.addGoal(priority, new NpcSitCommandGoal(entity));
        goalSelector.addGoal(priority, new NpcKneelCommandGoal(entity));
        goalSelector.addGoal(priority, new NpcPatrolCommandGoal(entity, 0.9));
        goalSelector.addGoal(priority, new NpcGuardCommandGoal(entity));
        goalSelector.addGoal(priority, new NpcFetchCommandGoal(entity, 1.1));
        goalSelector.addGoal(priority, new NpcCollectCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcFarmCommandGoal(entity, 0.9));
        goalSelector.addGoal(priority, new NpcCookCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcTransferCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcShearCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcMineCommandGoal(entity, 0.9));
        goalSelector.addGoal(priority, new NpcBreedCommandGoal(entity, 1.0));
        goalSelector.addGoal(priority, new NpcFishCommandGoal(entity, 0.9));
        goalSelector.addGoal(priority, new NpcSortCommandGoal(entity, 1.0));
    }

    // CALL FOR HELP SYSTEM

    /**
     * Periodically call for help to nearby players when being led as a captive.
     * Only works if:
     * - Damsel is tied up
     * - Damsel is a captive (being led)
     * - Not gagged (can still speak)
     * - There's a player nearby who is NOT the captor
     *
     * Call this from EntityDamsel.aiStep().
     */
    public void tickCallForHelp() {
        // Decrement cooldown
        if (this.callForHelpCooldown > 0) {
            this.callForHelpCooldown--;
            return;
        }

        // Check if this entity can call for help (overridable in EntityKidnapper)
        if (!entity.canCallForHelp()) {
            return;
        }

        // Must be tied and a captive to call for help
        if (!host.isTiedUp() || !host.isCaptive()) {
            return;
        }

        // Can't call for help if gagged
        if (host.isGagged()) {
            return;
        }

        // Find nearby players who could help
        List<Player> nearbyPlayers = host
            .level()
            .getEntitiesOfClass(
                Player.class,
                host
                    .getBoundingBox()
                    .inflate(SettingsAccessor.getDialogueRadius())
            );

        // Get captor entity for comparison
        Entity captorEntity =
            host.getBondageManager().getCaptor() != null
                ? host.getBondageManager().getCaptor().getEntity()
                : null;

        boolean foundTarget = false;

        for (Player player : nearbyPlayers) {
            // Skip creative/spectator
            if (player.isCreative() || player.isSpectator()) continue;

            // Skip the captor - don't ask your captor for help!
            if (captorEntity != null && player == captorEntity) continue;

            // Call for help to this player
            TiedUpMod.LOGGER.debug(
                "[EntityDamsel] {} calling for help to {}",
                host.getNpcName(),
                player.getName().getString()
            );
            com.tiedup.remake.dialogue.EntityDialogueManager.callForHelp(
                entity,
                player
            );

            // Found a target, set flag
            foundTarget = true;
            break;
        }

        // Reset cooldown
        int baseCooldown = SettingsAccessor.getDialogueCooldown();
        if (foundTarget) {
            // Full cooldown if we talked
            this.callForHelpCooldown =
                baseCooldown + host.getRandom().nextInt(baseCooldown);
        } else {
            // Short cooldown if no one found (prevent spam) - 2 to 4 seconds
            this.callForHelpCooldown = 40 + host.getRandom().nextInt(40);
        }
    }

    // LEASH TRACTION SYSTEM

    /**
     * Tick leash traction system for NPCs.
     * Detects when NPC is stuck while leashed and teleports to holder.
     * This mirrors the player leash system in MixinServerPlayer.
     *
     * FIX: Now applies ACTIVE traction force towards holder (was missing).
     * FIX: Increases step height temporarily when being pulled to climb stairs.
     *
     * Call this from EntityDamsel.aiStep().
     */
    public void tickLeashTraction() {
        // Only process if leashed
        Entity leashHolder = host.getLeashHolder();
        if (leashHolder == null) {
            this.leashStuckCounter = 0;
            // Reset step height when not leashed
            if (host.maxUpStep() > 0.6f) {
                host.setMaxUpStep(0.6f);
            }
            return;
        }

        // Same level check
        if (leashHolder.level() != host.level()) {
            return;
        }

        float distance = host.distanceTo(leashHolder);

        // Close enough: reset stuck counter, reset step height
        if (distance < LEASH_PULL_START_DISTANCE) {
            this.leashStuckCounter = 0;
            if (host.maxUpStep() > 0.6f) {
                host.setMaxUpStep(0.6f);
            }
            return;
        }

        // Too far: forced teleport to holder (regardless of stuck status)
        if (distance > LEASH_MAX_DISTANCE) {
            teleportToSafePositionNearHolder(leashHolder);
            this.leashStuckCounter = 0;
            return;
        }

        // Calculate direction to holder
        double dx = leashHolder.getX() - host.getX();
        double dy = leashHolder.getY() - host.getY();
        double dz = leashHolder.getZ() - host.getZ();

        // Calculate movement since last tick
        double movedX = host.getX() - this.leashPrevX;
        double movedZ = host.getZ() - this.leashPrevZ;
        double movedHorizontal = Math.sqrt(movedX * movedX + movedZ * movedZ);

        // Store current position for next tick
        this.leashPrevX = host.getX();
        this.leashPrevZ = host.getZ();

        // FIX: Apply ACTIVE traction force towards holder with smooth ramping
        if (distance > LEASH_PULL_START_DISTANCE) {
            // Normalize direction
            double horizontalDist = Math.sqrt(dx * dx + dz * dz);
            double dirX = horizontalDist > 0.01 ? dx / horizontalDist : 0;
            double dirZ = horizontalDist > 0.01 ? dz / horizontalDist : 0;

            // FIX: Smoother force calculation - gradual ramp up
            // Force increases with distance but is capped for smooth movement
            double distanceBeyond = distance - LEASH_PULL_START_DISTANCE;
            double forceFactor = Math.min(
                LEASH_MAX_FORCE,
                distanceBeyond * LEASH_FORCE_RAMP
            );

            // FIX: Blend with current motion instead of adding directly
            // This prevents jerky acceleration
            net.minecraft.world.phys.Vec3 currentMotion =
                host.getDeltaMovement();
            double blendFactor = 0.7; // 70% new direction, 30% current momentum

            double newVelX =
                currentMotion.x * (1 - blendFactor) +
                dirX * forceFactor * blendFactor * 3;
            double newVelZ =
                currentMotion.z * (1 - blendFactor) +
                dirZ * forceFactor * blendFactor * 3;
            double newVelY = currentMotion.y + (dy > 0.5 ? 0.03 : 0); // Gentler Y boost

            host.setDeltaMovement(
                new net.minecraft.world.phys.Vec3(newVelX, newVelY, newVelZ)
            );

            // Increase step height while being pulled (helps with stairs)
            host.setMaxUpStep(1.0f);
        }

        // Strict stuck detection: not moving despite being far
        boolean isStuck =
            distance > LEASH_TELEPORT_DISTANCE &&
            host.getDeltaMovement().horizontalDistanceSqr() < 0.001 &&
            movedHorizontal < 0.05;

        if (isStuck) {
            this.leashStuckCounter++;

            // Safety teleport if stuck for too long (30 ticks = 1.5 sec)
            if (this.leashStuckCounter >= LEASH_STUCK_THRESHOLD) {
                teleportToSafePositionNearHolder(leashHolder);
                this.leashStuckCounter = 0;
            }
        } else {
            // Reset stuck counter if moving
            this.leashStuckCounter = 0;
        }
    }

    /**
     * Teleport NPC to a safe position near the leash holder.
     */
    private void teleportToSafePositionNearHolder(Entity holder) {
        // Target: 2 blocks away from holder in our direction
        double dx = host.getX() - holder.getX();
        double dz = host.getZ() - holder.getZ();
        double dist = Math.sqrt(dx * dx + dz * dz);

        double offsetX = 0;
        double offsetZ = 0;
        if (dist > 0.1) {
            offsetX = (dx / dist) * 2.0;
            offsetZ = (dz / dist) * 2.0;
        }

        double targetX = holder.getX() + offsetX;
        double targetZ = holder.getZ() + offsetZ;

        // Find safe Y (ground level)
        double targetY = findSafeY(targetX, holder.getY(), targetZ);

        host.teleportTo(targetX, targetY, targetZ);

        TiedUpMod.LOGGER.debug(
            "[EntityDamsel] {} stuck while leashed, teleported to holder",
            host.getNpcName()
        );
    }

    /**
     * Find a safe Y coordinate for teleporting.
     */
    private double findSafeY(double x, double startY, double z) {
        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();

        // Search down first (max 5 blocks)
        for (int y = 0; y > -5; y--) {
            mutable.set((int) x, (int) startY + y, (int) z);
            if (
                host
                    .level()
                    .getBlockState(mutable)
                    .isSolidRender(host.level(), mutable) &&
                host.level().getBlockState(mutable.above()).isAir()
            ) {
                return mutable.getY() + 1;
            }
        }

        // Search up (max 5 blocks)
        for (int y = 1; y < 5; y++) {
            mutable.set((int) x, (int) startY + y, (int) z);
            if (
                host
                    .level()
                    .getBlockState(mutable.below())
                    .isSolidRender(host.level(), mutable.below()) &&
                host.level().getBlockState(mutable).isAir()
            ) {
                return mutable.getY();
            }
        }

        // Fallback: use holder's Y
        return startY;
    }
}
