import _ from 'lodash';
import '../../assert/js/assert.js';
import '../../globals.js';
import { Shape } from '../../kite/js/imports.js';
import merge from '../../phet-core/js/merge.js';
import optionize, { combineOptions } from '../../phet-core/js/optionize.js';
import required from '../../phet-core/js/required.js';
import Animation from './Animation.js';
import twixt from './twixt.js';
class Transition extends Animation {
    /**
     * NOTE: The nodes' transform/pickability/visibility/opacity/clipArea/etc. can be modified, and will be reset to
     * the default value when the transition finishes.
     */
    constructor(fromNode, toNode, config) {
        const defaults = {
            fromTargets: required(config.fromTargets),
            toTargets: required(config.toTargets),
            resetNode: required(config.resetNode),
            // {Object|null} (optional) - Passed as additional objects to every target
            targetOptions: null
        };
        config = merge({}, defaults, config);
        assert && assert(typeof config.resetNode === 'function');
        const targetOptions = merge({
        // NOTE: no defaults, but we want it to be an object so we merge anyways
        }, config.targetOptions);
        let targets = [];
        if (fromNode) {
            targets = targets.concat(config.fromTargets.map(target => {
                return combineOptions(target, {
                    object: fromNode
                }, targetOptions);
            }));
        }
        if (toNode) {
            targets = targets.concat(config.toTargets.map(target => {
                return combineOptions(target, {
                    object: toNode
                }, targetOptions);
            }));
        }
        super(combineOptions({
            // @ts-expect-error - Because we can't unroll the types in the maps above.
            targets: targets
        }, _.omit(config, _.keys(defaults))));
        // When this animation ends, reset the values for both nodes
        this.endedEmitter.addListener(() => {
            fromNode && config.resetNode(fromNode);
            toNode && config.resetNode(toNode);
        });
    }
    /**
     * Creates an animation that slides the `fromNode` out to the left (and the `toNode` in from the right).
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static slideLeft(bounds, fromNode, toNode, options) {
        return Transition.createSlide(fromNode, toNode, 'x', bounds.width, true, options);
    }
    /**
     * Creates an animation that slides the `fromNode` out to the right (and the `toNode` in from the left).
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static slideRight(bounds, fromNode, toNode, options) {
        return Transition.createSlide(fromNode, toNode, 'x', bounds.width, false, options);
    }
    /**
     * Creates an animation that slides the `fromNode` out to the top (and the `toNode` in from the bottom).
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static slideUp(bounds, fromNode, toNode, options) {
        return Transition.createSlide(fromNode, toNode, 'y', bounds.height, true, options);
    }
    /**
     * Creates an animation that slides the `fromNode` out to the bottom (and the `toNode` in from the top).
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static slideDown(bounds, fromNode, toNode, options) {
        return Transition.createSlide(fromNode, toNode, 'y', bounds.height, false, options);
    }
    /**
     * Creates a transition that wipes across the screen, moving to the left.
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static wipeLeft(bounds, fromNode, toNode, options) {
        return Transition.createWipe(bounds, fromNode, toNode, 'maxX', 'minX', options);
    }
    /**
     * Creates a transition that wipes across the screen, moving to the right.
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static wipeRight(bounds, fromNode, toNode, options) {
        return Transition.createWipe(bounds, fromNode, toNode, 'minX', 'maxX', options);
    }
    /**
     * Creates a transition that wipes across the screen, moving up.
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static wipeUp(bounds, fromNode, toNode, options) {
        return Transition.createWipe(bounds, fromNode, toNode, 'maxY', 'minY', options);
    }
    /**
     * Creates a transition that wipes across the screen, moving down.
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param [options] - Usually specify duration, easing, or other Animation options.
     */
    static wipeDown(bounds, fromNode, toNode, options) {
        return Transition.createWipe(bounds, fromNode, toNode, 'minY', 'maxY', options);
    }
    /**
     * Creates a transition that fades from `fromNode` to `toNode` by varying the opacity of both.
     *
     * @param fromNode
     * @param toNode
     * @param [providedOptions] - Usually specify duration, easing, or other Animation options.
     */
    static dissolve(fromNode, toNode, providedOptions) {
        const gammaBlend = (a, b, ratio) => {
            return Math.pow((1 - ratio) * a + ratio * b, options.gamma);
        };
        const options = optionize()({
            // Handles gamma correction for the opacity when required
            gamma: 1,
            fromTargets: [{
                    attribute: 'opacity',
                    from: 1,
                    to: 0,
                    blend: gammaBlend
                }],
            toTargets: [{
                    attribute: 'opacity',
                    from: 0,
                    to: 1,
                    blend: gammaBlend
                }],
            resetNode: (node) => {
                node.opacity = 1;
            }
        }, providedOptions);
        // @ts-expect-error WHY?
        return new Transition(fromNode, toNode, options);
    }
    /**
     * Creates a sliding transition within the bounds.
     *
     * @param fromNode
     * @param toNode
     * @param attribute - The positional attribute to animate
     * @param size - The size of the animation (for the positional attribute)
     * @param reversed - Whether to reverse the animation. By default it goes right/down.
     * @param [options]
     */
    static createSlide(fromNode, toNode, attribute, size, reversed, options) {
        const sign = reversed ? -1 : 1;
        return new Transition(fromNode, toNode, optionize()({
            fromTargets: [{
                    attribute: attribute,
                    from: 0,
                    to: size * sign
                }],
            toTargets: [{
                    attribute: attribute,
                    from: -size * sign,
                    to: 0
                }],
            resetNode: (node) => {
                // @ts-expect-error
                node[attribute] = 0;
            }
        }, options));
    }
    /**
     * Creates a wiping transition within the bounds.
     *
     * @param bounds
     * @param fromNode
     * @param toNode
     * @param minAttribute - One side of the bounds on the minimal side (where the animation starts)
     * @param maxAttribute - The other side of the bounds (where animation ends)
     * @param [options]
     */
    static createWipe(bounds, fromNode, toNode, minAttribute, maxAttribute, options) {
        const fromNodeBounds = bounds.copy();
        const toNodeBounds = bounds.copy();
        fromNodeBounds[minAttribute] = bounds[maxAttribute];
        toNodeBounds[maxAttribute] = bounds[minAttribute];
        // We need to apply custom clip area interpolation
        const clipBlend = (boundsA, boundsB, ratio) => {
            return Shape.bounds(boundsA.blend(boundsB, ratio));
        };
        return new Transition(fromNode, toNode, optionize()({
            fromTargets: [{
                    attribute: 'clipArea',
                    from: bounds,
                    to: fromNodeBounds,
                    // @ts-expect-error EEEEK - we're relying on blend to convert a bounds to a shape...?
                    blend: clipBlend
                }],
            toTargets: [{
                    attribute: 'clipArea',
                    from: toNodeBounds,
                    to: bounds,
                    // @ts-expect-error EEEEK - we're relying on blend to convert a bounds to a shape...?
                    blend: clipBlend
                }],
            resetNode: function (node) {
                node.clipArea = null;
            }
        }, options));
    }
}
twixt.register('Transition', Transition);
export default Transition;
//# sourceMappingURL=Transition.js.map