package com.tiedup.remake.entities;

import static com.tiedup.remake.util.GameConstants.*;

import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.SpeakerType;
import com.tiedup.remake.entities.ai.ConditionalGoal;
import com.tiedup.remake.entities.ai.kidnapper.*;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.entities.skins.MerchantKidnapperSkinManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.*;
import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.personality.PersonalityType;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
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.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 * Kidnapper Merchant - Elite kidnapper who trades mod items for gold.
 *
 * Behavior:
 * - MERCHANT mode (default): Neutral, wanders peacefully, accepts trades
 * - HOSTILE mode (when attacked): Full elite kidnapper AI, faster capture
 * - Reverts to MERCHANT after 5 minutes OR when attacker is captured/sold
 *
 * Trading:
 * - Sells 8-12 random mod items for gold ingots/nuggets
 * - Prices are tier-based (1-2 gold for basic binds, 10-20 for GPS collar)
 * - Trades are fixed once generated (persist in NBT)
 */
public class EntityKidnapperMerchant extends EntityKidnapperElite {

    // MERCHANT STATE

    public enum MerchantState {
        MERCHANT(0),
        HOSTILE(1);

        private final int id;

        MerchantState(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }

        public static MerchantState fromId(int id) {
            return id == 1 ? HOSTILE : MERCHANT;
        }
    }

    // DATA SYNC

    private static final EntityDataAccessor<Integer> DATA_MERCHANT_STATE =
        SynchedEntityData.defineId(
            EntityKidnapperMerchant.class,
            EntityDataSerializers.INT
        );

    // STATE (Server-side only)

    private MerchantState currentState = MerchantState.MERCHANT;
    private List<MerchantTrade> trades = new ArrayList<>();
    private int hostileCooldownTicks = 0;

    @Nullable
    private UUID attackerUUID = null;

    @Nullable
    private UUID lastSoldCaptiveUUID = null;

    // Track players currently trading with this merchant
    private final Set<UUID> tradingPlayers = new HashSet<>();

    // Static reverse-lookup: player UUID -> merchant entity UUID
    // Used by PlayerDisconnectHandler for O(1) cleanup instead of scanning all entities
    private static final ConcurrentHashMap<UUID, UUID> playerToMerchant =
        new ConcurrentHashMap<>();

    // CONSTRUCTOR

    public EntityKidnapperMerchant(
        EntityType<? extends EntityKidnapperMerchant> type,
        Level level
    ) {
        super(type, level);
    }

    // ATTRIBUTES (Same as Elite)

    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes()
            .add(Attributes.MAX_HEALTH, 40.0D)
            .add(Attributes.MOVEMENT_SPEED, 0.35D)
            .add(Attributes.KNOCKBACK_RESISTANCE, 0.9D)
            .add(Attributes.FOLLOW_RANGE, 60.0D)
            .add(Attributes.ATTACK_DAMAGE, 8.0D); // Same as Elite
    }

    // DATA SYNC

    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.define(
            DATA_MERCHANT_STATE,
            MerchantState.MERCHANT.getId()
        );
    }

    // INITIALIZATION

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

        // Server-side initialization
        if (!this.level().isClientSide) {
            // Generate trades on first spawn
            if (trades.isEmpty()) {
                generateRandomTrades();
                TiedUpMod.LOGGER.info(
                    "[EntityKidnapperMerchant] Generated {} trades for merchant at {}",
                    trades.size(),
                    this.blockPosition()
                );
            }
        }

        // Set custom name with gold color when in merchant mode
        updateCustomName();
    }

    /**
     * Update the custom name to show merchant status.
     */
    private void updateCustomName() {
        if (currentState == MerchantState.MERCHANT) {
            // Gold-colored name with symbols using ChatFormatting.GOLD
            this.setCustomName(
                Component.literal("⚜ " + this.getNpcName() + " ⚜")
                    .withStyle(net.minecraft.ChatFormatting.GOLD)
                    .withStyle(net.minecraft.ChatFormatting.BOLD)
            );
            this.setCustomNameVisible(true);
        } else {
            // In hostile mode, show name in dark red (same as elite)
            this.setCustomName(
                Component.literal(this.getNpcName()).withStyle(
                    net.minecraft.network.chat.Style.EMPTY.withColor(0xAA0000)
                )
            );
            this.setCustomNameVisible(true);
        }
    }

    // AI GOALS (Conditional)

    @Override
    protected void registerGoals() {
        // DON'T call super - we need full control over goals

        // Priority 0: Always swim
        this.goalSelector.addGoal(0, new FloatGoal(this));

        // Priority 1-10: Aggressive goals (ONLY when hostile)
        this.goalSelector.addGoal(
            1,
            new ConditionalGoal(
                new KidnapperFightBackGoal(this),
                this::isHostile
            )
        );

        this.goalSelector.addGoal(
            2,
            new ConditionalGoal(
                new KidnapperFindTargetGoal(this, 25),
                this::isHostile
            )
        );

        this.goalSelector.addGoal(
            3,
            new ConditionalGoal(new KidnapperCaptureGoal(this), this::isHostile)
        );

        this.goalSelector.addGoal(
            4,
            new ConditionalGoal(
                new KidnapperBringToCellGoal(this),
                this::isHostile
            )
        );

        // Priority 5: DecideNextAction - Active in BOTH modes
        // This goal decides if the merchant should sell or do a job with the captive
        // Must be active when reverting to MERCHANT mode after capturing attacker
        this.goalSelector.addGoal(5, new KidnapperDecideNextActionGoal(this));

        this.goalSelector.addGoal(
            6,
            new ConditionalGoal(new KidnapperPatrolGoal(this), this::isHostile)
        );

        this.goalSelector.addGoal(
            7,
            new ConditionalGoal(
                new KidnapperFleeWithCaptiveGoal(this),
                this::isHostile
            )
        );

        this.goalSelector.addGoal(
            8,
            new ConditionalGoal(
                new KidnapperFleeSafeGoal(this),
                this::isHostile
            )
        );

        // Priority 9-10: Sale and Job goals (active in MERCHANT mode)
        // These should work when merchant has a captive, regardless of hostile state
        this.goalSelector.addGoal(
            9,
            new ConditionalGoal(new KidnapperWaitForBuyerGoal(this), () ->
                !isHostile()
            )
        );

        this.goalSelector.addGoal(
            10,
            new ConditionalGoal(new KidnapperWaitForJobGoal(this), () ->
                !isHostile()
            )
        );

        // Priority 11-14: Peaceful goals (only when not trading)
        this.goalSelector.addGoal(
            11,
            new ConditionalGoal(
                new WaterAvoidingRandomStrollGoal(this, 1.0D),
                () -> !isTrading()
            )
        );
        this.goalSelector.addGoal(
            12,
            new LookAtPlayerGoal(this, Player.class, 8.0F)
        );
        this.goalSelector.addGoal(
            13,
            new ConditionalGoal(new RandomLookAroundGoal(this), () ->
                !isTrading()
            )
        );
        this.goalSelector.addGoal(14, new OpenDoorGoal(this, false));
    }

    // STATE TRANSITIONS

    @Override
    public boolean hurt(DamageSource source, float amount) {
        boolean result = super.hurt(source, amount);

        // Transition to hostile when attacked
        if (
            !level().isClientSide &&
            source.getEntity() instanceof LivingEntity attacker
        ) {
            if (currentState == MerchantState.MERCHANT) {
                transitionToHostile(attacker);
            }
        }

        return result;
    }

    /**
     * Override equip to detect restraint attempts and become hostile.
     * Consolidates former putBindOn/putGagOn/putBlindfoldOn overrides.
     */
    @Override
    public void equip(BodyRegionV2 region, ItemStack stack) {
        if (
            region == BodyRegionV2.ARMS ||
            region == BodyRegionV2.MOUTH ||
            region == BodyRegionV2.EYES
        ) {
            if (
                !level().isClientSide &&
                currentState == MerchantState.MERCHANT &&
                !stack.isEmpty()
            ) {
                LivingEntity attacker = findNearbyAttacker();
                if (attacker != null) {
                    transitionToHostile(attacker);
                }
            }
        }
        super.equip(region, stack);
    }

    /**
     * Find nearby player who might be the one trying to restrain us.
     */
    private LivingEntity findNearbyAttacker() {
        // Look for players within 5 blocks
        List<Player> nearbyPlayers = this.level().getEntitiesOfClass(
            Player.class,
            this.getBoundingBox().inflate(5.0),
            player -> !player.isSpectator()
        );

        // Return closest player
        if (!nearbyPlayers.isEmpty()) {
            return nearbyPlayers.get(0);
        }

        return null;
    }

    private void transitionToHostile(LivingEntity attacker) {
        currentState = MerchantState.HOSTILE;
        attackerUUID = attacker.getUUID();
        hostileCooldownTicks = SettingsAccessor.getMerchantHostileDuration();
        entityData.set(DATA_MERCHANT_STATE, MerchantState.HOSTILE.getId());

        // Equip kidnapper items
        setUpHeldItems();

        // Update name (hide merchant title)
        updateCustomName();

        // Talk to nearby players
        talkToPlayersInRadius("You'll regret that!", 20);

        TiedUpMod.LOGGER.info(
            "[EntityKidnapperMerchant] {} transitioned to HOSTILE (attacked by {})",
            this.getName().getString(),
            attacker.getName().getString()
        );
    }

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

        // Handle hostile cooldown
        if (!level().isClientSide && currentState == MerchantState.HOSTILE) {
            hostileCooldownTicks--;

            if (hostileCooldownTicks <= 0 || wasAttackerCaptured()) {
                revertToMerchant();
            }
        }

        // Spawn golden particles when in merchant mode (client-side)
        if (
            level().isClientSide &&
            currentState == MerchantState.MERCHANT &&
            this.random.nextFloat() < MERCHANT_SPARKLE_PARTICLE_CHANCE
        ) {
            // Golden sparkle particles around the merchant using DustParticleOptions
            double x =
                this.getX() +
                (this.random.nextDouble() - 0.5) * MERCHANT_PARTICLE_SPREAD_XZ;
            double y =
                this.getY() +
                this.random.nextDouble() * MERCHANT_PARTICLE_SPREAD_Y;
            double z =
                this.getZ() +
                (this.random.nextDouble() - 0.5) * MERCHANT_PARTICLE_SPREAD_XZ;

            // Gold color (R=1.0, G=0.843, B=0.0) with size 1.0
            net.minecraft.core.particles.DustParticleOptions goldDust =
                new net.minecraft.core.particles.DustParticleOptions(
                    new org.joml.Vector3f(1.0F, 0.843F, 0.0F),
                    1.0F
                );

            this.level().addParticle(goldDust, x, y, z, 0.0, 0.02, 0.0);
        }
    }

    private boolean wasAttackerCaptured() {
        if (attackerUUID == null) return false;

        // Check if current captive is the attacker
        IBondageState captive = getCaptive();
        if (
            captive != null &&
            captive.getKidnappedUniqueId().equals(attackerUUID)
        ) {
            return true;
        }

        // Check if we sold the attacker
        if (
            lastSoldCaptiveUUID != null &&
            lastSoldCaptiveUUID.equals(attackerUUID)
        ) {
            return true;
        }

        return false;
    }

    private void revertToMerchant() {
        currentState = MerchantState.MERCHANT;
        attackerUUID = null;
        lastSoldCaptiveUUID = null;
        hostileCooldownTicks = 0;
        entityData.set(DATA_MERCHANT_STATE, MerchantState.MERCHANT.getId());

        // Clear aggressive behavior
        setTarget(null);
        clearHeldItems();

        // Update name (show merchant title)
        updateCustomName();

        // Talk to nearby players
        talkToPlayersInRadius("Alright... business as usual.", 20);

        TiedUpMod.LOGGER.info(
            "[EntityKidnapperMerchant] {} reverted to MERCHANT",
            this.getName().getString()
        );
    }

    // VARIANT SYSTEM - Override virtual methods

    @Override
    public KidnapperVariant lookupVariantById(String variantId) {
        return MerchantKidnapperSkinManager.CORE.getVariant(variantId);
    }

    @Override
    public KidnapperVariant computeVariantForEntity(UUID entityUUID) {
        Gender preferredGender = SettingsAccessor.getPreferredSpawnGender(
            this.level() != null ? this.level().getGameRules() : null
        );
        return MerchantKidnapperSkinManager.CORE.getVariantForEntity(
            entityUUID,
            preferredGender
        );
    }

    @Override
    public String getVariantTextureFolder() {
        return "textures/entity/kidnapper/merchant/";
    }

    @Override
    public String getDefaultVariantId() {
        return "goldy";
    }

    @Override
    public void applyVariantName(KidnapperVariant variant) {
        // Merchant variants always use their default name
        this.setNpcName(variant.defaultName());
    }

    @Override
    public String getVariantNBTKey() {
        // Use different key for backward compatibility with existing saves
        return "MerchantVariantName";
    }

    // DISPLAY

    /**
     * Override getDisplayName to use customName when in merchant mode.
     * This ensures the gold-colored "⚜ Merchant ⚜" is displayed.
     */
    @Override
    public Component getDisplayName() {
        // In merchant mode, use the custom name (which has gold color)
        if (currentState == MerchantState.MERCHANT && this.hasCustomName()) {
            return this.getCustomName();
        }
        // In hostile mode, use parent's display name logic
        return super.getDisplayName();
    }

    // SALE OVERRIDE (Track sold attacker)

    @Override
    public boolean completeSale(ICaptor buyer) {
        IBondageState captive = getCaptive();
        if (captive != null) {
            lastSoldCaptiveUUID = captive.getKidnappedUniqueId();
        }
        return super.completeSale(buyer);
    }

    // TRADE GENERATION

    private void generateRandomTrades() {
        // GUARANTEED UTILITIES (always available)
        addGuaranteedUtilities();

        // RANDOM TRADES
        int min = SettingsAccessor.getMerchantMinTrades();
        int max = SettingsAccessor.getMerchantMaxTrades();
        int count = min + this.random.nextInt(Math.max(1, max - min + 1));

        // Collect all mod items
        List<ItemStack> items = collectAllModItems();

        // Shuffle manually (Collections.shuffle doesn't accept RandomSource)
        for (int i = items.size() - 1; i > 0; i--) {
            int j = this.random.nextInt(i + 1);
            ItemStack temp = items.get(i);
            items.set(i, items.get(j));
            items.set(j, temp);
        }

        // Generate trades
        for (int i = 0; i < Math.min(count, items.size()); i++) {
            MerchantTrade trade = generateRandomPriceForItem(items.get(i));
            trades.add(trade);
        }

        TiedUpMod.LOGGER.debug(
            "[EntityKidnapperMerchant] Generated {} trades ({} utilities + {} random)",
            trades.size(),
            getUtilityCount(),
            trades.size() - getUtilityCount()
        );
    }

    /**
     * Add guaranteed utility items that are ALWAYS available.
     * These are essential tools that players need access to.
     */
    private void addGuaranteedUtilities() {
        // Collar Key - needed to link to collared entities (Tier 2 pricing: 3-6 gold)
        trades.add(
            new MerchantTrade(
                new ItemStack(ModItems.COLLAR_KEY.get()),
                4,
                0 // 4 gold ingots
            )
        );

        // Command Wand - needed to give commands to NPCs (Tier 3 pricing: 5-10 gold)
        trades.add(
            new MerchantTrade(
                new ItemStack(ModItems.COMMAND_WAND.get()),
                6,
                0 // 6 gold ingots
            )
        );

        // Lockpick - useful utility (Tier 2 pricing)
        trades.add(
            new MerchantTrade(
                new ItemStack(ModItems.LOCKPICK.get()),
                3,
                0 // 3 gold ingots
            )
        );

        // Cell Core - essential for building cells (expensive, Tier 4 pricing)
        trades.add(
            new MerchantTrade(
                new ItemStack(
                    com.tiedup.remake.blocks.ModBlocks.CELL_CORE.get().asItem()
                ),
                12,
                0 // 12 gold ingots
            )
        );
    }

    private int getUtilityCount() {
        return 4; // Number of guaranteed utilities
    }

    private List<ItemStack> collectAllModItems() {
        List<ItemStack> items = new ArrayList<>();

        // All data-driven bondage items (binds, gags, blindfolds, earplugs, mittens, collars, etc.)
        for (com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition def :
                com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry.getAll()) {
            items.add(DataDrivenBondageItem.createStack(def.id()));
        }

        // Knives - no color support
        for (KnifeVariant variant : KnifeVariant.values()) {
            items.add(new ItemStack(ModItems.getKnife(variant)));
        }

        // Tools
        items.add(new ItemStack(ModItems.WHIP.get()));
        // BLACKLIST: TASER (too powerful)
        // BLACKLIST: LOCKPICK (now in guaranteed utilities)
        // BLACKLIST: MASTER_KEY (too OP - unlocks everything)
        items.add(new ItemStack(ModItems.CLOTHES.get()));

        return items;
    }

    private MerchantTrade generateRandomPriceForItem(ItemStack item) {
        int tier = getItemTier(item);

        // Calculate base price in nuggets
        int minPrice, maxPrice;
        switch (tier) {
            case 4:
                minPrice = SettingsAccessor.getTier4PriceMin();
                maxPrice = SettingsAccessor.getTier4PriceMax();
                break;
            case 3:
                minPrice = SettingsAccessor.getTier3PriceMin();
                maxPrice = SettingsAccessor.getTier3PriceMax();
                break;
            case 2:
                minPrice = SettingsAccessor.getTier2PriceMin();
                maxPrice = SettingsAccessor.getTier2PriceMax();
                break;
            case 1:
            default:
                minPrice = SettingsAccessor.getTier1PriceMin();
                maxPrice = SettingsAccessor.getTier1PriceMax();
                break;
        }

        int baseNuggets =
            minPrice +
            this.random.nextInt(Math.max(1, maxPrice - minPrice + 1));

        // Randomly split into ingots + nuggets
        int ingots = this.random.nextBoolean() ? baseNuggets / 9 : 0;
        int nuggets = baseNuggets - (ingots * 9);

        return new MerchantTrade(item.copy(), ingots, nuggets);
    }

    private int getItemTier(ItemStack item) {
        Item i = item.getItem();

        // Tier 4: GPS collar
        if (com.tiedup.remake.v2.bondage.CollarHelper.hasGPS(item)) {
            return 4;
        }

        // Tier 3: Shock collar, taser, master key
        if (
            com.tiedup.remake.v2.bondage.CollarHelper.canShock(item) ||
            i == ModItems.TASER.get() ||
            i == ModItems.MASTER_KEY.get()
        ) {
            return 3;
        }

        // Tier 2: Collars, whip, tools, complex items, clothes
        if (
            com.tiedup.remake.v2.bondage.CollarHelper.isCollar(item) ||
            i == ModItems.WHIP.get() ||
            i == ModItems.LOCKPICK.get() ||
            i instanceof GenericClothes
        ) {
            return 2;
        }

        // Tier 1: All other items (binds, gags, blindfolds, knives, etc.)
        return 1;
    }

    // NBT PERSISTENCE

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

        tag.putInt("MerchantState", currentState.getId());
        tag.putInt("HostileCooldown", hostileCooldownTicks);

        if (attackerUUID != null) {
            tag.putUUID("AttackerUUID", attackerUUID);
        }

        if (lastSoldCaptiveUUID != null) {
            tag.putUUID("LastSoldCaptiveUUID", lastSoldCaptiveUUID);
        }

        // Variant is saved by parent via getVariantNBTKey()

        // Save trades
        ListTag tradesTag = new ListTag();
        for (MerchantTrade trade : trades) {
            tradesTag.add(trade.save());
        }
        tag.put("Trades", tradesTag);
    }

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

        if (tag.contains("MerchantState")) {
            currentState = MerchantState.fromId(tag.getInt("MerchantState"));
            entityData.set(DATA_MERCHANT_STATE, currentState.getId());
        }

        hostileCooldownTicks = tag.getInt("HostileCooldown");

        if (tag.contains("AttackerUUID")) {
            attackerUUID = tag.getUUID("AttackerUUID");
        }

        if (tag.contains("LastSoldCaptiveUUID")) {
            lastSoldCaptiveUUID = tag.getUUID("LastSoldCaptiveUUID");
        }

        // Variant is restored by parent via getVariantNBTKey() and lookupVariantById()

        // Restore trades
        trades.clear();
        if (tag.contains("Trades")) {
            ListTag tradesTag = tag.getList("Trades", 10); // 10 = CompoundTag
            for (int i = 0; i < tradesTag.size(); i++) {
                trades.add(MerchantTrade.load(tradesTag.getCompound(i)));
            }
        }
    }

    // PUBLIC ACCESSORS

    public boolean isHostile() {
        return currentState == MerchantState.HOSTILE;
    }

    public boolean isMerchant() {
        return currentState == MerchantState.MERCHANT;
    }

    public List<MerchantTrade> getTrades() {
        return new ArrayList<>(trades);
    }

    /**
     * Check if the merchant is currently trading with a player.
     */
    public boolean isTrading() {
        return !tradingPlayers.isEmpty();
    }

    /**
     * Mark that a player has opened the trading screen.
     */
    public void startTrading(UUID playerUUID) {
        tradingPlayers.add(playerUUID);
        playerToMerchant.put(playerUUID, this.getUUID());
        TiedUpMod.LOGGER.debug(
            "[EntityKidnapperMerchant] Player {} started trading",
            playerUUID
        );
    }

    /**
     * Mark that a player has closed the trading screen.
     */
    public void stopTrading(UUID playerUUID) {
        tradingPlayers.remove(playerUUID);
        playerToMerchant.remove(playerUUID);
        TiedUpMod.LOGGER.debug(
            "[EntityKidnapperMerchant] Player {} stopped trading",
            playerUUID
        );
    }

    /**
     * BUG FIX: Clean up trading player on disconnect to prevent memory leak.
     * Called from PlayerDisconnectHandler.
     */
    public void cleanupTradingPlayer(UUID playerUUID) {
        tradingPlayers.remove(playerUUID);
        playerToMerchant.remove(playerUUID);
    }

    /**
     * Get the merchant entity UUID for a given trading player.
     * Used for O(1) lookup on disconnect instead of scanning all entities.
     */
    @Nullable
    public static UUID getMerchantForPlayer(UUID playerUUID) {
        return playerToMerchant.get(playerUUID);
    }

    // UTILITY

    @Override
    public void clearHeldItems() {
        this.setItemInHand(
            net.minecraft.world.InteractionHand.MAIN_HAND,
            ItemStack.EMPTY
        );
        this.setItemInHand(
            net.minecraft.world.InteractionHand.OFF_HAND,
            ItemStack.EMPTY
        );
    }

    public void talkToPlayersInRadius(String message, double radius) {
        MessageDispatcher.talkToNearby(this, message, radius);
    }

    // DIALOGUE SPEAKER (Merchant-specific)

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

    @Override
    public PersonalityType getSpeakerPersonality() {
        // Personality changes based on mode
        if (this.isHostile()) {
            return PersonalityType.FIERCE; // Vengeful when attacked
        }
        return PersonalityType.CURIOUS; // Greedy/business-oriented in merchant mode
    }

    @Override
    public int getSpeakerMood() {
        if (this.isHostile()) {
            return 30; // Angry
        }
        // In merchant mode, mood depends on having trades
        if (!trades.isEmpty()) {
            return 75; // Good business
        }
        return 60; // Waiting for customers
    }

    @Override
    public String getTargetRelation(Player player) {
        // If trading with player
        if (tradingPlayers.contains(player.getUUID())) {
            return "customer";
        }
        // Fall back to parent's implementation
        return super.getTargetRelation(player);
    }

    // No die() override needed: vanilla die() calls remove(KILLED), so this
    // remove() override already handles death cleanup. Parent EntityKidnapper.die()
    // handles captive freeing and loot drops.
    @Override
    public void remove(RemovalReason reason) {
        // Clear trading players to prevent dangling references
        if (!this.level().isClientSide) {
            int count = tradingPlayers.size();
            // Clean up reverse-lookup map BEFORE clearing to prevent memory leak
            for (UUID playerUuid : tradingPlayers) {
                playerToMerchant.remove(playerUuid);
            }
            this.tradingPlayers.clear();
            if (count > 0) {
                TiedUpMod.LOGGER.debug(
                    "[EntityKidnapperMerchant] {} clearing {} trading players on removal",
                    getNpcName(),
                    count
                );
            }
        }
        super.remove(reason);
    }
}
