import { NativeAnimationControls, isGenerator, createGeneratorEasing, supportsLinearEasing } from 'motion-dom';
import { invariant, secondsToMilliseconds } from 'motion-utils';
import { startWaapiAnimation } from './index.mjs';
import { browserNumberValueTypes } from '../../../render/dom/value-types/number-browser.mjs';
import { getFinalKeyframe } from './utils/get-final-keyframe.mjs';
import { setCSSVar, setStyle } from './utils/style.mjs';
import { supportsPartialKeyframes } from './utils/supports-partial-keyframes.mjs';
import { supportsWaapi } from './utils/supports-waapi.mjs';

const state = new WeakMap();
function hydrateKeyframes(valueName, keyframes, read) {
    for (let i = 0; i < keyframes.length; i++) {
        if (keyframes[i] === null) {
            keyframes[i] = i === 0 ? read() : keyframes[i - 1];
        }
        if (typeof keyframes[i] === "number" &&
            browserNumberValueTypes[valueName]) {
            keyframes[i] = browserNumberValueTypes[valueName].transform(keyframes[i]);
        }
    }
    if (!supportsPartialKeyframes() && keyframes.length < 2) {
        keyframes.unshift(read());
    }
}
const defaultEasing = "easeOut";
function getElementAnimationState(element) {
    const animationState = state.get(element) || new Map();
    state.set(element, animationState);
    return state.get(element);
}
class NativeAnimation extends NativeAnimationControls {
    constructor(element, valueName, valueKeyframes, options) {
        const isCSSVar = valueName.startsWith("--");
        invariant(typeof options.type !== "string", `animateMini doesn't support "type" as a string. Did you mean to import { spring } from "framer-motion"?`);
        const existingAnimation = getElementAnimationState(element).get(valueName);
        existingAnimation && existingAnimation.stop();
        const readInitialKeyframe = () => {
            return valueName.startsWith("--")
                ? element.style.getPropertyValue(valueName)
                : window.getComputedStyle(element)[valueName];
        };
        if (!Array.isArray(valueKeyframes)) {
            valueKeyframes = [valueKeyframes];
        }
        hydrateKeyframes(valueName, valueKeyframes, readInitialKeyframe);
        // TODO: Replace this with toString()?
        if (isGenerator(options.type)) {
            const generatorOptions = createGeneratorEasing(options, 100, options.type);
            options.ease = supportsLinearEasing()
                ? generatorOptions.ease
                : defaultEasing;
            options.duration = secondsToMilliseconds(generatorOptions.duration);
            options.type = "keyframes";
        }
        else {
            options.ease = options.ease || defaultEasing;
        }
        const onFinish = () => {
            this.setValue(element, valueName, getFinalKeyframe(valueKeyframes, options));
            this.cancel();
            this.resolveFinishedPromise();
        };
        const init = () => {
            this.setValue = isCSSVar ? setCSSVar : setStyle;
            this.options = options;
            this.updateFinishedPromise();
            this.removeAnimation = () => {
                const elementState = state.get(element);
                elementState && elementState.delete(valueName);
            };
        };
        if (!supportsWaapi()) {
            super();
            init();
            onFinish();
        }
        else {
            super(startWaapiAnimation(element, valueName, valueKeyframes, options));
            init();
            if (options.autoplay === false) {
                this.animation.pause();
            }
            this.animation.onfinish = onFinish;
            getElementAnimationState(element).set(valueName, this);
        }
    }
    /**
     * Allows the returned animation to be awaited or promise-chained. Currently
     * resolves when the animation finishes at all but in a future update could/should
     * reject if its cancels.
     */
    then(resolve, reject) {
        return this.currentFinishedPromise.then(resolve, reject);
    }
    updateFinishedPromise() {
        this.currentFinishedPromise = new Promise((resolve) => {
            this.resolveFinishedPromise = resolve;
        });
    }
    play() {
        if (this.state === "finished") {
            this.updateFinishedPromise();
        }
        super.play();
    }
    cancel() {
        this.removeAnimation();
        super.cancel();
    }
}

export { NativeAnimation };
