package com.tiedup.remake.v2.furniture.client;

import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.client.gltf.GltfData;
import com.tiedup.remake.client.gltf.GltfPoseConverter;
import com.tiedup.remake.v2.BodyRegionV2;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import java.util.HashSet;
import java.util.Set;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;

/**
 * Builds a {@link KeyframeAnimation} for a player seated on furniture.
 *
 * <p>The furniture animation layer sits at priority 43 (above the item layer at 42),
 * so it wins on shared bones. To allow bondage items on non-blocked regions to still
 * animate (e.g., a gag on the head while the chair blocks arms+legs), this factory
 * enables ONLY the bones corresponding to the seat's blocked regions and disables
 * all others. Disabled parts pass through to the lower-priority item layer.</p>
 *
 * <p>Conversion flow:
 * <ol>
 *   <li>Convert the raw glTF animation clip to a PlayerAnimator {@link KeyframeAnimation}
 *       using {@link GltfPoseConverter#convertWithSkeleton}</li>
 *   <li>Create a mutable copy of the animation</li>
 *   <li>Map blocked {@link BodyRegionV2}s to PlayerAnimator bone names via
 *       {@link RegionBoneMapper#getPartsForRegion}</li>
 *   <li>Disable all bones NOT in the blocked set</li>
 *   <li>Build and return the immutable result</li>
 * </ol>
 *
 * <p>In V1 (seat skeleton not yet parsed), returns null. Furniture animation
 * requires V2 skeleton parsing to provide the rest pose data needed by
 * {@link GltfPoseConverter}.</p>
 *
 * @see com.tiedup.remake.client.animation.BondageAnimationManager#playFurniture
 * @see RegionBoneMapper
 */
@OnlyIn(Dist.CLIENT)
public final class FurnitureAnimationContext {

    private static final Logger LOGGER = LogManager.getLogger(
        "FurnitureAnimation"
    );

    private FurnitureAnimationContext() {}

    /**
     * Create a KeyframeAnimation for a player seated on furniture.
     * Enables ONLY bones in blocked regions, disables all others.
     *
     * @param seatClip       the seat animation clip (from
     *                        {@link FurnitureGltfData#seatAnimations()})
     * @param seatSkeleton   the seat skeleton data providing rest pose and joint names
     *                        (from {@link FurnitureGltfData#seatSkeletons()}); null in V1
     * @param blockedRegions the body regions the furniture controls for this seat
     * @return a KeyframeAnimation with only blocked-region bones enabled, or null if
     *         the skeleton is unavailable (V1) or conversion fails
     */
    @Nullable
    public static KeyframeAnimation create(
        GltfData.AnimationClip seatClip,
        @Nullable GltfData seatSkeleton,
        Set<BodyRegionV2> blockedRegions
    ) {
        if (seatClip == null) {
            LOGGER.warn(
                "[FurnitureAnim] Cannot create animation: seatClip is null"
            );
            return null;
        }

        if (seatSkeleton == null) {
            // V1: skeleton parsing not yet implemented. Furniture animation requires
            // rest pose data for glTF-to-PlayerAnimator conversion.
            LOGGER.debug(
                "[FurnitureAnim] Seat skeleton unavailable (V1), skipping animation"
            );
            return null;
        }

        if (blockedRegions == null || blockedRegions.isEmpty()) {
            LOGGER.debug(
                "[FurnitureAnim] No blocked regions, skipping animation"
            );
            return null;
        }

        // Step 1: Convert the raw clip to a full KeyframeAnimation using skeleton data.
        // convertWithSkeleton returns an animation with ALL parts enabled.
        KeyframeAnimation fullAnim;
        try {
            fullAnim = GltfPoseConverter.convertWithSkeleton(
                seatSkeleton,
                seatClip,
                "furniture_seat"
            );
        } catch (Exception e) {
            LOGGER.error(
                "[FurnitureAnim] Failed to convert seat animation clip",
                e
            );
            return null;
        }

        // Step 2: Compute which PlayerAnimator parts correspond to blocked regions.
        Set<String> blockedParts = new HashSet<>();
        for (BodyRegionV2 region : blockedRegions) {
            blockedParts.addAll(RegionBoneMapper.getPartsForRegion(region));
        }

        if (blockedParts.isEmpty()) {
            // Blocked regions don't map to any animation bones (e.g., only NECK/FINGERS/TAIL/WINGS)
            LOGGER.debug(
                "[FurnitureAnim] Blocked regions {} map to zero bones, skipping",
                blockedRegions
            );
            return null;
        }

        // Step 3: Compute disabled parts = ALL parts MINUS blocked parts.
        Set<String> disabledParts = new HashSet<>(RegionBoneMapper.ALL_PARTS);
        disabledParts.removeAll(blockedParts);

        if (disabledParts.isEmpty()) {
            // All parts are blocked by the furniture -- return the full animation as-is.
            return fullAnim;
        }

        // Step 4: Create a mutable copy and disable non-blocked bones.
        KeyframeAnimation.AnimationBuilder builder = fullAnim.mutableCopy();
        disableParts(builder, disabledParts);

        KeyframeAnimation result = builder.build();
        LOGGER.debug(
            "[FurnitureAnim] Created animation: blocked={}, enabled={}, disabled={}",
            blockedRegions,
            blockedParts,
            disabledParts
        );
        return result;
    }

    /**
     * Disable all animation axes on the specified parts.
     *
     * <p>Uses the same pattern as
     * {@link com.tiedup.remake.client.animation.context.ContextAnimationFactory}.
     * Unknown part names are silently ignored.</p>
     *
     * @param builder       the mutable animation builder
     * @param disabledParts set of PlayerAnimator part names to disable
     */
    private static void disableParts(
        KeyframeAnimation.AnimationBuilder builder,
        Set<String> disabledParts
    ) {
        for (String partName : disabledParts) {
            KeyframeAnimation.StateCollection part = builder.getPart(partName);
            if (part != null) {
                part.setEnabled(false);
            }
        }
    }
}
