package com.tiedup.remake.entities;

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.IDialogueSpeaker;
import com.tiedup.remake.dialogue.KidnapperDialogueTriggerSystem;
import com.tiedup.remake.dialogue.SpeakerType;
import com.tiedup.remake.entities.ai.kidnapper.*;
import com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.entities.skins.KidnapperSkinManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.PersonalityType;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.tasks.ItemTask;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
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.server.level.ServerPlayer;
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.ai.goal.*;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 * EntityKidnapper - Aggressive NPC that captures and enslaves players.
 *
 * Based on original EntityKidnapper.java (~700 lines)
 *
 * <p><b>Key Differences from EntityDamsel:</b></p>
 * <ul>
 *   <li>Implements ICaptor - can enslave players</li>
 *   <li>Aggressive AI - hunts and captures players</li>
 *   <li>Doesn't panic or flee</li>
 *   <li>Can hold bind/gag items</li>
 * </ul>
 *
 * <p><b>Features:</b></p>
 * <ul>
 *   <li>Target detection and pursuit</li>
 *   <li>Capture mechanics (tie up + gag)</li>
 *   <li>Slave management via ICaptor</li>
 *   <li>Prison teleportation (via collar config)</li>
 *   <li>Job system (shock collar + tasks)</li>
 *   <li>Sale system (multiplayer)</li>
 * </ul>
 *
 * <p><b>ARCHITECTURE NOTE - Inherited Components (from AbstractTiedUpNpc):</b></p>
 * <ul>
 *   <li>DamselBondageManager (via AbstractTiedUpNpc) - bondage/restraint mechanics</li>
 *   <li>DamselInventoryManager (via AbstractTiedUpNpc) - equipment slots</li>
 *   <li>DamselAnimationController (via AbstractTiedUpNpc) - animation/poses</li>
 *   <li>V2BondageEquipment (via AbstractTiedUpNpc) - V2 bondage equipment</li>
 * </ul>
 * Kidnapper-specific components (KidnapperAppearance, KidnapperCaptiveManager, etc.)
 * are owned directly by EntityKidnapper.
 *
 * @see AbstractTiedUpNpc
 * @see ICaptor
 */
public class EntityKidnapper
    extends AbstractTiedUpNpc
    implements ICaptor, IDialogueSpeaker
{

    // DATA SYNC (Client-Server)

    /**
     * Synced to client for rendering (e.g., holding leash).
     */
    private static final EntityDataAccessor<Boolean> DATA_HAS_CAPTIVE =
        SynchedEntityData.defineId(
            EntityKidnapper.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Whether kidnapping mode is active (via collar config).
     * Synced to client for name color display.
     */
    private static final EntityDataAccessor<Boolean> DATA_KIDNAPPING_MODE =
        SynchedEntityData.defineId(
            EntityKidnapper.class,
            EntityDataSerializers.BOOLEAN
        );

    /**
     * Kidnapper variant ID (skin selection).
     * Synced to client for rendering.
     */
    private static final EntityDataAccessor<String> DATA_KIDNAPPER_VARIANT_ID =
        SynchedEntityData.defineId(
            EntityKidnapper.class,
            EntityDataSerializers.STRING
        );

    /**
     * Kidnapper theme (item set selection).
     * Synced to client for potential visual effects.
     */
    private static final EntityDataAccessor<String> DATA_THEME =
        SynchedEntityData.defineId(
            EntityKidnapper.class,
            EntityDataSerializers.STRING
        );

    /**
     * Kidnapper theme color.
     * Synced to client for rendering colored items.
     */
    private static final EntityDataAccessor<String> DATA_THEME_COLOR =
        SynchedEntityData.defineId(
            EntityKidnapper.class,
            EntityDataSerializers.STRING
        );

    // SERVER-SIDE STATE

    /** Appearance manager for skin variants, themes, and item selection. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperAppearance appearance;

    /** Captive manager for ICaptor implementation and captive lifecycle. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperCaptiveManager captiveManager;

    /** Post-capture "get out" state (for escape behavior). */
    private boolean getOutState = false;

    /** Whether this kidnapper is currently dogwalking a prisoner. */
    private boolean dogwalking = false;

    /** Loot manager for stolen items, collar keys, and death drops. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperLootManager lootManager;

    /** Dialogue speaker implementation for IDialogueSpeaker delegation. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperDialogue dialogue;

    /** Job manager handles job assignment and tracking. */
    private final KidnapperJobManager jobManager = new KidnapperJobManager(
        this
    );

    /** Collar config helper for accessing collar settings. */
    private final KidnapperCollarConfig collarConfig =
        new KidnapperCollarConfig(this);

    /** Capture equipment manager for themed bondage items. */
    private final KidnapperCaptureEquipment captureEquipment =
        new KidnapperCaptureEquipment(this);

    /** State manager for behavioral state tracking. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperStateManager stateManager;

    /** Aggression system for escape tracking, fight-back, and robbery immunity. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem aggressionSystem;

    /** Camp manager for structure association and hunter role. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperCampManager campManager;

    /** Alert manager for broadcasting and receiving alerts between kidnappers. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperAlertManager alertManager;

    /** Target selector for target validation and selection logic. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperTargetSelector targetSelector;

    /** Cell manager for cell queries and breach responses. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperCellManager cellManager;

    /** Sale manager for NPC sale transactions. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperSaleManager saleManager;

    /** AI manager for goal registration (lazy initialized in registerGoals). */
    private com.tiedup.remake.entities.kidnapper.components.KidnapperAIManager aiManager;

    /** Data serializer for NBT persistence. */
    private final com.tiedup.remake.entities.kidnapper.components.KidnapperDataSerializer dataSerializer;

    // AI STATE SYSTEM

    // ABSTRACT METHOD IMPLEMENTATIONS (from AbstractTiedUpNpc)

    @Override
    protected com.tiedup.remake.entities.damsel.components.IBondageHost createBondageHost() {
        return new com.tiedup.remake.entities.kidnapper.hosts.BondageHost(this);
    }

    @Override
    protected void registerNpcGoals() {
        // Kidnapper goal registration is handled directly by registerGoals() override,
        // which delegates to KidnapperAIManager. This abstract method exists for the
        // contract but is not called for kidnappers.
    }

    // CONSTRUCTOR

    public EntityKidnapper(
        EntityType<? extends EntityKidnapper> type,
        Level level
    ) {
        super(type, level);
        // Initialize appearance manager
        this.appearance =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperAppearance(
                this,
                new com.tiedup.remake.entities.kidnapper.hosts.AppearanceHost(
                    this
                ),
                DATA_KIDNAPPER_VARIANT_ID,
                DATA_THEME,
                DATA_THEME_COLOR
            );

        // Initialize loot manager
        this.lootManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperLootManager(
                new com.tiedup.remake.entities.kidnapper.hosts.LootHost(this)
            );

        // Initialize dialogue component
        this.dialogue =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperDialogue(
                new com.tiedup.remake.entities.kidnapper.hosts.DialogueHost(this)
            );

        // Initialize state manager
        this.stateManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperStateManager(
                new com.tiedup.remake.entities.kidnapper.hosts.StateHost(this)
            );

        // Initialize aggression system
        this.aggressionSystem =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperAggressionSystem(
                new com.tiedup.remake.entities.kidnapper.hosts.AggressionHost(
                    this
                )
            );

        // Initialize camp manager
        this.campManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperCampManager(
                new com.tiedup.remake.entities.kidnapper.hosts.CampHost(this)
            );

        // Initialize alert manager
        this.alertManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperAlertManager(
                this,
                new com.tiedup.remake.entities.kidnapper.hosts.AlertHost(this)
            );

        // Initialize target selector
        this.targetSelector =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperTargetSelector(
                this,
                new com.tiedup.remake.entities.kidnapper.hosts.TargetHost(this)
            );

        // Initialize cell manager
        this.cellManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperCellManager(
                new com.tiedup.remake.entities.kidnapper.hosts.CellHost(this)
            );

        // Initialize sale manager
        this.saleManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperSaleManager(
                new com.tiedup.remake.entities.kidnapper.hosts.SaleHost(this)
            );

        // Initialize captive manager
        this.captiveManager =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperCaptiveManager(
                this,
                new com.tiedup.remake.entities.kidnapper.hosts.CaptiveHost(
                    this
                ),
                DATA_HAS_CAPTIVE
            );

        // Note: aiManager is lazy-initialized in registerGoals() to avoid NPE
        // (registerGoals is called from Mob constructor before this point)

        // Initialize data serializer
        this.dataSerializer =
            new com.tiedup.remake.entities.kidnapper.components.KidnapperDataSerializer(
                new com.tiedup.remake.entities.kidnapper.hosts.DataSerializerHost(
                    this
                ),
                DATA_KIDNAPPING_MODE
            );

        // Additional navigation config beyond what AbstractTiedUpNpc sets
        // (AbstractTiedUpNpc already sets canOpenDoors + canPassDoors)
        if (
            this.getNavigation() instanceof
                net.minecraft.world.entity.ai.navigation.GroundPathNavigation groundNav
        ) {
            groundNav.setCanFloat(true); // Allow crossing small ponds in forests
        }
        // Increase pathfinding search range for better navigation in complex terrain
        this.getNavigation().setMaxVisitedNodesMultiplier(2.0f);
    }

    // ATTRIBUTES

    /**
     * Create kidnapper attributes.
     * Faster than damsel to be able to catch them.
     */
    public static AttributeSupplier.Builder createAttributes() {
        // NOTE: Using default values (ModConfig not loaded yet during attribute registration)
        return Mob.createMobAttributes()
            .add(Attributes.MAX_HEALTH, 20.0) // Default: 20.0
            .add(Attributes.MOVEMENT_SPEED, 0.27) // Default: 0.27
            .add(Attributes.KNOCKBACK_RESISTANCE, 0.7D)
            .add(Attributes.FOLLOW_RANGE, 60.0) // Default: 60.0
            .add(Attributes.ATTACK_DAMAGE, 6.0); // Default: 6.0
    }

    // DATA SYNC

    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.define(DATA_HAS_CAPTIVE, false);
        this.entityData.define(DATA_KIDNAPPING_MODE, false);
        this.entityData.define(DATA_KIDNAPPER_VARIANT_ID, "");
        this.entityData.define(DATA_THEME, "");
        this.entityData.define(DATA_THEME_COLOR, "");
    }

    @Override
    public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
        super.onSyncedDataUpdated(key);
        // Invalidate kidnapper variant cache when synced from server
        if (DATA_KIDNAPPER_VARIANT_ID.equals(key)) {
            appearance.invalidateVariantCache();
        }
    }

    /**
     * Set kidnapper variant and name.
     */
    public void setKidnapperVariant(KidnapperVariant variant) {
        appearance.setKidnapperVariant(variant);
    }

    /**
     * Get current kidnapper variant.
     */
    @Nullable
    public KidnapperVariant getKidnapperVariant() {
        return appearance.getKidnapperVariant();
    }

    /**
     * Get kidnapper variant ID (synced data).
     */
    public String getKidnapperVariantId() {
        return appearance.getKidnapperVariantId();
    }

    // VARIANT SYSTEM - Virtual methods for subclasses

    /**
     * Lookup a variant by ID from the appropriate skin manager.
     * Override in subclasses to use their specific skin manager.
     * Delegates to appearance component by default.
     */
    public KidnapperVariant lookupVariantById(String variantId) {
        return KidnapperSkinManager.CORE.getVariant(variantId);
    }

    /**
     * Compute which variant to use based on UUID.
     * Override in subclasses to use different skin managers.
     * Delegates to appearance component by default.
     */
    public KidnapperVariant computeVariantForEntity(UUID entityUUID) {
        Gender preferredGender = SettingsAccessor.getPreferredSpawnGender(
            this.level() != null ? this.level().getGameRules() : null
        );
        return KidnapperSkinManager.CORE.getVariantForEntity(
            entityUUID,
            preferredGender
        );
    }

    /**
     * Get the texture folder for this kidnapper type.
     * Override in subclasses for different texture paths.
     */
    public String getVariantTextureFolder() {
        return "textures/entity/kidnapper/";
    }

    /**
     * Get the default variant ID for fallback.
     * Override in subclasses for their default variant.
     */
    public String getDefaultVariantId() {
        return "knp_mob_1";
    }

    /**
     * Apply the name from variant to this entity.
     * Override in subclasses for different naming behavior.
     */
    public void applyVariantName(KidnapperVariant variant) {
        if (variant.id().startsWith("knp_mob_")) {
            this.setNpcName(
                com.tiedup.remake.util.NameGenerator.getRandomKidnapperName()
            );
        } else {
            this.setNpcName(variant.defaultName());
        }
    }

    /**
     * Get the NBT key for saving the variant.
     * Override in subclasses if they need different keys for backward compatibility.
     */
    public String getVariantNBTKey() {
        return "KidnapperVariantId";
    }

    /**
     * Select items for this kidnapper.
     * Override in EntityKidnapperElite for higher probabilities.
     */
    public KidnapperItemSelector.SelectionResult selectItemsForKidnapper() {
        return KidnapperItemSelector.selectForKidnapper();
    }

    /**
     * Default texture for kidnapper entities.
     */
    private static final ResourceLocation DEFAULT_KIDNAPPER_TEXTURE =
        ResourceLocation.fromNamespaceAndPath(
            "tiedup",
            "textures/entity/kidnapper/knp_mob_1.png"
        );

    /**
     * Get the skin texture for this kidnapper.
     * Computes texture path from variant ID - no local skin JSONs needed on client.
     */
    @Override
    public ResourceLocation getSkinTexture() {
        return appearance.getSkinTexture();
    }

    @Override
    public void onAddedToWorld() {
        super.onAddedToWorld();

        // Initialize appearance (variant and theme) on server side
        if (!this.level().isClientSide) {
            appearance.initializeAppearance();
        }
    }

    /**
     * Get current theme.
     */
    @Nullable
    public KidnapperTheme getTheme() {
        return appearance.getTheme();
    }

    /**
     * Get current theme color.
     */
    @Nullable
    public com.tiedup.remake.items.base.ItemColor getThemeColor() {
        return appearance.getThemeColor();
    }

    /**
     * Get the selected items for this kidnapper.
     */
    @Nullable
    public KidnapperItemSelector.SelectionResult getItemSelection() {
        return appearance.getItemSelection();
    }

    /**
     * Ensure appearance (variant and theme) is initialized.
     * Used by KidnapperCaptureEquipment when setting up held items.
     */
    public void ensureAppearanceInitialized() {
        if (!this.level().isClientSide) {
            appearance.initializeAppearance();
        }
    }

    // AI GOALS

    @Override
    protected void registerGoals() {
        // Lazy initialize AI manager (must be done here because registerGoals is called
        // from Mob constructor before EntityKidnapper fields are initialized)
        if (this.aiManager == null) {
            this.aiManager =
                new com.tiedup.remake.entities.kidnapper.components.KidnapperAIManager(
                    this,
                    new com.tiedup.remake.entities.kidnapper.hosts.AIHost(this)
                );
        }

        // Delegate to AI manager
        // Note: Does NOT call super() - kidnappers have completely different AI than damsels
        aiManager.registerGoals();
    }

    // TICK

    @Override
    protected void tickSubclass() {
        // Tick dialogue cooldown
        this.tickDialogueCooldown();

        // Proactive dialogue system
        KidnapperDialogueTriggerSystem.tick(this, this.tickCount);
    }

    // I_KIDNAPPER IMPLEMENTATION (slave→captive)

    // C6-V2: ICaptor now takes IBondageState; KidnapperCaptiveManager still uses
    // IRestrainable internally (needed for ISaleable access in sale goals).
    // All real IBondageState instances are IRestrainable, so the cast is safe.
    // Log a warning if the invariant is ever broken (future-proofing).

    private void withRestrainable(String method, IBondageState captive, java.util.function.Consumer<IRestrainable> action) {
        if (captive instanceof IRestrainable r) action.accept(r);
        else logBridgeWarning(method, captive);
    }

    private boolean testRestrainable(String method, IBondageState captive, java.util.function.Predicate<IRestrainable> test) {
        if (captive instanceof IRestrainable r) return test.test(r);
        logBridgeWarning(method, captive);
        return false;
    }

    @Override
    public void addCaptive(IBondageState captive) {
        withRestrainable("addCaptive", captive, captiveManager::addCaptive);
    }

    @Override
    public void removeCaptive(IBondageState captive, boolean transportState) {
        withRestrainable("removeCaptive", captive, r -> captiveManager.removeCaptive(r, transportState));
    }

    @Override
    public boolean canCapture(IBondageState captive) {
        return testRestrainable("canCapture", captive, captiveManager::canCapture);
    }

    @Override
    public boolean canRelease(IBondageState captive) {
        return testRestrainable("canRelease", captive, captiveManager::canRelease);
    }

    @Override
    public boolean allowCaptiveTransfer() {
        return captiveManager.allowCaptiveTransfer();
    }

    @Override
    public boolean allowMultipleCaptives() {
        return captiveManager.allowMultipleCaptives();
    }

    @Override
    public void onCaptiveLogout(IBondageState captive) {
        withRestrainable("onCaptiveLogout", captive, captiveManager::onCaptiveLogout);
    }

    @Override
    public void onCaptiveReleased(IBondageState captive) {
        withRestrainable("onCaptiveReleased", captive, captiveManager::onCaptiveReleased);
    }

    @Override
    public void onCaptiveStruggle(IBondageState captive) {
        withRestrainable("onCaptiveStruggle", captive, captiveManager::onCaptiveStruggle);
    }

    private void logBridgeWarning(String method, IBondageState captive) {
        TiedUpMod.LOGGER.error(
            "[EntityKidnapper] {} called with non-IRestrainable: {}",
            method,
            captive.getClass().getSimpleName()
        );
    }

    @Override
    public boolean hasCaptives() {
        return captiveManager.hasCaptives();
    }

    @Override
    public Entity getEntity() {
        return captiveManager.getEntity();
    }

    // TARGET MANAGEMENT

    /**
     * Get current hunt target.
     */
    @Nullable
    public LivingEntity getTarget() {
        return targetSelector.getTarget();
    }

    /**
     * Set current hunt target.
     */
    public void setTarget(@Nullable LivingEntity target) {
        targetSelector.setTarget(target);
    }

    /**
     * Check if an entity is a suitable target for capture.
     * Supports both Players and EntityDamsel.
     */
    public boolean isSuitableTarget(LivingEntity entity) {
        return targetSelector.isSuitableTarget(entity);
    }

    /**
     * Check if target is still valid during an ACTIVE chase.
     *
     * Unlike isSuitableTarget(), this does NOT require line-of-sight.
     * During a chase, the kidnapper should continue pursuing the target
     * even if obstacles temporarily block vision (trees, hills, etc).
     * The pathfinder will navigate around obstacles.
     *
     * Use this in canContinueToUse() for capture goals.
     */
    public boolean isTargetStillValidForChase(LivingEntity entity) {
        return targetSelector.isTargetStillValidForChase(entity);
    }

    /**
     * Check if kidnapper is close enough to capture target.
     */
    public boolean isCloseToTarget() {
        return targetSelector.isCloseToTarget();
    }

    /**
     * Check if target is too far away.
     */
    public boolean isTooFarFromTarget(int radius) {
        return targetSelector.isTooFarFromTarget(radius);
    }

    /**
     * Find closest suitable target within radius.
     * Searches for Players, EntityDamsel, and MCA villagers.
     */
    @Nullable
    public LivingEntity getClosestSuitableTarget(int radius) {
        return targetSelector.getClosestSuitableTarget(radius);
    }

    // KIDNAPPING MODE (Collar-based) - Delegates to KidnapperCollarConfig

    /**
     * Check if kidnapping mode is enabled via collar.
     */
    public boolean isKidnappingModeEnabled() {
        return collarConfig.isKidnappingModeEnabled();
    }

    /**
     * Check if kidnapping mode is fully ready (enabled + prison set).
     */
    public boolean isKidnappingModeReady() {
        return collarConfig.isKidnappingModeReady();
    }

    /**
     * Get cell ID from collar.
     */
    @Nullable
    public java.util.UUID getCellIdFromCollar() {
        return collarConfig.getCellId();
    }

    /**
     * Check if collar has a cell assigned.
     */
    public boolean hasCellAssignedFromCollar() {
        return collarConfig.hasCellAssigned();
    }

    /**
     * Check if should warn masters after capturing slave.
     */
    public boolean shouldWarnMastersFromCollar() {
        return collarConfig.shouldWarnMasters();
    }

    /**
     * Check if should tie slave to pole in prison.
     */
    public boolean shouldTieToPoleFromCollar() {
        return collarConfig.shouldTieToPole();
    }

    /**
     * Check if player has a token in their inventory.
     * Players with tokens are not targeted by kidnappers.
     */
    public static boolean hasTokenInInventory(Player player) {
        return com.tiedup.remake.entities.kidnapper.components.KidnapperTargetSelector.hasTokenInInventory(
            player
        );
    }

    // CAPTURE MECHANICS - Delegates to KidnapperCaptureEquipment

    /**
     * Get bind item to use for capture.
     */
    public ItemStack getBindItem() {
        return captureEquipment.getBindItem();
    }

    /**
     * Get gag item to use for capture.
     */
    public ItemStack getGagItem() {
        return captureEquipment.getGagItem();
    }

    /**
     * Equip themed bind and gag items before capture.
     */
    public void setUpHeldItems() {
        captureEquipment.setUpHeldItems();
    }

    /**
     * Get mittens item to apply after capture.
     */
    @Nullable
    public ItemStack getMittensItem() {
        return captureEquipment.getMittensItem();
    }

    /**
     * Get earplugs item to apply after capture.
     */
    @Nullable
    public ItemStack getEarplugsItem() {
        return captureEquipment.getEarplugsItem();
    }

    /**
     * Get blindfold item to apply after capture.
     */
    @Nullable
    public ItemStack getBlindfoldItem() {
        return captureEquipment.getBlindfoldItem();
    }

    /**
     * Get collar item to apply during capture.
     */
    @Nullable
    public ItemStack getCollarItem() {
        return captureEquipment.getCollarItem();
    }

    /**
     * Clear held items (hide them).
     * Note: Overridden in subclasses (Archer restores bow, Merchant may differ).
     */
    public void clearHeldItems() {
        captureEquipment.clearHeldItems();
    }

    // CAPTIVE STATE (slave→captive)

    /**
     */
    @Nullable
    public IRestrainable getCaptive() {
        return captiveManager.getCaptive();
    }

    /**
     */
    public void transferAllCaptivesTo(ICaptor newCaptor) {
        captiveManager.transferAllCaptivesTo(newCaptor);
    }

    // OVERRIDES

    @Override
    public void equip(BodyRegionV2 region, ItemStack stack) {
        if (region == BodyRegionV2.ARMS) {
            // When kidnapper gets tied up, free their captive
            if (!this.level().isClientSide) {
                this.getOutState = false;
                captiveManager.onPutBindOn();

                // Drop held items
                this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
                this.setItemSlot(EquipmentSlot.OFFHAND, ItemStack.EMPTY);
            }
        }
        super.equip(region, stack);
    }

    @Override
    public boolean canBeTiedUp() {
        // Can't be tied up if:
        // - Waiting for job completion
        // - Selling captive
        return (
            super.canBeTiedUp() &&
            !this.isWaitingForJobToBeCompleted() &&
            !this.isSellingCaptive()
        );
    }

    /**
     * Kidnappers don't panic.
     */
    @Override
    public boolean canPanic() {
        return false;
    }

    /**
     * Kidnappers don't call for help when captured.
     * They're tough criminals, not helpless damsels!
     */
    @Override
    public boolean canCallForHelp() {
        return false;
    }

    /**
     * This ensures players attached via leash are properly released.
     */
    @Override
    public void die(
        net.minecraft.world.damagesource.DamageSource damageSource
    ) {
        // Free current captive before dying
        if (!this.level().isClientSide) {
            captiveManager.onDie();
        }

        // Clear job tracking
        this.clearCurrentJob();

        super.die(damageSource);
    }

    /**
     * Prevent taser from dropping when kidnapper dies.
     * Delegates loot logic to KidnapperLootManager.
     */
    @Override
    protected void dropEquipment() {
        lootManager.dropEquipment();
        super.dropEquipment();
        lootManager.dropPostEquipment();
    }

    /**
     * Free captives when the NPC is removed from the world.
     */
    @Override
    public void remove(RemovalReason reason) {
        // Free captive on any removal (death, chunk unload, dimension change, etc.)
        if (!this.level().isClientSide) {
            captiveManager.onRemove(reason);
        }

        super.remove(reason);
    }

    // CAPTURE TIMING (Overridable for Elite)

    /**
     * Get the time in ticks to tie up a target.
     * Elite kidnappers override this to be faster.
     *
     * @return Ticks to complete binding (default: 20 = 1 second)
     */
    public int getCaptureBindTime() {
        return 20;
    }

    /**
     * Get the time in ticks to gag a target.
     * Elite kidnappers override this to be faster.
     *
     * @return Ticks to complete gagging (default: 20 = 1 second)
     */
    public int getCaptureGagTime() {
        return 20;
    }

    // JOB SYSTEM - Delegates to KidnapperJobManager

    /**
     * Check if waiting for worker to complete job.
     */
    public boolean isWaitingForJobToBeCompleted() {
        return jobManager.isWaitingForJobToBeCompleted();
    }

    /**
     * Get the current job assigned to worker.
     */
    @Nullable
    public ItemTask getCurrentJob() {
        return jobManager.getCurrentJob();
    }

    /**
     * Get the UUID of the worker doing the current job.
     */
    @Nullable
    public UUID getJobWorkerUUID() {
        return jobManager.getJobWorkerUUID();
    }

    /**
     * Set the UUID of the worker doing the current job.
     */
    public void setJobWorkerUUID(@Nullable UUID workerUUID) {
        jobManager.setJobWorkerUUID(workerUUID);
    }

    /**
     * Find the job worker entity by UUID.
     */
    @Nullable
    public Player getJobWorker() {
        return jobManager.getJobWorker();
    }

    /**
     * Assign a job to the current captive.
     */
    public boolean assignJob(ItemTask job) {
        return jobManager.assignJob(job);
    }

    /**
     * Assign a random job using JobLoader.
     */
    public boolean assignRandomJob() {
        return jobManager.assignRandomJob();
    }

    /**
     * Clear the current job and worker tracking.
     */
    public void clearCurrentJob() {
        jobManager.clearCurrentJob();
    }

    // SALE SYSTEM

    /**
     */
    public boolean isSellingCaptive() {
        return saleManager.isSellingCaptive();
    }

    /**
     * Uses SaleLoader to get a random price.
     * Price is multiplied for valuable captives:
     * - Players: 2x base price
     * - Shiny damsels: 3x base price
     *
     * @return true if sale started successfully
     */
    public boolean startSale() {
        return saleManager.startSale();
    }

    /**
     *
     * @param price The price to sell at
     * @return true if sale started successfully
     */
    public boolean startSale(ItemTask price) {
        return saleManager.startSale(price);
    }

    /**
     * Cancel current sale.
     */
    public void cancelSale() {
        saleManager.cancelSale();
    }

    // SOLO MODE FALLBACK

    /**
     * Keep the captive when no buyers are available (solo mode).
     * The kidnapper decides to keep the captive for themselves.
     * This will trigger DecideNextAction to assign a job or bring to cell.
     */
    public void keepCaptive() {
        captiveManager.keepCaptive();
    }

    /**
     * Abandon the captive when no buyers are available (solo mode).
     * Applies blindfold, frees the captive (keeps restraints), and teleports them far away.
     */
    public void abandonCaptive() {
        captiveManager.abandonCaptive();
    }

    /**
     *
     * @param buyer The ICaptor buying the captive
     * @return true if sale completed successfully
     */
    public boolean completeSale(ICaptor buyer) {
        return saleManager.completeSale(buyer);
    }

    // GET OUT STATE

    public boolean isGetOutState() {
        return this.getOutState;
    }

    public void setGetOutState(boolean state) {
        this.getOutState = state;
    }

    /**
     * Check if this kidnapper is currently walking a prisoner (dogwalk).
     */
    public boolean isDogwalking() {
        return this.dogwalking;
    }

    /**
     * Set the dogwalking state.
     */
    public void setDogwalking(boolean dogwalking) {
        this.dogwalking = dogwalking;
    }

    // NBT PERSISTENCE

    @Override
    public void addAdditionalSaveData(CompoundTag tag) {
        super.addAdditionalSaveData(tag);

        // Delegate to data serializer
        dataSerializer.saveToNBT(tag);

        // Delegate stolen items and collar keys to loot manager
        lootManager.saveToNBT(tag);
    }

    @Override
    public void readAdditionalSaveData(CompoundTag tag) {
        super.readAdditionalSaveData(tag);

        // Delegate to data serializer
        dataSerializer.loadFromNBT(tag);

        // Delegate stolen items and collar keys to loot manager
        lootManager.loadFromNBT(tag);
    }

    // ESCAPE TRACKING METHODS

    /**
     * Called when a captive escapes from this kidnapper.
     * Records the escaped entity for potential recapture.
     *
     * @param escaped The entity that escaped (IRestrainable or LivingEntity)
     */
    public void onCaptiveEscaped(@Nullable Object escaped) {
        aggressionSystem.onCaptiveEscaped(escaped);
    }

    /**
     * Get the escaped target if still within memory time.
     *
     * @return The escaped entity, or null if expired or none
     */
    @Nullable
    public LivingEntity getEscapedTarget() {
        return aggressionSystem.getEscapedTarget();
    }

    /**
     * Clear the escaped target memory.
     * Called after successfully recapturing or giving up.
     */
    public void clearEscapedTarget() {
        aggressionSystem.clearEscapedTarget();
    }

    // FIGHT BACK SYSTEM METHODS

    /**
     * Get the last entity that attacked this kidnapper.
     * Returns null if no recent attack (>5 seconds).
     */
    @Nullable
    public LivingEntity getLastAttacker() {
        return aggressionSystem.getLastAttacker();
    }

    /**
     * Set the last attacker manually.
     * Used to trigger fight back when someone tries to free a captive.
     */
    public void setLastAttacker(LivingEntity attacker) {
        aggressionSystem.setLastAttacker(attacker);
    }

    /**
     * Check if player is the master/owner of this kidnapper.
     * Master = owner of the kidnapper's collar.
     */
    public boolean isMaster(Player player) {
        if (!this.hasCollar()) return false;

        ItemStack collar = this.getEquipment(BodyRegionV2.NECK);
        return CollarHelper.isOwner(collar, player);
    }

    @Override
    public boolean hurt(
        net.minecraft.world.damagesource.DamageSource source,
        float amount
    ) {
        float modified = aggressionSystem.processIncomingDamage(source, amount);
        if (modified <= 0) return false;
        return super.hurt(source, modified);
    }

    /**
     * Punish a prisoner who attacks this kidnapper.
     * Checks if the player is a tied-up prisoner and applies shock + tightens binds.
     *
     * @param player The player who attacked
     * @return true if punishment was applied
     */
    public boolean punishAttackingPrisoner(ServerPlayer player) {
        return captiveManager.punishAttackingPrisoner(player);
    }

    @Override
    protected void customServerAiStep() {
        super.customServerAiStep();
        // Required for swing animation to work
        this.updateSwingTime();

        // Update alert system
        alertManager.tick();

        // Update captive manager (restores captive from UUID after chunk/server reload)
        captiveManager.tick();
    }

    // AI STATE SYSTEM

    /**
     * Get the current behavioral state.
     */
    public KidnapperState getCurrentState() {
        return stateManager.getCurrentState();
    }

    /**
     * Set the current behavioral state.
     *
     * @param state The new state
     */
    public void setCurrentState(KidnapperState state) {
        stateManager.setCurrentState(state);
    }

    /**
     * Get the alert target (escapee being searched for).
     */
    @Nullable
    public LivingEntity getAlertTarget() {
        return alertManager.getAlertTarget();
    }

    /**
     * Set the alert target.
     *
     * @param target The escapee to search for
     */
    public void setAlertTarget(@Nullable LivingEntity target) {
        alertManager.setAlertTarget(target);
    }

    /**
     * Get the associated structure UUID for this kidnapper.
     */
    @Nullable
    public UUID getAssociatedStructure() {
        return campManager.getAssociatedStructure();
    }

    /**
     * Set the associated structure UUID.
     * When first associated with a camp, randomly decides if this kidnapper
     * becomes a "hunter" (50% chance) that patrols far from camp.
     *
     * @param structureId The structure this kidnapper belongs to
     */
    public void setAssociatedStructure(@Nullable UUID structureId) {
        campManager.setAssociatedStructure(structureId);
    }

    /**
     * Check if this kidnapper is a "hunter" that patrols far from camp.
     *
     * @return true if this kidnapper hunts in the wilderness
     */
    public boolean isHunter() {
        return campManager.isHunter();
    }

    /**
     * Set whether this kidnapper is a hunter.
     *
     * @param hunter true to make this kidnapper a hunter
     */
    public void setHunter(boolean hunter) {
        campManager.setHunter(hunter);
    }

    // ROBBED IMMUNITY METHODS

    /**
     * Grant robbed immunity to a player.
     * This kidnapper will not target this player for 2 minutes.
     *
     * @param playerUUID UUID of the player who was robbed
     */
    public void grantRobbedImmunity(UUID playerUUID) {
        aggressionSystem.grantRobbedImmunity(playerUUID);
    }

    /**
     * Check if a player has robbed immunity from this kidnapper.
     *
     * @param playerUUID UUID of the player to check
     * @return true if the player has immunity
     */
    public boolean hasRobbedImmunity(UUID playerUUID) {
        return aggressionSystem.hasRobbedImmunity(playerUUID);
    }

    /**
     * Clear robbed immunity for a player (e.g., when they break their bindings).
     *
     * @param playerUUID UUID of the player
     */
    public void clearRobbedImmunity(UUID playerUUID) {
        aggressionSystem.clearRobbedImmunity(playerUUID);
    }

    // COMPONENT ACCESSORS

    /**
     * Get the aggression system component (for TargetHost).
     *
     * @return The aggression system
     */
    public KidnapperAggressionSystem getAggressionSystem() {
        return this.aggressionSystem;
    }

    /**
     * Get the alert manager component (for CaptiveHost).
     *
     * @return The alert manager
     */
    public com.tiedup.remake.entities.kidnapper.components.KidnapperAlertManager getAlertManager() {
        return this.alertManager;
    }

    /**
     * Get the job manager component (for TargetHost).
     *
     * @return The job manager
     */
    public KidnapperJobManager getJobManager() {
        return this.jobManager;
    }

    /**
     * Get the collar config component (for TargetHost).
     *
     * @return The collar config
     */
    public KidnapperCollarConfig getCollarConfig() {
        return this.collarConfig;
    }

    /**
     * Get the appearance component (for DataSerializerHost).
     *
     * @return The appearance component
     */
    public com.tiedup.remake.entities.kidnapper.components.KidnapperAppearance getAppearanceComponent() {
        return this.appearance;
    }

    /**
     * Get the captive manager component (for DataSerializerHost).
     *
     * @return The captive manager
     */
    public com.tiedup.remake.entities.kidnapper.components.KidnapperCaptiveManager getCaptiveManagerComponent() {
        return this.captiveManager;
    }

    /**
     * Get the state manager component (for DataSerializerHost).
     *
     * @return The state manager
     */
    public com.tiedup.remake.entities.kidnapper.components.KidnapperStateManager getStateManagerComponent() {
        return this.stateManager;
    }

    /**
     * Get the camp manager component (for DataSerializerHost).
     *
     * @return The camp manager
     */
    public com.tiedup.remake.entities.kidnapper.components.KidnapperCampManager getCampManagerComponent() {
        return this.campManager;
    }

    /**
     * Get the captive transfer flag (for SaleHost).
     *
     * @return true if captive transfer is allowed
     */
    public boolean getAllowCaptiveTransferFlag() {
        return captiveManager.getAllowCaptiveTransferFlag();
    }

    /**
     * Set the captive transfer flag (for SaleHost).
     *
     * @param flag true to allow captive transfer
     */
    public void setAllowCaptiveTransferFlag(boolean flag) {
        captiveManager.setAllowCaptiveTransferFlag(flag);
    }

    /**
     * Get the active KidnapperWaitForBuyerGoal for this kidnapper.
     * Used by MasterBuyPlayerGoal to notify when purchase is complete.
     *
     * @return The WaitForBuyerGoal if active, or null if not found
     */
    @Nullable
    public KidnapperWaitForBuyerGoal getWaitForBuyerGoal() {
        // Search through running goals to find the WaitForBuyerGoal
        for (var wrappedGoal : this.goalSelector.getAvailableGoals()) {
            if (
                wrappedGoal.getGoal() instanceof
                    KidnapperWaitForBuyerGoal waitGoal
            ) {
                return waitGoal;
            }
        }
        return null;
    }

    // TARGET PURSUIT CHECK

    /**
     * Check if a target is already being pursued by another kidnapper.
     * Elite kidnappers ignore this check and can always pursue.
     *
     * @param target The potential target
     * @return true if another kidnapper is already pursuing this target
     */
    public boolean isTargetBeingPursuedByOther(LivingEntity target) {
        return targetSelector.isTargetBeingPursuedByOther(target);
    }

    /**
     * Broadcast an alert to nearby kidnappers about an escapee.
     * Other kidnappers within 50 blocks will enter ALERT state.
     *
     * @param escapee The entity that escaped
     */
    public void broadcastAlert(LivingEntity escapee) {
        alertManager.broadcastAlert(escapee);
    }

    /**
     * Receive an alert broadcast from another kidnapper.
     *
     * @param escapee The entity that escaped
     * @param source  The kidnapper that sent the alert
     */
    public void receiveAlertBroadcast(
        LivingEntity escapee,
        EntityKidnapper source
    ) {
        alertManager.receiveAlertBroadcast(escapee, source);
    }

    /**
     * Called when a prisoner is detected struggling nearby.
     * Uses LINE OF SIGHT - will only react if the kidnapper can SEE the prisoner.
     *
     * If the prisoner is seen struggling:
     * 1. Shock the prisoner (punishment)
     * 2. Set target to approach and tighten their binds
     *
     * @param prisoner The player who is struggling
     */
    public void onStruggleDetected(ServerPlayer prisoner) {
        captiveManager.onStruggleDetected(prisoner, prisoner.blockPosition());
    }

    /**
     * Set the target prisoner to approach after catching them struggling.
     * @param target The prisoner to approach
     */
    public void setStrugglePunishmentTarget(@Nullable LivingEntity target) {
        captiveManager.setStrugglePunishmentTarget(target);
    }

    /**
     * Get the current struggle punishment target.
     * @return The prisoner to approach, or null if none
     */
    @Nullable
    public LivingEntity getStrugglePunishmentTarget() {
        return captiveManager.getStrugglePunishmentTarget();
    }

    /**
     * Clear the struggle punishment target (after approaching or timeout).
     */
    public void clearStrugglePunishmentTarget() {
        captiveManager.clearStrugglePunishmentTarget();
    }

    /**
     * Called when a WALL marker is destroyed near this kidnapper.
     * Kidnapper will investigate the breach and enter ALERT state if they have a prisoner in that cell.
     *
     * @param breachPos The position where the wall was destroyed
     * @param cellId The UUID of the affected cell
     */
    public void onCellBreach(BlockPos breachPos, UUID cellId) {
        cellManager.onCellBreach(breachPos, cellId);
    }

    /**
     * Find a cell that contains a specific prisoner.
     *
     * @param prisonerId The prisoner UUID
     * @return The CellDataV2, or null if not found
     */
    @Nullable
    public CellDataV2 findCellContainingPrisoner(UUID prisonerId) {
        return cellManager.findCellContainingPrisoner(prisonerId);
    }

    /**
     * Get all nearby cells that have prisoners in them.
     * Used by KidnapperGuardGoal to find cells to watch.
     *
     * @return List of occupied cells within 32 blocks
     */
    public List<CellDataV2> getNearbyCellsWithPrisoners() {
        return cellManager.getNearbyCellsWithPrisoners();
    }

    /**
     * Check if a position is inside a specific cell (using WALL markers).
     *
     * @param pos  The position to check
     * @param cell The cell to check against
     * @return true if the position is inside the cell boundaries
     */
    public boolean isInsideCell(BlockPos pos, CellDataV2 cell) {
        return cellManager.isInsideCell(pos, cell);
    }

    /**
     * Get the PATROL markers near this kidnapper.
     * Used for patrol path following.
     *
     * @param radius Search radius
     * @return List of PATROL marker positions
     */
    public List<BlockPos> getNearbyPatrolMarkers(int radius) {
        return cellManager.getNearbyPatrolMarkers(radius);
    }

    // DISPLAY

    @Override
    public Component getDisplayName() {
        // Red name for kidnappers
        return Component.literal(this.getNpcName()).withStyle(style ->
            style.withColor(0xFF5555)
        ); // Red
    }

    // LEASH HOLDER POSITION

    /**
     * Override to position leash at right hand instead of chest.
     * Vanilla Entity.getRopeHoldPosition returns position + (0, eyeHeight*0.7, 0) which is chest level.
     * We want it to come from the hand like Player.getRopeHoldPosition does.
     *
     * @param partialTicks Partial tick for interpolation
     * @return Position where the leash rope should attach (right hand)
     */
    @Override
    public net.minecraft.world.phys.Vec3 getRopeHoldPosition(
        float partialTicks
    ) {
        return captiveManager.getRopeHoldPosition(partialTicks);
    }

    // DIALOGUE SPEAKER IMPLEMENTATION

    // DIALOGUE CONVENIENCE METHODS

    /**
     * Talk to a specific player using a message string.
     * Kidnapper equivalent of EntityDamsel.talkTo(Player, String).
     */
    public void talkTo(Player player, String message) {
        if (player == null || message == null) return;
        if (this.level().isClientSide()) return;
        com.tiedup.remake.util.MessageDispatcher.talkTo(this, player, message);
    }

    /**
     * Talk to a specific player using a dialogue category.
     * Kidnapper equivalent of EntityDamsel.talkTo(Player, DialogueCategory).
     */
    public void talkTo(
        Player player,
        com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category
    ) {
        if (player == null || category == null) return;
        if (this.level().isClientSide()) return;
        String message =
            com.tiedup.remake.dialogue.EntityDialogueManager.getDialogue(
                category
            );
        com.tiedup.remake.util.MessageDispatcher.talkTo(this, player, message);
    }

    /**
     * Broadcast a message to all players in radius.
     * Kidnapper equivalent of EntityDamsel.talkToPlayersInRadius(String, int).
     */
    public void talkToPlayersInRadius(String message, int radius) {
        if (message == null) return;
        if (this.level().isClientSide()) return;
        com.tiedup.remake.util.MessageDispatcher.talkToNearby(
            this,
            message,
            (double) radius
        );
    }

    /**
     * Broadcast a dialogue category to all players in radius.
     * Kidnapper equivalent of EntityDamsel.talkToPlayersInRadius(DialogueCategory, int).
     */
    public void talkToPlayersInRadius(
        com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category,
        int radius
    ) {
        if (category == null) return;
        if (this.level().isClientSide()) return;
        String message =
            com.tiedup.remake.dialogue.EntityDialogueManager.getDialogue(
                category
            );
        com.tiedup.remake.util.MessageDispatcher.talkToNearby(
            this,
            message,
            (double) radius
        );
    }

    /**
     * Perform an action message to a specific player.
     * Kidnapper equivalent of EntityDamsel.actionTo(Player, String).
     */
    public void actionTo(Player player, String action) {
        if (player == null || action == null) return;
        if (this.level().isClientSide()) return;
        com.tiedup.remake.util.MessageDispatcher.actionTo(this, player, action);
    }

    /**
     * Perform an action message to a specific player using a category.
     * Kidnapper equivalent of EntityDamsel.actionTo(Player, DialogueCategory).
     */
    public void actionTo(
        Player player,
        com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory category
    ) {
        if (player == null || category == null) return;
        if (this.level().isClientSide()) return;
        String action =
            com.tiedup.remake.dialogue.EntityDialogueManager.getDialogue(
                category
            );
        com.tiedup.remake.util.MessageDispatcher.actionTo(this, player, action);
    }

    @Override
    public String getDialogueName() {
        return dialogue.getDialogueName();
    }

    @Override
    public SpeakerType getSpeakerType() {
        return dialogue.getSpeakerType();
    }

    @Override
    @Nullable
    public PersonalityType getSpeakerPersonality() {
        return dialogue.getSpeakerPersonality();
    }

    @Override
    public int getSpeakerMood() {
        return dialogue.getSpeakerMood();
    }

    @Override
    @Nullable
    public String getTargetRelation(Player player) {
        return dialogue.getTargetRelation(player);
    }

    @Override
    public LivingEntity asEntity() {
        return this;
    }

    @Override
    public int getDialogueCooldown() {
        return dialogue.getDialogueCooldown();
    }

    @Override
    public void setDialogueCooldown(int ticks) {
        dialogue.setDialogueCooldown(ticks);
    }

    /**
     * Tick the dialogue cooldown.
     * Called from the main tick method.
     */
    protected void tickDialogueCooldown() {
        dialogue.tickDialogueCooldown();
    }

    // STOLEN ITEMS (Thief Goal)

    /**
     * Add an item to the stolen items list.
     * Called by KidnapperThiefGoal when stealing from a player.
     */
    public void addStolenItem(ItemStack stack) {
        lootManager.addStolenItem(stack);
    }

    // COLLAR KEYS (Capture Goal)

    /**
     * Add a collar key to be stored on this kidnapper.
     * Called by KidnapperCaptureGoal when collaring a captive.
     */
    public void addCollarKey(ItemStack keyStack) {
        lootManager.addCollarKey(keyStack);
    }
}
