package com.tiedup.remake.state;

import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import java.util.function.Supplier;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

/**
 * Equipment CRUD, state checks, bulk operations, resistance, and lock safety
 * for bondage items on kidnapped entities.
 *
 * <p>This is the largest sub-interface, covering:</p>
 * <ul>
 *   <li>State query methods (isTiedUp, isGagged, etc.)</li>
 *   <li>Bind mode defaults (hasArmsBound, hasLegsBound, getBindModeId)</li>
 *   <li>Deprecated per-slot put-on / take-off / get-current / replace methods</li>
 *   <li>Bulk operations (applyBondage, untie, dropBondageItems)</li>
 *   <li>Post-apply callbacks (checkXAfterApply defaults)</li>
 *   <li>Lock safety helpers (ifUnlocked, isLocked, ifUnlockedReturn, takeBondageItemIfUnlocked)</li>
 *   <li>Clothes permissions</li>
 *   <li>Resistance system defaults</li>
 * </ul>
 *
 * @see ICapturable
 * @see IRestrainableEntity
 * @see IRestrainable
 */
public interface IBondageState extends ICapturable {
    // V2 REGION-BASED EQUIPMENT ACCESS

    /**
     * Get the item equipped in a V2 body region.
     *
     * @param region The body region to query
     * @return The equipped ItemStack, or {@link ItemStack#EMPTY} if empty
     */
    ItemStack getEquipment(BodyRegionV2 region);

    /**
     * Equip an item in a V2 body region.
     * Dispatches to the appropriate slot-specific equip logic (sounds, hooks, sync).
     *
     * @param region The body region to equip into
     * @param stack The ItemStack to equip
     */
    void equip(BodyRegionV2 region, ItemStack stack);

    /**
     * Unequip the item from a V2 body region, respecting locks.
     *
     * @param region The body region to unequip from
     * @return The removed ItemStack, or {@link ItemStack#EMPTY} if locked/empty
     */
    ItemStack unequip(BodyRegionV2 region);

    /**
     * Force-unequip the item from a V2 body region, ignoring locks.
     * Used for admin commands, kidnapper theft, kill cleanup, etc.
     *
     * @param region The body region to force-unequip from
     * @return The removed ItemStack, or {@link ItemStack#EMPTY} if empty
     */
    ItemStack forceUnequip(BodyRegionV2 region);

    /**
     * Replace the item in a body region, returning the old item.
     *
     * @param region The body region
     * @param newStack The new ItemStack to equip
     * @param force If true, replace even if current item is locked
     * @return The old ItemStack, or empty if locked (and !force) or nothing equipped
     */
    ItemStack replaceEquipment(
        BodyRegionV2 region,
        ItemStack newStack,
        boolean force
    );

    // STATE QUERIES - BONDAGE EQUIPMENT

    /**
     * Check if entity has bind/ropes equipped.
     * @return true if BIND slot is not empty
     */
    boolean isTiedUp();

    /**
     * Check if entity's arms are bound.
     * True if tied with mode ARMS or FULL.
     * Leg Binding System.
     *
     * @return true if arms are bound
     */
    default boolean hasArmsBound() {
        if (!isTiedUp()) return false;
        ItemStack bind = getEquipment(BodyRegionV2.ARMS);
        if (bind.isEmpty()) return false;
        return BindModeHelper.hasArmsBound(bind);
    }

    /**
     * Check if entity's legs are bound.
     * True if tied with mode LEGS or FULL.
     * Leg Binding System.
     *
     * @return true if legs are bound
     */
    default boolean hasLegsBound() {
        if (!isTiedUp()) return false;
        ItemStack bind = getEquipment(BodyRegionV2.ARMS);
        if (bind.isEmpty()) return false;
        return BindModeHelper.hasLegsBound(bind);
    }

    /**
     * Get the bind mode ID string.
     * Leg Binding System.
     *
     * @return "full", "arms", or "legs"
     */
    default String getBindModeId() {
        if (!isTiedUp()) return BindModeHelper.MODE_FULL;
        ItemStack bind = getEquipment(BodyRegionV2.ARMS);
        if (bind.isEmpty()) return BindModeHelper.MODE_FULL;
        return BindModeHelper.getBindModeId(bind);
    }

    /**
     * Check if entity is gagged.
     * @return true if GAG slot is not empty
     */
    boolean isGagged();

    /**
     * Check if entity is blindfolded.
     * @return true if BLINDFOLD slot is not empty
     */
    boolean isBlindfolded();

    /**
     * Check if entity has earplugs.
     * @return true if EARPLUGS slot is not empty
     */
    boolean hasEarplugs();

    /**
     * Check if entity has collar.
     * @return true if COLLAR slot is not empty
     */
    boolean hasCollar();

    /**
     * Check if entity is wearing a locked collar.
     * @return true if has collar AND collar is locked
     */
    boolean hasLockedCollar();

    /**
     * Check if entity's collar has a custom name/nickname.
     * Used for collar ownership display.
     *
     * @return true if collar has NBT nickname
     */
    boolean hasNamedCollar();

    /**
     * Check if entity has clothes equipped.
     * @return true if CLOTHES slot is not empty
     */
    boolean hasClothes();

    /**
     * Check if entity has mittens equipped.
     * @return true if MITTENS slot is not empty
     */
    boolean hasMittens();

    /**
     * Check if clothes have "small arms" rendering flag.
     * Used by EntityDamsel for custom model rendering.
     *
     * @return true if clothes enable small arms mode
     */
    boolean hasClothesWithSmallArms();

    /**
     * Check if entity is both tied AND gagged.
     * Common check for "fully restrained" state.
     *
     * @return true if {@code isTiedUp() && isGagged()}
     */
    boolean isBoundAndGagged();

    /**
     * Check if gag item has gagging sound effect.
     * @return true if gag has a GaggingComponent
     */
    boolean hasGaggingEffect();

    /**
     * Check if blindfold item has blinding visual effect.
     * @return true if blindfold implements IHasBlindingEffect
     */
    boolean hasBlindingEffect();

    /**
     * Check if entity has knives in inventory (for struggling).
     * @return true if inventory contains knife items
     */
    boolean hasKnives();

    // BULK OPERATIONS

    /**
     * Apply a full set of bondage equipment at once.
     *
     * <p>Typically used when:</p>
     * <ul>
     *   <li>EntityKidnapper captures a player</li>
     *   <li>Trapped bed activates</li>
     *   <li>Rope arrow hits target</li>
     *   <li>Admin command applies full restraint</li>
     * </ul>
     *
     * @param bind Bind ItemStack (or empty)
     * @param gag Gag ItemStack (or empty)
     * @param blindfold Blindfold ItemStack (or empty)
     * @param earplugs Earplugs ItemStack (or empty)
     * @param collar Collar ItemStack (or empty)
     * @param clothes Clothes ItemStack (or empty)
     */
    void applyBondage(
        ItemStack bind,
        ItemStack gag,
        ItemStack blindfold,
        ItemStack earplugs,
        ItemStack collar,
        ItemStack clothes
    );

    /**
     * Untie this entity, removing all bondage items.
     *
     * @param drop If true, drop items on ground. If false, items are deleted.
     */
    void untie(boolean drop);

    /**
     * Drop all equipped bondage items on the ground.
     * Equivalent to {@code dropBondageItems(true)}.
     */
    void dropBondageItems(boolean drop);

    /**
     * Drop specific bondage items with granular control.
     *
     * @param drop If false, skip all drops (no-op)
     * @param dropBind If true, drop bind item
     */
    void dropBondageItems(boolean drop, boolean dropBind);

    /**
     * Drop bondage items with full granular control.
     *
     * @param drop If false, skip all drops (no-op)
     * @param dropBind If true, drop bind
     * @param dropGag If true, drop gag
     * @param dropBlindfold If true, drop blindfold
     * @param dropEarplugs If true, drop earplugs
     * @param dropCollar If true, drop collar
     * @param dropClothes If true, drop clothes
     */
    void dropBondageItems(
        boolean drop,
        boolean dropBind,
        boolean dropGag,
        boolean dropBlindfold,
        boolean dropEarplugs,
        boolean dropCollar,
        boolean dropClothes
    );

    /**
     * Drop only clothes item.
     */
    void dropClothes();

    /**
     * Count how many bondage items can be removed (are not locked).
     *
     * @return Count of removable items (0-6)
     */
    int getBondageItemsWhichCanBeRemovedCount();

    // CLOTHES PERMISSION SYSTEM

    /**
     * Check if a player can remove clothes from this entity.
     * Used for permission checks in multiplayer.
     *
     * @param player The player attempting to remove clothes
     * @return true if allowed
     */
    boolean canTakeOffClothes(Player player);

    /**
     * Check if a player can change clothes on this entity.
     *
     * @param player The player attempting to change clothes
     * @return true if allowed
     */
    boolean canChangeClothes(Player player);

    /**
     * Check if clothes can be changed (global permission).
     *
     * @return true if clothes are changeable
     */
    boolean canChangeClothes();

    // POST-APPLY CALLBACKS

    /**
     * Called after a bind is applied.
     * Implementations can trigger additional effects (sounds, particles, etc.).
     *
     */
    default void checkBindAfterApply() {
        // Default: no-op
        // NPCs don't need post-apply logic by default
    }

    /**
     * Called after a gag is applied.
     * Implementations can trigger additional effects (sounds, particles, etc.).
     */
    default void checkGagAfterApply() {
        // Default: no-op
        // NPCs don't need post-apply logic by default
    }

    /**
     * Called after a blindfold is applied.
     */
    default void checkBlindfoldAfterApply() {
        // Default: no-op
    }

    /**
     * Called after earplugs are applied.
     */
    default void checkEarplugsAfterApply() {
        // Default: no-op
    }

    /**
     * Called after a collar is applied.
     */
    default void checkCollarAfterApply() {
        // Default: no-op
    }

    /**
     * Called after mittens are applied.
     */
    default void checkMittensAfterApply() {
        // Default: no-op
        // NPCs don't need post-apply logic by default
    }

    // LOCK SAFETY HELPERS

    /**
     * Execute a runnable only if the item is unlocked.
     *
     * <p><b>Lock Check:</b> If {@code stack} is ILockable and locked, runnable is NOT executed.</p>
     *
     * <p><b>Usage Example:</b></p>
     * <pre>{@code
     * ifUnlocked(currentGag, false, () -> {
     *     // Remove gag code here
     * });
     * }</pre>
     *
     * @param stack The item to check
     * @param force If true, ignore lock (NOT USED in default implementation - for consistency)
     * @param run The code to execute if unlocked
     */
    default void ifUnlocked(ItemStack stack, boolean force, Runnable run) {
        if (!stack.isEmpty()) {
            if (
                stack.getItem() instanceof ILockable lockable &&
                lockable.isLocked(stack)
            ) {
                return; // Locked - don't run
            }
            run.run(); // Unlocked or not lockable - safe to run
        }
    }

    /**
     * Check if an item is locked.
     *
     * @param stack The item to check
     * @param force If true, returns false (treat as unlocked)
     * @return true if item is locked AND force is false
     */
    default boolean isLocked(ItemStack stack, boolean force) {
        return (
            !force &&
            stack.getItem() instanceof ILockable lockable &&
            lockable.isLocked(stack)
        );
    }

    /**
     * Return a value based on item lock state.
     *
     * <p><b>Usage Example:</b></p>
     * <pre>{@code
     * return ifUnlockedReturn(currentGag, false,
     *     () -> removeAndReturnGag(),  // If unlocked
     *     () -> ItemStack.EMPTY        // If locked
     * );
     * }</pre>
     *
     * @param stack The item to check
     * @param force If true, always returns {@code run.get()}
     * @param run Supplier to call if unlocked
     * @param def Supplier to call if locked or stack is empty
     * @param <T> Return type
     * @return Result from run or def supplier
     */
    default <T> T ifUnlockedReturn(
        ItemStack stack,
        boolean force,
        Supplier<T> run,
        Supplier<T> def
    ) {
        if (stack.isEmpty()) {
            return def.get();
        }
        if (
            stack.getItem() instanceof ILockable lockable &&
            lockable.isLocked(stack) &&
            !force
        ) {
            return def.get(); // Locked
        }
        return run.get(); // Unlocked
    }

    /**
     * Take a bondage item if it's unlocked, then drop it.
     *
     * <p>Helper method combining lock check + take + drop logic.</p>
     *
     * @param stack The current item in slot
     * @param takeOff Supplier that removes and returns the item
     */
    default void takeBondageItemIfUnlocked(
        ItemStack stack,
        Supplier<ItemStack> takeOff
    ) {
        if (stack.isEmpty()) return;
        // Locked ILockable items cannot be removed
        if (
            stack.getItem() instanceof ILockable lockable &&
            lockable.isLocked(stack)
        ) return;
        // Non-ILockable items or unlocked items: remove and drop
        ItemStack removed = takeOff.get();
        if (!removed.isEmpty()) {
            kidnappedDropItem(removed);
        }
    }

    // RESISTANCE SYSTEM

    /**
     * Get current bind resistance value.
     *
     * <p><b>Resistance System:</b></p>
     * Higher resistance = harder to struggle out of restraints.
     * Resistance decreases with each struggle attempt.
     * When resistance reaches 0, the entity escapes.
     *
     * <p><b>For Players:</b> Stored in NBT, persists across sessions
     * <p><b>For NPCs:</b> Default returns 0 (instant escape, can be overridden)
     *
     *
     * @return Current resistance value (0 = can escape)
     */
    default int getCurrentBindResistance() {
        // Default for NPCs: 0 resistance (they escape instantly)
        // PlayerBindState overrides this to return NBT-stored value
        return 0;
    }

    /**
     * Set current bind resistance.
     *
     * <p><b>For Players:</b> Updates NBT storage
     * <p><b>For NPCs:</b> Default no-op (no persistent resistance)
     *
     *
     * @param resistance New resistance value
     */
    default void setCurrentBindResistance(int resistance) {
        // Default for NPCs: no-op (they don't store resistance)
        // PlayerBindState overrides this to update NBT
    }

    /**
     * Get current collar resistance value.
     *
     * <p>Same as bind resistance but for collars.
     *
     *
     * @return Current collar resistance value
     */
    default int getCurrentCollarResistance() {
        // Default for NPCs: 0 resistance
        return 0;
    }

    /**
     * Set current collar resistance.
     *
     *
     * @param resistance New resistance value
     */
    default void setCurrentCollarResistance(int resistance) {
        // Default for NPCs: no-op
    }
}
