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

import java.util.Optional;

import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.patch.LivingEntityPatch;

@OnlyIn(Dist.CLIENT)
public class ConcurrentLinkAnimation extends DynamicAnimation implements AnimationAccessor<ConcurrentLinkAnimation> {
	protected AssetAccessor<? extends StaticAnimation> nextAnimation;
	protected AssetAccessor<? extends DynamicAnimation> currentAnimation;
	protected float startsAt;
	
	public ConcurrentLinkAnimation() {
		this.animationClip = new AnimationClip();
	}
	
	public void acceptFrom(AssetAccessor<? extends DynamicAnimation> currentAnimation, AssetAccessor<? extends StaticAnimation> nextAnimation, float time) {
		this.currentAnimation = currentAnimation;
		this.nextAnimation = nextAnimation;
		this.startsAt = time;
		this.setTotalTime(nextAnimation.get().getTransitionTime());
	}
	
	@Override
	public void tick(LivingEntityPatch<?> entitypatch) {
		this.nextAnimation.get().linkTick(entitypatch, this);
	}
	
	@Override
	public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
		if (!isEnd) {
			this.nextAnimation.get().end(entitypatch, nextAnimation, isEnd);
		} else {
			if (this.startsAt > 0.0F) {
				entitypatch.getAnimator().getPlayer(this).ifPresent(player -> {
					player.setElapsedTime(this.startsAt);
					player.markDoNotResetTime();
				});
				
				this.startsAt = 0.0F;
			}
		}
	}
	
	@Override
	public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
		return this.nextAnimation.get().getState(entitypatch, 0.0F);
	}
	
	@Override
	public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
		return this.nextAnimation.get().getState(stateFactor, entitypatch, 0.0F);
	}
	
	@Override
	public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
		float elapsed = time + this.startsAt;
		float currentElapsed = elapsed % this.currentAnimation.get().getTotalTime();
		float nextElapsed = elapsed % this.nextAnimation.get().getTotalTime();
		Pose currentAnimPose = this.currentAnimation.get().getPoseByTime(entitypatch, currentElapsed, 1.0F);
		Pose nextAnimPose = this.nextAnimation.get().getPoseByTime(entitypatch, nextElapsed, 1.0F);
		float interpolate = time / this.getTotalTime();
		
		Pose interpolatedPose = Pose.interpolatePose(currentAnimPose, nextAnimPose, interpolate);
		JointMaskEntry maskEntry = this.nextAnimation.get().getJointMaskEntry(entitypatch, true).orElse(null);
		
		if (maskEntry != null && entitypatch.isLogicalClient()) {
			interpolatedPose.disableJoint((entry) ->
				maskEntry.isMasked(
				  this.nextAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ? entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion()
				, entry.getKey()
			));
		}
		
		return interpolatedPose;
	}
	
	@Override
	public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
		this.nextAnimation.get().modifyPose(this, pose, entitypatch, time, partialTicks);
	}
	
	@Override
	public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
		return this.nextAnimation.get().getPlaySpeed(entitypatch, animation);
	}
	
	public void setNextAnimation(AnimationAccessor<? extends StaticAnimation> animation) {
		this.nextAnimation = animation;
	}
	
	@OnlyIn(Dist.CLIENT)
	@Override
	public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
		return this.nextAnimation.get().getJointMaskEntry(entitypatch, useCurrentMotion);
	}
	
	@Override
	public boolean isMainFrameAnimation() {
		return this.nextAnimation.get().isMainFrameAnimation();
	}
	
	@Override
	public boolean isReboundAnimation() {
		return this.nextAnimation.get().isReboundAnimation();
	}
	
	@Override
	public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
		return this.nextAnimation;
	}
	
	@Override
	public String toString() {
		return "ConcurrentLinkAnimation: Mix " + this.currentAnimation + " and " + this.nextAnimation;
	}
	
	@Override
	public AnimationClip getAnimationClip() {
		return this.animationClip;
	}
	
	@Override
	public boolean hasTransformFor(String joint) {
		return this.nextAnimation.get().hasTransformFor(joint);
	}
	
	@Override
	public boolean isLinkAnimation() {
		return true;
	}
	
	@Override
	public ConcurrentLinkAnimation get() {
		return this;
	}

	@Override
	public ResourceLocation registryName() {
		return null;
	}

	@Override
	public boolean isPresent() {
		return true;
	}

	@Override
	public int id() {
		return -1;
	}
	
	@Override
	public AnimationAccessor<? extends DynamicAnimation> getAccessor() {
		return this;
	}

	@Override
	public boolean inRegistry() {
		return false;
	}
}
