package com.tiedup.remake.client.gltf;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;

/**
 * Immutable container for parsed glTF/GLB data.
 * Holds mesh geometry, skinning data, bone hierarchy, and optional animations.
 * <p>
 * Supports multiple named animations per GLB file. The "default" animation
 * (first clip) is accessible via {@link #animation()} and {@link #rawGltfAnimation()}
 * for backward compatibility. All animations are available via
 * {@link #namedAnimations()}.
 */
public final class GltfData {

    // -- Mesh geometry (flattened arrays) --
    private final float[] positions; // VEC3, length = vertexCount * 3
    private final float[] normals; // VEC3, length = vertexCount * 3
    private final float[] texCoords; // VEC2, length = vertexCount * 2
    private final int[] indices; // triangle indices

    // -- Skinning data (per-vertex, 4 influences) --
    private final int[] joints; // 4 joint indices per vertex, length = vertexCount * 4
    private final float[] weights; // 4 weights per vertex, length = vertexCount * 4

    // -- Bone hierarchy (MC-converted for skinning) --
    private final String[] jointNames;
    private final int[] parentJointIndices; // -1 for root
    private final Matrix4f[] inverseBindMatrices;
    private final Quaternionf[] restRotations;
    private final Vector3f[] restTranslations;

    // -- Raw glTF rotations (unconverted, for pose conversion) --
    private final Quaternionf[] rawGltfRestRotations;

    @Nullable
    private final AnimationClip rawGltfAnimation;

    // -- Optional animation clip (MC-converted for skinning) --
    @Nullable
    private final AnimationClip animation;

    // -- Multiple named animations --
    private final Map<String, AnimationClip> namedAnimations; // MC-converted
    private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space

    // -- Per-primitive material/tint info --
    private final List<Primitive> primitives;

    // -- Counts --
    private final int vertexCount;
    private final int jointCount;

    /**
     * Full constructor with multiple named animations and per-primitive data.
     */
    public GltfData(
        float[] positions,
        float[] normals,
        float[] texCoords,
        int[] indices,
        int[] joints,
        float[] weights,
        String[] jointNames,
        int[] parentJointIndices,
        Matrix4f[] inverseBindMatrices,
        Quaternionf[] restRotations,
        Vector3f[] restTranslations,
        Quaternionf[] rawGltfRestRotations,
        @Nullable AnimationClip rawGltfAnimation,
        @Nullable AnimationClip animation,
        Map<String, AnimationClip> namedAnimations,
        Map<String, AnimationClip> rawNamedAnimations,
        List<Primitive> primitives,
        int vertexCount,
        int jointCount
    ) {
        this.positions = positions;
        this.normals = normals;
        this.texCoords = texCoords;
        this.indices = indices;
        this.joints = joints;
        this.weights = weights;
        this.jointNames = jointNames;
        this.parentJointIndices = parentJointIndices;
        this.inverseBindMatrices = inverseBindMatrices;
        this.restRotations = restRotations;
        this.restTranslations = restTranslations;
        this.rawGltfRestRotations = rawGltfRestRotations;
        this.rawGltfAnimation = rawGltfAnimation;
        this.animation = animation;
        this.namedAnimations = Collections.unmodifiableMap(
            new LinkedHashMap<>(namedAnimations)
        );
        this.rawNamedAnimations = Collections.unmodifiableMap(
            new LinkedHashMap<>(rawNamedAnimations)
        );
        this.primitives = List.copyOf(primitives);
        this.vertexCount = vertexCount;
        this.jointCount = jointCount;
    }

    /**
     * Legacy constructor for backward compatibility (single animation only).
     */
    public GltfData(
        float[] positions,
        float[] normals,
        float[] texCoords,
        int[] indices,
        int[] joints,
        float[] weights,
        String[] jointNames,
        int[] parentJointIndices,
        Matrix4f[] inverseBindMatrices,
        Quaternionf[] restRotations,
        Vector3f[] restTranslations,
        Quaternionf[] rawGltfRestRotations,
        @Nullable AnimationClip rawGltfAnimation,
        @Nullable AnimationClip animation,
        int vertexCount,
        int jointCount
    ) {
        this(
            positions,
            normals,
            texCoords,
            indices,
            joints,
            weights,
            jointNames,
            parentJointIndices,
            inverseBindMatrices,
            restRotations,
            restTranslations,
            rawGltfRestRotations,
            rawGltfAnimation,
            animation,
            new LinkedHashMap<>(),
            new LinkedHashMap<>(),
            List.of(new Primitive(indices, null, false, null)),
            vertexCount,
            jointCount
        );
    }

    public float[] positions() {
        return positions;
    }

    public float[] normals() {
        return normals;
    }

    public float[] texCoords() {
        return texCoords;
    }

    public int[] indices() {
        return indices;
    }

    public int[] joints() {
        return joints;
    }

    public float[] weights() {
        return weights;
    }

    public String[] jointNames() {
        return jointNames;
    }

    public int[] parentJointIndices() {
        return parentJointIndices;
    }

    public Matrix4f[] inverseBindMatrices() {
        return inverseBindMatrices;
    }

    public Quaternionf[] restRotations() {
        return restRotations;
    }

    public Vector3f[] restTranslations() {
        return restTranslations;
    }

    public Quaternionf[] rawGltfRestRotations() {
        return rawGltfRestRotations;
    }

    @Nullable
    public AnimationClip rawGltfAnimation() {
        return rawGltfAnimation;
    }

    @Nullable
    public AnimationClip animation() {
        return animation;
    }

    public int vertexCount() {
        return vertexCount;
    }

    public int jointCount() {
        return jointCount;
    }

    /** Per-primitive material and tint metadata. One entry per glTF primitive in the mesh. */
    public List<Primitive> primitives() {
        return primitives;
    }

    /** All named animations in MC-converted space. Keys are animation names (e.g. "BasicPose", "Struggle"). */
    public Map<String, AnimationClip> namedAnimations() {
        return namedAnimations;
    }

    /** Get a specific named animation in MC-converted space, or null if not found. */
    @Nullable
    public AnimationClip getAnimation(String name) {
        return namedAnimations.get(name);
    }

    /** Get a specific named animation in raw glTF space, or null if not found. */
    @Nullable
    public AnimationClip getRawAnimation(String name) {
        return rawNamedAnimations.get(name);
    }

    /**
     * Animation clip: per-bone timestamps, quaternion rotations, and optional translations.
     */
    public static final class AnimationClip {

        private final float[] timestamps; // shared timestamps
        private final Quaternionf[][] rotations; // [jointIndex][frameIndex], null if no anim

        @Nullable
        private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim

        private final int frameCount;

        public AnimationClip(
            float[] timestamps,
            Quaternionf[][] rotations,
            @Nullable Vector3f[][] translations,
            int frameCount
        ) {
            this.timestamps = timestamps;
            this.rotations = rotations;
            this.translations = translations;
            this.frameCount = frameCount;
        }

        public float[] timestamps() {
            return timestamps;
        }

        public Quaternionf[][] rotations() {
            return rotations;
        }

        @Nullable
        public Vector3f[][] translations() {
            return translations;
        }

        public int frameCount() {
            return frameCount;
        }
    }

    /**
     * Per-primitive metadata parsed from the glTF mesh.
     * Each primitive corresponds to a material assignment in Blender.
     *
     * @param indices       triangle indices for this primitive (already offset to the unified vertex buffer)
     * @param materialName  the glTF material name, or null if unassigned
     * @param tintable      true if the material name starts with "tintable_"
     * @param tintChannel   the tint channel key (e.g. "tintable_0"), or null if not tintable
     */
    public record Primitive(
        int[] indices,
        @Nullable String materialName,
        boolean tintable,
        @Nullable String tintChannel
    ) {}
}
