package com.tiedup.remake.entities;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.damsel.components.*;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.component.BlindingComponent;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity;
import com.tiedup.remake.util.tasks.ItemTask;
import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.util.teleport.TeleportHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.IV2EquipmentHolder;
import dev.kosmx.playerAnim.api.layered.AnimationStack;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.*;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 * AbstractTiedUpNpc - Base class for all TiedUp! humanoid NPCs.
 *
 * so that EntityKidnapper can extend this directly (instead of incorrectly extending EntityDamsel).
 *
 * <p><b>Shared functionality:</b></p>
 * <ul>
 *   <li>IRestrainable bondage delegation (via DamselBondageManager)</li>
 *   <li>V2 equipment system (IV2EquipmentHolder)</li>
 *   <li>Animation system (IAnimatedPlayer via DamselAnimationController)</li>
 *   <li>Inventory/equipment slots (via DamselInventoryManager)</li>
 *   <li>Pose state (sitting, kneeling, dog, struggling)</li>
 *   <li>Name system (getNpcName/setNpcName)</li>
 *   <li>Gender/slim arms</li>
 *   <li>Despawn protection</li>
 *   <li>Leash offset</li>
 * </ul>
 *
 * <p><b>Subclass-specific (NOT in this class):</b></p>
 * <ul>
 *   <li>Personality system (EntityDamsel)</li>
 *   <li>Variant/appearance (each subclass has its own)</li>
 *   <li>AI goals (each subclass registers its own)</li>
 *   <li>Dialogue handler (EntityDamsel)</li>
 *   <li>MenuProvider (EntityDamsel)</li>
 * </ul>
 *
 * @see EntityDamsel
 * @see EntityKidnapper
 */
public abstract class AbstractTiedUpNpc
    extends PathfinderMob
    implements
        IRestrainable,
        ISkinnedEntity,
        IAnimatedPlayer,
        com.tiedup.remake.dialogue.IDialogueSpeaker,
        IV2EquipmentHolder
{

    // DATA SYNC (Client-Server) - Shared across all NPC types

    /**
     * NPC's custom name.
     * Synced to client for display.
     */
    public static final EntityDataAccessor<String> DATA_DAMSEL_NAME =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.STRING
        );

    /**
     * Variant ID (e.g., "classic_1", "guest_fuya_kitty").
     * Synced to client for texture selection.
     */
    public static final EntityDataAccessor<String> DATA_VARIANT_ID =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.STRING
        );

    /**
     * V2 bondage equipment -- single CompoundTag replacing 7 individual ItemStack accessors.
     * Epic 4B: Serialized from internal V2BondageEquipment via copy-on-write pattern.
     * Synced to client for rendering (client deserializes in onSyncedDataUpdated).
     */
    public static final EntityDataAccessor<CompoundTag> DATA_V2_EQUIPMENT =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.COMPOUND_TAG
        );

    /**
     * Whether this entity uses slim arm model.
     * Synced to client for correct model rendering.
     */
    public static final EntityDataAccessor<Boolean> DATA_SLIM_ARMS =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Gender of the entity (MALE/FEMALE).
     * Synced to client for voice, behavior, and model rendering.
     */
    public static final EntityDataAccessor<String> DATA_GENDER =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.STRING
        );

    /**
     * Is NPC currently in sitting pose?
     * Synced to client for animation/rendering.
     */
    public static final EntityDataAccessor<Boolean> DATA_SITTING =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Is NPC currently in kneeling pose?
     * Synced to client for animation/rendering.
     */
    public static final EntityDataAccessor<Boolean> DATA_KNEELING =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Is NPC currently struggling against restraints?
     * Synced to client for struggle animation.
     */
    public static final EntityDataAccessor<Boolean> DATA_STRUGGLING =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Main hand item.
     * Synced to client for rendering and behavior determination.
     */
    public static final EntityDataAccessor<ItemStack> DATA_MAIN_HAND =
        SynchedEntityData.defineId(
            AbstractTiedUpNpc.class,
            EntityDataSerializers.ITEM_STACK
        );

    // COMPONENTS (shared across all NPC types)

    /**
     * Manages inventory, equipment, and feeding.
     */
    private final DamselInventoryManager inventoryManager;

    /**
     * Manages all bondage and captivity mechanics.
     */
    private final DamselBondageManager bondageManager;

    /**
     * V2 bondage equipment storage (internal, not a Forge capability for entities).
     * Epic 4B: Replaces 7 EntityDataAccessor<ItemStack> with single CompoundTag sync.
     */
    private final com.tiedup.remake.v2.bondage.capability.V2BondageEquipment v2Equipment =
        new com.tiedup.remake.v2.bondage.capability.V2BondageEquipment();

    /**
     * Manages all animation-related systems:
     * - IAnimatedPlayer implementation
     * - Animation stack management
     * - Pose management (sitting, kneeling, dog, struggling, trembling)
     * - Dog pose rotation smoothing
     */
    private DamselAnimationController animationController;

    // CONSTRUCTOR

    public AbstractTiedUpNpc(
        EntityType<? extends AbstractTiedUpNpc> type,
        Level level
    ) {
        super(type, level);
        this.setCanPickUpLoot(false);

        // Initialize inventory component
        this.inventoryManager = new DamselInventoryManager(this);

        // Initialize bondage component
        this.bondageManager = new DamselBondageManager(
            this,
            createBondageHost()
        );

        // Initialize animation controller component
        this.animationController = new DamselAnimationController(
            createAnimationHost()
        );

        // Allow NPC to pathfind through doors
        if (
            this.getNavigation() instanceof
                net.minecraft.world.entity.ai.navigation.GroundPathNavigation groundNav
        ) {
            groundNav.setCanOpenDoors(true);
            groundNav.setCanPassDoors(true);
        }

        // Allow pathfinding through decorative blocks (snow, leaves, etc.)
        this.setPathfindingMalus(
            net.minecraft.world.level.pathfinder.BlockPathTypes.POWDER_SNOW,
            0.0f
        );
        this.setPathfindingMalus(
            net.minecraft.world.level.pathfinder.BlockPathTypes.LEAVES,
            0.0f
        );
        this.setPathfindingMalus(
            net.minecraft.world.level.pathfinder.BlockPathTypes.DANGER_POWDER_SNOW,
            0.0f
        );
    }

    /**
     * Create the bondage host for this NPC type.
     * Subclasses MUST override this because the default BondageHost
     * requires EntityDamsel-specific methods (personality, dialogue).
     *
     * NOTE: Called during super() constructor -- subclass fields are NOT initialized yet.
     * The host implementation must only store the reference, not access fields.
     */
    protected abstract IBondageHost createBondageHost();

    /**
     * Create the animation host for this NPC type.
     * Subclasses can override if they need different host behavior.
     * Default creates an AnimationHost that delegates to this entity.
     */
    protected IAnimationHost createAnimationHost() {
        return new com.tiedup.remake.entities.damsel.hosts.AnimationHost(this);
    }

    // ABSTRACT METHODS - Subclasses must implement

    /**
     * Get the skin texture for this entity.
     * Each NPC type has its own variant/skin system.
     */
    @Override
    public abstract ResourceLocation getSkinTexture();

    /**
     * Register AI goals specific to this NPC type.
     * Called after all components are initialized.
     * Avoids name clash with Mob.registerGoals().
     */
    protected abstract void registerNpcGoals();

    /**
     * Get the speaker type for dialogue routing.
     * Each NPC type returns its own SpeakerType.
     */
    @Override
    public abstract com.tiedup.remake.dialogue.SpeakerType getSpeakerType();

    // ENTITY INITIALIZATION

    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.define(DATA_DAMSEL_NAME, "");
        this.entityData.define(DATA_VARIANT_ID, "");
        this.entityData.define(DATA_V2_EQUIPMENT, new CompoundTag());
        this.entityData.define(DATA_SLIM_ARMS, false);
        this.entityData.define(DATA_GENDER, Gender.FEMALE.getSerializedName());
        // Pose states
        this.entityData.define(DATA_SITTING, false);
        this.entityData.define(DATA_KNEELING, false);
        this.entityData.define(DATA_STRUGGLING, false);
        // Equipment
        this.entityData.define(DATA_MAIN_HAND, ItemStack.EMPTY);
    }

    @Override
    public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
        super.onSyncedDataUpdated(key);
        // Epic 4B: Deserialize V2 equipment on client when synced data changes
        if (DATA_V2_EQUIPMENT.equals(key)) {
            if (
                this.v2Equipment != null &&
                this.level() != null &&
                this.level().isClientSide
            ) {
                CompoundTag tag = this.entityData.get(DATA_V2_EQUIPMENT);
                this.v2Equipment.deserializeNBT(tag);
            }
        }
    }

    // DESPAWN PROTECTION

    /**
     * Prevent NPCs from despawning.
     * These are important NPCs that should persist in the world.
     *
     * @param distanceToClosestPlayer Distance to nearest player
     * @return false to never despawn
     */
    @Override
    public boolean removeWhenFarAway(double distanceToClosestPlayer) {
        return false; // Never despawn
    }

    /**
     * Check if this entity should be saved to disk.
     * Always true for NPCs - they are persistent.
     *
     * @return true to always save
     */
    @Override
    public boolean isPersistenceRequired() {
        return true; // Always save to disk
    }

    // TICK - PERIODIC UPDATES

    @Override
    public void tick() {
        // DOG pose transition detection (before super.tick)
        animationController.tickAnimationBeforeSuperTick();

        super.tick();

        // DOG pose rotation smoothing AFTER super.tick (runs on both client/server)
        // Must be before tickAnimationStack() which returns early on client
        animationController.tickDogPoseRotationSmoothing();

        // Animation stack tick (client-side only, returns early if client)
        if (animationController.tickAnimationStack()) {
            return; // Client-side, animation stack handled
        }

        // Server-side only below

        // Restore pending captor reference
        bondageManager.restoreCaptorFromUUID();

        // Subclass-specific server tick behavior
        tickSubclass();
    }

    /**
     * Override point for subclass-specific tick logic.
     * Called every server tick after animation and bondage restoration.
     * Default is empty -- subclasses override as needed.
     */
    protected void tickSubclass() {
        // Default: no-op
    }

    // BEHAVIORAL FLAGS (overridable by subclasses)

    /**
     * Check if this entity can call for help when captured.
     * Damsels call for help, kidnappers don't.
     *
     * @return true if can call for help (default: true)
     */
    public boolean canCallForHelp() {
        return true;
    }

    /**
     * Check if this entity can panic.
     * Damsels panic, kidnappers don't.
     *
     * @return true if can panic (default: true)
     */
    public boolean canPanic() {
        return true;
    }

    // POSE STATE

    /**
     * Check if NPC is currently sitting.
     * @return true if in sitting pose
     */
    public boolean isSitting() {
        return this.entityData.get(DATA_SITTING);
    }

    /**
     * Set sitting pose state.
     * @param sitting true to enter sitting pose
     */
    public void setSitting(boolean sitting) {
        this.entityData.set(DATA_SITTING, sitting);
        // Clear kneeling if sitting
        if (sitting) {
            this.entityData.set(DATA_KNEELING, false);
        }
    }

    /**
     * Check if NPC is currently kneeling.
     * @return true if in kneeling pose
     */
    public boolean isKneeling() {
        return this.entityData.get(DATA_KNEELING);
    }

    /**
     * Set kneeling pose state.
     * @param kneeling true to enter kneeling pose
     */
    public void setKneeling(boolean kneeling) {
        this.entityData.set(DATA_KNEELING, kneeling);
        // Clear sitting if kneeling
        if (kneeling) {
            this.entityData.set(DATA_SITTING, false);
        }
    }

    /**
     * Check if NPC is in any pose (sitting or kneeling).
     * @return true if in a pose
     */
    public boolean isInPose() {
        return this.isSitting() || this.isKneeling() || this.isDogPose();
    }

    /**
     * Check if NPC is in DOG pose (based on equipped bind).
     * @return true if equipped bind has DOG pose type
     */
    public boolean isDogPose() {
        ItemStack bind = this.getEquipment(BodyRegionV2.ARMS);
        if (bind.isEmpty()) return false;
        return PoseTypeHelper.getPoseType(bind) == com.tiedup.remake.items.base.PoseType.DOG;
    }

    /**
     * Check if NPC is currently struggling against restraints.
     * Used for animation sync.
     * @return true if struggling
     */
    public boolean isStruggling() {
        return this.entityData.get(DATA_STRUGGLING);
    }

    /**
     * Set struggling state for animation.
     * @param struggling true if starting struggle animation
     */
    public void setStruggling(boolean struggling) {
        this.entityData.set(DATA_STRUGGLING, struggling);
    }

    // NAME SYSTEM

    /**
     * Get NPC's custom name.
     */
    public String getNpcName() {
        return this.entityData.get(DATA_DAMSEL_NAME);
    }

    /**
     * Set NPC's custom name.
     * Also updates Minecraft's custom name display.
     */
    public void setNpcName(String name) {
        this.entityData.set(DATA_DAMSEL_NAME, name);
        // Make the name visible in-game
        this.setCustomName(net.minecraft.network.chat.Component.literal(name));
        this.setCustomNameVisible(true);
    }

    /**
     * @deprecated Use {@link #getNpcName()} instead. Will be removed in a future version.
     */
    @Deprecated(forRemoval = true)
    public String getDamselName() {
        return getNpcName();
    }

    /**
     * @deprecated Use {@link #setNpcName(String)} instead. Will be removed in a future version.
     */
    @Deprecated(forRemoval = true)
    public void setDamselName(String name) {
        setNpcName(name);
    }

    // GENDER / SLIM ARMS

    /**
     * Check if this NPC uses slim arms model.
     */
    public boolean hasSlimArms() {
        return this.entityData.get(DATA_SLIM_ARMS);
    }

    /**
     * Set slim arms flag directly.
     * Used by subclasses that have their own variant systems.
     */
    public void setSlimArms(boolean slimArms) {
        this.entityData.set(DATA_SLIM_ARMS, slimArms);
    }

    /**
     * Set gender.
     */
    public void setGender(Gender gender) {
        this.entityData.set(DATA_GENDER, gender.getSerializedName());
    }

    /**
     * Get gender.
     */
    public Gender getGender() {
        return Gender.fromName(this.entityData.get(DATA_GENDER));
    }

    // EQUIPMENT SLOTS (Delegated to DamselInventoryManager)

    @Override
    public ItemStack getItemBySlot(EquipmentSlot slot) {
        return this.inventoryManager.getItemBySlot(slot);
    }

    @Override
    public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
        this.verifyEquippedItem(stack);
        this.inventoryManager.setItemSlot(slot, stack);
    }

    @Override
    public Iterable<ItemStack> getArmorSlots() {
        return this.inventoryManager.getArmorSlots();
    }

    @Override
    public ItemStack getMainHandItem() {
        return this.inventoryManager.getMainHandItem();
    }

    /**
     * Set the main hand item.
     *
     * @param stack Item to hold
     */
    public void setMainHandItem(ItemStack stack) {
        this.inventoryManager.setMainHandItem(stack);
    }

    // IRestrainable - BONDAGE STATE QUERIES (Delegated to DamselBondageManager)

    @Override
    public boolean isTiedUp() {
        return bondageManager.isTiedUp();
    }

    @Override
    public boolean isGagged() {
        return bondageManager.isGagged();
    }

    @Override
    public boolean isBlindfolded() {
        return bondageManager.isBlindfolded();
    }

    @Override
    public boolean hasEarplugs() {
        return bondageManager.hasEarplugs();
    }

    @Override
    public boolean hasCollar() {
        return bondageManager.hasCollar();
    }

    /**
     * Check if a player is an owner of this NPC's collar.
     */
    public boolean isCollarOwner(Player player) {
        return bondageManager.isCollarOwner(player);
    }

    @Override
    public boolean hasClothes() {
        return bondageManager.hasClothes();
    }

    @Override
    public boolean hasMittens() {
        return bondageManager.hasMittens();
    }

    @Override
    public boolean isBoundAndGagged() {
        return bondageManager.isBoundAndGagged();
    }

    // V2 Region-Based Equipment Access (Delegated to DamselBondageManager)

    @Override
    public ItemStack getEquipment(BodyRegionV2 region) {
        return bondageManager.getEquipment(region);
    }

    @Override
    public void equip(BodyRegionV2 region, ItemStack stack) {
        bondageManager.equip(region, stack);
    }

    // IRestrainable - EQUIPMENT UNEQUIP (Delegated to DamselBondageManager)

    @Override
    public ItemStack unequip(BodyRegionV2 region) {
        return bondageManager.unequip(region);
    }

    @Override
    public ItemStack forceUnequip(BodyRegionV2 region) {
        return bondageManager.forceUnequip(region);
    }

    // IRestrainable - ENSLAVEMENT

    @Override
    public boolean isCaptive() {
        return this.isLeashed();
    }

    @Override
    public boolean isEnslavable() {
        if (this.isLeashed()) return false;
        return this.isTiedUp();
    }

    @Override
    public boolean canBeLeashed(Player player) {
        if (this.isLeashed()) return false;

        // Standard check: must be tied up
        if (this.isTiedUp()) return true;

        // Exception: collar owner can leash even if not tied
        if (this.hasCollar()) {
            ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
            if (CollarHelper.isOwner(collar, player)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public ICaptor getCaptor() {
        return bondageManager.getCaptor();
    }

    @Override
    public boolean getCapturedBy(ICaptor newCaptor) {
        return bondageManager.getCapturedBy(newCaptor);
    }

    /**
     * Force capture for managed camp operations (dogwalk, extract).
     * Bypasses canCapture() PrisonerManager state check.
     */
    public boolean forceCapturedBy(ICaptor newCaptor) {
        return bondageManager.forceCapturedBy(newCaptor);
    }

    @Override
    public void free() {
        bondageManager.free();
    }

    @Override
    public void free(boolean transportState) {
        bondageManager.free(transportState);
    }

    // IRestrainable - UTILITY (Delegated to DamselBondageManager)

    @Override
    public UUID getKidnappedUniqueId() {
        return bondageManager.getKidnappedUniqueId();
    }

    @Override
    public String getKidnappedName() {
        return bondageManager.getKidnappedName();
    }

    @Override
    public LivingEntity asLivingEntity() {
        return bondageManager.asLivingEntity();
    }

    @Override
    public void kidnappedDropItem(ItemStack stack) {
        bondageManager.kidnappedDropItem(stack);
    }

    // IRestrainable - STATE QUERIES (Advanced) (Delegated to DamselBondageManager)

    @Override
    public boolean canBeTiedUp() {
        return bondageManager.canBeTiedUp();
    }

    @Override
    public boolean isTiedToPole() {
        return bondageManager.isTiedToPole();
    }

    @Override
    public boolean tieToClosestPole(int searchRadius) {
        return bondageManager.tieToClosestPole(searchRadius);
    }

    @Override
    public boolean isForSell() {
        return bondageManager.isForSell();
    }

    @Override
    @Nullable
    public ItemTask getSalePrice() {
        return bondageManager.getSalePrice();
    }

    @Override
    public void putForSale(ItemTask price) {
        bondageManager.putForSale(price);
    }

    @Override
    public void cancelSale() {
        bondageManager.cancelSale();
    }

    @Override
    public boolean canBeKidnappedByEvents() {
        return bondageManager.canBeKidnappedByEvents();
    }

    @Override
    public boolean hasLockedCollar() {
        return bondageManager.hasLockedCollar();
    }

    @Override
    public boolean hasNamedCollar() {
        return bondageManager.hasNamedCollar();
    }

    @Override
    public boolean hasClothesWithSmallArms() {
        return bondageManager.hasClothesWithSmallArms();
    }

    @Override
    public boolean hasGaggingEffect() {
        ItemStack gag = this.getEquipment(BodyRegionV2.MOUTH);
        if (gag.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(gag, ComponentType.GAGGING, GaggingComponent.class) != null;
    }

    @Override
    public boolean hasBlindingEffect() {
        ItemStack blindfold = this.getEquipment(BodyRegionV2.EYES);
        if (blindfold.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(blindfold, ComponentType.BLINDING, BlindingComponent.class) != null;
    }

    @Override
    public boolean hasKnives() {
        // NPCs don't have inventories for knives
        return false;
    }

    // IRestrainable - REPLACE (Delegated to DamselBondageManager)

    @Override
    public ItemStack replaceEquipment(
        BodyRegionV2 region,
        ItemStack newStack,
        boolean force
    ) {
        return bondageManager.replaceEquipment(region, newStack, force);
    }

    // IRestrainable - BULK OPERATIONS (Delegated to DamselBondageManager)

    @Override
    public void applyBondage(
        ItemStack bind,
        ItemStack gag,
        ItemStack blindfold,
        ItemStack earplugs,
        ItemStack collar,
        ItemStack clothes
    ) {
        bondageManager.applyBondage(
            bind,
            gag,
            blindfold,
            earplugs,
            collar,
            clothes
        );
    }

    @Override
    public void untie(boolean drop) {
        bondageManager.untie(drop);
    }

    @Override
    public void dropBondageItems(boolean drop) {
        bondageManager.dropBondageItems(drop);
    }

    @Override
    public void dropBondageItems(boolean drop, boolean dropBind) {
        bondageManager.dropBondageItems(drop, dropBind);
    }

    @Override
    public void dropBondageItems(
        boolean drop,
        boolean dropBind,
        boolean dropGag,
        boolean dropBlindfold,
        boolean dropEarplugs,
        boolean dropCollar,
        boolean dropClothes
    ) {
        bondageManager.dropBondageItems(
            drop,
            dropBind,
            dropGag,
            dropBlindfold,
            dropEarplugs,
            dropCollar,
            dropClothes
        );
    }

    @Override
    public void dropClothes() {
        bondageManager.dropClothes();
    }

    @Override
    public int getBondageItemsWhichCanBeRemovedCount() {
        return bondageManager.getBondageItemsWhichCanBeRemovedCount();
    }

    // IRestrainable - PERMISSIONS (Delegated to DamselBondageManager)

    @Override
    public boolean canTakeOffClothes(Player player) {
        return bondageManager.canTakeOffClothes(player);
    }

    @Override
    public boolean canChangeClothes(Player player) {
        return bondageManager.canChangeClothes(player);
    }

    @Override
    public boolean canChangeClothes() {
        return bondageManager.canChangeClothes();
    }

    // IRestrainable - SPECIAL INTERACTIONS (Delegated to DamselBondageManager)

    @Override
    public void tighten(Player tightener) {
        bondageManager.tighten(tightener);
    }

    @Override
    public void applyChloroform(int duration) {
        bondageManager.applyChloroform(duration);
    }

    @Override
    public void shockKidnapped() {
        bondageManager.shockKidnapped();
    }

    @Override
    public void shockKidnapped(String messageAddon, float damage) {
        bondageManager.shockKidnapped(messageAddon, damage);
    }

    @Override
    public void takeBondageItemBy(IRestrainableEntity taker, int slotIndex) {
        bondageManager.takeBondageItemBy(taker, slotIndex);
    }

    // IRestrainable - CAPTIVITY TRANSFER (Delegated to DamselBondageManager)

    @Override
    public void transferCaptivityTo(ICaptor newCaptor) {
        bondageManager.transferCaptivityTo(newCaptor);
    }

    // IRestrainable - METADATA

    @Override
    public String getNameFromCollar() {
        ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
        if (collar.isEmpty()) {
            return this.getNpcName();
        }

        if (collar.hasCustomHoverName()) {
            return collar.getHoverName().getString();
        }

        return this.getNpcName();
    }

    @Override
    public net.minecraft.world.entity.Entity getTransport() {
        // NPCs use vanilla leash directly, not a transport entity
        return null;
    }

    // IRestrainable - TELEPORT / DEATH

    @Override
    public void teleportToPosition(Position position) {
        if (position == null || this.level().isClientSide) return;
        TeleportHelper.teleportEntity(this, position);
    }

    @Override
    public boolean onDeathKidnapped(Level world) {
        if (world.isClientSide) return false;

        // Check if we have a collar with cell configured
        ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
        if (collar.isEmpty()) return false;

        if (!CollarHelper.isCollar(collar)) return false;

        java.util.UUID cellId = CollarHelper.getCellId(collar);
        if (cellId == null) return false;

        // Get cell position from registry
        if (
            !(this.level() instanceof
                    net.minecraft.server.level.ServerLevel serverLevel)
        ) return false;
        com.tiedup.remake.cells.CellDataV2 cell =
            com.tiedup.remake.cells.CellRegistryV2.get(serverLevel).getCell(
                cellId
            );
        if (cell == null) return false;

        Position cellPosition = new Position(
            cell.getSpawnPoint().above(),
            serverLevel.dimension()
        );

        // We have a cell - respawn there instead of dying

        // 1. Free from captivity if captured
        if (this.isCaptive()) {
            this.free(false);
        }

        // 2. Unlock all locked items
        unlockAllItems();

        // 3. Heal to full
        this.setHealth(this.getMaxHealth());

        // 4. Teleport to cell
        this.teleportToPosition(cellPosition);

        TiedUpMod.LOGGER.info(
            "[AbstractTiedUpNpc] {} respawned at cell instead of dying",
            this.getName().getString()
        );

        return true; // Cancel death
    }

    /**
     * Unlock all locked bondage items on this entity.
     * Used before respawning at prison.
     * Epic 4B: Reads from V2 regions, syncs once at end after mutations.
     */
    private void unlockAllItems() {
        com.tiedup.remake.v2.BodyRegionV2[] regions = {
            com.tiedup.remake.v2.BodyRegionV2.ARMS,
            com.tiedup.remake.v2.BodyRegionV2.MOUTH,
            com.tiedup.remake.v2.BodyRegionV2.EYES,
            com.tiedup.remake.v2.BodyRegionV2.EARS,
            com.tiedup.remake.v2.BodyRegionV2.NECK,
        };
        boolean changed = false;
        for (com.tiedup.remake.v2.BodyRegionV2 region : regions) {
            ItemStack stack = v2Equipment.getInRegion(region);
            if (
                !stack.isEmpty() &&
                stack.getItem() instanceof ILockable lockable &&
                lockable.isLocked(stack)
            ) {
                lockable.setLocked(stack, false);
                changed = true;
            }
        }
        if (changed) {
            syncV2Equipment();
        }
    }

    // IV2EquipmentHolder IMPLEMENTATION (Epic 4B)

    @Override
    public IV2BondageEquipment getV2Equipment() {
        return v2Equipment;
    }

    /**
     * Serialize V2 equipment to the synched entity data.
     * Called by DamselBondageManager after every mutation (copy-on-write pattern).
     */
    public void syncV2Equipment() {
        this.entityData.set(DATA_V2_EQUIPMENT, this.v2Equipment.serializeNBT());
    }

    @Override
    public void syncEquipmentToData() {
        syncV2Equipment();
    }

    // SHOCK COLLAR CHECK

    /**
     * Check if this NPC has a shock collar equipped.
     * Shock collars activate automatically when fear reaches flee threshold.
     *
     * @return true if wearing a shock collar
     */
    public boolean hasShockCollar() {
        ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
        if (collar.isEmpty()) return false;
        return com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar);
    }

    // BONDAGE SERVICE (delegated to BondageManager)

    /**
     * Check if bondage service is enabled for this NPC.
     */
    public boolean isBondageServiceEnabled() {
        return bondageManager.isBondageServiceEnabled();
    }

    /**
     * Get the custom bondage service message from the collar.
     */
    public String getBondageServiceMessage() {
        return bondageManager.getBondageServiceMessage();
    }

    // COMPONENT ACCESSORS

    /**
     * Get inventory manager component.
     */
    public DamselInventoryManager getInventoryManager() {
        return inventoryManager;
    }

    /**
     * Get bondage manager component.
     */
    public DamselBondageManager getBondageManager() {
        return bondageManager;
    }

    /**
     * Get animation controller component.
     */
    public DamselAnimationController getAnimationController() {
        return animationController;
    }

    // LEASH ATTACHMENT POINT

    /**
     * Override leash offset to attach at neck level instead of eye level.
     * Vanilla default is (0, eyeHeight, bbWidth*0.4) which is too high for humanoids.
     */
    @Override
    protected net.minecraft.world.phys.Vec3 getLeashOffset() {
        // Neck height (~1.3 blocks from feet)
        // Slight forward offset to prevent clipping with body
        return new net.minecraft.world.phys.Vec3(
            0.0,
            1.3,
            this.getBbWidth() * 0.2
        );
    }

    // IANIMATEDPLAYER INTERFACE (PlayerAnimator)

    /**
     * Get the animation stack for this entity.
     * Required by IAnimatedPlayer interface.
     *
     * @return AnimationStack containing all animation layers
     */
    @Override
    public AnimationStack getAnimationStack() {
        return animationController.getAnimationStack();
    }

    /**
     * Get the animation applier for model rendering.
     * Required by IAnimatedPlayer interface.
     *
     * @return AnimationApplier for this entity
     */
    @Override
    public AnimationApplier playerAnimator_getAnimation() {
        return animationController.playerAnimator_getAnimation();
    }

    /**
     * Get a stored animation by ID.
     * Required by IAnimatedPlayer interface.
     *
     * @param id Animation identifier
     * @return The stored animation, or null if not found
     */
    @Override
    @Nullable
    public IAnimation playerAnimator_getAnimation(ResourceLocation id) {
        return animationController.playerAnimator_getAnimation(id);
    }

    /**
     * Store an animation by ID.
     * Required by IAnimatedPlayer interface.
     *
     * @param id Animation identifier
     * @param animation Animation to store, or null to remove
     * @return The previously stored animation, or null
     */
    @Override
    @Nullable
    public IAnimation playerAnimator_setAnimation(
        ResourceLocation id,
        @Nullable IAnimation animation
    ) {
        return animationController.playerAnimator_setAnimation(id, animation);
    }

    // IDIALOGUESPEAKER PARTIAL IMPLEMENTATION

    /**
     * Get the name displayed in dialogue chat messages.
     */
    @Override
    public String getDialogueName() {
        return getNpcName();
    }

    /**
     * Get this speaker as a LivingEntity.
     */
    @Override
    public LivingEntity asEntity() {
        return this;
    }

    /**
     * Check if dialogue should be processed through gag filter.
     */
    @Override
    public boolean isDialogueGagged() {
        return isGagged();
    }

    // NBT PERSISTENCE (shared portion)

    /**
     * Save shared NPC data to NBT.
     * Subclasses MUST call super.addAdditionalSaveData(tag) then add their own data.
     *
     * Bondage and inventory are saved here. Damsel-specific data (appearance, personality,
     * rewards) is saved by DamselDataSerializer via EntityDamsel.addAdditionalSaveData().
     */
    @Override
    public void addAdditionalSaveData(CompoundTag tag) {
        super.addAdditionalSaveData(tag);
        // Bondage (restraints, captor, sale state)
        bondageManager.saveToTag(tag);
        // Inventory (NPC inventory, equipment)
        inventoryManager.saveToTag(tag);
    }

    /**
     * Load shared NPC data from NBT.
     * Subclasses MUST call super.readAdditionalSaveData(tag) then load their own data.
     */
    @Override
    public void readAdditionalSaveData(CompoundTag tag) {
        super.readAdditionalSaveData(tag);
        // Bondage (restraints, captor, sale state)
        bondageManager.loadFromTag(tag);
        // Inventory (NPC inventory, equipment)
        inventoryManager.loadFromTag(tag);
    }

    /**
     * Check if this NPC is currently in a training session.
     * Always returns false -- training sessions have been removed.
     *
     * @return false
     */
    public boolean isInTrainingSession() {
        return false;
    }
}
