/*
 * 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.anim;

import java.util.Map;
import java.util.Optional;

import javax.annotation.Nullable;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair;

import net.minecraftforge.common.MinecraftForge;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.EntityState;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.event.InitAnimatorEvent;
import com.tiedup.remake.rig.TiedUpRigRegistry;
import com.tiedup.remake.rig.TiedUpRigConstants;
import com.tiedup.remake.rig.patch.LivingEntityPatch;

public abstract class Animator {
	protected final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> livingAnimations = Maps.newHashMap();
	protected final AnimationVariables animationVariables = new AnimationVariables(this);
	protected final LivingEntityPatch<?> entitypatch;
	
	public Animator(LivingEntityPatch<?> entitypatch) {
		this.entitypatch = entitypatch;
	}
	
	/**
	 * Play an animation
	 * 
	 * @param nextAnimation the animation that is meant to be played.
	 * @param transitionTimeModifier extends the transition time if positive value provided, or starts in time as an amount of time (e.g. -0.1F starts in 0.1F frame time)
	 */
	public abstract void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier);
	
	public final void playAnimation(int id, float transitionTimeModifier) {
		this.playAnimation(AnimationManager.byId(id), transitionTimeModifier);
	}
	
	/**
	 * Play a given animation without transition animation.
	 * @param nextAnimation
	 */
	public abstract void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation);
	
	public final void playAnimationInstantly(int id) {
		this.playAnimationInstantly(AnimationManager.byId(id));
	}
	
	/**
	 * Reserve a given animation until the current animation ends.
	 * If the given animation has a higher priority than current animation, it terminates the current animation by force and play the next animation
	 * @param nextAnimation
	 */
	public abstract void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation);
	
	public final void reserveAnimation(int id) {
		this.reserveAnimation(AnimationManager.byId(id));
	}
	
	/**
	 * Stop playing given animation if exist
	 * @param targetAnimation
	 * @return true when found and successfully stop the target animation
	 */
	public abstract boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation);
	
	/**
	 * Play an shooting animation to end aiming pose
	 */
	public abstract void playShootingAnimation();
	
	public final boolean stopPlaying(int id) {
		return this.stopPlaying(AnimationManager.byId(id));
	}
	
	public abstract void setSoftPause(boolean paused);
	public abstract void setHardPause(boolean paused);
	public abstract void tick();
	
	public abstract EntityState getEntityState();
	
	/**
	 * Searches an animation player playing the given animation parameter or return base layer if it's null
	 * Secure non-null but returned animation player won't match with a given animation
	 */
	@Nullable
	public abstract AnimationPlayer getPlayerFor(@Nullable AssetAccessor<? extends DynamicAnimation> playingAnimation);
	
	/**
	 * Searches an animation player playing the given animation parameter
	 */
	@Nullable
	public abstract Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation);
	
	public abstract <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType);
	public abstract Pose getPose(float partialTicks);
	
	public void postInit() {
		InitAnimatorEvent initAnimatorEvent = new InitAnimatorEvent(this.entitypatch, this);
		MinecraftForge.EVENT_BUS.post(initAnimatorEvent);
	}
	
	public void playDeathAnimation() {
		// RIG : Animations.BIPED_DEATH (EF combat asset ARR) retiré.
		// Fallback sur EMPTY_ANIMATION — TiedUp authored ses propres
		// anims de mort Phase 4 et les binde via addLivingAnimation(DEATH, ...).
		this.playAnimation(this.livingAnimations.getOrDefault(LivingMotions.DEATH, TiedUpRigRegistry.EMPTY_ANIMATION), 0);
	}
	
	public void addLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> animation) {
		if (AnimationManager.checkNull(animation)) {
			TiedUpRigConstants.LOGGER.warn("Unable to put an empty animation for " + livingMotion);
			return;
		}
		
		this.livingAnimations.put(livingMotion, animation);
	}
	
	public AssetAccessor<? extends StaticAnimation> getLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> defaultGetter) {
		return this.livingAnimations.getOrDefault(livingMotion, defaultGetter);
	}
	
	public Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> getLivingAnimations() {
		return ImmutableMap.copyOf(this.livingAnimations);
	}
	
	public AnimationVariables getVariables() {
		return this.animationVariables;
	}
	
	public LivingEntityPatch<?> getEntityPatch() {
		return this.entitypatch;
	}
	
	public void resetLivingAnimations() {
		this.livingAnimations.clear();
	}
}
