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

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.Map;
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 DOG and HUMAN_CHAIR pose rendering adjustments.
 *
 * <p>Applies vertical offset and smooth body rotation for DOG/HUMAN_CHAIR poses.
 * Runs at HIGH priority to ensure transforms are applied before other Pre handlers.
 *
 * <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 DogPoseRenderHandler {

    /**
     * DOG pose state per player, keyed by UUID (stable across dimension
     * change, unlike the int entity id which gets reassigned when the
     * entity re-enters the level). Stores: [0: smoothedTarget, 1: currentRot,
     * 2: appliedDelta, 3: isMoving (0/1)]
     */
    private static final Map<UUID, float[]> dogPoseState =
        new ConcurrentHashMap<>();

    // Array indices for dogPoseState
    private static final int IDX_TARGET = 0;
    private static final int IDX_CURRENT = 1;
    private static final int IDX_DELTA = 2;
    private static final int IDX_MOVING = 3;

    /**
     * Get the rotation delta applied to a player's render for DOG pose.
     *
     * @deprecated since Phase 2.8 — this getter fed {@code MixinPlayerModel}
     *     (removed Phase 2.8 RIG cleanup) so head rotation could be compensated
     *     against the body's -90° pitch. No remaining reader. To be deleted
     *     when V3-REW-07 re-expresses dog pose head compensation as a RIG
     *     {@code StaticAnimation pose_dog.json}. See
     *     {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
     */
    @Deprecated(since = "2.8")
    public static float getAppliedRotationDelta(UUID playerUuid) {
        float[] state = dogPoseState.get(playerUuid);
        return state != null ? state[IDX_DELTA] : 0f;
    }

    /**
     * Check if a player is currently moving in DOG pose.
     *
     * @deprecated since Phase 2.8 — same cause as {@link #getAppliedRotationDelta}
     *     (fed {@code MixinPlayerModel}, now removed). To be deleted alongside
     *     V3-REW-07 when dog pose head compensation is re-expressed as a RIG
     *     {@code StaticAnimation pose_dog.json}. See
     *     {@code docs/plans/rig/V3_REWORK_BACKLOG.md#V3-REW-07}.
     */
    @Deprecated(since = "2.8")
    public static boolean isDogPoseMoving(UUID playerUuid) {
        float[] state = dogPoseState.get(playerUuid);
        return state != null && state[IDX_MOVING] > 0.5f;
    }

    /**
     * Clear all DOG pose state data.
     * Called on world unload to prevent memory leaks.
     */
    public static void clearState() {
        dogPoseState.clear();
    }

    /**
     * Drop the state for a single entity leaving the level.
     */
    public static void onEntityLeave(UUID entityUuid) {
        dogPoseState.remove(entityUuid);
    }

    /**
     * Before player render: Apply vertical offset and rotation for DOG/HUMAN_CHAIR poses.
     * HIGH priority ensures this runs before arm/item hiding handlers.
     */
    @SubscribeEvent(priority = EventPriority.HIGH)
    public static void onRenderPlayerPre(RenderPlayerEvent.Pre event) {
        Player player = event.getEntity();
        if (!(player instanceof AbstractClientPlayer)) {
            return;
        }

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

        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) {
            return;
        }

        ItemStack bindForPose = state.getEquipment(BodyRegionV2.ARMS);
        if (bindForPose.isEmpty()) {
            return;
        }

        PoseType bindPoseType = PoseTypeHelper.getPoseType(bindForPose);
        // Check for humanChairMode NBT override
        bindPoseType = HumanChairHelper.resolveEffectivePose(
            bindPoseType,
            bindForPose
        );

        if (
            bindPoseType != PoseType.DOG && bindPoseType != PoseType.HUMAN_CHAIR
        ) {
            return;
        }

        // Lower player by 6 model units (6/16 = 0.375 blocks)
        event
            .getPoseStack()
            .translate(0, RenderConstants.DOG_AND_PETBED_Y_OFFSET, 0);

        UUID playerUuid = player.getUUID();
        net.minecraft.world.phys.Vec3 movement = player.getDeltaMovement();
        boolean isMoving = movement.horizontalDistanceSqr() > 0.0001;

        // Get or create state - initialize to current body rotation
        float[] s = dogPoseState.get(playerUuid);
        if (s == null) {
            s = new float[] { player.yBodyRot, player.yBodyRot, 0f, 0f };
            dogPoseState.put(playerUuid, s);
        }

        // Human chair: lock rotation state — body must not turn
        if (bindPoseType == PoseType.HUMAN_CHAIR) {
            s[IDX_CURRENT] = player.yBodyRot;
            s[IDX_TARGET] = player.yBodyRot;
            s[IDX_DELTA] = 0f;
            s[IDX_MOVING] = 0f;
        } else {
            // Determine target rotation
            float rawTarget;
            if (isMoving) {
                // Moving: face movement direction
                rawTarget = (float) Math.toDegrees(
                    Math.atan2(-movement.x, movement.z)
                );
            } else {
                // Stationary: face where head is looking
                rawTarget = player.yHeadRot;
            }

            // Check if head would be clamped (body lagging behind head)
            float predictedHeadYaw = net.minecraft.util.Mth.wrapDegrees(
                player.yHeadRot - s[IDX_CURRENT]
            );
            float maxYaw = isMoving
                ? RenderConstants.HEAD_MAX_YAW_MOVING
                : RenderConstants.HEAD_MAX_YAW_STATIONARY;
            boolean headAtLimit =
                Math.abs(predictedHeadYaw) >
                maxYaw * RenderConstants.HEAD_AT_LIMIT_RATIO;

            if (headAtLimit && !isMoving) {
                // Head at limit while stationary: snap body to release head
                float sign = predictedHeadYaw > 0 ? 1f : -1f;
                s[IDX_CURRENT] =
                    player.yHeadRot -
                    sign * maxYaw * RenderConstants.HEAD_SNAP_RELEASE_RATIO;
                s[IDX_TARGET] = s[IDX_CURRENT];
            } else {
                // Normal smoothing
                float targetDelta = net.minecraft.util.Mth.wrapDegrees(
                    rawTarget - s[IDX_TARGET]
                );
                float targetSpeed = isMoving
                    ? RenderConstants.DOG_TARGET_SPEED_MOVING
                    : RenderConstants.DOG_TARGET_SPEED_STATIONARY;
                s[IDX_TARGET] += targetDelta * targetSpeed;

                float rotDelta = net.minecraft.util.Mth.wrapDegrees(
                    s[IDX_TARGET] - s[IDX_CURRENT]
                );
                float speed = isMoving
                    ? RenderConstants.DOG_ROT_SPEED_MOVING
                    : RenderConstants.DOG_ROT_SPEED_STATIONARY;
                s[IDX_CURRENT] += rotDelta * speed;
            }
        }

        // Calculate and store the delta we apply to poseStack
        s[IDX_DELTA] = player.yBodyRot - s[IDX_CURRENT];
        s[IDX_MOVING] = isMoving ? 1f : 0f;

        // Apply rotation to make body face our custom direction
        event
            .getPoseStack()
            .mulPose(com.mojang.math.Axis.YP.rotationDegrees(s[IDX_DELTA]));
    }
}
