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

import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.PersonalityState;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;

/**
 * AI Goal: NPC follows the player and mines nearby ores.
 *
 * <p>Requires a pickaxe in main hand. Drops are stored in the NPC's
 * internal inventory (accessible via the NPC inventory GUI).</p>
 *
 * <p><b>Behavior:</b></p>
 * <ul>
 *   <li>Follows the commanding player</li>
 *   <li>Scans for ores within 6 blocks of the player</li>
 *   <li>Moves to ore, mines it, stores drops in NPC inventory</li>
 *   <li>Returns to following the player</li>
 *   <li>Teleports if player gets too far away</li>
 * </ul>
 */
public class NpcMineCommandGoal extends Goal {

    private final EntityDamsel npc;
    private final double speedModifier;

    /** Ore search radius around the player */
    private static final int ORE_SEARCH_RADIUS = 6;

    /** Vertical search range for ores */
    private static final int ORE_SEARCH_Y = 3;

    /** Distance to interact with a block */
    private static final double INTERACT_DISTANCE = 2.5;

    /** Distance at which to start following master */
    private static final double FOLLOW_START_DISTANCE = 6.0;

    /** Distance to teleport to master */
    private static final double TELEPORT_DISTANCE = 20.0;

    /** Base ticks to mine a block (modified by efficiency) */
    private static final int BASE_MINE_DELAY = 20;

    /** Ticks between ore scans */
    private static final int SCAN_INTERVAL = 30;

    /** Ticks between XP awards */
    private static final int XP_INTERVAL = 1200;

    /** Rest drain intensity for mining (heavy work) */
    private static final float WORK_INTENSITY = 1.5f;

    private enum MinePhase {
        FOLLOWING,
        MOVING_TO_ORE,
        MINING,
        RETURNING,
    }

    private MinePhase phase;

    @Nullable
    private Player master;

    @Nullable
    private BlockPos targetOre;

    private int pathRecalcTimer;
    private int scanTimer;
    private int mineTimer;
    private int xpTimer;

    public NpcMineCommandGoal(EntityDamsel npc, double speedModifier) {
        this.npc = npc;
        this.speedModifier = speedModifier;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
    }

    @Override
    public boolean canUse() {
        if (npc.getActiveCommand() != NpcCommand.MINE) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        if (!isPickaxe(npc.getMainHandItem())) {
            return false;
        }

        this.master = NpcGoalHelper.findCommandingPlayer(npc);
        return this.master != null;
    }

    @Override
    public boolean canContinueToUse() {
        if (npc.getActiveCommand() != NpcCommand.MINE) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        if (this.master == null || !this.master.isAlive()) {
            return false;
        }
        if (this.master.level() != this.npc.level()) {
            return false;
        }
        return true;
    }

    @Override
    public void start() {
        NpcGoalHelper.ensureOutsideCell(npc);
        this.phase = MinePhase.FOLLOWING;
        this.pathRecalcTimer = 0;
        this.scanTimer = 0;
        this.mineTimer = 0;
        this.xpTimer = 0;
        this.targetOre = null;
    }

    @Override
    public void stop() {
        this.master = null;
        this.targetOre = null;
        npc.getNavigation().stop();
    }

    @Override
    public void tick() {
        if (this.master == null) return;

        // Check pickaxe
        if (!isPickaxe(npc.getMainHandItem())) {
            // Lost pickaxe, just follow
            this.phase = MinePhase.FOLLOWING;
            this.targetOre = null;
        }

        // Award XP periodically
        if (++this.xpTimer >= XP_INTERVAL) {
            this.xpTimer = 0;
            NpcGoalHelper.incrementJobExperience(npc, NpcCommand.MINE);
        }

        // Teleport if too far
        double distToMaster = npc.distanceTo(master);
        if (distToMaster > TELEPORT_DISTANCE) {
            tryTeleportNear();
            this.phase = MinePhase.FOLLOWING;
            this.targetOre = null;
            return;
        }

        switch (this.phase) {
            case FOLLOWING -> tickFollowing();
            case MOVING_TO_ORE -> tickMovingToOre();
            case MINING -> tickMining();
            case RETURNING -> tickReturning();
        }
    }

    @Override
    public boolean requiresUpdateEveryTick() {
        return true;
    }

    private void tickFollowing() {
        if (this.master == null) return;

        double distToMaster = npc.distanceTo(master);

        // Follow master if far enough
        if (distToMaster > FOLLOW_START_DISTANCE) {
            if (--this.pathRecalcTimer <= 0) {
                this.pathRecalcTimer = 10;
                npc.getNavigation().moveTo(master, speedModifier);
            }
        } else {
            npc.getNavigation().stop();
        }

        // Look at master
        npc.getLookControl().setLookAt(master, 30.0f, 30.0f);

        // Scan for ores periodically (only when close to master)
        if (
            distToMaster <= FOLLOW_START_DISTANCE + 4 &&
            isPickaxe(npc.getMainHandItem())
        ) {
            if (--this.scanTimer <= 0) {
                float efficiency = NpcGoalHelper.getJobEfficiency(
                    npc,
                    NpcCommand.MINE
                );
                this.scanTimer = (int) (SCAN_INTERVAL / efficiency);

                this.targetOre = findOreNearPlayer();
                if (this.targetOre != null) {
                    this.phase = MinePhase.MOVING_TO_ORE;
                    this.pathRecalcTimer = 0;
                }
            }
        }
    }

    private void tickMovingToOre() {
        if (this.targetOre == null || this.master == null) {
            this.phase = MinePhase.FOLLOWING;
            return;
        }

        // Check ore is still valid
        BlockState state = npc.level().getBlockState(targetOre);
        if (!isOre(state)) {
            this.targetOre = null;
            this.phase = MinePhase.FOLLOWING;
            return;
        }

        // If master moves too far, abort and follow
        double distToMaster = npc.distanceTo(master);
        if (distToMaster > TELEPORT_DISTANCE * 0.75) {
            this.targetOre = null;
            this.phase = MinePhase.FOLLOWING;
            return;
        }

        // Move towards ore
        if (--this.pathRecalcTimer <= 0) {
            this.pathRecalcTimer = 10;
            npc
                .getNavigation()
                .moveTo(
                    targetOre.getX() + 0.5,
                    targetOre.getY(),
                    targetOre.getZ() + 0.5,
                    speedModifier
                );
        }

        // Look at ore
        npc
            .getLookControl()
            .setLookAt(
                targetOre.getX() + 0.5,
                targetOre.getY() + 0.5,
                targetOre.getZ() + 0.5
            );

        // Check if close enough to mine
        double dist = npc.blockPosition().distSqr(targetOre);
        if (dist <= INTERACT_DISTANCE * INTERACT_DISTANCE) {
            npc.getNavigation().stop();
            float efficiency = NpcGoalHelper.getJobEfficiency(
                npc,
                NpcCommand.MINE
            );
            this.mineTimer = (int) (BASE_MINE_DELAY / efficiency);
            this.phase = MinePhase.MINING;
        }
    }

    private void tickMining() {
        if (this.targetOre == null) {
            this.phase = MinePhase.FOLLOWING;
            return;
        }

        // Look at block while mining
        npc
            .getLookControl()
            .setLookAt(
                targetOre.getX() + 0.5,
                targetOre.getY() + 0.5,
                targetOre.getZ() + 0.5
            );

        if (--this.mineTimer > 0) {
            return;
        }

        Level level = npc.level();
        BlockState state = level.getBlockState(targetOre);

        if (isOre(state) && level instanceof ServerLevel serverLevel) {
            // Get drops using the NPC's tool (respects harvest level)
            List<ItemStack> drops = Block.getDrops(
                state,
                serverLevel,
                targetOre,
                level.getBlockEntity(targetOre),
                npc,
                npc.getMainHandItem()
            );

            // Store drops in NPC inventory
            for (ItemStack drop : drops) {
                NpcGoalHelper.addToNpcInventory(npc, drop);
            }

            // Apply yield bonus from experience
            float yieldMult = NpcGoalHelper.getJobYieldMultiplier(
                npc,
                NpcCommand.MINE
            );
            if (
                yieldMult > 1.0f &&
                npc.getRandom().nextFloat() < (yieldMult - 1.0f)
            ) {
                if (!drops.isEmpty()) {
                    ItemStack bonusDrop = drops
                        .get(npc.getRandom().nextInt(drops.size()))
                        .copy();
                    bonusDrop.setCount(1);
                    NpcGoalHelper.addToNpcInventory(npc, bonusDrop);
                }
            }

            // Destroy block
            level.destroyBlock(targetOre, false);

            // Damage pickaxe
            ItemStack pickaxe = npc.getMainHandItem();
            if (isPickaxe(pickaxe)) {
                pickaxe.hurtAndBreak(1, npc, entity -> {});
            }

            // Sound
            level.playSound(
                null,
                targetOre,
                SoundEvents.STONE_BREAK,
                SoundSource.BLOCKS,
                1.0f,
                1.0f
            );

            // Drain rest
            PersonalityState personalityState = npc.getPersonalityState();
            if (personalityState != null) {
                personalityState.getNeeds().drainFromWork(WORK_INTENSITY);
            }
        }

        this.targetOre = null;
        this.phase = MinePhase.RETURNING;
        this.pathRecalcTimer = 0;
    }

    private void tickReturning() {
        if (this.master == null) {
            this.phase = MinePhase.FOLLOWING;
            return;
        }

        double distToMaster = npc.distanceTo(master);

        // If close enough, switch back to following (which also scans for ore)
        if (distToMaster <= FOLLOW_START_DISTANCE + 2) {
            this.phase = MinePhase.FOLLOWING;
            this.scanTimer = 10; // Short scan delay after mining
            return;
        }

        // Move towards master
        if (--this.pathRecalcTimer <= 0) {
            this.pathRecalcTimer = 10;
            npc.getNavigation().moveTo(master, speedModifier);
        }

        npc.getLookControl().setLookAt(master, 30.0f, 30.0f);
    }

    // ========== Helper Methods ==========

    @Nullable
    private BlockPos findOreNearPlayer() {
        if (this.master == null) return null;

        BlockPos masterPos = master.blockPosition();
        Level level = npc.level();
        BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();

        BlockPos nearest = null;
        double nearestDist = Double.MAX_VALUE;

        for (int x = -ORE_SEARCH_RADIUS; x <= ORE_SEARCH_RADIUS; x++) {
            for (int z = -ORE_SEARCH_RADIUS; z <= ORE_SEARCH_RADIUS; z++) {
                for (int y = -ORE_SEARCH_Y; y <= ORE_SEARCH_Y; y++) {
                    mutable.set(
                        masterPos.getX() + x,
                        masterPos.getY() + y,
                        masterPos.getZ() + z
                    );
                    BlockState state = level.getBlockState(mutable);

                    if (isOre(state)) {
                        double dist = npc.blockPosition().distSqr(mutable);
                        if (dist < nearestDist) {
                            nearest = mutable.immutable();
                            nearestDist = dist;
                        }
                    }
                }
            }
        }

        return nearest;
    }

    private boolean isOre(BlockState state) {
        Block block = state.getBlock();
        return (
            block == Blocks.IRON_ORE ||
            block == Blocks.COAL_ORE ||
            block == Blocks.GOLD_ORE ||
            block == Blocks.COPPER_ORE ||
            block == Blocks.DIAMOND_ORE ||
            block == Blocks.LAPIS_ORE ||
            block == Blocks.REDSTONE_ORE ||
            block == Blocks.EMERALD_ORE ||
            block == Blocks.NETHER_GOLD_ORE ||
            block == Blocks.NETHER_QUARTZ_ORE ||
            block == Blocks.DEEPSLATE_IRON_ORE ||
            block == Blocks.DEEPSLATE_COAL_ORE ||
            block == Blocks.DEEPSLATE_GOLD_ORE ||
            block == Blocks.DEEPSLATE_COPPER_ORE ||
            block == Blocks.DEEPSLATE_DIAMOND_ORE ||
            block == Blocks.DEEPSLATE_LAPIS_ORE ||
            block == Blocks.DEEPSLATE_REDSTONE_ORE ||
            block == Blocks.DEEPSLATE_EMERALD_ORE
        );
    }

    private boolean isPickaxe(ItemStack stack) {
        return (
            stack.is(Items.WOODEN_PICKAXE) ||
            stack.is(Items.STONE_PICKAXE) ||
            stack.is(Items.IRON_PICKAXE) ||
            stack.is(Items.GOLDEN_PICKAXE) ||
            stack.is(Items.DIAMOND_PICKAXE) ||
            stack.is(Items.NETHERITE_PICKAXE)
        );
    }

    private void tryTeleportNear() {
        if (this.master == null) return;
        if (this.npc.isTiedUp()) return;

        double x = master.getX() + (npc.getRandom().nextDouble() - 0.5) * 4;
        double y = master.getY();
        double z = master.getZ() + (npc.getRandom().nextDouble() - 0.5) * 4;

        if (
            npc
                .level()
                .noCollision(
                    npc,
                    npc
                        .getBoundingBox()
                        .move(x - npc.getX(), y - npc.getY(), z - npc.getZ())
                )
        ) {
            npc.teleportTo(x, y, z);
            npc.getNavigation().stop();
        }
    }
}
