package com.tiedup.remake.client.animation;

import com.mojang.logging.LogUtils;
import dev.kosmx.playerAnim.api.layered.IAnimation;
import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer;
import dev.kosmx.playerAnim.api.layered.ModifierLayer;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.slf4j.Logger;

/**
 * Manages pending animations for remote players whose animation layers
 * may not be immediately available due to timing issues.
 *
 * <p>When a player is tied, the sync packet may arrive before the remote player's
 * animation layer is initialized by PlayerAnimator. This class queues failed
 * animation attempts and retries them each tick until success or timeout.
 *
 * <p>This follows the same pattern as SyncManager's pending queue for inventory sync.
 */
@OnlyIn(Dist.CLIENT)
public class PendingAnimationManager {

    private static final Logger LOGGER = LogUtils.getLogger();

    /** Pending animations waiting for layer initialization */
    private static final Map<UUID, PendingEntry> pending =
        new ConcurrentHashMap<>();

    /** Maximum retry attempts before giving up (~2 seconds at 20 ticks/sec) */
    private static final int MAX_RETRIES = 40;

    /**
     * Queue a player's animation for retry.
     * Called when playAnimation fails due to null layer.
     *
     * @param uuid    The player's UUID
     * @param animId  The animation ID (without namespace)
     */
    public static void queueForRetry(UUID uuid, String animId) {
        pending.compute(uuid, (k, existing) -> {
            if (existing == null) {
                LOGGER.debug(
                    "Queued animation '{}' for retry on player {}",
                    animId,
                    uuid
                );
                return new PendingEntry(animId, 0);
            }
            // Update animation ID but preserve retry count
            return new PendingEntry(animId, existing.retries);
        });
    }

    /**
     * Remove a player from the pending queue.
     * Called when animation succeeds or player disconnects.
     *
     * @param uuid The player's UUID
     */
    public static void remove(UUID uuid) {
        pending.remove(uuid);
    }

    /**
     * Check if a player has a pending animation.
     *
     * @param uuid The player's UUID
     * @return true if pending
     */
    public static boolean hasPending(UUID uuid) {
        return pending.containsKey(uuid);
    }

    /**
     * Process pending animations. Called every tick from AnimationTickHandler.
     * Attempts to play queued animations and removes successful or expired entries.
     *
     * @param level The client level
     */
    public static void processPending(ClientLevel level) {
        if (pending.isEmpty()) return;

        Iterator<Map.Entry<UUID, PendingEntry>> it = pending
            .entrySet()
            .iterator();

        while (it.hasNext()) {
            Map.Entry<UUID, PendingEntry> entry = it.next();
            UUID uuid = entry.getKey();
            PendingEntry pe = entry.getValue();

            // Check expiration
            if (pe.retries >= MAX_RETRIES) {
                LOGGER.warn("Animation retry exhausted for player {}", uuid);
                it.remove();
                continue;
            }

            // Try to find player and play animation
            Player player = level.getPlayerByUUID(uuid);
            if (player instanceof AbstractClientPlayer clientPlayer) {
                ModifierLayer<IAnimation> layer =
                    BondageAnimationManager.getPlayerLayerSafe(clientPlayer);

                if (layer != null) {
                    ResourceLocation loc =
                        ResourceLocation.fromNamespaceAndPath(
                            "tiedup",
                            pe.animId
                        );
                    KeyframeAnimation anim =
                        PlayerAnimationRegistry.getAnimation(loc);

                    if (anim != null) {
                        layer.setAnimation(new KeyframeAnimationPlayer(anim));
                        LOGGER.info(
                            "Animation retry succeeded for {} after {} attempts",
                            clientPlayer.getName().getString(),
                            pe.retries
                        );
                        it.remove();
                        continue;
                    }
                }
            }

            // Increment retry count
            pending.put(uuid, new PendingEntry(pe.animId, pe.retries + 1));
        }
    }

    /**
     * Clear all pending animations.
     * Called on world unload.
     */
    public static void clearAll() {
        pending.clear();
        LOGGER.debug("Cleared all pending animations");
    }

    /**
     * Record to store pending animation data.
     */
    private record PendingEntry(String animId, int retries) {}
}
