package com.tiedup.remake.state.struggle;

import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.time.Timer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameRules;

/**
 *
 * Handles the logic for players/NPCs struggling against restraints:
 * - Cooldown timer between attempts
 * - Probability-based success (dice roll)
 * - Resistance decrease on success
 * - Automatic removal when resistance reaches 0
 *
 * v2.5: This is now used for NPC struggle only.
 * Players use the continuous struggle mini-game (ContinuousStruggleMiniGameState).
 * Knife bonuses have been removed - knives now work by active cutting.
 *
 * Architecture:
 * - StruggleState (abstract base)
 *   ├─ StruggleBinds (struggle against bind restraints)
 *   └─ StruggleCollar (struggle against collar/ownership)
 */
public abstract class StruggleState {

    /**
     * Cooldown timer for struggle attempts.
     * Reset after each struggle attempt.
     * Volatile for thread safety across network/tick threads.
     */
    protected volatile Timer struggleCooldownTimer;

    /**
     * Main struggle logic (dice roll based).
     * Used for NPC struggle - players use QTE mini-game instead.
     *
     * Flow:
     * 1. Check if struggle is enabled (GameRule)
     * 2. Check if item can be struggled out of
     * 3. Check cooldown timer
     * 4. Roll dice for success (probability %)
     * 5. If success: decrease resistance by random amount
     * 6. If resistance <= 0: call successAction()
     * 7. Set cooldown for next attempt
     *
     * @param state The player's bind state
     */
    public synchronized void struggle(PlayerBindState state) {
        Player player = state.getPlayer();
        if (player == null || player.level() == null) {
            return;
        }

        GameRules gameRules = player.level().getGameRules();

        // 1. Check if struggle system is enabled
        if (!SettingsAccessor.isStruggleEnabled(gameRules)) {
            return;
        }

        // 2. Check if item can be struggled out of (not locked or forced)
        if (!canStruggle(state)) {
            return;
        }

        // 3. Check cooldown timer (prevent spamming)
        if (
            struggleCooldownTimer != null && !struggleCooldownTimer.isExpired()
        ) {
            TiedUpMod.LOGGER.debug(
                "[STRUGGLE] Cooldown not expired yet - ignoring struggle attempt"
            );
            return;
        }

        // Start struggle animation (after cooldown check passes)
        if (!state.isStruggling()) {
            state.setStruggling(true, player.level().getGameTime());
            com.tiedup.remake.network.sync.SyncManager.syncStruggleState(
                player
            );
        }

        if (!onAttempt(state)) {
            return; // Interrupted by pain
        }

        // 4. Roll for success
        int probability = SettingsAccessor.getProbabilityStruggle(gameRules);
        int roll = player.getRandom().nextInt(100) + 1;
        boolean success = roll <= probability;

        if (success) {
            // Calculate resistance decrease
            int currentResistance = getResistanceState(state);
            int minDecrease = SettingsAccessor.getStruggleMinDecrease(
                gameRules
            );
            int maxDecrease = SettingsAccessor.getStruggleMaxDecrease(
                gameRules
            );

            int decrease =
                minDecrease +
                player.getRandom().nextInt(maxDecrease - minDecrease + 1);

            int newResistance = Math.max(0, currentResistance - decrease);
            setResistanceState(state, newResistance);

            // Feedback
            notifySuccess(player, newResistance);

            // Check for escape
            if (newResistance <= 0) {
                TiedUpMod.LOGGER.debug(
                    "[STRUGGLE] {} broke free! (resistance reached 0)",
                    player.getName().getString()
                );
                successAction(state);
            }
        } else {
            notifyFailure(player);
        }

        // 5. Set cooldown
        int cooldownTicks = SettingsAccessor.getStruggleTimer(gameRules);
        struggleCooldownTimer = new Timer(cooldownTicks / 20, player.level());
    }

    /**
     * Get the message category for successful struggle attempts.
     */
    protected abstract MessageCategory getSuccessCategory();

    /**
     * Get the message category for failed struggle attempts.
     */
    protected abstract MessageCategory getFailCategory();

    /**
     * Send success notification to the player.
     *
     * @param player The player to notify
     * @param newResistance The new resistance value after decrease
     */
    protected void notifySuccess(Player player, int newResistance) {
        SystemMessageManager.sendWithResistance(
            player,
            getSuccessCategory(),
            newResistance
        );
    }

    /**
     * Send failure notification to the player.
     *
     * @param player The player to notify
     */
    protected void notifyFailure(Player player) {
        SystemMessageManager.sendToPlayer(player, getFailCategory());
    }

    /**
     * Called when the player successfully struggles free (resistance reaches 0).
     * Subclasses implement this to handle the escape action.
     *
     * @param state The player's bind state
     */
    protected abstract void successAction(PlayerBindState state);

    /**
     * Called when a struggle attempt is performed (regardless of success/fail).
     * Can be used for side effects like random shocks.
     *
     * @param state The player's bind state
     * @return true to proceed with struggle, false to interrupt
     */
    protected boolean onAttempt(PlayerBindState state) {
        return true; // Default: proceed
    }

    /**
     * Get the current resistance value from the player's state.
     *
     * @param state The player's bind state
     * @return Current resistance value
     */
    protected abstract int getResistanceState(PlayerBindState state);

    /**
     * Set the current resistance value in the player's state.
     *
     * @param state The player's bind state
     * @param resistance The new resistance value
     */
    protected abstract void setResistanceState(
        PlayerBindState state,
        int resistance
    );

    /**
     * Check if the player can struggle.
     * Returns false if the item cannot be struggled out of.
     *
     * @param state The player's bind state
     * @return True if struggling is allowed
     */
    protected abstract boolean canStruggle(PlayerBindState state);

    /**
     * Check if debug logging is enabled.
     *
     * @return True if debug logging should be printed
     */
    protected boolean isDebugEnabled() {
        return true;
    }
}
