// Copyright 2017-2021, University of Colorado Boulder

/**
 * Assists "changing" options for types of nodes where the node does not support modifying the option.
 * This will create a new copy of the node whenever the options change, and will swap it into place.
 *
 * Given a type that has an option that can only be provided on construction (e.g. 'color' option for NumberPicker),
 * MutableOptionsNode can act like a mutable form of that Node. For example, if you have a color property:
 *
 * var colorProperty = new Property( 'red' );
 *
 * You can create a NumberPicker equivalent:
 *
 * var pickerContainer = new MutableOptionsNode( NumberPicker, [ arg1, arg2 ], {
 *   font: new PhetFont( 30 ) // normal properties that are passed in directly
 * }, {
 *   color: colorProperty // values wrapped with Property. When these change, a new NumberPicker is created and swapped.
 * }, {
 *   // Options passed to the wrapper node.
 * } );
 *
 * Now pickerContainer will have a child that is a NumberPicker, and pickerContainer.nodeProperty will point to the
 * current NumberPicker instance. The NumberPicker above will be created with like:
 *
 * new NumberPicker( arg1, arg2, {
 *   font: new PhetFont( 30 ),
 *   color: colorProperty.value
 * } )
 *
 * @author Jonathan Olson (PhET Interactive Simulations)
 */

import Property from '../../axon/js/Property.js';
import merge from '../../phet-core/js/merge.js';
import { Node } from '../../scenery/js/imports.js';
import sun from './sun.js';

class MutableOptionsNode extends Node {

  /**
   * @param {Function} nodeSubtype - The type of the node that we'll be constructing copies of.
   * @param {Array.<*>} parameters - Arbitrary initial parameters that will be passed to the type's constructor
   * @param {Object} staticOptions - Options passed in that won't change (will not unwrap properties)
   * @param {Object} dynamicOptions - Options passed in that will change. Should be a map from key names to
   *                                  Property.<*> values.
   * @param {Object} [wrapperOptions] - Node options passed to MutableOptionsNode itself (the wrapper).
   */
  constructor( nodeSubtype, parameters, staticOptions, dynamicOptions, wrapperOptions ) {
    super();

    // @public {Property.<Node|null>} [read-only] - Holds our current copy of the node (or null, so we don't have a
    //                                              specific initial value).
    this.nodeProperty = new Property( null );

    // @private {function} - The constructor for our custom subtype, unwraps the Properties in dynamicOptions.
    this._constructInstance = () => Reflect.construct( nodeSubtype, [
      ...parameters,
      merge( _.mapValues( dynamicOptions, property => property.value ), staticOptions ) // options
    ] );

    // @private {Multilink} - Make a copy, and replace it when one of our dyanmic options changes.
    this.multilink = Property.multilink( _.values( dynamicOptions ), this.replaceCopy.bind( this ) );

    // Apply any options that make more sense on the wrapper (typically like positioning)
    this.mutate( wrapperOptions );
  }

  /**
   * Creates a copy of our type of node, and replaces any existing copy.
   * @private
   */
  replaceCopy() {
    const newCopy = this._constructInstance();
    const oldCopy = this.nodeProperty.value;
    this.nodeProperty.value = newCopy;

    // Add first, so that there's a good chance we won't change bounds (depending on the type)
    this.addChild( newCopy );
    if ( oldCopy ) {
      this.removeChild( oldCopy );
      this.disposeCopy( oldCopy );
    }
  }

  /**
   * Attempt to dispose an instance of our node.
   * @private
   *
   * @param {Node} copy
   */
  disposeCopy( copy ) {
    copy.dispose && copy.dispose();
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.multilink.dispose();
    this.disposeCopy( this.nodeProperty.value );
    this.nodeProperty.dispose();
    super.dispose();
  }
}

sun.register( 'MutableOptionsNode', MutableOptionsNode );
export default MutableOptionsNode;
@@ -1489,16 +1489,125 @@
 n.js';%0A%0A
+/**%0A * @deprecated Not a good fit for PhET-iO. Please design your component so that the item is mutable.%0A */%0A
 class Mu

mark MutableOptionsNode as deprecated, per dev meeting