/*
 * 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;

import java.util.LinkedHashMap;
import java.util.Map;

import net.minecraft.resources.ResourceLocation;

import com.tiedup.remake.rig.armature.HumanoidArmature;
import com.tiedup.remake.rig.armature.Joint;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.math.OpenMatrix4f;

/**
 * Registry des armatures TiedUp exposé via {@link AssetAccessor} constants.
 *
 * <h2>Phase 2.4 — version procédurale</h2>
 *
 * <p>Cette classe construit le biped TiedUp <b>from scratch en Java</b>
 * (hiérarchie + offsets identity). Suffisant pour débloquer le rendering RIG
 * Phase 2.4 : les joints existent dans le map, {@code searchJointByName}
 * fonctionne, le GLB → SkinnedMesh bridge a un mapping valide, etc.</p>
 *
 * <p><b>Phase 2.7 remplacera par un JSON Blender-authored hot-reloadable</b>.
 * Pour l'instant, les joints sont tous à l'identité (offset/rotation nuls).
 * Visuellement ça donnera un biped "effondré" sur le point d'origine si on
 * rend sans animation — c'est acceptable car :</p>
 * <ul>
 *   <li>Phase 2.4 n'a pas encore de renderer player patched complet (Phase 2.5)</li>
 *   <li>Phase 2.7 rechargera des offsets depuis {@code assets/tiedup/armatures/biped.json}
 *       co-authored via addon Blender (cf. MIGRATION.md §2.2.1)</li>
 *   <li>Les tests existants `GltfToSkinnedMeshTest` utilisent déjà le même pattern
 *       (Armature identity, {@code bakeOriginMatrices}) et sont verts</li>
 * </ul>
 *
 * <h2>Hiérarchie biped EF (20 joints)</h2>
 *
 * <pre>
 *   Root                                                    id=0
 *   ├─ Thigh_R ── Leg_R ── Knee_R                           id=1,2,3
 *   ├─ Thigh_L ── Leg_L ── Knee_L                           id=4,5,6
 *   └─ Torso                                                id=7
 *      └─ Chest                                             id=8
 *         ├─ Head                                           id=9
 *         ├─ Shoulder_R ── Arm_R ── Elbow_R ── Hand_R ── Tool_R   ids=10,11,14,12,13
 *         └─ Shoulder_L ── Arm_L ── Elbow_L ── Hand_L ── Tool_L   ids=15,16,19,17,18
 * </pre>
 *
 * <p>Les IDs des bras ne suivent pas l'ordre hiérarchique parent→enfant : c'est
 * voulu pour rester aligné avec le layout attendu par {@code VanillaModelTransformer}
 * (upperJoint=Arm, lowerJoint=Hand, middleJoint=Elbow). Voir {@link #buildBiped()}.</p>
 *
 * <p><b>Noms conservés verbatim EF</b> (pas renommés en TiedUp style) car :</p>
 * <ul>
 *   <li>Le {@code VanillaModelTransformer} forké EF (Phase 2.2) référence ces
 *       noms dans ses AABB / {@code WEIGHT_ALONG_Y} / {@code yClipCoord}</li>
 *   <li>Le bridge GLB ({@code LegacyJointNameMapper}) mappe déjà les joints
 *       PlayerAnimator legacy sur ces noms-là</li>
 *   <li>Re-authored serait un risque régression sans gain fonctionnel</li>
 * </ul>
 */
public final class TiedUpArmatures {

    private TiedUpArmatures() {}

    /** ResourceLocation registry pour l'accessor (même path que EF pour cohérence doc). */
    private static final ResourceLocation BIPED_REGISTRY_NAME =
        ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "armature/biped");

    /**
     * Holder idiome pour init lazy + thread-safe sans synchronized.
     *
     * <p>Le class loader JVM garantit qu'une classe est initialisée au plus une
     * fois, et que l'init est visible à tous les threads (JLS §12.4.1 — the class
     * initialization lock est acquis automatiquement). Deux threads qui touchent
     * {@code Holder.INSTANCE} simultanément ne peuvent pas observer l'instance
     * non-initialisée ni en créer deux exemplaires. Intégré SP (client + server
     * threads concurrents sur la même JVM) safe.</p>
     *
     * <p>Raison du fix (review Phase 2.4, P0-BUG-002) : le pattern précédent
     * {@code if (BIPED_INSTANCE == null) BIPED_INSTANCE = buildBiped();} est
     * un double-init race — deux threads entrent tous les deux dans le if,
     * les deux créent un HumanoidArmature distinct, dernier gagne et pollue
     * le cache.</p>
     */
    private static final class Holder {
        static final HumanoidArmature INSTANCE;
        static {
            // Signal visible au dev que les joints sont en identity transform.
            // Sans ça, Phase 2.6+ câblera le renderer et le mesh apparaîtra
            // "effondré à l'origine" sans signal — debug cauchemar. Le warn
            // n'apparaît qu'une fois (class-init lock JVM).
            TiedUpRigConstants.LOGGER.warn(
                "TiedUpArmatures.BIPED initialized with IDENTITY joint transforms (Phase 2.4 stub). "
                + "Mesh will render collapsed-to-origin until Phase 2.7 provides biped.json "
                + "Blender-authored offsets. See docs/plans/rig/PHASE0_DEGRADATIONS.md "
                + "Phase 2.4 backlog entry #1."
            );
            INSTANCE = buildBiped();
        }
        private Holder() {}
    }

    /**
     * AssetAccessor biped TiedUp. L'instance est construite lazy à la première
     * référence {@code Holder.INSTANCE} (thread-safe via class-init lock JVM).
     *
     * <p>Utilisé par {@link com.tiedup.remake.rig.patch.PlayerPatch#getArmature()}
     * et par les futurs {@code StaticAnimation(… , BIPED)} Phase 2.7+.</p>
     */
    public static final AssetAccessor<HumanoidArmature> BIPED = new AssetAccessor<>() {
        @Override
        public HumanoidArmature get() {
            return Holder.INSTANCE;
        }

        @Override
        public ResourceLocation registryName() {
            return BIPED_REGISTRY_NAME;
        }

        @Override
        public boolean inRegistry() {
            // Pas dans un JsonAssetLoader registry tant que Phase 2.7 n'a pas
            // posé le biped.json. Une fois fait, ce flag repassera à true via
            // un nouveau registry layer.
            return false;
        }
    };

    /**
     * Build procédural de la hiérarchie biped EF. 20 joints, IDs 0..19 assignés
     * explicitement pour matcher le layout attendu par
     * {@link com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer}
     * (cf. constantes {@code RIGHT_ARM}/{@code LEFT_ARM} — upperJoint/lowerJoint/middleJoint).
     *
     * <p>Ordre d'insertion LinkedHashMap ≠ ordre des IDs pour les bras (hand
     * vient AVANT elbow dans les IDs, pour aligner sur la sémantique EF où
     * {@code middleJoint = elbow}). Voir commentaires inline.</p>
     *
     * <p>Toutes les transforms sont identity — Phase 2.7 remplacera par les
     * offsets Blender mesurés (cf. doc header).</p>
     */
    private static HumanoidArmature buildBiped() {
        // ID Joint assigné explicitement au constructeur (pas par position dans
        // la map) — Armature.jointById est construit depuis joint.getId().
        // On utilise LinkedHashMap malgré tout pour garantir un ordre d'itération
        // stable (utile pour le debug et pour OpenMatrix4f.allocateMatrixArray
        // qui dimensionne sur jointCount).
        Map<String, Joint> joints = new LinkedHashMap<>(20);

        // Pattern EF : tous les joints démarrent avec une localTransform identity.
        // bakeOriginMatrices() calcule ensuite les toOrigin relatives à la
        // hiérarchie parent→enfant.
        Joint root = joint(joints, "Root", 0);

        // Jambes
        Joint thighR = joint(joints, "Thigh_R", 1);
        Joint legR = joint(joints, "Leg_R", 2);
        Joint kneeR = joint(joints, "Knee_R", 3);
        Joint thighL = joint(joints, "Thigh_L", 4);
        Joint legL = joint(joints, "Leg_L", 5);
        Joint kneeL = joint(joints, "Knee_L", 6);

        // Tronc
        Joint torso = joint(joints, "Torso", 7);
        Joint chest = joint(joints, "Chest", 8);
        Joint head = joint(joints, "Head", 9);

        // Bras droit — IDs alignés sur le layout EF (VanillaModelTransformer.RIGHT_ARM
        // encode upperJoint=11, lowerJoint=12, middleJoint=14, cf.
        // VanillaModelTransformer:50). L'insertion dans le LinkedHashMap reste
        // dans l'ordre hiérarchique (shoulder → arm → elbow → hand → tool) pour
        // préserver la lisibilité de l'iteration ; les IDs déterminent le
        // mapping jointById utilisé par VanillaModelTransformer + SimpleTransformer.
        Joint shoulderR = joint(joints, "Shoulder_R", 10);
        Joint armR = joint(joints, "Arm_R", 11);
        Joint handR = joint(joints, "Hand_R", 12);
        Joint toolR = joint(joints, "Tool_R", 13);
        Joint elbowR = joint(joints, "Elbow_R", 14);

        // Bras gauche — symétrique : Arm_L=16, Hand_L=17, Tool_L=18, Elbow_L=19
        // (VanillaModelTransformer.LEFT_ARM upperJoint=16, lowerJoint=17, middleJoint=19).
        Joint shoulderL = joint(joints, "Shoulder_L", 15);
        Joint armL = joint(joints, "Arm_L", 16);
        Joint handL = joint(joints, "Hand_L", 17);
        Joint toolL = joint(joints, "Tool_L", 18);
        Joint elbowL = joint(joints, "Elbow_L", 19);

        // Hiérarchie. addSubJoints est idempotent (skip si déjà présent) — safe
        // de le réappeler, utile si on étend plus tard.
        root.addSubJoints(thighR, thighL, torso);
        thighR.addSubJoints(legR);
        legR.addSubJoints(kneeR);
        thighL.addSubJoints(legL);
        legL.addSubJoints(kneeL);

        torso.addSubJoints(chest);
        chest.addSubJoints(head, shoulderR, shoulderL);

        shoulderR.addSubJoints(armR);
        armR.addSubJoints(elbowR);
        elbowR.addSubJoints(handR);
        handR.addSubJoints(toolR);

        shoulderL.addSubJoints(armL);
        armL.addSubJoints(elbowL);
        elbowL.addSubJoints(handL);
        handL.addSubJoints(toolL);

        HumanoidArmature arm = new HumanoidArmature("biped", joints.size(), root, joints);

        // Calcule les toOrigin relatifs — obligatoire après la construction
        // sinon Pose.orElseEmpty retournerait des matrices non initialisées.
        arm.bakeOriginMatrices();

        return arm;
    }

    private static Joint joint(Map<String, Joint> target, String name, int id) {
        Joint j = new Joint(name, id, new OpenMatrix4f());
        target.put(name, j);
        return j;
    }
}
