text: import { createScript } from '../../../script/script.js';
  
  import { Component } from '../component.js';
  import { Entity } from '../../entity.js';
  import { SortedLoopArray } from '../../utils/sorted-loop-array.js';
  
  /**
   * @component
   * @class
   * @name pc.ScriptComponent
   * @augments pc.Component
   * @classdesc The ScriptComponent allows you to extend the functionality of an Entity by attaching your own Script Types defined in JavaScript files
   * to be executed with access to the Entity. For more details on scripting see <a href="//developer.playcanvas.com/user-manual/scripting/">Scripting</a>.
   * @param {pc.ScriptComponentSystem} system - The ComponentSystem that created this Component.
   * @param {pc.Entity} entity - The Entity that this Component is attached to.
   * @property {pc.ScriptType[]} scripts An array of all script instances attached to an entity. This Array shall not be modified by developer.
   */
  function ScriptComponent(system, entity) {
      Component.call(this, system, entity);
  
      // holds all script instances for this component
      this._scripts = [];
      // holds all script instances with an update method
      this._updateList = new SortedLoopArray({ sortBy: '__executionOrder' });
      // holds all script instances with a postUpdate method
      this._postUpdateList = new SortedLoopArray({ sortBy: '__executionOrder' });
  
      this._scriptsIndex = {};
      this._destroyedScripts = [];
      this._destroyed = false;
      this._scriptsData = null;
      this._oldState = true;
  
      // override default 'enabled' property of base pc.Component
      // because this is faster
      this._enabled = true;
  
      // whether this component is currently being enabled
      this._beingEnabled = false;
      // if true then we are currently looping through
      // script instances. This is used to prevent a scripts array
      // from being modified while a loop is being executed
      this._isLoopingThroughScripts = false;
  
      // the order that this component will be updated
      // by the script system. This is set by the system itself.
      this._executionOrder = -1;
  
      this.on('set_enabled', this._onSetEnabled, this);
  }
  ScriptComponent.prototype = Object.create(Component.prototype);
  ScriptComponent.prototype.constructor = ScriptComponent;
  
  ScriptComponent.scriptMethods = {
      initialize: 'initialize',
      postInitialize: 'postInitialize',
      update: 'update',
      postUpdate: 'postUpdate',
      swap: 'swap'
  };
  
  /**
   * @event
   * @name pc.ScriptComponent#enable
   * @description Fired when Component becomes enabled
   * Note: this event does not take in account entity or any of its parent enabled state.
   * @example
   * entity.script.on('enable', function () {
   *     // component is enabled
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#disable
   * @description Fired when Component becomes disabled
   * Note: this event does not take in account entity or any of its parent enabled state.
   * @example
   * entity.script.on('disable', function () {
   *     // component is disabled
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#state
   * @description Fired when Component changes state to enabled or disabled
   * Note: this event does not take in account entity or any of its parent enabled state.
   * @param {boolean} enabled - True if now enabled, False if disabled.
   * @example
   * entity.script.on('state', function (enabled) {
   *     // component changed state
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#remove
   * @description Fired when Component is removed from entity.
   * @example
   * entity.script.on('remove', function () {
   *     // entity has no more script component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#create
   * @description Fired when a script instance is created and attached to component.
   * @param {string} name - The name of the Script Type.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been created.
   * @example
   * entity.script.on('create', function (name, scriptInstance) {
   *     // new script instance added to component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#create:[name]
   * @description Fired when a script instance is created and attached to component.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been created.
   * @example
   * entity.script.on('create:playerController', function (scriptInstance) {
   *     // new script instance 'playerController' is added to component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#destroy
   * @description Fired when a script instance is destroyed and removed from component.
   * @param {string} name - The name of the Script Type.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been destroyed.
   * @example
   * entity.script.on('destroy', function (name, scriptInstance) {
   *     // script instance has been destroyed and removed from component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#destroy:[name]
   * @description Fired when a script instance is destroyed and removed from component.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been destroyed.
   * @example
   * entity.script.on('destroy:playerController', function (scriptInstance) {
   *     // script instance 'playerController' has been destroyed and removed from component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#move
   * @description Fired when a script instance is moved in component.
   * @param {string} name - The name of the Script Type.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been moved.
   * @param {number} ind - New position index.
   * @param {number} indOld - Old position index.
   * @example
   * entity.script.on('move', function (name, scriptInstance, ind, indOld) {
   *     // script instance has been moved in component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#move:[name]
   * @description Fired when a script instance is moved in component.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that has been moved.
   * @param {number} ind - New position index.
   * @param {number} indOld - Old position index.
   * @example
   * entity.script.on('move:playerController', function (scriptInstance, ind, indOld) {
   *     // script instance 'playerController' has been moved in component
   * });
   */
  
  /**
   * @event
   * @name pc.ScriptComponent#error
   * @description Fired when a script instance had an exception.
   * @param {pc.ScriptType} scriptInstance - The instance of the {@link pc.ScriptType} that raised the exception.
   * @param {Error} err - Native JS Error object with details of an error.
   * @param {string} method - The method of the script instance that the exception originated from.
   * @example
   * entity.script.on('error', function (scriptInstance, err, method) {
   *     // script instance caught an exception
   * });
   */
  
  Object.assign(ScriptComponent.prototype, {
      onEnable: function () {
          this._beingEnabled = true;
          this._checkState();
  
          if (!this.entity._beingEnabled) {
              this.onPostStateChange();
          }
  
          this._beingEnabled = false;
      },
  
      onDisable: function () {
          this._checkState();
      },
  
      onPostStateChange: function () {
          var script;
  
          var wasLooping = this._beginLooping();
  
          for (var i = 0, len = this.scripts.length; i < len; i++) {
              script = this.scripts[i];
  
              if (script._initialized && !script._postInitialized && script.enabled) {
                  script._postInitialized = true;
  
                  if (script.postInitialize)
                      this._scriptMethod(script, ScriptComponent.scriptMethods.postInitialize);
              }
          }
  
          this._endLooping(wasLooping);
      },
  
      // Sets isLoopingThroughScripts to false and returns
      // its previous value
      _beginLooping: function () {
          var looping = this._isLoopingThroughScripts;
          this._isLoopingThroughScripts = true;
          return looping;
      },
  
      // Restores isLoopingThroughScripts to the specified parameter
      // If all loops are over then remove destroyed scripts form the _scripts array
      _endLooping: function (wasLoopingBefore) {
          this._isLoopingThroughScripts = wasLoopingBefore;
          if (!this._isLoopingThroughScripts) {
              this._removeDestroyedScripts();
          }
      },
  
      // We also need this handler because it is fired
      // when value === old instead of onEnable and onDisable
      // which are only fired when value !== old
      _onSetEnabled: function (prop, old, value) {
          this._beingEnabled = true;
          this._checkState();
          this._beingEnabled = false;
      },
  
      _checkState: function () {
          var state = this.enabled && this.entity.enabled;
          if (state === this._oldState)
              return;
  
          this._oldState = state;
  
          this.fire(state ? 'enable' : 'disable');
          this.fire('state', state);
  
          if (state) {
              this.system._addComponentToEnabled(this);
          } else {
              this.system._removeComponentFromEnabled(this);
          }
  
          var wasLooping = this._beginLooping();
  
          var script;
          for (var i = 0, len = this.scripts.length; i < len; i++) {
              script = this.scripts[i];
              script.enabled = script._enabled;
          }
  
          this._endLooping(wasLooping);
      },
  
      _onBeforeRemove: function () {
          this.fire('remove');
  
          var wasLooping = this._beginLooping();
  
          // destroy all scripts
          for (var i = 0; i < this.scripts.length; i++) {
              var script = this.scripts[i];
              if (!script) continue;
  
              this.destroy(script.__scriptType.__name);
          }
  
          this._endLooping(wasLooping);
      },
  
      _removeDestroyedScripts: function () {
          var len = this._destroyedScripts.length;
          if (!len) return;
  
          var i;
          for (i = 0; i < len; i++) {
              var script = this._destroyedScripts[i];
              this._removeScriptInstance(script);
          }
  
          this._destroyedScripts.length = 0;
  
          // update execution order for scripts
          this._resetExecutionOrder(0, this._scripts.length);
      },
  
      _onInitializeAttributes: function () {
          for (var i = 0, len = this.scripts.length; i < len; i++)
              this.scripts[i].__initializeAttributes();
      },
  
      _scriptMethod: function (script, method, arg) {
          // #ifdef DEBUG
          try {
          // #endif
              script[method](arg);
          // #ifdef DEBUG
          } catch (ex) {
              // disable script if it fails to call method
              script.enabled = false;
  
              if (!script._callbacks || !script._callbacks.error) {
                  console.warn('unhandled exception while calling "' + method + '" for "' + script.__scriptType.__name + '" script: ', ex);
                  console.error(ex);
              }
  
              script.fire('error', ex, method);
              this.fire('error', script, ex, method);
          }
          // #endif
      },
  
      _onInitialize: function () {
          var script, scripts = this._scripts;
  
          var wasLooping = this._beginLooping();
  
          for (var i = 0, len = scripts.length; i < len; i++) {
              script = scripts[i];
              if (!script._initialized && script.enabled) {
                  script._initialized = true;
                  if (script.initialize)
                      this._scriptMethod(script, ScriptComponent.scriptMethods.initialize);
              }
          }
  
          this._endLooping(wasLooping);
      },
  
      _onPostInitialize: function () {
          this.onPostStateChange();
      },
  
      _onUpdate: function (dt) {
          var self = this;
          var list = self._updateList;
          if (! list.length) return;
  
          var script;
  
          var wasLooping = self._beginLooping();
  
          for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
              script = list.items[list.loopIndex];
              if (script.enabled) {
                  self._scriptMethod(script, ScriptComponent.scriptMethods.update, dt);
              }
          }
  
          self._endLooping(wasLooping);
      },
  
      _onPostUpdate: function (dt) {
          var self = this;
          var list = self._postUpdateList;
          if (! list.length) return;
  
          var wasLooping = self._beginLooping();
  
          var script;
  
          for (list.loopIndex = 0; list.loopIndex < list.length; list.loopIndex++) {
              script = list.items[list.loopIndex];
              if (script.enabled) {
                  self._scriptMethod(script, ScriptComponent.scriptMethods.postUpdate, dt);
              }
          }
  
          self._endLooping(wasLooping);
      },
  
      /**
       * @private
       * @function
       * @description Inserts script instance into the scripts array at the specified index. Also inserts the script
       * into the update list if it has an update method and the post update list if it has a postUpdate method.
       * @param {object} scriptInstance - The script instance.
       * @param {number} index - The index where to insert the script at. If -1 then append it at the end.
       * @param {number} scriptsLength - The length of the scripts array.
       */
      _insertScriptInstance: function (scriptInstance, index, scriptsLength) {
          if (index === -1) {
              // append script at the end and set execution order
              this._scripts.push(scriptInstance);
              scriptInstance.__executionOrder = scriptsLength;
  
              // append script to the update list if it has an update method
              if (scriptInstance.update) {
                  this._updateList.append(scriptInstance);
              }
  
              // add script to the postUpdate list if it has a postUpdate method
              if (scriptInstance.postUpdate) {
                  this._postUpdateList.append(scriptInstance);
              }
          } else {
              // insert script at index and set execution order
              this._scripts.splice(index, 0, scriptInstance);
              scriptInstance.__executionOrder = index;
  
              // now we also need to update the execution order of all
              // the script instances that come after this script
              this._resetExecutionOrder(index + 1, scriptsLength + 1);
  
              // insert script to the update list if it has an update method
              // in the right order
              if (scriptInstance.update) {
                  this._updateList.insert(scriptInstance);
              }
  
              // insert script to the postUpdate list if it has a postUpdate method
              // in the right order
              if (scriptInstance.postUpdate) {
                  this._postUpdateList.insert(scriptInstance);
              }
          }
      },
  
      _removeScriptInstance: function (scriptInstance) {
          var idx = this._scripts.indexOf(scriptInstance);
          if (idx === -1) return idx;
  
          this._scripts.splice(idx, 1);
  
          if (scriptInstance.update) {
              this._updateList.remove(scriptInstance);
          }
  
          if (scriptInstance.postUpdate) {
              this._postUpdateList.remove(scriptInstance);
          }
  
          return idx;
      },
  
      _resetExecutionOrder: function (startIndex, scriptsLength) {
          for (var i = startIndex; i < scriptsLength; i++) {
              this._scripts[i].__executionOrder = i;
          }
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @function
       * @name pc.ScriptComponent#has
       * @description Detect if script is attached to an entity.
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @returns {boolean} If script is attached to an entity.
       * @example
       * if (entity.script.has('playerController')) {
       *     // entity has script
       * }
       */
      /* eslint-enable jsdoc/no-undefined-types */
      has: function (nameOrType) {
          if (typeof nameOrType === 'string') {
              return !!this._scriptsIndex[nameOrType];
          }
  
          if (!nameOrType) return false;
          var scriptType = nameOrType;
          var scriptName = scriptType.__name;
          var scriptData = this._scriptsIndex[scriptName];
          var scriptInstance = scriptData && scriptData.instance;
          return scriptInstance instanceof scriptType; // will return false if scriptInstance undefined
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @function
       * @name pc.ScriptComponent#get
       * @description Get a script instance (if attached).
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @returns {pc.ScriptType|null} If script is attached, the instance is returned. Otherwise null is returned.
       * @example
       * var controller = entity.script.get('playerController');
       */
      /* eslint-enable jsdoc/no-undefined-types */
      get: function (nameOrType) {
          if (typeof nameOrType === 'string') {
              var data = this._scriptsIndex[nameOrType];
              return data ? data.instance : null;
          }
  
          if (!nameOrType) return null;
          var scriptType = nameOrType;
          var scriptName = scriptType.__name;
          var scriptData = this._scriptsIndex[scriptName];
          var scriptInstance = scriptData && scriptData.instance;
          return scriptInstance instanceof scriptType ? scriptInstance : null;
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @function
       * @name pc.ScriptComponent#create
       * @description Create a script instance and attach to an entity script component.
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @param {object} [args] - Object with arguments for a script.
       * @param {boolean} [args.enabled] - If script instance is enabled after creation. Defaults to true.
       * @param {object} [args.attributes] - Object with values for attributes (if any), where key is name of an attribute.
       * @param {boolean} [args.preloading] - If script instance is created during preload. If true, script and attributes must be initialized manually. Defaults to false.
       * @param {number} [args.ind] - The index where to insert the script instance at. Defaults to -1, which means append it at the end.
       * @returns {pc.ScriptType} Returns an instance of a {@link pc.ScriptType} if successfully attached to an entity,
       * or null if it failed because a script with a same name has already been added
       * or if the {@link pc.ScriptType} cannot be found by name in the {@link pc.ScriptRegistry}.
       * @example
       * entity.script.create('playerController', {
       *     attributes: {
       *         speed: 4
       *     }
       * });
       */
      /* eslint-enable jsdoc/no-undefined-types */
      create: function (nameOrType, args) {
          var self = this;
          args = args || { };
  
          var scriptType = nameOrType;
          var scriptName = nameOrType;
  
          // shorthand using script name
          if (typeof scriptType === 'string') {
              scriptType = this.system.app.scripts.get(scriptType);
          } else if (scriptType) {
              scriptName = scriptType.__name;
          }
  
          if (scriptType) {
              if (!this._scriptsIndex[scriptName] || !this._scriptsIndex[scriptName].instance) {
                  // create script instance
                  var scriptInstance = new scriptType({
                      app: this.system.app,
                      entity: this.entity,
                      enabled: args.hasOwnProperty('enabled') ? args.enabled : true,
                      attributes: args.attributes
                  });
  
                  var len = this._scripts.length;
                  var ind = -1;
                  if (typeof args.ind === 'number' && args.ind !== -1 && len > args.ind)
                      ind = args.ind;
  
                  this._insertScriptInstance(scriptInstance, ind, len);
  
                  this._scriptsIndex[scriptName] = {
                      instance: scriptInstance,
                      onSwap: function () {
                          self.swap(scriptName);
                      }
                  };
  
                  this[scriptName] = scriptInstance;
  
                  if (!args.preloading)
                      scriptInstance.__initializeAttributes();
  
                  this.fire('create', scriptName, scriptInstance);
                  this.fire('create:' + scriptName, scriptInstance);
  
                  this.system.app.scripts.on('swap:' + scriptName, this._scriptsIndex[scriptName].onSwap);
  
                  if (!args.preloading) {
  
                      if (scriptInstance.enabled && !scriptInstance._initialized) {
                          scriptInstance._initialized = true;
  
                          if (scriptInstance.initialize)
                              this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.initialize);
                      }
  
                      if (scriptInstance.enabled && !scriptInstance._postInitialized) {
                          scriptInstance._postInitialized = true;
                          if (scriptInstance.postInitialize)
                              this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.postInitialize);
                      }
                  }
  
  
                  return scriptInstance;
              }
  
              console.warn('script \'' + scriptName + '\' is already added to entity \'' + this.entity.name + '\'');
          } else {
              this._scriptsIndex[scriptName] = {
                  awaiting: true,
                  ind: this._scripts.length
              };
  
              console.warn('script \'' + scriptName + '\' is not found, awaiting it to be added to registry');
          }
  
          return null;
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @function
       * @name pc.ScriptComponent#destroy
       * @description Destroy the script instance that is attached to an entity.
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @returns {boolean} If it was successfully destroyed.
       * @example
       * entity.script.destroy('playerController');
       */
      /* eslint-enable jsdoc/no-undefined-types */
      destroy: function (nameOrType) {
          var scriptName = nameOrType;
          var scriptType = nameOrType;
  
          // shorthand using script name
          if (typeof scriptType === 'string') {
              scriptType = this.system.app.scripts.get(scriptType);
          } else if (scriptType) {
              scriptName = scriptType.__name;
          }
  
          var scriptData = this._scriptsIndex[scriptName];
          delete this._scriptsIndex[scriptName];
          if (!scriptData) return false;
  
          var scriptInstance = scriptData.instance;
          if (scriptInstance && !scriptInstance._destroyed) {
              scriptInstance.enabled = false;
              scriptInstance._destroyed = true;
  
              // if we are not currently looping through our scripts
              // then it's safe to remove the script
              if (!this._isLoopingThroughScripts) {
                  var ind = this._removeScriptInstance(scriptInstance);
                  if (ind >= 0) {
                      this._resetExecutionOrder(ind, this._scripts.length);
                  }
              } else {
                  // otherwise push the script in _destroyedScripts and
                  // remove it from _scripts when the loop is over
                  this._destroyedScripts.push(scriptInstance);
              }
          }
  
          // remove swap event
          this.system.app.scripts.off('swap:' + scriptName, scriptData.onSwap);
  
          delete this[scriptName];
  
          this.fire('destroy', scriptName, scriptInstance || null);
          this.fire('destroy:' + scriptName, scriptInstance || null);
  
          if (scriptInstance)
              scriptInstance.fire('destroy');
  
          return true;
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @private
       * @function
       * @name pc.ScriptComponent#swap
       * @description Swap the script instance.
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @returns {boolean} If it was successfully swapped.
       */
      /* eslint-enable jsdoc/no-undefined-types */
      swap: function (nameOrType) {
          var scriptName = nameOrType;
          var scriptType = nameOrType;
  
          // shorthand using script name
          if (typeof scriptType === 'string') {
              scriptType = this.system.app.scripts.get(scriptType);
          } else if (scriptType) {
              scriptName = scriptType.__name;
          }
  
          var old = this._scriptsIndex[scriptName];
          if (!old || !old.instance) return false;
  
          var scriptInstanceOld = old.instance;
          var ind = this._scripts.indexOf(scriptInstanceOld);
  
          var scriptInstance = new scriptType({
              app: this.system.app,
              entity: this.entity,
              enabled: scriptInstanceOld.enabled,
              attributes: scriptInstanceOld.__attributes
          });
  
          if (!scriptInstance.swap)
              return false;
  
          scriptInstance.__initializeAttributes();
  
          // add to component
          this._scripts[ind] = scriptInstance;
          this._scriptsIndex[scriptName].instance = scriptInstance;
          this[scriptName] = scriptInstance;
  
          // set execution order and make sure we update
          // our update and postUpdate lists
          scriptInstance.__executionOrder = ind;
          if (scriptInstanceOld.update) {
              this._updateList.remove(scriptInstanceOld);
          }
          if (scriptInstanceOld.postUpdate) {
              this._postUpdateList.remove(scriptInstanceOld);
          }
  
          if (scriptInstance.update) {
              this._updateList.insert(scriptInstance);
          }
          if (scriptInstance.postUpdate) {
              this._postUpdateList.insert(scriptInstance);
          }
  
          this._scriptMethod(scriptInstance, ScriptComponent.scriptMethods.swap, scriptInstanceOld);
  
          this.fire('swap', scriptName, scriptInstance);
          this.fire('swap:' + scriptName, scriptInstance);
  
          return true;
      },
  
      /**
       * @function
       * @private
       * @name pc.ScriptComponent#resolveDuplicatedEntityReferenceProperties
       * @description When an entity is cloned and it has entity script attributes that point
       * to other entities in the same subtree that is cloned, then we want the new script attributes to point
       * at the cloned entities. This method remaps the script attributes for this entity and it assumes that this
       * entity is the result of the clone operation.
       * @param {pc.ScriptComponent} oldScriptComponent - The source script component that belongs to the entity that was being cloned.
       * @param {object} duplicatedIdsMap - A dictionary with guid-entity values that contains the entities that were cloned.
       */
      resolveDuplicatedEntityReferenceProperties: function (oldScriptComponent, duplicatedIdsMap) {
          var newScriptComponent = this.entity.script;
  
          // for each script in the old component
          for (var scriptName in oldScriptComponent._scriptsIndex) {
              // get the script type from the script registry
              var scriptType = this.system.app.scripts.get(scriptName);
              if (! scriptType) {
                  continue;
              }
  
              // get the script from the component's index
              var script = oldScriptComponent._scriptsIndex[scriptName];
              if (! script || ! script.instance) {
                  continue;
              }
  
              // if __attributesRaw exists then it means that the new entity
              // has not yet initialized its attributes so put the new guid in there,
              // otherwise it means that the attributes have already been initialized
              // so convert the new guid to an entity
              // and put it in the new attributes
              var newAttributesRaw = newScriptComponent[scriptName].__attributesRaw;
              var newAttributes = newScriptComponent[scriptName].__attributes;
              if (! newAttributesRaw && ! newAttributes) {
                  continue;
              }
  
              // get the old script attributes from the instance
              var oldAttributes = script.instance.__attributes;
              for (var attributeName in oldAttributes) {
                  if (! oldAttributes[attributeName]) {
                      continue;
                  }
  
                  // get the attribute definition from the script type
                  var attribute = scriptType.attributes.get(attributeName);
                  if (! attribute || attribute.type !== 'entity') {
                      continue;
                  }
  
                  if (attribute.array) {
                      // handle entity array attribute
                      var oldGuidArray = oldAttributes[attributeName];
                      var len = oldGuidArray.length;
                      if (! len) {
                          continue;
                      }
  
                      var newGuidArray = oldGuidArray.slice();
                      for (var i = 0; i < len; i++) {
                          var guid = newGuidArray[i] instanceof Entity ? newGuidArray[i].getGuid() : newGuidArray[i];
                          if (duplicatedIdsMap[guid]) {
                              // if we are using attributesRaw then use the guid otherwise use the entity
                              newGuidArray[i] = newAttributesRaw ? duplicatedIdsMap[guid].getGuid() : duplicatedIdsMap[guid];
                          }
                      }
  
                      if (newAttributesRaw) {
                          newAttributesRaw[attributeName] = newGuidArray;
                      } else {
                          newAttributes[attributeName] = newGuidArray;
                      }
                  } else {
                      // handle regular entity attribute
                      var oldGuid = oldAttributes[attributeName];
                      if (oldGuid instanceof Entity) {
                          oldGuid = oldGuid.getGuid();
                      } else if (typeof oldGuid !== 'string') {
                          continue;
                      }
  
                      if (duplicatedIdsMap[oldGuid]) {
                          if (newAttributesRaw) {
                              newAttributesRaw[attributeName] = duplicatedIdsMap[oldGuid].getGuid();
                          } else {
                              newAttributes[attributeName] = duplicatedIdsMap[oldGuid];
                          }
                      }
  
                  }
              }
          }
      },
  
      /* eslint-disable jsdoc/no-undefined-types */
      /**
       * @function
       * @name pc.ScriptComponent#move
       * @description Move script instance to different position to alter update order of scripts within entity.
       * @param {string|Class<pc.ScriptType>} nameOrType - The name or type of {@link pc.ScriptType}.
       * @param {number} ind - New position index.
       * @returns {boolean} If it was successfully moved.
       * @example
       * entity.script.move('playerController', 0);
       */
      /* eslint-enable jsdoc/no-undefined-types */
      move: function (nameOrType, ind) {
          var len = this._scripts.length;
          if (ind >= len || ind < 0)
              return false;
  
          var scriptType = nameOrType;
          var scriptName = nameOrType;
  
          if (typeof scriptName !== 'string') {
              scriptName = nameOrType.__name;
          } else {
              scriptType = null;
          }
  
          var scriptData = this._scriptsIndex[scriptName];
          if (!scriptData || !scriptData.instance)
              return false;
  
          // if script type specified, make sure instance of said type
          var scriptInstance = scriptData.instance;
          if (scriptType && !(scriptInstance instanceof scriptType))
              return false;
  
          var indOld = this._scripts.indexOf(scriptInstance);
          if (indOld === -1 || indOld === ind)
              return false;
  
          // move script to another position
          this._scripts.splice(ind, 0, this._scripts.splice(indOld, 1)[0]);
  
          // reset execution order for scripts and re-sort update and postUpdate lists
          this._resetExecutionOrder(0, len);
          this._updateList.sort();
          this._postUpdateList.sort();
  
          this.fire('move', scriptName, scriptInstance, ind, indOld);
          this.fire('move:' + scriptName, scriptInstance, ind, indOld);
  
          return true;
      }
  });
  
  Object.defineProperty(ScriptComponent.prototype, 'enabled', {
      get: function () {
          return this._enabled;
      },
      set: function (value) {
          var oldValue = this._enabled;
          this._enabled = value;
          this.fire('set', 'enabled', oldValue, value);
      }
  });
  
  Object.defineProperty(ScriptComponent.prototype, 'scripts', {
      get: function () {
          return this._scripts;
      },
      set: function (value) {
          this._scriptsData = value;
  
          for (var key in value) {
              if (!value.hasOwnProperty(key))
                  continue;
  
              var script = this._scriptsIndex[key];
              if (script) {
                  // existing script
  
                  // enabled
                  if (typeof value[key].enabled === 'boolean')
                      script.enabled = !!value[key].enabled;
  
                  // attributes
                  if (typeof value[key].attributes === 'object') {
                      for (var attr in value[key].attributes) {
                          if (createScript.reservedAttributes[attr])
                              continue;
  
                          if (!script.__attributes.hasOwnProperty(attr)) {
                              // new attribute
                              var scriptType = this.system.app.scripts.get(key);
                              if (scriptType)
                                  scriptType.attributes.add(attr, { });
                          }
  
                          // update attribute
                          script[attr] = value[key].attributes[attr];
                      }
                  }
              } else {
                  // TODO scripts2
                  // new script
                  console.log(this.order);
              }
          }
      }
  });
  
  export { ScriptComponent };
  