/*
 * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
 * by the Epic Fight Team, licensed under GPLv3.
 * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
 */

package com.tiedup.remake.rig.patch;

import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

import com.google.common.collect.Maps;

import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullSupplier;

import com.tiedup.remake.rig.TiedUpRigConstants;

/**
 * Dispatcher + {@link ICapabilityProvider} qui construit l'{@link EntityPatch}
 * approprié pour un {@link Entity} donné, basé sur son {@link EntityType}.
 *
 * <p>Simplifié de EF {@code yesman.epicfight.world.capabilities.provider.EntityPatchProvider} :</p>
 * <ul>
 *   <li>Pas de projectile (ProjectilePatch / ArrowPatch strippés)</li>
 *   <li>Pas de GlobalMobPatch fallback sur gamerule stun</li>
 *   <li>Pas d'event posté à ModLoader (pas de tiers mods EF pour l'instant)</li>
 * </ul>
 *
 * <p><b>Attention MCA</b> : on ne doit PAS enregistrer {@link EntityType#VILLAGER}
 * dans {@link #CAPABILITIES}. MCA gère son propre rendering custom via
 * {@code MixinVillagerEntityMCAAnimated} et attacher notre patch causerait
 * double-rendering + rupture des anims MCA (cf.
 * {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-10).</p>
 *
 * <p><b>Init order</b> : {@link #registerEntityPatches()} doit être appelé en
 * {@code FMLCommonSetupEvent} (serveur + client), {@link #registerEntityPatchesClient()}
 * en {@code FMLClientSetupEvent} uniquement.</p>
 */
public class EntityPatchProvider implements ICapabilityProvider, NonNullSupplier<EntityPatch<?>> {

	/** Provider map populée au setup, lu au spawn entity. */
	private static final Map<EntityType<?>, Function<Entity, Supplier<EntityPatch<?>>>> CAPABILITIES = Maps.newHashMap();

	/**
	 * À appeler en {@code FMLCommonSetupEvent.event.enqueueWork(...)}.
	 *
	 * <p>Phase 2 : enregistre uniquement {@link EntityType#PLAYER} côté serveur.
	 * Les NPCs TiedUp (Damsel, Maid, Master, Kidnapper) seront ajoutés Phase 5
	 * quand on aura des animations adaptées.</p>
	 */
	public static void registerEntityPatches() {
		CAPABILITIES.put(EntityType.PLAYER, (entityIn) -> ServerPlayerPatch::new);

		TiedUpRigConstants.LOGGER.debug(
			"EntityPatchProvider: registered {} entity types (common)", CAPABILITIES.size()
		);
	}

	/**
	 * À appeler en {@code FMLClientSetupEvent.event.enqueueWork(...)}.
	 *
	 * <p>Surcharge {@link EntityType#PLAYER} côté client pour dispatcher vers
	 * {@link LocalPlayerPatch} (joueur local) vs {@link ClientPlayerPatch}
	 * (remote players) vs {@link ServerPlayerPatch} (si logical server même JVM).</p>
	 */
	public static void registerEntityPatchesClient() {
		CAPABILITIES.put(EntityType.PLAYER, (entityIn) -> {
			if (entityIn instanceof net.minecraft.client.player.LocalPlayer) {
				return LocalPlayerPatch::new;
			} else if (entityIn instanceof net.minecraft.client.player.RemotePlayer) {
				return ClientPlayerPatch<net.minecraft.client.player.RemotePlayer>::new;
			} else if (entityIn instanceof net.minecraft.server.level.ServerPlayer) {
				return ServerPlayerPatch::new;
			}
			return () -> null;
		});

		TiedUpRigConstants.LOGGER.debug(
			"EntityPatchProvider: client-side player dispatch installed"
		);
	}

	// ---------- instance ----------

	private EntityPatch<?> capability;
	private final LazyOptional<EntityPatch<?>> optional = LazyOptional.of(this);

	public EntityPatchProvider(Entity entity) {
		Function<Entity, Supplier<EntityPatch<?>>> provider = CAPABILITIES.get(entity.getType());

		if (provider != null) {
			try {
				this.capability = provider.apply(entity).get();
			} catch (Exception e) {
				TiedUpRigConstants.LOGGER.error(
					"EntityPatchProvider: failed to construct patch for {}",
					entity.getType(), e
				);
			}
		}
	}

	/**
	 * @return true si ce provider a bien construit un patch pour l'entity
	 *         (= entity est dans la whitelist PLAYER / future NPCs TiedUp).
	 */
	public boolean hasCapability() {
		return this.capability != null;
	}

	@Override
	public EntityPatch<?> get() {
		return this.capability;
	}

	@Override
	public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
		return cap == TiedUpCapabilities.CAPABILITY_ENTITY ? this.optional.cast() : LazyOptional.empty();
	}

	/**
	 * Invalide la {@link LazyOptional} portée par ce provider. À appeler depuis
	 * {@code AttachCapabilitiesEvent.addListener} — sans ça les références vers
	 * le patch, l'animator et l'armature persistent à chaque respawn / dimension
	 * change → fuite mémoire.
	 */
	public void invalidate() {
		this.optional.invalidate();
	}
}
