import * as Core from "@intelligentgraphics/ig.gfx.core";
import { isNullOrUndefined, EventEmitter, isValue, noop, } from "@intelligentgraphics/ig.utilities";
import { getPosition as getAnimationPosition } from "./Util";
/**
 * Creates an animation manager that manages animations and positions for all animatable components in the whole scene
 *
 * @param {PlannerManager} plannerManager
 * @returns {SceneAnimationManager}
 */
export const createSceneAnimationManager = (session, plannerManager, { continueAnimationsAfterUpdate = true, reapplyAnimationProgressAfterUpdate = true, } = {}, animatorOptions) => {
    if (continueAnimationsAfterUpdate) {
        reapplyAnimationProgressAfterUpdate = true;
    }
    const eventEmitter = new EventEmitter();
    const getAnimatableComIds = () => session
        .getFilteredArticles(article => !isNullOrUndefined(article.gfxId) &&
        !isNullOrUndefined(Core.State.API.GetComponent(article.gfxId)) &&
        Core.State.API.GetComponentData(article.gfxId)
            .hasAnimations === true)
        .map(article => article.comId);
    const getRootIds = () => getAnimatableComIds()
        .map(comId => session.getArticleGfxId(comId))
        .filter(isValue);
    const getPosition = () => getAnimationPosition(Core.State.API, getRootIds());
    const canAnimate = () => getAnimatableComIds().length > 0;
    let activeStatus = getPosition() === "end" ? "end" : "start";
    let animatable = false;
    let activePlaybackControl;
    let activeAnimationParams;
    const getStatus = () => activeStatus;
    const getActiveOrNextAnimationDirection = () => {
        if (preUpdateState !== undefined &&
            reapplyAnimationProgressAfterUpdate) {
            return preUpdateState.continueForward ? "forwards" : "backwards";
        }
        const position = getPosition();
        return position === "end" ? "backwards" : "forwards";
    };
    const updateStatus = (newStatus) => {
        activeStatus = newStatus;
        eventEmitter.emit("statusChange", newStatus);
    };
    const setPosition = (position) => {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.stop();
            activePlaybackControl = undefined;
        }
        Core.Animation.setPosition(getRootIds(), position === "end" ? "End" : "Start");
        updateStatus(position);
    };
    const showAnimation = (params) => {
        if (activePlaybackControl !== undefined) {
            return;
        }
        activeAnimationParams = params;
        activePlaybackControl = Core.Animation.showAnimation(getRootIds(), params, animatorOptions);
        updateStatus("running");
        const newStatus = params.forward === !params.invertAnimationAfter ? "end" : "start";
        activePlaybackControl.done
            .then(() => {
            updateStatus(newStatus);
        })
            .finally(() => {
            activePlaybackControl = undefined;
        })
            .catch(noop);
    };
    const showAnimationFromCurrentPosition = (invertAnimationAfter) => {
        const position = getPosition();
        return showAnimation({
            forward: position !== "end",
            invertAnimationAfter,
        });
    };
    const pauseAnimation = () => {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.pause();
            updateStatus("paused");
        }
    };
    const continueAnimation = () => {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.continue();
            updateStatus("running");
        }
    };
    const stopAnimation = () => {
        if (activePlaybackControl !== undefined) {
            activePlaybackControl.stop();
            activePlaybackControl = undefined;
            updateStatus("stopped");
        }
    };
    animatable = canAnimate();
    let preUpdateState;
    plannerManager.hooks.sceneUpdateStart.tap("AnimationManager", () => {
        preUpdateState = {
            maxProgress: 0,
            continueAfterUpdate: false,
            continueForward: true,
            animationWasPaused: false,
        };
        const animatableComIds = getAnimatableComIds();
        for (const comId of animatableComIds) {
            const gfxId = session.getArticleGfxId(comId);
            const { animationProgress, animationPosition, } = Core.State.API.GetComponentData(gfxId);
            if (animationProgress !== undefined) {
                preUpdateState.maxProgress = Math.max(animationProgress, preUpdateState.maxProgress);
            }
            if (animationPosition !== undefined &&
                preUpdateState.continueForward) {
                preUpdateState.continueForward = animationPosition !== "End";
            }
        }
        if (activePlaybackControl !== undefined) {
            preUpdateState.animationWasPaused = activeStatus === "paused";
            preUpdateState.continueAfterUpdate = true;
            stopAnimation();
        }
    });
    plannerManager.hooks.articleUpdateDone.tap("AnimationManager", (article, update) => {
        const { maxProgress } = preUpdateState;
        if (!continueAnimationsAfterUpdate) {
            if (update.updateType !== "materialOnly") {
                setPosition("start");
            }
            return;
        }
        if (reapplyAnimationProgressAfterUpdate) {
            Core.Animation.setProgress([article.gfxId], maxProgress);
        }
    });
    plannerManager.hooks.sceneUpdateDone.tap("AnimationManager", () => {
        const { maxProgress, continueAfterUpdate, continueForward, animationWasPaused, } = preUpdateState;
        if (continueAnimationsAfterUpdate) {
            // reapply animation positions here, since they might have been lost in the update
            for (const comId of getAnimatableComIds()) {
                const gfxId = session.getArticleGfxId(comId);
                if (gfxId !== undefined) {
                    Core.State.API.SetComponentData(gfxId, "animationPosition", continueForward ? "Start" : "End");
                }
            }
            if (continueAfterUpdate) {
                showAnimation({
                    forward: continueForward,
                    startFromProgress: maxProgress,
                });
                if (animationWasPaused) {
                    pauseAnimation();
                }
            }
        }
        preUpdateState = undefined;
        const canNowAnimate = canAnimate();
        if (canNowAnimate !== animatable) {
            animatable = canNowAnimate;
            eventEmitter.emit("animateabilityChange", animatable);
        }
    });
    return {
        on: eventEmitter.on.bind(eventEmitter),
        off: eventEmitter.off.bind(eventEmitter),
        once: eventEmitter.once.bind(eventEmitter),
        canAnimate,
        setPosition,
        getPosition,
        getStatus,
        getActiveOrNextAnimationDirection,
        showAnimation,
        showAnimationFromCurrentPosition,
        pauseAnimation,
        continueAnimation,
        stopAnimation,
    };
};
