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

package com.tiedup.remake.rig;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;

import org.junit.jupiter.api.Test;

import com.tiedup.remake.rig.armature.HumanoidArmature;

/**
 * Tests du biped procédural TiedUp.
 *
 * <p>Ces tests verrouillent en particulier le mapping {@code id → joint name}
 * qui doit rester aligné sur les constantes {@code RIGHT_ARM} / {@code LEFT_ARM}
 * de {@code VanillaModelTransformer} — sinon les bras rendent tordus (ex :
 * {@code lowerJoint=17} pointerait vers Elbow_L au lieu de Hand_L).</p>
 *
 * <p>La review Phase 2.4 (P0-BUG-001) a montré que l'ordre précédent
 * Arm_R=11, Elbow_R=12, Hand_R=13, Tool_R=14 cassait silencieusement le
 * transformer. {@link #jointIdsMatchEfLayout()} aurait attrapé ce bug.</p>
 */
class TiedUpArmaturesTest {

	@Test
	void bipedHas20Joints() {
		assertEquals(20, TiedUpArmatures.BIPED.get().getJointNumber(),
			"Le biped EF standard doit avoir exactement 20 joints");
	}

	@Test
	void searchBipedJointByNameReturnsNonNull() {
		HumanoidArmature biped = TiedUpArmatures.BIPED.get();

		String[] expectedNames = {
			"Root", "Head",
			"Torso", "Chest",
			"Shoulder_L", "Shoulder_R",
			"Arm_L", "Arm_R",
			"Hand_L", "Hand_R",
			"Elbow_L", "Elbow_R",
			"Tool_L", "Tool_R",
			"Thigh_L", "Thigh_R",
			"Leg_L", "Leg_R",
			"Knee_L", "Knee_R"
		};

		for (String name : expectedNames) {
			assertNotNull(biped.searchJointByName(name),
				"Joint '" + name + "' introuvable dans le biped — alignement EF cassé");
		}
	}

	/**
	 * Verrou explicite contre P0-BUG-001. Les constantes EF
	 * {@code VanillaModelTransformer.RIGHT_ARM} encodent
	 * {@code upperJoint=11, lowerJoint=12, middleJoint=14}, i.e.
	 * Arm_R=11, Hand_R=12, Elbow_R=14 (symétrique gauche : 16/17/19).
	 *
	 * <p>Ce test DOIT échouer si quelqu'un réordonne les IDs dans
	 * {@link TiedUpArmatures#buildBiped()} sans mettre à jour
	 * {@code VanillaModelTransformer} en miroir.</p>
	 */
	@Test
	void jointIdsMatchEfLayout() {
		HumanoidArmature biped = TiedUpArmatures.BIPED.get();

		// Right arm layout (VanillaModelTransformer.RIGHT_ARM : 11, 12, _, 14)
		assertEquals("Arm_R", biped.searchJointById(11).getName(),
			"id=11 doit être Arm_R (VanillaModelTransformer.RIGHT_ARM.upperJoint)");
		assertEquals("Hand_R", biped.searchJointById(12).getName(),
			"id=12 doit être Hand_R (VanillaModelTransformer.RIGHT_ARM.lowerJoint)");
		assertEquals("Elbow_R", biped.searchJointById(14).getName(),
			"id=14 doit être Elbow_R (VanillaModelTransformer.RIGHT_ARM.middleJoint)");

		// Left arm layout (VanillaModelTransformer.LEFT_ARM : 16, 17, _, 19)
		assertEquals("Arm_L", biped.searchJointById(16).getName(),
			"id=16 doit être Arm_L (VanillaModelTransformer.LEFT_ARM.upperJoint)");
		assertEquals("Hand_L", biped.searchJointById(17).getName(),
			"id=17 doit être Hand_L (VanillaModelTransformer.LEFT_ARM.lowerJoint)");
		assertEquals("Elbow_L", biped.searchJointById(19).getName(),
			"id=19 doit être Elbow_L (VanillaModelTransformer.LEFT_ARM.middleJoint)");

		// Tool_R=13, Tool_L=18 (pas dans les LimbPartTransformer mais utilisé
		// par StaticAnimation itemInHand — on verrouille l'emplacement aussi).
		assertEquals("Tool_R", biped.searchJointById(13).getName(),
			"id=13 doit être Tool_R (attaché après Hand_R dans la chaîne)");
		assertEquals("Tool_L", biped.searchJointById(18).getName(),
			"id=18 doit être Tool_L (attaché après Hand_L dans la chaîne)");
	}

	/**
	 * Verrou contre P0-BUG-002. La première implémentation utilisait un
	 * double-null-check non thread-safe — SP intégré (client + server threads
	 * concurrent) pouvait créer deux instances distinctes.
	 *
	 * <p>Le Holder idiome (static inner class) garantit via le class-init lock
	 * JVM qu'il n'y a qu'une seule init. Ce test vérifie que deux appels
	 * successifs de {@code BIPED.get()} renvoient bien la même référence —
	 * ne prouve pas l'atomicité mais couvre le cas régression "retour à un
	 * nouveau build à chaque get".</p>
	 */
	@Test
	void bipedSingleton() {
		HumanoidArmature first = TiedUpArmatures.BIPED.get();
		HumanoidArmature second = TiedUpArmatures.BIPED.get();
		assertSame(first, second,
			"BIPED.get() doit retourner la même instance (singleton Holder)");
	}
}
