import { transformProps } from '../../render/html/utils/transform.mjs';
import { optimizedAppearDataAttribute } from '../optimized-appear/data-id.mjs';
import { animateMotionValue } from './motion-value.mjs';
import { isWillChangeMotionValue } from '../../value/use-will-change/is.mjs';
import { setTarget } from '../../render/utils/setters.mjs';
import { getValueTransition } from '../utils/transitions.mjs';
import { frame } from '../../frameloop/frame.mjs';

/**
 * Decide whether we should block this animation. Previously, we achieved this
 * just by checking whether the key was listed in protectedKeys, but this
 * posed problems if an animation was triggered by afterChildren and protectedKeys
 * had been set to true in the meantime.
 */
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
    const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
    needsAnimating[key] = false;
    return shouldBlock;
}
function hasKeyframesChanged(value, target) {
    const current = value.get();
    if (Array.isArray(target)) {
        for (let i = 0; i < target.length; i++) {
            if (target[i] !== current)
                return true;
        }
    }
    else {
        return current !== target;
    }
}
function animateTarget(visualElement, definition, { delay = 0, transitionOverride, type } = {}) {
    let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = visualElement.makeTargetAnimatable(definition);
    const willChange = visualElement.getValue("willChange");
    if (transitionOverride)
        transition = transitionOverride;
    const animations = [];
    const animationTypeState = type &&
        visualElement.animationState &&
        visualElement.animationState.getState()[type];
    for (const key in target) {
        const value = visualElement.getValue(key);
        const valueTarget = target[key];
        if (!value ||
            valueTarget === undefined ||
            (animationTypeState &&
                shouldBlockAnimation(animationTypeState, key))) {
            continue;
        }
        const valueTransition = {
            delay,
            elapsed: 0,
            ...getValueTransition(transition || {}, key),
        };
        /**
         * If this is the first time a value is being animated, check
         * to see if we're handling off from an existing animation.
         */
        if (window.HandoffAppearAnimations) {
            const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
            if (appearId) {
                const elapsed = window.HandoffAppearAnimations(appearId, key, value, frame);
                if (elapsed !== null) {
                    valueTransition.elapsed = elapsed;
                    valueTransition.isHandoff = true;
                }
            }
        }
        let canSkip = !valueTransition.isHandoff &&
            !hasKeyframesChanged(value, valueTarget);
        if (valueTransition.type === "spring" &&
            (value.getVelocity() || valueTransition.velocity)) {
            canSkip = false;
        }
        /**
         * Temporarily disable skipping animations if there's an animation in
         * progress. Better would be to track the current target of a value
         * and compare that against valueTarget.
         */
        if (value.animation) {
            canSkip = false;
        }
        if (canSkip)
            continue;
        value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
            ? { type: false }
            : valueTransition));
        const animation = value.animation;
        if (isWillChangeMotionValue(willChange)) {
            willChange.add(key);
            animation.then(() => willChange.remove(key));
        }
        animations.push(animation);
    }
    if (transitionEnd) {
        Promise.all(animations).then(() => {
            transitionEnd && setTarget(visualElement, transitionEnd);
        });
    }
    return animations;
}

export { animateTarget };
