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

import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.personality.NpcCommand;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

/**
 * Base class for NPC job goals that follow the chest-hub pattern:
 * scan zone for work targets, perform job-specific work, store results in chest.
 *
 * <p>Shared phases: SCANNING, STORING, IDLE. Subclasses define job-specific phases
 * via their own enums and handle them in {@link #tickJobSpecific()}.</p>
 *
 * <p>Phase dispatch uses an int to distinguish common vs job-specific phases:
 * <ul>
 *   <li>{@link #PHASE_SCANNING} = 0</li>
 *   <li>{@link #PHASE_STORING} = 1</li>
 *   <li>{@link #PHASE_IDLE} = 2</li>
 *   <li>Values >= {@link #PHASE_JOB_SPECIFIC} are dispatched to {@link #tickJobSpecific()}</li>
 * </ul>
 */
public abstract class AbstractNpcJobGoal extends Goal {

    protected static final int PHASE_SCANNING = 0;
    protected static final int PHASE_STORING = 1;
    protected static final int PHASE_IDLE = 2;
    /** Subclass phases start at this value */
    protected static final int PHASE_JOB_SPECIFIC = 10;

    /** Work zone radius from chest */
    protected static final int WORK_RADIUS = 8;

    /** Ticks between zone scans */
    protected static final int SCAN_INTERVAL = 40;

    /** Ticks to wait when no work before considering idle */
    protected static final int IDLE_TIMEOUT = 400;

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

    /** Max items to hold before storing */
    protected static final int MAX_HELD_ITEMS = 16;

    protected final EntityDamsel npc;
    protected final double speedModifier;

    @Nullable
    protected BlockPos chestPos;

    @Nullable
    protected Player master;

    protected List<ItemStack> heldItems = new ArrayList<>();
    protected int phase;
    protected int scanTimer;
    protected int idleTimer;
    protected int xpTimer;
    protected int pathRecalcTimer;

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

    // ==================== Abstract Methods ====================

    /** The NpcCommand this goal serves (FARM, FISH, etc.) */
    protected abstract NpcCommand getCommand();

    /**
     * Find the nearest work target in the zone around the chest.
     * @return a BlockPos if work is available, null otherwise
     */
    @Nullable
    protected abstract BlockPos findWorkTarget();

    /**
     * Called when a work target is found during scanning.
     * Subclass should store the target and set its job-specific phase.
     * @param target the work target position
     */
    protected abstract void onWorkTargetFound(BlockPos target);

    /**
     * Tick job-specific phases (anything not SCANNING, STORING, or IDLE).
     * The current phase is available via {@link #phase}.
     */
    protected abstract void tickJobSpecific();

    /**
     * Additional preconditions beyond collar + command check.
     * Default returns true. Override to add checks (e.g. fishing rod in hand).
     */
    protected boolean extraCanUse() {
        return true;
    }

    /**
     * Additional continue conditions beyond collar + command check.
     * Default returns true.
     */
    protected boolean extraCanContinueToUse() {
        return true;
    }

    /**
     * Called during start() after common init. Subclasses initialize their own fields here.
     */
    protected abstract void onStart();

    /**
     * Called during stop() before common cleanup. Subclasses clean up their own fields here.
     */
    protected abstract void onStop();

    /**
     * Hook called each tick during IDLE phase, after the re-scan check.
     * Default is empty. Farm overrides this for dialogue.
     */
    protected void onIdleTick() {
        // Default: no-op
    }

    /**
     * Interact distance for this job (squared comparison).
     * Default 2.0. Fish overrides to 2.5.
     */
    protected double getInteractDistance() {
        return 2.0;
    }

    // ==================== Goal Lifecycle ====================

    @Override
    public boolean canUse() {
        if (npc.getActiveCommand() != getCommand()) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        if (!extraCanUse()) {
            return false;
        }
        this.master = NpcGoalHelper.findCommandingPlayer(npc);
        return this.master != null;
    }

    @Override
    public boolean canContinueToUse() {
        if (npc.getActiveCommand() != getCommand()) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        return extraCanContinueToUse();
    }

    @Override
    public void start() {
        NpcGoalHelper.ensureOutsideCell(npc);
        var state = npc.getPersonalityState();
        if (state != null && state.getCommandTarget() != null) {
            this.chestPos = state.getCommandTarget();
        } else {
            npc.cancelCommand();
            return;
        }

        this.phase = PHASE_SCANNING;
        this.heldItems.clear();
        this.scanTimer = 0;
        this.idleTimer = 0;
        this.xpTimer = 0;
        this.pathRecalcTimer = 0;
        onStart();
    }

    @Override
    public void stop() {
        onStop();

        if (!heldItems.isEmpty() && chestPos != null) {
            NpcGoalHelper.storeItemsInChest(
                npc,
                npc.level(),
                chestPos,
                heldItems
            );
        }

        this.chestPos = null;
        this.master = null;
        this.heldItems.clear();
        npc.getNavigation().stop();
    }

    @Override
    public void tick() {
        // Award XP periodically
        if (++this.xpTimer >= XP_INTERVAL) {
            this.xpTimer = 0;
            NpcGoalHelper.incrementJobExperience(npc, getCommand());
        }

        switch (this.phase) {
            case PHASE_SCANNING -> tickScanning();
            case PHASE_STORING -> tickStoring();
            case PHASE_IDLE -> tickIdle();
            default -> tickJobSpecific();
        }
    }

    // ==================== Common Phase Ticks ====================

    private void tickScanning() {
        if (--this.scanTimer <= 0) {
            float efficiency = NpcGoalHelper.getJobEfficiency(
                npc,
                getCommand()
            );
            this.scanTimer = (int) (SCAN_INTERVAL / efficiency);

            // Check if we need to store items first
            if (heldItems.size() >= MAX_HELD_ITEMS) {
                this.phase = PHASE_STORING;
                return;
            }

            BlockPos target = findWorkTarget();

            if (target != null) {
                onWorkTargetFound(target);
                this.idleTimer = 0;
            } else {
                // No work available
                this.idleTimer += SCAN_INTERVAL;

                if (!heldItems.isEmpty()) {
                    this.phase = PHASE_STORING;
                } else if (this.idleTimer >= IDLE_TIMEOUT) {
                    this.phase = PHASE_IDLE;
                }
            }
        }
    }

    private void tickStoring() {
        if (this.chestPos == null) {
            this.phase = PHASE_SCANNING;
            return;
        }

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

        // Check if close enough to store
        double dist = npc.blockPosition().distSqr(chestPos);
        double interactDist = getInteractDistance();
        if (dist <= interactDist * interactDist) {
            NpcGoalHelper.storeItemsInChest(
                npc,
                npc.level(),
                chestPos,
                heldItems
            );
            heldItems.clear();
            this.phase = PHASE_SCANNING;
            this.scanTimer = 0;
        }
    }

    private void tickIdle() {
        // Periodically check for new work
        if (--this.scanTimer <= 0) {
            this.scanTimer = SCAN_INTERVAL * 2;
            BlockPos target = findWorkTarget();

            if (target != null) {
                onWorkTargetFound(target);
                this.idleTimer = 0;
                return;
            }
        }

        onIdleTick();
    }
}
