package com.tiedup.remake.client.animation.context;

import com.tiedup.remake.client.gltf.GlbParser;
import com.tiedup.remake.client.gltf.GltfData;
import com.tiedup.remake.client.gltf.GltfPoseConverter;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;

/**
 * Registry for context animations loaded from GLB files.
 *
 * <p>Scans the {@code tiedup_contexts/} resource directory for {@code .glb} files,
 * parses each one via {@link GlbParser}, converts to a {@link KeyframeAnimation}
 * via {@link GltfPoseConverter#convert(GltfData)}, and stores the result keyed by
 * the file name suffix (e.g., {@code "stand_walk"} from {@code tiedup_contexts/stand_walk.glb}).</p>
 *
 * <p>GLB context animations take priority over JSON-based PlayerAnimator context
 * animations. This allows artists to author posture animations directly in Blender
 * instead of hand-editing JSON keyframes.</p>
 *
 * <p>Reloaded on resource pack reload (F3+T) via the listener registered in
 * {@link com.tiedup.remake.client.gltf.GltfClientSetup}.</p>
 *
 * <p>Thread safety: the registry field is a volatile reference to an unmodifiable map.
 * {@link #reload} builds a new map on the reload thread then atomically swaps the
 * reference, so the render thread never sees a partially populated registry.</p>
 */
@OnlyIn(Dist.CLIENT)
public final class ContextGlbRegistry {

    private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");

    /** Resource directory containing context GLB files. */
    private static final String DIRECTORY = "tiedup_contexts";

    /**
     * Registry keyed by context suffix (e.g., "stand_walk", "sit_idle").
     * Values are fully converted KeyframeAnimations with all parts enabled.
     *
     * <p>Volatile reference to an unmodifiable map. Reload builds a new map
     * and swaps atomically; the render thread always sees a consistent snapshot.</p>
     */
    private static volatile Map<String, KeyframeAnimation> REGISTRY = Map.of();

    private ContextGlbRegistry() {}

    /**
     * Reload all context GLB files from the resource manager.
     *
     * <p>Scans {@code assets/<namespace>/tiedup_contexts/} for {@code .glb} files.
     * Each file is parsed and converted to a full-body KeyframeAnimation.
     * The context suffix is extracted from the file path:
     * {@code tiedup_contexts/stand_walk.glb} becomes key {@code "stand_walk"}.</p>
     *
     * <p>GLB files without animation data or with parse errors are logged and skipped.</p>
     *
     * @param resourceManager the current resource manager (from reload listener)
     */
    public static void reload(ResourceManager resourceManager) {
        Map<String, KeyframeAnimation> newRegistry = new HashMap<>();

        Map<ResourceLocation, Resource> resources =
            resourceManager.listResources(DIRECTORY, loc ->
                loc.getPath().endsWith(".glb")
            );

        for (Map.Entry<
            ResourceLocation,
            Resource
        > entry : resources.entrySet()) {
            ResourceLocation loc = entry.getKey();
            Resource resource = entry.getValue();

            // Extract suffix from path: "tiedup_contexts/stand_walk.glb" -> "stand_walk"
            String path = loc.getPath();
            String fileName = path.substring(path.lastIndexOf('/') + 1);
            String suffix = fileName.substring(0, fileName.length() - 4); // strip ".glb"

            try (InputStream is = resource.open()) {
                GltfData data = GlbParser.parse(is, loc.toString());

                // Convert to a full-body KeyframeAnimation (all parts enabled)
                KeyframeAnimation anim = GltfPoseConverter.convert(data);
                newRegistry.put(suffix, anim);

                LOGGER.info(
                    "[GltfPipeline] Loaded context GLB: '{}' -> suffix '{}'",
                    loc,
                    suffix
                );
            } catch (Exception e) {
                LOGGER.error(
                    "[GltfPipeline] Failed to load context GLB: {}",
                    loc,
                    e
                );
            }
        }

        // Atomic swap: render thread never sees a partially populated registry
        REGISTRY = Collections.unmodifiableMap(newRegistry);
        LOGGER.info(
            "[ContextGlb] Loaded {} context GLB animations",
            newRegistry.size()
        );
    }

    /**
     * Get a context animation by suffix.
     *
     * @param contextSuffix the context suffix (e.g., "stand_walk", "sit_idle")
     * @return the KeyframeAnimation, or null if no GLB was found for this suffix
     */
    @Nullable
    public static KeyframeAnimation get(String contextSuffix) {
        return REGISTRY.get(contextSuffix);
    }

    /**
     * Clear all cached context animations.
     * Called on resource reload and world unload.
     */
    public static void clear() {
        REGISTRY = Map.of();
    }
}
