package com.tiedup.remake.entities;

import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.SpeakerType;
import com.tiedup.remake.entities.ai.master.*;
import com.tiedup.remake.entities.master.components.IMasterStateHost;
import com.tiedup.remake.entities.master.components.MasterPetManager;
import com.tiedup.remake.entities.master.components.MasterStateManager;
import com.tiedup.remake.entities.master.components.MasterTaskManager;
import com.tiedup.remake.entities.skins.Gender;
import com.tiedup.remake.entities.skins.MasterSkinManager;
import com.tiedup.remake.minigame.ContinuousStruggleMiniGameState;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.master.PacketMasterStateSync;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.nbt.CompoundTag;
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.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
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.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 * EntityMaster - Rare NPC that buys solo players from Kidnappers.
 *
 * Master NPCs implement the pet play system:
 * - Buys solo players from Kidnappers (100% chance when no buyers)
 * - Follows the player (inverted follower mechanic)
 * - Restricts eating/sleeping to special blocks (Bowl/Pet Bed)
 * - Monitors player for escape attempts
 * - Punishes detected struggle attempts
 * - Has distraction windows where escape is possible
 *
 * Key Differences from Kidnapper:
 * - Does not actively hunt players
 * - Follows the player instead of other way around
 * - Much higher combat stats (100+ HP, damage reduction from slave)
 * - Cannot be easily defeated by the enslaved player
 */
public class EntityMaster extends EntityKidnapperElite {

    // CONSTANTS

    public static final double MASTER_MAX_HEALTH = 150.0D;
    public static final double MASTER_MOVEMENT_SPEED = 0.30D;
    public static final double MASTER_KNOCKBACK_RESISTANCE = 0.95D;
    public static final double MASTER_FOLLOW_RANGE = 60.0D;
    public static final double MASTER_ATTACK_DAMAGE = 10.0D;
    public static final double MASTER_ARMOR = 10.0D;

    /** Damage reduction from slave attacks (75% reduction) */
    public static final float SLAVE_DAMAGE_MULTIPLIER = 0.25f;

    /** Regeneration rate (HP per tick, 0.5 HP/sec = 0.025 HP/tick) */
    public static final float REGEN_PER_TICK = 0.025f;

    /** Name color (dark purple) */
    public static final int MASTER_NAME_COLOR = 0x8B008B;

    /** NBT keys */
    private static final String NBT_MASTER_STATE = "MasterState";
    private static final String NBT_PET_UUID = "PetPlayerUUID";
    private static final String NBT_MASTER_VARIANT = "MasterVariantId";
    private static final String NBT_PLACED_BLOCK_POS = "PlacedBlockPos";
    private static final String NBT_DOGWALK_MASTER_LEADS = "DogwalkMasterLeads";

    // DATA SYNC

    /** Current state synced to client */
    private static final EntityDataAccessor<String> DATA_MASTER_STATE =
        SynchedEntityData.defineId(
            EntityMaster.class,
            EntityDataSerializers.STRING
        );

    /** Whether master is distracted */
    private static final EntityDataAccessor<Boolean> DATA_DISTRACTED =
        SynchedEntityData.defineId(
            EntityMaster.class,
            EntityDataSerializers.BOOLEAN
        );

    // COMPONENTS

    /** State manager for master behavioral states */
    private final MasterStateManager stateManager;

    /** Pet manager for pet player lifecycle (collar, leash, freedom) */
    private final MasterPetManager petManager;

    /** Task manager for pet tasks, engagement cadence, and cold shoulder */
    private final MasterTaskManager taskManager;

    /** The kidnapper that is selling the player (for approach/buy logic) */
    @Nullable
    private EntityKidnapper sellingKidnapper;

    /** Reference to the place block goal for triggering feeding/resting */
    @Nullable
    private MasterPlaceBlockGoal placeBlockGoal;

    /** Reference to the dogwalk goal */
    @Nullable
    private MasterDogwalkGoal dogwalkGoal;

    /** Whether master leads during dogwalk (true = master pulls, false = master follows) */
    private boolean dogwalkMasterLeads = false;

    /** Reference to task assign goal for force assignment */
    @Nullable
    private MasterTaskAssignGoal taskAssignGoal;

    /** Reference to task watch goal */
    @Nullable
    private MasterTaskWatchGoal taskWatchGoal;

    /** Forced punishment type for next PUNISH cycle (null = random selection) */
    @Nullable
    private PunishmentType pendingForcedPunishment = null;

    /** Whether the next punishment is an attack punishment (dual: choke + physical restraint) */
    private boolean pendingAttackPunishment = false;

    // CONSTRUCTOR

    public EntityMaster(EntityType<? extends EntityMaster> type, Level level) {
        super(type, level);
        // Initialize state manager
        this.stateManager = new MasterStateManager(new MasterStateHost());
        // Initialize pet manager (depends on stateManager)
        this.petManager = new MasterPetManager(this, this.stateManager);
        // Initialize task manager
        this.taskManager = new MasterTaskManager(this);
    }

    // ATTRIBUTES

    /**
     * Create master attributes.
     * Very tanky - designed to be nearly impossible for slave to defeat.
     */
    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes()
            .add(Attributes.MAX_HEALTH, MASTER_MAX_HEALTH)
            .add(Attributes.MOVEMENT_SPEED, MASTER_MOVEMENT_SPEED)
            .add(Attributes.KNOCKBACK_RESISTANCE, MASTER_KNOCKBACK_RESISTANCE)
            .add(Attributes.FOLLOW_RANGE, MASTER_FOLLOW_RANGE)
            .add(Attributes.ATTACK_DAMAGE, MASTER_ATTACK_DAMAGE)
            .add(Attributes.ARMOR, MASTER_ARMOR);
    }

    // DATA SYNC

    @Override
    protected void defineSynchedData() {
        super.defineSynchedData();
        this.entityData.define(DATA_MASTER_STATE, MasterState.IDLE.name());
        this.entityData.define(DATA_DISTRACTED, false);
    }

    /**
     * Sync master state to all tracking clients.
     */
    private void syncState() {
        this.entityData.set(
            DATA_MASTER_STATE,
            stateManager.getCurrentState().name()
        );
        this.entityData.set(
            DATA_DISTRACTED,
            stateManager.getCurrentState() == MasterState.DISTRACTED
        );

        // Send network packet for detailed sync
        if (
            !this.level().isClientSide &&
            this.level() instanceof ServerLevel serverLevel
        ) {
            ModNetwork.sendToTracking(
                new PacketMasterStateSync(
                    this.getId(),
                    stateManager.getCurrentState().ordinal(),
                    stateManager.getPetPlayerUUID(),
                    stateManager.getRemainingDistractionTicks()
                ),
                this
            );
        }
    }

    /**
     * Get master state from synced data (client-side).
     */
    public MasterState getMasterState() {
        try {
            return MasterState.valueOf(this.entityData.get(DATA_MASTER_STATE));
        } catch (IllegalArgumentException e) {
            return MasterState.IDLE;
        }
    }

    /**
     * Check if master is distracted (client-safe).
     */
    public boolean isDistracted() {
        return this.entityData.get(DATA_DISTRACTED);
    }

    // SELLING KIDNAPPER (for approach/purchase)

    /**
     * Set the kidnapper that is selling a player to this Master.
     * Called when Master spawns to approach a sale.
     */
    public void setSellingKidnapper(@Nullable EntityKidnapper kidnapper) {
        this.sellingKidnapper = kidnapper;
        if (kidnapper != null) {
            // Set state to purchasing - Master will approach
            this.stateManager.setCurrentState(MasterState.PURCHASING);
            TiedUpMod.LOGGER.info(
                "[EntityMaster] {} set to purchase from {}",
                this.getNpcName(),
                kidnapper.getNpcName()
            );
        }
    }

    /**
     * Get the kidnapper that is selling a player.
     */
    @Nullable
    public EntityKidnapper getSellingKidnapper() {
        return sellingKidnapper;
    }

    /**
     * Check if this Master has a valid selling kidnapper.
     */
    public boolean hasSellingKidnapper() {
        return sellingKidnapper != null && sellingKidnapper.isAlive();
    }

    /**
     * Clear the selling kidnapper reference (after purchase complete).
     */
    public void clearSellingKidnapper() {
        this.sellingKidnapper = null;
    }

    // AI GOALS

    @Override
    protected void registerGoals() {
        // Float goal always highest priority
        this.goalSelector.addGoal(0, new FloatGoal(this));

        // Priority 1: Hunt monsters near pet (protect the pet!)
        this.goalSelector.addGoal(1, new MasterHuntMonstersGoal(this));

        // Priority 1: Buy player from kidnapper (initial state)
        this.goalSelector.addGoal(1, new MasterBuyPlayerGoal(this));

        // Priority 2: Punish + Place block (urgent reactions)
        this.goalSelector.addGoal(2, new MasterPunishGoal(this));
        this.placeBlockGoal = new MasterPlaceBlockGoal(this);
        this.goalSelector.addGoal(2, this.placeBlockGoal);

        // Priority 3: Dogwalk + Human Chair (exclusive movement modes)
        this.dogwalkGoal = new MasterDogwalkGoal(this);
        this.goalSelector.addGoal(3, this.dogwalkGoal);
        this.goalSelector.addGoal(3, new MasterHumanChairGoal(this));

        // Priority 4: Inventory inspection (short burst, higher than observe)
        this.goalSelector.addGoal(4, new MasterInventoryInspectGoal(this));

        // Priority 5: Observe player (watching state)
        this.goalSelector.addGoal(5, new MasterObservePlayerGoal(this));

        // Priority 6: Task assign, task watch, random events (engagement goals)
        this.taskAssignGoal = new MasterTaskAssignGoal(this);
        this.goalSelector.addGoal(6, this.taskAssignGoal);
        this.taskWatchGoal = new MasterTaskWatchGoal(this);
        this.goalSelector.addGoal(6, this.taskWatchGoal);
        this.goalSelector.addGoal(6, new MasterRandomEventGoal(this));

        // Priority 7: Idle behaviors (micro-actions between engagements)
        this.goalSelector.addGoal(7, new MasterIdleBehaviorGoal(this));

        // Priority 8: Follow player - DEFAULT behavior, lowest master priority
        // Must be below all engagement goals so they can interrupt following.
        // FollowPlayer (MOVE+LOOK) at prio 3 was blocking all prio 4-7 goals!
        this.goalSelector.addGoal(8, new MasterFollowPlayerGoal(this));

        // These were non-functional before (Masters have no PersonalityState).
        // Proper fix: Create MasterCommandGoals that use collar owner instead.
        // DamselAIController.registerCommandGoals(this.goalSelector, this, 9);

        // Look goals - lowest priority
        this.goalSelector.addGoal(
            10,
            new LookAtPlayerGoal(this, Player.class, 8.0F)
        );
        this.goalSelector.addGoal(11, new RandomLookAroundGoal(this));

        // Note: No target goals - Master doesn't hunt, only follows their pet
    }

    // VARIANT SYSTEM OVERRIDES

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

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

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

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

    @Override
    public String getVariantNBTKey() {
        return NBT_MASTER_VARIANT;
    }

    // PLAYER INTERACTION

    /**
     * Handle player right-click interaction.
     * - Pet + Shift + empty hand = open pet request menu
     * - Pet + empty hand = pet greeting
     * - Non-pet + Shift + empty hand = open conversation
     * - Non-pet + empty hand = stranger greeting
     */
    @Override
    protected net.minecraft.world.InteractionResult mobInteract(
        Player player,
        net.minecraft.world.InteractionHand hand
    ) {
        // Enslaved: use base NPC behavior (feeding, conversation via EntityDamsel)
        if (this.isTiedUp()) {
            return super.mobInteract(player, hand);
        }

        ItemStack heldItem = player.getItemInHand(hand);
        if (this.level().isClientSide()) {
            return net.minecraft.world.InteractionResult.SUCCESS;
        }

        if (!(player instanceof ServerPlayer serverPlayer)) {
            return net.minecraft.world.InteractionResult.PASS;
        }

        boolean isPet = isPetPlayer(player);

        // Pet interactions
        if (isPet) {
            // Cold shoulder - ignore pet interactions
            if (isGivingColdShoulder()) {
                return net.minecraft.world.InteractionResult.PASS;
            }
            // If holding an item and FETCH_ITEM or DEMAND task is active, try to give it
            if (
                !heldItem.isEmpty() &&
                (taskManager.getCurrentTask() == PetTask.FETCH_ITEM ||
                    taskManager.getCurrentTask() == PetTask.DEMAND)
            ) {
                if (taskManager.handleFetchItemGive(heldItem.getItem())) {
                    // Take the item from player
                    heldItem.shrink(1);
                    return net.minecraft.world.InteractionResult.SUCCESS;
                }
                // Item wasn't the right one - don't consume the interaction
            }

            // SPEAK task: pet right-clicks on master to complete
            if (taskManager.getCurrentTask() == PetTask.SPEAK) {
                com.tiedup.remake.dialogue.DialogueBridge.talkTo(
                    this,
                    player,
                    "petplay.task_complete"
                );
                taskManager.clearActiveTask();
                setMasterState(MasterState.FOLLOWING);
                return net.minecraft.world.InteractionResult.SUCCESS;
            }

            // Empty hand interactions
            if (heldItem.isEmpty()) {
                if (player.isShiftKeyDown()) {
                    // Shift + empty hand = open pet request menu
                    com.tiedup.remake.dialogue.conversation.PetRequestManager.openRequestMenu(
                        this,
                        serverPlayer
                    );
                    return net.minecraft.world.InteractionResult.SUCCESS;
                } else {
                    // Simple greeting
                    com.tiedup.remake.dialogue.DialogueBridge.talkTo(
                        this,
                        player,
                        "petplay.pet_greeting"
                    );
                    return net.minecraft.world.InteractionResult.SUCCESS;
                }
            }
        }

        // Non-pet interactions
        if (!isPet && heldItem.isEmpty()) {
            if (player.isShiftKeyDown()) {
                // Shift + empty hand = open conversation GUI (for non-pets)
                if (
                    com.tiedup.remake.dialogue.conversation.ConversationManager.openConversation(
                        this,
                        serverPlayer
                    )
                ) {
                    return net.minecraft.world.InteractionResult.SUCCESS;
                }
            } else {
                // Right click = greeting dialogue
                com.tiedup.remake.dialogue.DialogueBridge.talkTo(
                    this,
                    player,
                    "idle.stranger_greeting"
                );
                return net.minecraft.world.InteractionResult.SUCCESS;
            }
        }

        return super.mobInteract(player, hand);
    }

    // DISPLAY

    @Override
    public Component getDisplayName() {
        return Component.literal(this.getNpcName()).withStyle(
            Style.EMPTY.withColor(MASTER_NAME_COLOR)
        );
    }

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

    /**
     * Get relationship type for dialogue context.
     * Returns "pet" for the owned player.
     */
    @Override
    public String getTargetRelation(Player player) {
        if (
            stateManager.getPetPlayerUUID() != null &&
            stateManager.getPetPlayerUUID().equals(player.getUUID())
        ) {
            return "pet";
        }
        return null;
    }

    /**
     * Get mood for dialogue context.
     * Based on current state and pet behavior.
     */
    @Override
    public int getSpeakerMood() {
        int mood = 50; // Neutral
        MasterState state = stateManager.getCurrentState();

        switch (state) {
            case DISTRACTED -> mood += 20;
            case PUNISH -> mood -= 30;
            case OBSERVING -> mood += 10;
            case FOLLOWING -> mood += 15;
            default -> {
            }
        }

        return Math.max(0, Math.min(100, mood));
    }

    // TICK

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

        if (!this.level().isClientSide) {
            // Tick distraction system
            if (stateManager.tickDistraction()) {
                syncState();
            }

            // Passive regeneration
            if (this.getHealth() < this.getMaxHealth()) {
                this.heal(REGEN_PER_TICK);
            }

            // Check pet struggle sessions
            tickStruggleDetection();

            // Sync Master data to collar every second (for disconnect persistence)
            if (this.tickCount % 20 == 0) {
                ServerPlayer pet = getPetPlayer();
                if (pet != null && hasPetCollar(pet)) {
                    syncDataToCollar(pet);
                }
            }

            // FIX: Check for expired temporary event items every 5 seconds
            if (this.tickCount % 100 == 0) {
                ServerPlayer pet = getPetPlayer();
                if (pet != null) {
                    MasterRandomEventGoal.cleanupExpiredTempItems(
                        pet,
                        this.level().getGameTime()
                    );
                }
            }
        }
    }

    /**
     * Check if pet is struggling and detect if master is watching.
     */
    private void tickStruggleDetection() {
        UUID petUUID = stateManager.getPetPlayerUUID();
        if (petUUID == null) return;

        // Get struggle session for pet
        ContinuousStruggleMiniGameState session =
            StruggleSessionManager.getInstance().getContinuousStruggleSession(
                petUUID
            );

        if (session != null && session.getHeldDirection() >= 0) {
            // Pet is actively struggling
            ServerPlayer pet = getPetPlayer();
            if (
                pet != null &&
                stateManager.isWatching() &&
                this.hasLineOfSight(pet)
            ) {
                // Detected! Interrupt and punish
                onStruggleDetected(session);
            }
        }
    }

    /**
     * Called when master detects pet struggling.
     */
    private void onStruggleDetected(ContinuousStruggleMiniGameState session) {
        ServerPlayer pet = getPetPlayer();
        if (pet == null) return;

        TiedUpMod.LOGGER.info(
            "[EntityMaster] {} detected {} struggling!",
            this.getNpcName(),
            pet.getName().getString()
        );

        // Reset collar resistance (repair the lock)
        resetCollarResistance(pet);

        // Transition to punish state
        stateManager.onStruggleAttempt();
        syncState();

        // Send warning message to pet
        pet.sendSystemMessage(
            Component.translatable(
                "entity.tiedup.master.caught_escaping", this.getNpcName()
            ).withStyle(Style.EMPTY.withColor(MASTER_NAME_COLOR))
        );
    }

    /** @see MasterPetManager#resetCollarResistance(ServerPlayer) */
    private void resetCollarResistance(ServerPlayer pet) {
        petManager.resetCollarResistance(pet);
    }

    // COMBAT - DAMAGE REDUCTION

    @Override
    public boolean hurt(DamageSource source, float amount) {
        // Check if attacker is the pet
        boolean attackedByPet = false;
        ServerPlayer petAttacker = null;

        if (source.getEntity() instanceof Player player) {
            if (player.getUUID().equals(stateManager.getPetPlayerUUID())) {
                attackedByPet = true;
                petAttacker = (player instanceof ServerPlayer sp) ? sp : null;

                // Reduce damage from slave attacks
                amount *= SLAVE_DAMAGE_MULTIPLIER;
                TiedUpMod.LOGGER.debug(
                    "[EntityMaster] Reduced slave damage: {} -> {}",
                    amount / SLAVE_DAMAGE_MULTIPLIER,
                    amount
                );
            }
        }

        // Interrupt interruptible states if attacked
        if (amount > 0) {
            stateManager.interruptDistraction();
            interruptIfVulnerable();
            syncState();
        }

        // PUNISHMENT: If attacked by pet, trigger choke collar and enter PUNISH state
        if (attackedByPet && petAttacker != null && amount > 0) {
            triggerPunishmentForAttack(petAttacker);
        }

        return super.hurt(source, amount);
    }

    /**
     * Trigger punishment when pet attacks the Master.
     * Applies dual punishment: choke collar + a physical restraint (bind, blindfold, gag, or leash tug).
     */
    private void triggerPunishmentForAttack(ServerPlayer pet) {
        // Don't overwrite an active punishment
        if (stateManager.getCurrentState() == MasterState.PUNISH) {
            return;
        }

        TiedUpMod.LOGGER.info(
            "[EntityMaster] {} attacked by pet {} - triggering attack punishment",
            getNpcName(),
            pet.getName().getString()
        );

        // Dialogue - Master is angry
        com.tiedup.remake.dialogue.DialogueBridge.talkTo(
            this,
            pet,
            "punishment.attacked"
        );

        // Flag for dual punishment (choke + physical restraint)
        this.pendingAttackPunishment = true;

        // Enter PUNISH state
        setMasterState(MasterState.PUNISH);
    }

    /**
     * Interrupt states where the Master is vulnerable (sitting, walking pet).
     * Called when the Master takes damage from a non-pet source.
     * Forces the Master to get up / stop walking and return to FOLLOWING.
     */
    private void interruptIfVulnerable() {
        MasterState current = stateManager.getCurrentState();
        if (
            current == MasterState.HUMAN_CHAIR || current == MasterState.DOGWALK
        ) {
            TiedUpMod.LOGGER.info(
                "[EntityMaster] {} interrupted {} due to taking damage",
                getNpcName(),
                current
            );
            // State change triggers goal stop() which handles cleanup
            setMasterState(MasterState.FOLLOWING);
        }
    }

    /**
     * Called when the pet player takes damage.
     * If the Master is sitting on them (HUMAN_CHAIR), get up immediately.
     */
    public void onPetHurt(DamageSource source, float amount) {
        if (amount <= 0) return;
        MasterState current = stateManager.getCurrentState();
        if (current == MasterState.HUMAN_CHAIR) {
            TiedUpMod.LOGGER.info(
                "[EntityMaster] {} getting up - pet was hurt",
                getNpcName()
            );
            ServerPlayer pet = getPetPlayer();
            if (pet != null) {
                com.tiedup.remake.dialogue.DialogueBridge.talkTo(
                    this,
                    pet,
                    "petplay.human_chair_end"
                );
            }
            setMasterState(MasterState.FOLLOWING);
        }
    }

    @Override
    public void die(DamageSource source) {
        // Free the pet when master dies
        ServerPlayer pet = getPetPlayer();
        if (pet != null) {
            freePet(pet);
        }

        super.die(source);
    }

    // PET MANAGEMENT (delegates to MasterPetManager)

    /** Get the pet manager component. */
    public MasterPetManager getPetManager() {
        return petManager;
    }

    /** @see MasterPetManager#getPetPlayer() */
    @Nullable
    public ServerPlayer getPetPlayer() {
        return petManager.getPetPlayer();
    }

    /** @see MasterPetManager#setPetPlayer(ServerPlayer) */
    public void setPetPlayer(ServerPlayer player) {
        petManager.setPetPlayer(player);
        syncState();
    }

    /** @see MasterPetManager#hasPet() */
    public boolean hasPet() {
        return petManager.hasPet();
    }

    /** @see MasterPetManager#isPetPlayer(Player) */
    public boolean isPetPlayer(Player player) {
        return petManager.isPetPlayer(player);
    }

    // GOAL ACCESSORS

    /**
     * Get the place block goal for triggering feeding/resting.
     */
    @Nullable
    public MasterPlaceBlockGoal getPlaceBlockGoal() {
        return placeBlockGoal;
    }

    /**
     * Get the dogwalk goal.
     */
    @Nullable
    public MasterDogwalkGoal getDogwalkGoal() {
        return dogwalkGoal;
    }

    // DOGWALK MODE

    /**
     * Set dogwalk mode.
     *
     * @param masterLeads If true, master walks and pulls pet. If false, master follows pet.
     */
    public void setDogwalkMode(boolean masterLeads) {
        this.dogwalkMasterLeads = masterLeads;
        if (dogwalkGoal != null) {
            dogwalkGoal.setMasterLeads(masterLeads);
        }
    }

    /**
     * Check if master leads during dogwalk.
     */
    public boolean isDogwalkMasterLeads() {
        return dogwalkMasterLeads;
    }

    // TASK MANAGEMENT (delegates to MasterTaskManager)

    /** Get the task manager component. */
    public MasterTaskManager getTaskManager() {
        return taskManager;
    }

    /** @see MasterTaskManager#hasActiveTask() */
    public boolean hasActiveTask() {
        return taskManager.hasActiveTask();
    }

    /** @see MasterTaskManager#getCurrentTask() */
    @Nullable
    public PetTask getCurrentTask() {
        return taskManager.getCurrentTask();
    }

    /** @see MasterTaskManager#setActiveTask(PetTask) */
    public void setActiveTask(PetTask task) {
        taskManager.setActiveTask(task);
    }

    /** @see MasterTaskManager#clearActiveTask() */
    public void clearActiveTask() {
        taskManager.clearActiveTask();
    }

    /** @see MasterTaskManager#getTaskStartPosition() */
    @Nullable
    public net.minecraft.world.phys.Vec3 getTaskStartPosition() {
        return taskManager.getTaskStartPosition();
    }

    /** @see MasterTaskManager#setTaskStartPosition(net.minecraft.world.phys.Vec3) */
    public void setTaskStartPosition(net.minecraft.world.phys.Vec3 position) {
        taskManager.setTaskStartPosition(position);
    }

    /** @see MasterTaskManager#getRequestedItem() */
    @Nullable
    public net.minecraft.world.item.Item getRequestedItem() {
        return taskManager.getRequestedItem();
    }

    /** @see MasterTaskManager#setRequestedItem(net.minecraft.world.item.Item) */
    public void setRequestedItem(net.minecraft.world.item.Item item) {
        taskManager.setRequestedItem(item);
    }

    /**
     * Get the task assign goal for force assignment.
     */
    @Nullable
    public MasterTaskAssignGoal getTaskAssignGoal() {
        return taskAssignGoal;
    }

    // LEASH MANAGEMENT (delegates to MasterPetManager)

    /** @see MasterPetManager#attachLeashToPet() */
    public void attachLeashToPet() {
        petManager.attachLeashToPet();
    }

    /** @see MasterPetManager#detachLeashFromPet() */
    public void detachLeashFromPet() {
        petManager.detachLeashFromPet();
    }

    /** @see MasterPetManager#isPetLeashed() */
    public boolean isPetLeashed() {
        return petManager.isPetLeashed();
    }

    // ENGAGEMENT CADENCE (delegates to MasterTaskManager)

    /** @see MasterTaskManager#markEngagement() */
    public void markEngagement() {
        taskManager.markEngagement();
    }

    /** @see MasterTaskManager#getEngagementMultiplier() */
    public float getEngagementMultiplier() {
        return taskManager.getEngagementMultiplier();
    }

    // COLD SHOULDER (delegates to MasterTaskManager)

    /** @see MasterTaskManager#isGivingColdShoulder() */
    public boolean isGivingColdShoulder() {
        return taskManager.isGivingColdShoulder();
    }

    /** @see MasterTaskManager#startColdShoulder(int) */
    public void startColdShoulder(int durationTicks) {
        taskManager.startColdShoulder(durationTicks);
    }

    /** @see MasterPetManager#putPetCollar(ServerPlayer) */
    public void putPetCollar(ServerPlayer player) {
        petManager.putPetCollar(player);
    }

    /** @see MasterPetManager#freePet(ServerPlayer) */
    public void freePet(ServerPlayer player) {
        petManager.freePet(player);
        syncState();
    }

    /**
     * Check if player has pet play collar from this master.
     */
    public static boolean hasPetCollar(Player player) {
        ItemStack collarStack = V2EquipmentHelper.getInRegion(
            player,
            BodyRegionV2.NECK
        );
        if (collarStack.isEmpty()) return false;

        CompoundTag tag = collarStack.getTag();
        return tag != null && tag.getBoolean("petPlayMode");
    }

    /** @see MasterPetManager#syncDataToCollar(ServerPlayer) */
    private void syncDataToCollar(ServerPlayer pet) {
        petManager.syncDataToCollar(pet);
    }

    /**
     * Get master UUID from player's pet collar.
     */
    @Nullable
    public static UUID getMasterUUID(Player player) {
        ItemStack collarStack = V2EquipmentHelper.getInRegion(
            player,
            BodyRegionV2.NECK
        );
        if (collarStack.isEmpty()) return null;

        CompoundTag tag = collarStack.getTag();
        if (tag != null && tag.hasUUID("masterUUID")) {
            return tag.getUUID("masterUUID");
        }
        return null;
    }

    // STATE ACCESSORS

    public MasterStateManager getStateManager() {
        return stateManager;
    }

    public void setMasterState(MasterState state) {
        stateManager.setCurrentState(state);
        syncState();
    }

    /**
     * Get and consume the forced punishment type (returns null if none pending).
     */
    @Nullable
    public PunishmentType consumeForcedPunishment() {
        PunishmentType type = this.pendingForcedPunishment;
        this.pendingForcedPunishment = null;
        return type;
    }

    /**
     * Get and consume the attack punishment flag.
     * Returns true if this punishment was triggered by pet attacking the master.
     */
    public boolean consumeAttackPunishment() {
        boolean pending = this.pendingAttackPunishment;
        this.pendingAttackPunishment = false;
        return pending;
    }

    // NBT SERIALIZATION

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

        tag.putString(NBT_MASTER_STATE, stateManager.serializeState());
        String petUUID = stateManager.serializePetUUID();
        if (petUUID != null) {
            tag.putString(NBT_PET_UUID, petUUID);
        }

        // Save task, engagement, and cold shoulder data
        taskManager.save(tag);

        // Save dogwalk mode preference
        tag.putBoolean(NBT_DOGWALK_MASTER_LEADS, dogwalkMasterLeads);

        // FIX: Save placed block position for cleanup after restart
        if (placeBlockGoal != null) {
            net.minecraft.core.BlockPos placedPos =
                placeBlockGoal.getPlacedBlockPos();
            if (placedPos != null) {
                tag.putInt(NBT_PLACED_BLOCK_POS + "X", placedPos.getX());
                tag.putInt(NBT_PLACED_BLOCK_POS + "Y", placedPos.getY());
                tag.putInt(NBT_PLACED_BLOCK_POS + "Z", placedPos.getZ());
            }
        }
    }

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

        if (tag.contains(NBT_MASTER_STATE)) {
            stateManager.deserializeState(tag.getString(NBT_MASTER_STATE));
        }
        if (tag.contains(NBT_PET_UUID)) {
            stateManager.deserializePetUUID(tag.getString(NBT_PET_UUID));
        }

        // Load task, engagement, and cold shoulder data
        taskManager.load(tag);

        // Load dogwalk mode preference
        if (tag.contains(NBT_DOGWALK_MASTER_LEADS)) {
            dogwalkMasterLeads = tag.getBoolean(NBT_DOGWALK_MASTER_LEADS);
        }

        // FIX: Load placed block position and schedule cleanup
        if (
            tag.contains(NBT_PLACED_BLOCK_POS + "X") && placeBlockGoal != null
        ) {
            net.minecraft.core.BlockPos placedPos =
                new net.minecraft.core.BlockPos(
                    tag.getInt(NBT_PLACED_BLOCK_POS + "X"),
                    tag.getInt(NBT_PLACED_BLOCK_POS + "Y"),
                    tag.getInt(NBT_PLACED_BLOCK_POS + "Z")
                );
            placeBlockGoal.setPlacedBlockPos(placedPos);
            // Schedule cleanup for next tick (level not fully loaded yet)
            placeBlockGoal.cleanupOrphanedBlock();
        }
    }

    @Override
    public void remove(RemovalReason reason) {
        // MEDIUM FIX: Master-specific cleanup (pet is NOT in captiveManager)
        if (!this.level().isClientSide) {
            // 0. If master was in HUMAN_CHAIR, clean up the pet pose.
            //    discard() triggers remove() but NOT goal.stop(), so without this
            //    the pet keeps the humanChairMode bind and freeze effects forever.
            if (stateManager.getCurrentState() == MasterState.HUMAN_CHAIR) {
                ServerPlayer pet = getPetPlayer();
                if (pet != null) {
                    PlayerBindState bindState = PlayerBindState.getInstance(
                        pet
                    );
                    if (bindState != null && bindState.isTiedUp()) {
                        net.minecraft.world.item.ItemStack bind =
                            bindState.getEquipment(BodyRegionV2.ARMS);
                        if (!bind.isEmpty()) {
                            net.minecraft.nbt.CompoundTag tag = bind.getTag();
                            if (
                                tag != null &&
                                tag.getBoolean(
                                    com.tiedup.remake.util.HumanChairHelper.NBT_KEY
                                )
                            ) {
                                bindState.unequip(BodyRegionV2.ARMS);
                            }
                        }
                    }
                    pet.removeEffect(
                        net.minecraft.world.effect.MobEffects.MOVEMENT_SLOWDOWN
                    );
                    pet.removeEffect(
                        net.minecraft.world.effect.MobEffects.JUMP
                    );
                    TiedUpMod.LOGGER.info(
                        "[EntityMaster] {} cleanup: removed human chair pose from pet",
                        getNpcName()
                    );
                }
            }

            // 1. Detach leash from pet
            // This prevents the player from being leashed to a non-existent entity
            this.detachLeashFromPet();

            // 2. End any active struggle session for the pet
            // If master vanishes, the struggle session must end or it will bug out
            UUID petUUID = stateManager.getPetPlayerUUID();
            if (petUUID != null) {
                com.tiedup.remake.minigame.StruggleSessionManager.getInstance().endContinuousStruggleSession(
                    petUUID,
                    false
                );
                TiedUpMod.LOGGER.info(
                    "[EntityMaster] {} cleanup: detached leash and ended struggle session for pet {}",
                    getNpcName(),
                    petUUID.toString().substring(0, 8)
                );
            }
        }

        // Call super to handle any standard kidnapper cleanup (if used)
        super.remove(reason);
    }

    // STATE HOST IMPLEMENTATION

    private class MasterStateHost implements IMasterStateHost {

        @Override
        public String getNpcName() {
            return EntityMaster.this.getNpcName();
        }

        @Override
        public long getCurrentTick() {
            return EntityMaster.this.level().getGameTime();
        }

        @Override
        public void onDistracted() {
            // Could play animation or particles
            TiedUpMod.LOGGER.debug(
                "[EntityMaster] {} became distracted",
                getNpcName()
            );
        }

        @Override
        public void onDistractionEnd() {
            TiedUpMod.LOGGER.debug(
                "[EntityMaster] {} is no longer distracted",
                getNpcName()
            );
        }

        @Override
        public void onStruggleDetected() {
            // Already handled in onStruggleDetected method
        }
    }

    // BODY ROTATION OVERRIDE

    @Override
    protected float tickHeadTurn(float yRot, float animStep) {
        // While sitting on pet, skip BodyRotationControl and force body to match
        // entity yRot (which IS network-synced, unlike yBodyRot).
        // Server: positionOnPet() sets yRot = sideYaw → synced to client.
        // Client: we read the synced yRot and apply it to yBodyRot.
        if (isSitting()) {
            this.yBodyRot = this.getYRot();
            this.yBodyRotO = this.yRotO;
            return animStep;
        }
        return super.tickHeadTurn(yRot, animStep);
    }

    // COLLISION OVERRIDE

    @Override
    public boolean isPushable() {
        // Disable entity pushing during human chair so master can sit on pet
        if (getMasterState() == MasterState.HUMAN_CHAIR) {
            return false;
        }
        return super.isPushable();
    }

    @Override
    protected void pushEntities() {
        // Don't push anyone while sitting on pet (blocks master→player collision direction)
        if (getMasterState() == MasterState.HUMAN_CHAIR) {
            return;
        }
        super.pushEntities();
    }
}
