package com.tiedup.remake.client.gltf;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;

/**
 * Maps glTF bone names to Minecraft HumanoidModel parts.
 * Handles upper bones (full rotation) and lower bones (bend only).
 */
@OnlyIn(Dist.CLIENT)
public final class GltfBoneMapper {

    /** Maps glTF bone name -> MC model part field name */
    private static final Map<String, String> BONE_TO_PART = new HashMap<>();

    /** Lower bones that represent bend (elbow/knee) */
    private static final Set<String> LOWER_BONES = Set.of(
        "leftLowerArm",
        "rightLowerArm",
        "leftLowerLeg",
        "rightLowerLeg"
    );

    /** Maps lower bone name -> corresponding upper bone name */
    private static final Map<String, String> LOWER_TO_UPPER = Map.of(
        "leftLowerArm",
        "leftUpperArm",
        "rightLowerArm",
        "rightUpperArm",
        "leftLowerLeg",
        "leftUpperLeg",
        "rightLowerLeg",
        "rightUpperLeg"
    );

    static {
        BONE_TO_PART.put("body", "body");
        BONE_TO_PART.put("torso", "body");
        BONE_TO_PART.put("head", "head");
        BONE_TO_PART.put("leftUpperArm", "leftArm");
        BONE_TO_PART.put("leftLowerArm", "leftArm");
        BONE_TO_PART.put("rightUpperArm", "rightArm");
        BONE_TO_PART.put("rightLowerArm", "rightArm");
        BONE_TO_PART.put("leftUpperLeg", "leftLeg");
        BONE_TO_PART.put("leftLowerLeg", "leftLeg");
        BONE_TO_PART.put("rightUpperLeg", "rightLeg");
        BONE_TO_PART.put("rightLowerLeg", "rightLeg");
    }

    private GltfBoneMapper() {}

    /**
     * Get the ModelPart corresponding to a glTF bone name.
     *
     * @param model    the HumanoidModel
     * @param boneName glTF bone name
     * @return the ModelPart, or null if not mapped
     */
    public static ModelPart getModelPart(
        HumanoidModel<?> model,
        String boneName
    ) {
        String partName = BONE_TO_PART.get(boneName);
        if (partName == null) return null;

        return switch (partName) {
            case "body" -> model.body;
            case "head" -> model.head;
            case "leftArm" -> model.leftArm;
            case "rightArm" -> model.rightArm;
            case "leftLeg" -> model.leftLeg;
            case "rightLeg" -> model.rightLeg;
            default -> null;
        };
    }

    /**
     * Check if this bone represents a lower segment (bend: elbow/knee).
     */
    public static boolean isLowerBone(String boneName) {
        return LOWER_BONES.contains(boneName);
    }

    /**
     * Get the upper bone name for a given lower bone.
     * Returns null if not a lower bone.
     */
    public static String getUpperBoneFor(String lowerBoneName) {
        return LOWER_TO_UPPER.get(lowerBoneName);
    }

    /**
     * Get the PlayerAnimator part name for a glTF bone.
     * Both glTF and PlayerAnimator use "body" for the torso part.
     */
    public static String getAnimPartName(String boneName) {
        String partName = BONE_TO_PART.get(boneName);
        if (partName == null) return null;
        return partName;
    }

    /**
     * Check if a bone name is known/mapped.
     */
    public static boolean isKnownBone(String boneName) {
        return BONE_TO_PART.containsKey(boneName);
    }

    /**
     * Get all known bone names for validation/suggestion purposes.
     */
    public static Set<String> knownBoneNames() {
        return BONE_TO_PART.keySet();
    }

    /**
     * Suggest a known bone name for a case-insensitive match.
     * Returns null if no case-insensitive match is found.
     */
    @Nullable
    public static String suggestBoneName(String unknownBone) {
        for (String known : BONE_TO_PART.keySet()) {
            if (known.equalsIgnoreCase(unknownBone)) {
                return known;
            }
        }
        return null;
    }
}
