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

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.EnumSet;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;

/**
 * AI Goal for EntityMaster dogwalk mode.
 *
 * Two modes:
 * - Master leads (masterLeads=true): Master walks, player is pulled by leash
 * - Pet leads (masterLeads=false): Master follows the pet at close distance
 *
 * The leash physics automatically pulls the player when Master moves away,
 * handled by the LeashProxyEntity and player leash mixin.
 */
public class MasterDogwalkGoal extends Goal {

    private final EntityMaster master;

    /** If true, Master walks and pulls pet. If false, Master follows pet. */
    private boolean masterLeads = false;

    /** Distance to maintain from pet when following */
    private static final double LEASH_DISTANCE_PET_LEADS = 3.5;

    /** Maximum distance before waiting for pet (when master leads) */
    private static final double MAX_DISTANCE = 10.0;

    /** Navigation speed (used by masterLeads) */
    private static final double WALK_SPEED = 0.6;

    /** Speed when pet is close (3.5-6 blocks) - gentle follow */
    private static final double FOLLOW_CLOSE_SPEED = 0.5;

    /** Speed when pet is far (6+ blocks) - catching up */
    private static final double FOLLOW_CATCHUP_SPEED = 0.9;

    /** Stop following when closer than this */
    private static final double FOLLOW_STOP_DISTANCE = 2.5;

    /** Timer for random direction changes when master leads */
    private int directionChangeTimer = 0;

    /** Current walk destination (when master leads) */
    private Vec3 walkTarget = null;

    /** Cooldown for "wait for pet" dialogue */
    private int waitDialogueCooldown = 0;

    /** Maximum dogwalk duration (ticks) - 1-2 minutes */
    private static final int MIN_WALK_DURATION = 1200; // 1 minute
    private static final int MAX_WALK_DURATION = 2400; // 2 minutes

    /** Current walk duration for this session */
    private int walkDuration = 0;

    /** Walk timer */
    private int walkTimer = 0;

    /** FIX: Stuck timer - counts ticks while waiting for pet */
    private int stuckTimer = 0;

    /** FIX: Maximum stuck time before teleporting pet (15 seconds) */
    private static final int MAX_STUCK_TIME = 300;

    public MasterDogwalkGoal(EntityMaster master) {
        this.master = master;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
    }

    /**
     * Set whether Master leads the walk.
     */
    public void setMasterLeads(boolean masterLeads) {
        this.masterLeads = masterLeads;
    }

    @Override
    public boolean canUse() {
        return (
            master.getStateManager().getCurrentState() == MasterState.DOGWALK &&
            master.hasPet()
        );
    }

    @Override
    public boolean canContinueToUse() {
        if (!master.hasPet()) {
            return false;
        }

        ServerPlayer pet = master.getPetPlayer();
        if (pet == null || !pet.isAlive()) {
            return false;
        }

        // End walk if duration exceeded
        if (walkTimer >= walkDuration) {
            return false;
        }

        return (
            master.getStateManager().getCurrentState() == MasterState.DOGWALK
        );
    }

    @Override
    public void start() {
        this.directionChangeTimer = 0;
        this.walkTarget = null;
        this.waitDialogueCooldown = 0;
        this.walkTimer = 0;
        this.stuckTimer = 0;

        // Random walk duration between 2-3 minutes
        this.walkDuration =
            MIN_WALK_DURATION +
            master.getRandom().nextInt(MAX_WALK_DURATION - MIN_WALK_DURATION);

        // Get current mode from Master
        this.masterLeads = master.isDogwalkMasterLeads();

        // When pet leads, give extra leash slack so they can walk ahead
        if (!masterLeads) {
            ServerPlayer pet = master.getPetPlayer();
            if (pet instanceof IPlayerLeashAccess access) {
                access.tiedup$setLeashSlack(5.0); // 3+5=8 blocks before pull
            }
        }

        TiedUpMod.LOGGER.debug(
            "[MasterDogwalkGoal] {} started dogwalk (masterLeads={}, duration={} ticks)",
            master.getNpcName(),
            masterLeads,
            walkDuration
        );
    }

    @Override
    public void stop() {
        master.getNavigation().stop();
        this.walkTarget = null;

        // Clean up: remove dogbind and detach leash
        cleanupDogwalk();

        // FIX: Transition out of DOGWALK state so the goal doesn't restart
        if (master.getStateManager().getCurrentState() == MasterState.DOGWALK) {
            master.setMasterState(MasterState.FOLLOWING);
        }

        TiedUpMod.LOGGER.debug(
            "[MasterDogwalkGoal] {} stopped dogwalk",
            master.getNpcName()
        );
    }

    /**
     * Clean up after dogwalk ends.
     * Removes dogbind from player and detaches leash.
     */
    private void cleanupDogwalk() {
        ServerPlayer pet = master.getPetPlayer();
        if (pet == null) return;

        // Reset leash slack
        if (pet instanceof IPlayerLeashAccess access) {
            access.tiedup$setLeashSlack(0.0);
        }

        // Detach leash
        master.detachLeashFromPet();

        // Remove dogbind from player
        PlayerBindState state = PlayerBindState.getInstance(pet);
        if (state != null && state.isTiedUp()) {
            state.unequip(BodyRegionV2.ARMS);
            TiedUpMod.LOGGER.debug(
                "[MasterDogwalkGoal] Removed dogbind from {} after walk",
                pet.getName().getString()
            );
        }

        // Dialogue for walk end
        DialogueBridge.talkTo(master, pet, "petplay.walk_end");
    }

    @Override
    public void tick() {
        ServerPlayer pet = master.getPetPlayer();
        if (pet == null) return;

        // Always look at pet
        master.getLookControl().setLookAt(pet, 30.0F, 30.0F);

        double dist = master.distanceTo(pet);

        // Increment walk timer
        walkTimer++;

        // Decrement dialogue cooldown
        if (waitDialogueCooldown > 0) {
            waitDialogueCooldown--;
        }

        if (masterLeads) {
            tickMasterLeads(pet, dist);
        } else {
            tickMasterFollows(pet, dist);
        }
    }

    /**
     * Tick when Master leads the walk.
     * Master walks in a direction, pet is pulled by leash physics.
     */
    private void tickMasterLeads(ServerPlayer pet, double dist) {
        // If pet is too far behind, wait for them
        if (dist > MAX_DISTANCE) {
            master.getNavigation().stop();
            stuckTimer++;

            // Occasionally tell pet to keep up
            if (waitDialogueCooldown <= 0) {
                DialogueBridge.talkTo(master, pet, "petplay.wait_pet");
                waitDialogueCooldown = 200; // 10 seconds cooldown
            }

            // FIX: If stuck too long, teleport pet to master
            if (stuckTimer >= MAX_STUCK_TIME) {
                teleportPetToMaster(pet);
                stuckTimer = 0;
            }
            return;
        }

        // Pet caught up, reset stuck timer
        stuckTimer = 0;

        // Update direction timer
        directionChangeTimer--;

        // Pick new random direction periodically or if no target
        if (
            directionChangeTimer <= 0 ||
            walkTarget == null ||
            !master.getNavigation().isInProgress()
        ) {
            pickNewWalkDirection();
            directionChangeTimer = 100 + master.getRandom().nextInt(100); // 5-10 seconds
        }

        // Navigate to target
        if (walkTarget != null && !master.getNavigation().isInProgress()) {
            master
                .getNavigation()
                .moveTo(walkTarget.x, walkTarget.y, walkTarget.z, WALK_SPEED);
        }
    }

    /**
     * Pick a new random direction for walking.
     */
    private void pickNewWalkDirection() {
        // Random angle
        double angle = master.getRandom().nextDouble() * Math.PI * 2;
        double distance = 8.0 + master.getRandom().nextDouble() * 8.0; // 8-16 blocks

        double x = master.getX() + Math.cos(angle) * distance;
        double z = master.getZ() + Math.sin(angle) * distance;

        // FIX: Use safe ground finder instead of heightmap (works indoors)
        int groundY = findSafeGroundY(
            master.level(),
            (int) x,
            (int) z,
            (int) master.getY() + 10
        );
        double y = groundY;

        walkTarget = new Vec3(x, y, z);

        TiedUpMod.LOGGER.debug(
            "[MasterDogwalkGoal] {} picked new walk target: ({}, {}, {})",
            master.getNpcName(),
            (int) x,
            (int) y,
            (int) z
        );
    }

    /**
     * Find a safe ground Y position by scanning downward.
     * Works correctly indoors (doesn't return roof height like heightmap).
     *
     * @param level The level
     * @param x Target X coordinate
     * @param z Target Z coordinate
     * @param startY Starting Y to scan from (usually entity Y + some offset)
     * @return A safe Y position with solid ground below and 2 air blocks above
     */
    private int findSafeGroundY(Level level, int x, int z, int startY) {
        BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(
            x,
            startY,
            z
        );

        // Scan downward to find a solid floor with 2 air blocks above
        for (int y = startY; y > level.getMinBuildHeight(); y--) {
            pos.setY(y);
            BlockState below = level.getBlockState(pos.below());
            BlockState at = level.getBlockState(pos);
            BlockState above = level.getBlockState(pos.above());

            // Solid floor + 2 blocks of clearance above
            if (below.isSolid() && !at.isSolid() && !above.isSolid()) {
                return y;
            }
        }
        return startY; // Fallback if nothing found
    }

    /**
     * Tick when Master follows the pet.
     * Uses progressive speed ramp: slow when close, faster to catch up.
     */
    private void tickMasterFollows(ServerPlayer pet, double dist) {
        if (dist > LEASH_DISTANCE_PET_LEADS) {
            // Speed ramp: slow when close, faster to catch up
            double speed;
            if (dist > 6.0) {
                speed = FOLLOW_CATCHUP_SPEED; // 0.9 - catching up
            } else {
                speed = FOLLOW_CLOSE_SPEED; // 0.5 - gentle follow
            }
            master.getNavigation().moveTo(pet, speed);
        } else if (dist < FOLLOW_STOP_DISTANCE) {
            master.getNavigation().stop();
        }
        // Between FOLLOW_STOP_DISTANCE and LEASH_DISTANCE_PET_LEADS:
        // keep current movement, don't snap
    }

    /**
     * FIX: Teleport pet to master when stuck too long.
     */
    private void teleportPetToMaster(ServerPlayer pet) {
        double angle = master.getRandom().nextDouble() * Math.PI * 2;
        double distance = 1.5;

        double x = master.getX() + Math.cos(angle) * distance;
        double z = master.getZ() + Math.sin(angle) * distance;
        double y = master.getY();

        pet.teleportTo(x, y, z);

        DialogueBridge.talkTo(master, pet, "petplay.come_here");

        TiedUpMod.LOGGER.debug(
            "[MasterDogwalkGoal] {} teleported stuck pet {} to master",
            master.getNpcName(),
            pet.getName().getString()
        );
    }

    /**
     * End the dogwalk and return to FOLLOWING state.
     * Cleanup (remove dogbind, detach leash) happens automatically in stop().
     */
    public void endDogwalk() {
        // Just change state - stop() will be called automatically and handle cleanup
        master.setMasterState(MasterState.FOLLOWING);
    }
}
