/*
 * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
 * by the Epic Fight Team, licensed under GPLv3.
 * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
 */

package com.tiedup.remake.rig.render;

import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.mesh.HumanoidMesh;
import com.tiedup.remake.rig.patch.LivingEntityPatch;

/**
 * Niveau intermédiaire humanoïde — s'utilise pour tout ce qui rentre dans le
 * moule biped (player, kidnapper, damsel, maid, master). Ajoute :
 *
 * <ul>
 *   <li>Un mesh par défaut injecté au constructor (évite la duplication de
 *       {@link #getDefaultMesh()} dans chaque sous-classe)</li>
 *   <li>Le scale baby Head 1.25x via {@link #setJointTransforms} — pattern EF
 *       strict, le modèle vanilla scale le head au rendu, donc on doit matcher
 *       côté pose pour que les items (gags etc.) restent attachés</li>
 *   <li>Les stubs {@link PatchedArmorLayer} et {@link PatchedItemInHandLayer}
 *       pour "manger" les layers vanilla équivalents (sinon rendu double)</li>
 *   <li>{@link #getDefaultLayerHeightCorrection()} = 0.75F (hauteur standard
 *       humanoïde pour les particles overlays)</li>
 * </ul>
 *
 * @param <E>  entité humanoïde
 * @param <T>  patch RIG associé
 * @param <M>  HumanoidModel vanilla
 * @param <R>  LivingEntityRenderer vanilla qui gère {@code M}
 * @param <AM> mesh humanoïde (typiquement {@link HumanoidMesh} direct ou
 *             dérivé)
 */
@OnlyIn(Dist.CLIENT)
public class PHumanoidRenderer<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends HumanoidModel<E>, R extends LivingEntityRenderer<E, M>, AM extends HumanoidMesh> extends PatchedLivingEntityRenderer<E, T, M, R, AM> {

	private final AssetAccessor<AM> mesh;

	public PHumanoidRenderer(AssetAccessor<AM> mesh, EntityRendererProvider.Context context, EntityType<?> entityType) {
		super(context, entityType);

		this.mesh = mesh;

		// RIG Phase 2.5 : les 2 layers vanilla pertinents (armor + main items)
		// sont mangés par des stubs no-op. Voir PatchedArmorLayer / PatchedItemInHandLayer
		// et V3-REW-04 (armor) / Phase 3 (item-in-hand réel).
		//
		// ElytraLayer, CustomHeadLayer volontairement non-mappés Phase 2.5 — le
		// layer vanilla les dessinera directement (couche vanilla séparée du
		// mesh RIG). Cohérent avec la stratégie "on mange juste les layers dont
		// la géométrie entre en conflit avec le mesh skinné".
		this.addPatchedLayer(HumanoidArmorLayer.class, new PatchedArmorLayer<E, T, M>());
		this.addPatchedLayer(ItemInHandLayer.class, new PatchedItemInHandLayer<E, T, M>());
	}

	@Override
	public void setJointTransforms(T entitypatch, Armature armature, Pose pose, float partialTicks) {
		if (entitypatch.getOriginal().isBaby()) {
			// Compensation du scale 1.25x que le HumanoidModel vanilla applique sur
			// la tête quand young==true — sans ça les items tête (gags, masks) se
			// détachent visuellement du mesh.
			//
			// P1-RISK-001 (review Phase 2.5) : Pose.orElseEmpty("Head") utilise
			// Map.getOrDefault(name, JointTransform.empty()) — si "Head" est absent
			// de la pose, l'instance retournée est détachée (nouvelle, non dans la
			// map) et toute mutation .frontResult sur elle est perdue au prochain
			// getOrDefault. En pratique l'Animator peuple toujours "Head" via le
			// composed layer pose (canonical joints armature), mais on sécurise :
			// on récupère explicitement l'instance et on la ré-écrit dans la map.
			//
			// (Même latent bug en EF upstream — cf. Pose:61 EF, PHumanoidRenderer:41 EF —
			// masqué par composeLayerPose qui itère armature.rootJoint et insère Head.)
			JointTransform headTransform = pose.orElseEmpty("Head");
			headTransform.frontResult(JointTransform.scale(new Vec3f(1.25F, 1.25F, 1.25F)), OpenMatrix4f::mul);
			pose.putJointData("Head", headTransform);
		}
	}

	@Override
	protected float getDefaultLayerHeightCorrection() {
		return 0.75F;
	}

	@Override
	public AssetAccessor<AM> getDefaultMesh() {
		return this.mesh;
	}
}
