package com.tiedup.remake.client.animation.render;

import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

/**
 * Handles pet bed render adjustments (SIT and SLEEP modes).
 *
 * <p>Applies vertical offset and forced standing pose for pet bed states.
 * Runs at LOW priority — observes earlier cancellations from HIGH/NORMAL/LOW
 * mods but precedes LOWEST-tier cancellers. The co-ordering with
 * DogPoseRenderHandler is state-based (checking {@code isDogOrChairPose}),
 * not priority-based.
 *
 * <p>Extracted from PlayerArmHideEventHandler for single-responsibility.
 */
@OnlyIn(Dist.CLIENT)
@Mod.EventBusSubscriber(
    modid = TiedUpMod.MOD_ID,
    bus = Mod.EventBusSubscriber.Bus.FORGE,
    value = Dist.CLIENT
)
public class PetBedRenderHandler {

    /**
     * Players whose forced pose we mutated in {@link #onRenderPlayerPre}.
     * {@link #onRenderPlayerPost} only restores the pose for players in this
     * set, keeping the mutation/restore pair atomic even when another mod
     * cancels Pre (so our Pre returned early without mutating) — otherwise
     * Post would null-out a forced pose we never set, potentially clobbering
     * state owned by another mod.
     */
    private static final Set<UUID> FORCED_POSE_PLAYERS =
        ConcurrentHashMap.newKeySet();

    /**
     * Before player render: Apply vertical offset and forced pose for pet bed.
     *
     * <p>LOW priority + {@code isCanceled} guard: skip mutation when any
     * earlier-priority canceller fired. The paired Post uses
     * {@code receiveCanceled = true} + {@link #FORCED_POSE_PLAYERS} so
     * mutations still get restored even if a LOWEST-tier canceller runs
     * after our Pre.</p>
     */
    @SubscribeEvent(priority = EventPriority.LOW)
    public static void onRenderPlayerPre(RenderPlayerEvent.Pre event) {
        if (event.isCanceled()) {
            return;
        }

        Player player = event.getEntity();
        if (!(player instanceof AbstractClientPlayer)) {
            return;
        }

        if (player.isRemoved() || !player.isAlive()) {
            return;
        }

        UUID petBedUuid = player.getUUID();
        byte petBedMode = PetBedClientState.get(petBedUuid);

        if (petBedMode == 1 || petBedMode == 2) {
            // Skip Y-offset if DogPoseRenderHandler already applies it
            // (DOG/HUMAN_CHAIR pose uses the same offset amount)
            if (!isDogOrChairPose(player)) {
                event
                    .getPoseStack()
                    .translate(0, RenderConstants.DOG_AND_PETBED_Y_OFFSET, 0);
            }
        }
        if (petBedMode == 2) {
            // SLEEP: force STANDING pose to prevent vanilla sleeping rotation
            player.setForcedPose(net.minecraft.world.entity.Pose.STANDING);
            FORCED_POSE_PLAYERS.add(petBedUuid);

            // Compensate for vanilla sleeping Y offset
            player
                .getSleepingPos()
                .ifPresent(pos -> {
                    double yOffset = player.getY() - pos.getY();
                    if (yOffset > 0.01) {
                        event.getPoseStack().translate(0, -yOffset, 0);
                    }
                });
        }
    }

    /**
     * Check if the player is in DOG or HUMAN_CHAIR pose.
     * Used to avoid double Y-offset with DogPoseRenderHandler.
     */
    private static boolean isDogOrChairPose(Player player) {
        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) return false;
        ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
        if (bind.isEmpty()) return false;
        PoseType pose = HumanChairHelper.resolveEffectivePose(
            PoseTypeHelper.getPoseType(bind),
            bind
        );
        return pose == PoseType.DOG || pose == PoseType.HUMAN_CHAIR;
    }

    /**
     * After player render: Restore forced pose for pet bed SLEEP mode.
     *
     * <p>Only restores when Pre actually mutated the pose (tracked via
     * {@link #FORCED_POSE_PLAYERS}). If Pre was cancelled upstream or
     * mode flipped between Pre and Post, we never touched this player's
     * forced pose — so nulling it out here would clobber another mod's
     * state. Symmetric with the LOWEST priority + cancel guard on Pre.</p>
     */
    @SubscribeEvent(receiveCanceled = true)
    public static void onRenderPlayerPost(RenderPlayerEvent.Post event) {
        Player player = event.getEntity();
        if (!(player instanceof AbstractClientPlayer)) {
            return;
        }

        UUID playerUuid = player.getUUID();
        if (FORCED_POSE_PLAYERS.remove(playerUuid)) {
            player.setForcedPose(null);
        }
    }

    /**
     * Drain tracked state for an entity leaving the level.
     * Called from {@code EntityCleanupHandler} to prevent stale UUIDs from
     * lingering when players disconnect mid-render-cycle. Fires for every
     * departing entity — non-player UUIDs are simply absent from the set,
     * so {@code remove} is a cheap no-op.
     */
    public static void onEntityLeave(UUID entityUuid) {
        FORCED_POSE_PLAYERS.remove(entityUuid);
    }

    /**
     * Drain all tracked state. Called from
     * {@link com.tiedup.remake.client.animation.AnimationStateRegistry#clearAll}
     * on world unload so a UUID added between Pre and a world-unload event
     * doesn't linger into the next world.
     */
    public static void clearAll() {
        FORCED_POSE_PLAYERS.clear();
    }
}
