import _ from 'lodash';
import '../../assert/js/assert.js';
import '../../globals.js';
import { Shape } from '../../kite/js/imports.js';
import optionize from '../../phet-core/js/optionize.js';
import { Node } from '../../scenery/js/imports.js';
import Transition from './Transition.js';
import twixt from './twixt.js';
class TransitionNode extends Node {
    /**
     * NOTE: The content's transform/pickability/visibility/opacity/clipArea/etc. can be modified, and will be reset to
     * the default value
     *
     * @param transitionBoundsProperty - Use visibleBoundsProperty (from the ScreenView) for full-screen transitions.
     *                                   Generally TransitionNode assumes all content, when it has no transform applied,
     *                                   is meant to by laid out within these bounds.
     * @param [options]
     */
    constructor(transitionBoundsProperty, providedOptions) {
        const options = optionize()({
            content: null,
            useBoundsClip: true,
            cachedNodes: []
        }, providedOptions);
        assert && assert(!options.children, 'Children should not be specified, since cachedNodes will be applied');
        super();
        // Holds the content that we are animating towards.
        this.toContent = null;
        this.transitionBoundsProperty = transitionBoundsProperty;
        this.useBoundsClip = options.useBoundsClip;
        this.cachedNodes = options.cachedNodes;
        this.fromContent = options.content;
        this.children = this.cachedNodes;
        for (let i = 0; i < this.cachedNodes.length; i++) {
            const cachedNode = this.cachedNodes[i];
            cachedNode.visible = cachedNode === this.fromContent;
        }
        if (this.fromContent && !_.includes(this.cachedNodes, this.fromContent)) {
            this.addChild(this.fromContent);
        }
        this.transition = null;
        this.paddingNode = new Node();
        this.addChild(this.paddingNode);
        this.boundsListener = this.onBoundsChange.bind(this);
        this.transitionBoundsProperty.link(this.boundsListener);
        this.mutate(options);
    }
    /**
     * Steps forward in time, animating the transition.
     */
    step(dt) {
        this.transition && this.transition.step(dt);
    }
    /**
     * Interrupts the transition, ending it and resetting the animated values.
     */
    interrupt() {
        this.transition && this.transition.stop();
    }
    /**
     * Called on bounds changes.
     */
    onBoundsChange(bounds) {
        this.interrupt();
        if (this.useBoundsClip) {
            this.clipArea = Shape.bounds(bounds);
        }
        // Provide a localBounds override so that we take up at least the provided bounds. This makes layout easier so
        // that the TransitionNode always provides consistent bounds with clipping. See
        // https://github.com/phetsims/twixt/issues/15.
        this.paddingNode.localBounds = bounds;
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.slideLeft.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    slideLeftTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.slideLeft(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.slideRight.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    slideRightTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.slideRight(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.slideUp.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    slideUpTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.slideUp(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.slideDown.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    slideDownTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.slideDown(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.wipeLeft.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    wipeLeftTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.wipeLeft(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.wipeRight.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    wipeRightTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.wipeRight(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.wipeUp.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    wipeUpTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.wipeUp(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.wipeDown.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    wipeDownTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.wipeDown(this.transitionBoundsProperty.value, this.fromContent, content, config));
    }
    /**
     * Start a transition to replace our content with the new content, using Transition.dissolve.
     *
     * @param content - If null, the current content will still animate out (with nothing replacing it).
     * @param config - Passed as config to the Animation. Usually a duration should be included.
     * @returns - Available to add end listeners, etc.
     */
    dissolveTo(content, config) {
        this.interrupt();
        return this.startTransition(content, Transition.dissolve(this.fromContent, content, config));
    }
    /**
     * Starts a transition, and hooks up a listener to handle state changes when it ends.
     *
     * @returns - Available to add end listeners, etc. (chained)
     */
    startTransition(content, transition) {
        // Stop animating if we were before
        this.interrupt();
        this.toContent = content;
        if (content) {
            if (_.includes(this.cachedNodes, content)) {
                content.visible = true;
            }
            else {
                this.addChild(content);
            }
            assert && assert(this.hasChild(content), 'Should always have the content as a child at the start of a transition');
        }
        this.transition = transition;
        // Simplifies many things if the user can't mess with things while animating.
        if (this.fromContent) {
            this.fromContent.pickable = false;
        }
        if (this.toContent) {
            this.toContent.pickable = false;
        }
        transition.endedEmitter.addListener(() => {
            if (this.fromContent) {
                this.fromContent.pickable = null;
            }
            if (this.toContent) {
                this.toContent.pickable = null;
            }
            this.transition = null;
            if (this.fromContent) {
                if (_.includes(this.cachedNodes, this.fromContent)) {
                    this.fromContent.visible = false;
                }
                else {
                    this.removeChild(this.fromContent);
                }
                assert && assert(this.hasChild(this.fromContent) === _.includes(this.cachedNodes, this.fromContent), 'Should have removed the child if it is not included in our cachedNodes');
            }
            this.fromContent = this.toContent;
            this.toContent = null;
        });
        transition.start();
        return transition;
    }
    dispose() {
        this.interrupt();
        this.transitionBoundsProperty.unlink(this.boundsListener);
        super.dispose();
    }
}
twixt.register('TransitionNode', TransitionNode);
export default TransitionNode;
//# sourceMappingURL=TransitionNode.js.map