package net.Chipperfluff.chipi.entity;

import net.minecraft.entity.EntityType;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.goal.LookAroundGoal;
import net.minecraft.entity.ai.goal.LookAtEntityGoal;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.entity.attribute.DefaultAttributeContainer;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.UUID;

public class PlayerJrEntity extends PathAwareEntity {

    /* ================= RULES (CHEATING FRIEND) ================= */

    private static final double FORCE_FLY_DISTANCE = 50.0;      // fly if farther than this (horizontal)
    private static final int STUCK_TICKS_TO_FLY = 40;           // 2 seconds of no horizontal progress
    private static final double STUCK_MIN_CLOSING = 0.10;       // horizontal progress threshold
    private static final double STUCK_MIN_DISTANCE = 6.0;       // don't care if already close
    private static final double DAD_MOVING_EPS_SQ = 0.02;       // if dad is moving, don't call it stuck

    private static final int LAND_RADIUS = 6;                   // search dry landing within this radius
    private static final double LAND_HORIZONTAL_DISTANCE = 2.0; // must be this close (horiz) to land
    private static final double HOVER_HEIGHT = 1.25;            // hover above landing block
    private static final int MIN_FLY_TICKS = 20;                // must fly at least this long

    private static final double FLY_SPEED = 0.55;
    private static final double FLY_ACCEL = 0.25;
    private static final int MAX_VERTICAL_GAP = 6;              // if dad is this high above -> fly

    /* ================= STATE ================= */

    private UUID dadUuid;
    private String dadName = "";

    private enum Mode { GROUND, FLYING }
    private Mode mode = Mode.GROUND;

    private int flyTicks = 0;

    // Stuck tracking (ground)
    private int stuckTicks = 0;
    private double lastDadHorizDist = -1;

    // Landing / hover state
    private BlockPos landingSpot = null;
    private BlockPos forcedLandingSpot = null;

    /* ================= CONSTRUCTOR ================= */

    public PlayerJrEntity(EntityType<? extends PathAwareEntity> type, World world) {
        super(type, world);
    }

    public static DefaultAttributeContainer.Builder createAttributes() {
        return MobEntity.createMobAttributes()
            .add(EntityAttributes.GENERIC_MAX_HEALTH, 10.0)
            .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25)
            .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 64.0);
    }

    /* ================= DAD ================= */

    public void setDad(ServerPlayerEntity dad) {
        dadUuid = dad.getUuid();
        dadName = dad.getGameProfile().getName();
        setCustomName(Text.literal(dadName + " jr."));
        setCustomNameVisible(true);
    }

    private ServerPlayerEntity getDad() {
        if (dadUuid == null || getWorld().isClient) return null;
        return getWorld().getServer().getPlayerManager().getPlayer(dadUuid);
    }

    public String getDadName() { return dadName; }

    /* ================= AI ================= */

    @Override
    protected void initGoals() {
        goalSelector.add(1, new SwimGoal(this));
        goalSelector.add(2, new FollowDadGoal(this));
        goalSelector.add(3, new LookAtEntityGoal(this, PlayerEntity.class, 6f));
        goalSelector.add(4, new LookAroundGoal(this));
    }

    /* ================= TICK ================= */

    @Override
    public void tick() {
        super.tick();
        if (getWorld().isClient) return;

        // Never fall damage ever (you asked this specifically)
        this.fallDistance = 0.0f;

        ServerPlayerEntity dad = getDad();
        if (dad == null) return;

        switch (mode) {
            case GROUND -> doGroundBrain(dad);
            case FLYING -> doFlyingBrain(dad);
        }
    }

    /* ================= CORE BRAIN: GROUND ================= */

    private void doGroundBrain(ServerPlayerEntity dad) {
        // Water cheat: if land is nearby, prefer walking out; if no path, fly briefly to land.
        if (this.isTouchingWater()) {
            BlockPos escape = findLandingSpotNear(this.getBlockPos(), LAND_RADIUS);
            if (escape != null) {
                boolean canWalk = getNavigation().startMovingTo(
                    escape.getX() + 0.5,
                    escape.getY(),
                    escape.getZ() + 0.5,
                    1.1
                );
                if (canWalk) {
                    resetStuckTracking(dad);
                    return;
                }
                forcedLandingSpot = escape;
                startFlying(dad);
                return;
            }
        }

        // Update stuck tracking (only on ground mode)
        updateStuckTracking(dad);
        if (stuckTicks >= STUCK_TICKS_TO_FLY) {
            startFlying(dad);
            return;
        }

        // Too far behind (impatient cheat)
        if (horizontalDistanceTo(dad) > FORCE_FLY_DISTANCE) {
            startFlying(dad);
            return;
        }

        // Dad is way above us (can't reach)
        double dy = dad.getY() - getY();
        if (dy > MAX_VERTICAL_GAP && horizontalDistanceTo(dad) > 2.0) {
            startFlying(dad);
        }
    }

    private void updateStuckTracking(ServerPlayerEntity dad) {
        boolean tryingToWalk = !getNavigation().isIdle();
        double dadDist = horizontalDistanceTo(dad);
        double dadSpeedSq = dad.getVelocity().horizontalLengthSquared();

        if (!tryingToWalk || dadDist <= STUCK_MIN_DISTANCE || dadSpeedSq > DAD_MOVING_EPS_SQ) {
            stuckTicks = 0;
            lastDadHorizDist = dadDist;
            return;
        }

        if (lastDadHorizDist < 0) {
            lastDadHorizDist = dadDist;
            stuckTicks = 0;
            return;
        }

        double closing = lastDadHorizDist - dadDist; // positive means getting closer
        if (closing < STUCK_MIN_CLOSING) stuckTicks++;
        else stuckTicks = 0;

        lastDadHorizDist = dadDist;
    }

    private void resetStuckTracking(ServerPlayerEntity dad) {
        stuckTicks = 0;
        lastDadHorizDist = horizontalDistanceTo(dad);
    }

    /* ================= CORE BRAIN: FLYING ================= */

    private void startFlying(ServerPlayerEntity dad) {
        mode = Mode.FLYING;
        flyTicks = 0;

        landingSpot = null;

        setNoGravity(true);
        getNavigation().stop();
        setVelocity(Vec3d.ZERO);
        velocityDirty = true;

        dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Creative"), false);
    }

    private void doFlyingBrain(ServerPlayerEntity dad) {
        flyTicks++;

        double horiz = horizontalDistanceTo(dad);

        if (flyTicks >= MIN_FLY_TICKS) {
            BlockPos candidate = forcedLandingSpot;
            if (candidate == null) {
                candidate = findLandingSpotNear(dad.getBlockPos(), LAND_RADIUS);
            }
            if (candidate != null && !wouldImmediatelyRefly(dad, candidate)) {
                landingSpot = candidate;
                if (horizontalDistanceTo(landingSpot) <= LAND_HORIZONTAL_DISTANCE) {
                    stopFlying(dad);
                    return;
                }
            }
        }

        BlockPos targetSpot = forcedLandingSpot != null ? forcedLandingSpot : landingSpot;
        Vec3d target = targetSpot != null
            ? Vec3d.ofCenter(targetSpot).add(0, HOVER_HEIGHT, 0)
            : dad.getPos().add(0, 2.2, 0);

        flyTowardSmooth(target);
        lookAtEntity(dad, 30f, 30f);
    }
    private void stopFlying(ServerPlayerEntity dad) {
        setNoGravity(false);
        mode = Mode.GROUND;

        landingSpot = null;
        forcedLandingSpot = null;

        dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Survival"), false);
    }

    /* ================= MOVEMENT HELPERS ================= */

    private void flyTowardSmooth(Vec3d target) {
        Vec3d delta = target.subtract(getPos());
        double distSq = delta.lengthSquared();
        if (distSq < 0.0006) {
            setVelocity(getVelocity().multiply(0.6));
            velocityDirty = true;
            return;
        }

        Vec3d desired = delta.normalize().multiply(FLY_SPEED);

        // stronger steer when far (prevents drifting)
        double accel = (distSq > 64.0) ? Math.min(0.55, FLY_ACCEL * 2.0) : FLY_ACCEL;

        Vec3d blended = getVelocity().multiply(1.0 - accel).add(desired.multiply(accel));
        setVelocity(blended);
        velocityDirty = true;
    }

    private double horizontalDistanceTo(ServerPlayerEntity dad) {
        double dx = dad.getX() - getX();
        double dz = dad.getZ() - getZ();
        return Math.sqrt(dx * dx + dz * dz);
    }

    private double horizontalDistanceTo(BlockPos pos) {
        double dx = pos.getX() + 0.5 - getX();
        double dz = pos.getZ() + 0.5 - getZ();
        return Math.sqrt(dx * dx + dz * dz);
    }

    /* ================= LAND SEARCH (DRY + SAFE) ================= */

    private BlockPos findLandingSpotNear(BlockPos center, int radius) {
        BlockPos best = null;
        double bestScore = Double.MAX_VALUE;

        int minY = center.getY() - 3;
        int maxY = center.getY() + 3;

        for (int dx = -radius; dx <= radius; dx++) {
            for (int dz = -radius; dz <= radius; dz++) {
                if ((dx * dx + dz * dz) > radius * radius) continue;

                for (int y = minY; y <= maxY; y++) {
                    BlockPos p = new BlockPos(center.getX() + dx, y, center.getZ() + dz);
                    if (!canStandAt(p)) continue;

                    // prefer closer to dad AND closer to us a bit
                    double toCenter = p.getSquaredDistance(center);
                    double toMe = this.getPos().squaredDistanceTo(Vec3d.ofCenter(p));
                    double score = toCenter * 1.0 + toMe * 0.10;

                    if (score < bestScore) {
                        bestScore = score;
                        best = p;
                    }
                }
            }
        }
        return best;
    }

    private boolean canStandAt(BlockPos pos) {
        World w = getWorld();

        // feet + head air
        if (!w.getBlockState(pos).isAir()) return false;
        if (!w.getBlockState(pos.up()).isAir()) return false;

        // solid below
        BlockPos below = pos.down();
        if (!w.getBlockState(below).isSolidBlock(w, below)) return false;

        // not in fluid, not standing on fluid
        if (!w.getFluidState(pos).isEmpty()) return false;
        if (!w.getFluidState(below).isEmpty()) return false;

        // also avoid lava blocks around feet area (simple “don’t be stupid”)
        // (fluid check already catches lava fluid; this catches blocks like magma? optional)
        return true;
    }

    private boolean wouldImmediatelyRefly(ServerPlayerEntity dad, BlockPos landPos) {
        if (horizontalDistanceTo(dad) > FORCE_FLY_DISTANCE) return true;
        if (dad.getY() - landPos.getY() > MAX_VERTICAL_GAP) return true;
        return false;
    }

    /* ================= FALL DAMAGE ================= */

    @Override
    public boolean handleFallDamage(float fallDistance, float damageMultiplier, DamageSource source) {
        return false;
    }

    /* ================= SAVE / LOAD ================= */

    @Override
    public void writeCustomDataToNbt(NbtCompound nbt) {
        if (dadUuid != null) nbt.putUuid("Dad", dadUuid);
        nbt.putString("DadName", dadName);
        nbt.putString("Mode", mode.name());
        nbt.putInt("FlyTicks", flyTicks);
    }

    @Override
    public void readCustomDataFromNbt(NbtCompound nbt) {
        if (nbt.containsUuid("Dad")) dadUuid = nbt.getUuid("Dad");
        if (nbt.contains("DadName")) dadName = nbt.getString("DadName");
        if (nbt.contains("Mode")) {
            mode = Mode.valueOf(nbt.getString("Mode"));
            setNoGravity(mode != Mode.GROUND);
        }
        if (nbt.contains("FlyTicks")) flyTicks = nbt.getInt("FlyTicks");

        // volatile reset
        landingSpot = null;
        forcedLandingSpot = null;
        lastDadHorizDist = -1;
        stuckTicks = 0;
    }

    /* ================= FOLLOW GOAL ================= */

    static class FollowDadGoal extends Goal {
        private final PlayerJrEntity jr;
        FollowDadGoal(PlayerJrEntity jr) { this.jr = jr; }

        @Override
        public boolean canStart() {
            return jr.mode == Mode.GROUND && jr.getDad() != null;
        }

        @Override
        public void tick() {
            ServerPlayerEntity dad = jr.getDad();
            if (dad == null) return;

            if (jr.squaredDistanceTo(dad) > 4) {
                jr.getNavigation().startMovingTo(dad, 1.0);
            }
        }
    }
}
