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

package com.tiedup.remake.rig.bridge;

import java.util.Map;

import javax.annotation.Nullable;

import com.google.common.collect.ImmutableMap;

/**
 * Table d'alias runtime pour mapper les noms de joints des GLB legacy TiedUp
 * (riggés via PlayerAnimator / bendy-lib avec noms type {@code leftUpperArm})
 * vers le skeleton biped Epic Fight utilisé par RIG ({@code Arm_L}, etc.).
 *
 * <p>Voir {@code docs/plans/rig/ARCHITECTURE.md §6.3} pour la source de vérité
 * du mapping. Tout joint inconnu après lookup doit être loggé WARN par le
 * caller et fallback sur {@code Root}.</p>
 *
 * <p><b>Cas spécial "body/torso"</b> — le GLB legacy a souvent un unique joint
 * couvrant l'ensemble du torse. On le mappe sur {@code Chest} par défaut
 * (meilleur fit pour les items bondage majoritairement attachés au haut du
 * corps : harnais, menottes de poitrine, collier). Si un item a besoin
 * d'attachement à {@code Torso} (ceinture), le modeler devra renommer son
 * joint en {@code waist} explicitement.</p>
 */
public final class GlbJointAliasTable {

	/**
	 * Mapping direct PlayerAnimator → biped EF. Les clés sont la forme
	 * lowercase EXACTE des noms exportés par les GLB legacy.
	 */
	private static final Map<String, String> ALIAS = ImmutableMap.<String, String>builder()
		// Torso region
		.put("body", "Chest")
		.put("torso", "Chest")
		.put("chest", "Chest")
		.put("waist", "Torso")
		.put("hip", "Torso")

		// Head
		.put("head", "Head")

		// Arms left
		.put("leftshoulder", "Shoulder_L")
		.put("leftupperarm", "Arm_L")
		.put("leftarm", "Arm_L")
		.put("leftlowerarm", "Elbow_L")
		.put("leftforearm", "Elbow_L")
		.put("leftelbow", "Elbow_L")
		.put("lefthand", "Hand_L")

		// Arms right
		.put("rightshoulder", "Shoulder_R")
		.put("rightupperarm", "Arm_R")
		.put("rightarm", "Arm_R")
		.put("rightlowerarm", "Elbow_R")
		.put("rightforearm", "Elbow_R")
		.put("rightelbow", "Elbow_R")
		.put("righthand", "Hand_R")

		// Legs left
		.put("leftupperleg", "Thigh_L")
		.put("leftleg", "Thigh_L")
		.put("leftlowerleg", "Knee_L")
		.put("leftknee", "Knee_L")
		.put("leftfoot", "Leg_L")

		// Legs right
		.put("rightupperleg", "Thigh_R")
		.put("rightleg", "Thigh_R")
		.put("rightlowerleg", "Knee_R")
		.put("rightknee", "Knee_R")
		.put("rightfoot", "Leg_R")

		// Root fallback (déjà nommé Root dans GLB modernes)
		.put("root", "Root")
		.put("armature", "Root")
		.build();

	private GlbJointAliasTable() {}

	/**
	 * Traduit un nom de joint GLB legacy vers le nom biped EF équivalent.
	 * Case-insensitive. Les noms déjà au format biped EF (ex: {@code Arm_L}) sont
	 * retournés tels quels après vérification.
	 *
	 * @param gltfJointName nom tel qu'exporté dans le GLB (jointNames[])
	 * @return nom biped EF (ex: {@code Arm_L}), ou null si inconnu
	 */
	@Nullable
	public static String mapGltfJointName(String gltfJointName) {
		if (gltfJointName == null || gltfJointName.isEmpty()) {
			return null;
		}

		// Direct hit sur le biped EF (GLB moderne déjà bien rigged).
		if (isBipedJointName(gltfJointName)) {
			return gltfJointName;
		}

		return ALIAS.get(gltfJointName.toLowerCase());
	}

	/**
	 * Vérifie si un nom est déjà au format biped EF. Utilisé pour court-circuiter
	 * l'alias lookup sur les GLB modernes.
	 */
	public static boolean isBipedJointName(String name) {
		// Heuristique : les noms biped EF sont en PascalCase avec suffixe _R/_L,
		// ou parmi {Root, Torso, Chest, Head}.
		return switch (name) {
			case "Root", "Torso", "Chest", "Head" -> true;
			default -> name.endsWith("_R") || name.endsWith("_L");
		};
	}
}
