package com.tiedup.remake.entities.skins;

import com.tiedup.remake.core.TiedUpMod;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import net.minecraft.util.RandomSource;

/**
 * Core skin manager logic shared across all skin managers.
 *
 * This class provides the common functionality for variant registration,
 * retrieval, and management. Each specific skin manager uses this via
 * composition to avoid code duplication.
 *
 * @param <V> The variant type (must implement SkinVariant)
 */
public class SkinManagerCore<V extends SkinVariant> {

    private final Map<String, V> variants = new LinkedHashMap<>();
    private final List<V> variantsList = new ArrayList<>();
    private final RandomSource random = RandomSource.create();

    private final String managerName;
    private final String registryKey;
    private final Function<SkinDefinition, V> variantConverter;
    private final Runnable hardcodedDefaultsLoader;

    /**
     * Create a new skin manager core.
     *
     * @param managerName Name for logging (e.g., "DamselSkinManager")
     * @param registryKey Key for SkinLoader registry (e.g., "damsel")
     * @param variantConverter Function to convert SkinDefinition to variant type
     * @param hardcodedDefaultsLoader Runnable to load hardcoded defaults
     */
    public SkinManagerCore(
        String managerName,
        String registryKey,
        Function<SkinDefinition, V> variantConverter,
        Runnable hardcodedDefaultsLoader
    ) {
        this.managerName = managerName;
        this.registryKey = registryKey;
        this.variantConverter = variantConverter;
        this.hardcodedDefaultsLoader = hardcodedDefaultsLoader;
    }

    /**
     * Initialize and register all variants.
     * Loads from data files first, falls back to hardcoded defaults.
     */
    public void registerDefaults() {
        TiedUpMod.LOGGER.info("[{}] Registering variants...", managerName);

        clear();

        EntitySkinRegistry registry = SkinLoader.getRegistry(registryKey);

        if (registry.count() == 0) {
            TiedUpMod.LOGGER.warn(
                "[{}] No JSON data found, using hardcoded defaults",
                managerName
            );
            hardcodedDefaultsLoader.run();
        } else {
            for (SkinDefinition def : registry.getAllSkins()) {
                registerVariant(variantConverter.apply(def));
            }
            TiedUpMod.LOGGER.info(
                "[{}] Loaded {} variants from data files",
                managerName,
                variants.size()
            );
        }

        TiedUpMod.LOGGER.info(
            "[{}] Registered {} variants total",
            managerName,
            variants.size()
        );
    }

    /**
     * Register a variant.
     *
     * @param variant The variant to register
     */
    public void registerVariant(V variant) {
        if (variant == null) {
            TiedUpMod.LOGGER.warn(
                "[{}] Attempted to register null variant",
                managerName
            );
            return;
        }

        if (variants.containsKey(variant.id())) {
            TiedUpMod.LOGGER.warn(
                "[{}] Variant {} already registered, skipping",
                managerName,
                variant.id()
            );
            return;
        }

        variants.put(variant.id(), variant);
        variantsList.add(variant);
    }

    /**
     * Get a variant by ID.
     *
     * @param id The variant ID
     * @return The variant, or null if not found
     */
    public V getVariant(String id) {
        return variants.get(id);
    }

    /**
     * Get a random variant.
     *
     * @return A random variant
     * @throws IllegalStateException if no variants are registered
     */
    public V getRandomVariant() {
        if (variantsList.isEmpty()) {
            throw new IllegalStateException(
                "No " + managerName + " variants registered!"
            );
        }
        return variantsList.get(random.nextInt(variantsList.size()));
    }

    /**
     * Get a deterministic variant based on entity UUID.
     * This ensures all clients see the same variant for the same entity.
     *
     * @param entityUUID The entity's UUID
     * @return A variant determined by the UUID, or null if none registered
     */
    public V getVariantForEntity(UUID entityUUID) {
        return getVariantForEntity(entityUUID, null);
    }

    /**
     * Get a deterministic variant based on entity UUID, optionally filtering by gender.
     *
     * @param entityUUID The entity's UUID
     * @param preferredGender The preferred gender, or null for any
     * @return A variant determined by the UUID
     */
    public V getVariantForEntity(UUID entityUUID, Gender preferredGender) {
        if (variantsList.isEmpty()) {
            return null;
        }

        List<V> candidates;
        if (preferredGender == null) {
            candidates = variantsList;
        } else {
            candidates = new ArrayList<>();
            for (V v : variantsList) {
                if (v.gender() == preferredGender) {
                    candidates.add(v);
                }
            }
            // Fallback if no match (e.g., no males registered)
            if (candidates.isEmpty()) {
                candidates = variantsList;
            }
        }

        int index = Math.abs(entityUUID.hashCode()) % candidates.size();
        return candidates.get(index);
    }

    /**
     * Get all registered variants.
     *
     * @return Unmodifiable collection of all variants
     */
    public Collection<V> getAllVariants() {
        return Collections.unmodifiableCollection(variants.values());
    }

    /**
     * Get total count of registered variants.
     *
     * @return The variant count
     */
    public int getVariantCount() {
        return variants.size();
    }

    /**
     * Clear all registered variants.
     */
    public void clear() {
        variants.clear();
        variantsList.clear();
        TiedUpMod.LOGGER.debug("[{}] Cleared all variants", managerName);
    }
}
