/*
 * © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
 */

package com.tiedup.remake.v2.client;

import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.Map;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

/**
 * Static null-safe helpers pour détecter l'état bondage d'un {@link Player}
 * côté client. Consommé par {@code PlayerPatch.updateMotion} (P3-08) afin de
 * router la {@code LivingMotion} active vers les variantes
 * {@link com.tiedup.remake.rig.anim.TiedUpLivingMotions} appropriées
 * (ex : {@code POSE_DOG}, {@code POSE_SLEEP_BOUND}, {@code FALL_BOUND}).
 *
 * <p>Chaque helper est pure-Java / null-safe et lit une API existante
 * (capability V2, {@link PlayerBindState}). Pas d'état interne, pas de
 * cache : toute fraîcheur est déléguée à la source backing.</p>
 *
 * <p>Sources backing :</p>
 * <ul>
 *   <li>{@link #isBound(Player)} → {@link V2EquipmentHelper#getAllEquipped}
 *       (capability {@code V2_BONDAGE_EQUIPMENT} sync par
 *       {@code PacketSyncV2Equipment}).</li>
 *   <li>{@link #isDogPoseActive(Player)} → lit le bind en
 *       {@link BodyRegionV2#ARMS} puis {@link PoseTypeHelper#getPoseType}
 *       (convention partagée par {@code MixinCamera},
 *       {@code DogPoseRenderHandler}, {@code LeashProxyClientHandler}).</li>
 *   <li>{@link #isStrugglingClient(Player)} → {@link PlayerBindState#isStruggling()}
 *       (instances client-side mises à jour par
 *       {@code PacketSyncStruggleState}).</li>
 * </ul>
 *
 * <p><strong>Thread-safety</strong> : toutes les lectures backing sont
 * déjà thread-safe (volatile / ConcurrentHashMap / EntityData). Ces
 * helpers peuvent être appelés depuis les threads MC usuels (client
 * tick, render thread) sans synchronisation additionnelle.</p>
 *
 * <p><strong>Note sur {@code @OnlyIn(Dist.CLIENT)}</strong> : ces helpers
 * fonctionnent aussi server-side par construction (ils lisent les mêmes
 * APIs). Le naming "Client" reflète l'intention d'appel (patch client
 * d'Epic Fight) mais le code n'est pas side-gated — pas d'import
 * client-only à l'intérieur.</p>
 */
public final class BondageStateHelpers {

    private BondageStateHelpers() {
        // Utility class
    }

    /**
     * Retourne {@code true} si le joueur a au moins un item V2 bondage équipé.
     *
     * <p>API backing : {@link V2EquipmentHelper#getAllEquipped(net.minecraft.world.entity.LivingEntity)}.
     * Retour défaut {@code Map.of()} si la capability n'est pas présente
     * (entité non-Player ou Player sans capability) → renvoie {@code false}.</p>
     *
     * @param player le joueur à tester (peut être {@code null})
     * @return {@code true} si au moins un slot est occupé par un
     *         {@link IV2BondageItem}
     */
    public static boolean isBound(Player player) {
        if (player == null) return false;
        Map<BodyRegionV2, ItemStack> equipped =
            V2EquipmentHelper.getAllEquipped(player);
        if (equipped == null || equipped.isEmpty()) return false;
        for (ItemStack stack : equipped.values()) {
            if (stack != null && !stack.isEmpty()
                && stack.getItem() instanceof IV2BondageItem) {
                return true;
            }
        }
        return false;
    }

    /**
     * Retourne {@code true} si le joueur est en pose DOG (à quatre pattes).
     *
     * <p>Convention projet : la pose DOG est déterminée par le bind équipé
     * dans la région {@link BodyRegionV2#ARMS}. Utilisée telle quelle par
     * {@code MixinCamera}, {@code DogPoseRenderHandler},
     * {@code LeashProxyClientHandler}, {@code FirstPersonMittensRenderer}.</p>
     *
     * <p>API backing : {@link PlayerBindState#getEquipment(BodyRegionV2)}
     * + {@link PoseTypeHelper#getPoseType(ItemStack)}.</p>
     *
     * @param player le joueur à tester (peut être {@code null})
     * @return {@code true} si bind ARMS a {@code poseType == DOG}
     */
    public static boolean isDogPoseActive(Player player) {
        if (player == null) return false;
        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) return false;
        ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
        if (bind == null || bind.isEmpty()) return false;
        return PoseTypeHelper.getPoseType(bind) == PoseType.DOG;
    }

    /**
     * Retourne {@code true} si l'animation de struggle est active pour le
     * joueur côté client.
     *
     * <p>API backing : {@link PlayerBindState#isStruggling()} lit un
     * {@code StruggleSnapshot} volatile atomique, mis à jour côté client
     * par {@code PacketSyncStruggleState} via
     * {@link PlayerBindState#setStrugglingClient(boolean)}.</p>
     *
     * @param player le joueur à tester (peut être {@code null})
     * @return {@code true} si le struggle snapshot est actif
     */
    public static boolean isStrugglingClient(Player player) {
        if (player == null) return false;
        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) return false;
        return state.isStruggling();
    }

    /**
     * Retourne {@code true} si le joueur dort tout en étant bondage-équipé.
     *
     * <p>UX P0 POSE_SLEEP_BOUND : un joueur endormi alors qu'il est entravé
     * doit jouer une animation spécifique (pas l'animation vanilla de lit).</p>
     *
     * @param player le joueur à tester (peut être {@code null})
     * @return {@code true} si {@link Player#isSleeping()} et
     *         {@link #isBound(Player)}
     */
    public static boolean isSleepingBound(Player player) {
        if (player == null) return false;
        return player.isSleeping() && isBound(player);
    }

    /**
     * Retourne {@code true} si le joueur est en chute libre tout en étant
     * bondage-équipé.
     *
     * <p>UX P1 FALL_BOUND : joueur qui tombe (Y-velocity &lt; 0, pas au sol)
     * alors qu'il est entravé → anim de chute impuissante (bras bloqués).</p>
     *
     * @param player le joueur à tester (peut être {@code null})
     * @return {@code true} si {@code !onGround && deltaY < 0 &&} {@link #isBound(Player)}
     */
    public static boolean isFallingBound(Player player) {
        if (player == null) return false;
        if (player.onGround()) return false;
        if (player.getDeltaMovement().y >= 0) return false;
        return isBound(player);
    }
}
