package com.tiedup.remake.client.state;

import com.tiedup.remake.items.clothes.ClothesProperties;
import java.util.EnumSet;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;

/**
 * Client-side cache for storing clothes configuration of remote players.
 * This allows rendering dynamic textures and settings for other players
 * without requiring constant server queries.
 *
 * Thread-safe: Uses ConcurrentHashMap for multi-threaded access
 * (network thread writes, render thread reads).
 */
@OnlyIn(Dist.CLIENT)
public class ClothesClientCache {

    /**
     * Cache entry storing all clothes configuration for a player.
     */
    public record CachedClothesData(
        @Nullable String dynamicUrl,
        boolean fullSkin,
        boolean smallArms,
        boolean keepHead,
        EnumSet<ClothesProperties.LayerPart> visibleLayers,
        long timestamp
    ) {
        public boolean hasDynamicTexture() {
            return dynamicUrl != null && !dynamicUrl.isEmpty();
        }
    }

    // Main cache: playerUUID -> clothes data
    private static final Map<UUID, CachedClothesData> playerClothesCache =
        new ConcurrentHashMap<>();

    // Cache expiry time (5 minutes) - entries older than this may be considered stale
    private static final long CACHE_EXPIRY_MS = 5 * 60 * 1000;

    /**
     * Update or add clothes data for a player.
     * Called when receiving PacketSyncClothesConfig from server.
     *
     * @param playerUUID The player's UUID
     * @param dynamicUrl The dynamic texture URL (may be null)
     * @param fullSkin Whether full-skin mode is enabled
     * @param smallArms Whether small arms are forced
     * @param keepHead Whether to keep the wearer's head/hat
     * @param visibleLayers Which layers are visible
     */
    public static void updatePlayerClothes(
        UUID playerUUID,
        @Nullable String dynamicUrl,
        boolean fullSkin,
        boolean smallArms,
        boolean keepHead,
        EnumSet<ClothesProperties.LayerPart> visibleLayers
    ) {
        playerClothesCache.put(
            playerUUID,
            new CachedClothesData(
                dynamicUrl,
                fullSkin,
                smallArms,
                keepHead,
                visibleLayers,
                System.currentTimeMillis()
            )
        );
    }

    /**
     * Remove clothes data for a player.
     * Called when a player unequips clothes or disconnects.
     *
     * @param playerUUID The player's UUID
     */
    public static void removePlayerClothes(UUID playerUUID) {
        playerClothesCache.remove(playerUUID);
    }

    /**
     * Get cached clothes data for a player.
     *
     * @param playerUUID The player's UUID
     * @return The cached data, or null if not cached
     */
    @Nullable
    public static CachedClothesData getPlayerClothes(UUID playerUUID) {
        return playerClothesCache.get(playerUUID);
    }

    /**
     * Check if a player has cached clothes data.
     *
     * @param playerUUID The player's UUID
     * @return true if data exists in cache
     */
    public static boolean hasPlayerClothes(UUID playerUUID) {
        return playerClothesCache.containsKey(playerUUID);
    }

    /**
     * Get the dynamic texture URL for a player, if available.
     *
     * @param playerUUID The player's UUID
     * @return The URL or null if not available
     */
    @Nullable
    public static String getPlayerDynamicUrl(UUID playerUUID) {
        CachedClothesData data = playerClothesCache.get(playerUUID);
        return data != null ? data.dynamicUrl() : null;
    }

    /**
     * Check if a player has full-skin mode enabled.
     *
     * @param playerUUID The player's UUID
     * @return true if full-skin mode is enabled
     */
    public static boolean isFullSkinEnabled(UUID playerUUID) {
        CachedClothesData data = playerClothesCache.get(playerUUID);
        return data != null && data.fullSkin();
    }

    /**
     * Check if a player has small arms forced.
     *
     * @param playerUUID The player's UUID
     * @return true if small arms are forced
     */
    public static boolean isSmallArmsForced(UUID playerUUID) {
        CachedClothesData data = playerClothesCache.get(playerUUID);
        return data != null && data.smallArms();
    }

    /**
     * Check if a player has keep head mode enabled.
     *
     * @param playerUUID The player's UUID
     * @return true if keep head is enabled
     */
    public static boolean isKeepHeadEnabled(UUID playerUUID) {
        CachedClothesData data = playerClothesCache.get(playerUUID);
        return data != null && data.keepHead();
    }

    /**
     * Get visible layers for a player.
     *
     * @param playerUUID The player's UUID
     * @return The visible layers, or all layers if not cached
     */
    public static EnumSet<ClothesProperties.LayerPart> getVisibleLayers(
        UUID playerUUID
    ) {
        CachedClothesData data = playerClothesCache.get(playerUUID);
        return data != null
            ? data.visibleLayers()
            : EnumSet.allOf(ClothesProperties.LayerPart.class);
    }

    /**
     * Clear all cached data.
     * Called on world unload or disconnect.
     */
    public static void clearAll() {
        playerClothesCache.clear();
    }

    /**
     * Remove stale entries from cache.
     * Can be called periodically to prevent memory leaks.
     */
    public static void cleanupStale() {
        long now = System.currentTimeMillis();
        playerClothesCache
            .entrySet()
            .removeIf(
                entry -> (now - entry.getValue().timestamp()) > CACHE_EXPIRY_MS
            );
    }

    /**
     * Get the number of cached entries (for debugging).
     */
    public static int getCacheSize() {
        return playerClothesCache.size();
    }
}
