“use strict”; // heycam.github.io/webidl/#idl-named-properties

const IS_NAMED_PROPERTY = Symbol(); const TRACKER = Symbol();

/**

* Create a new NamedPropertiesTracker for the given `object`.
*
* Named properties are used in DOM to let you lookup (for example) a Node by accessing a property on another object.
* For example `window.foo` might resolve to an image element with id "foo".
*
* This tracker is a workaround because the ES6 Proxy feature is not yet available.
*
* @param {Object} object
* @param {Function} resolverFunc Each time a property is accessed, this function is called to determine the value of
*        the property. The function is passed 3 arguments: (object, name, values).
*        `object` is identical to the `object` parameter of this `create` function.
*        `name` is the name of the property.
*        `values` is a function that returns a Set with all the tracked values for this name. The order of these
*        values is undefined.
*
* @returns {NamedPropertiesTracker}
*/

exports.create = function (object, resolverFunc) {

if (object[TRACKER]) {
  throw Error("A NamedPropertiesTracker has already been created for this object");
}

const tracker = new NamedPropertiesTracker(object, resolverFunc);
object[TRACKER] = tracker;
return tracker;

};

exports.get = function (object) {

if (!object) {
  return null;
}

return object[TRACKER] || null;

};

function NamedPropertiesTracker(object, resolverFunc) {

this.object = object;
this.resolverFunc = resolverFunc;
this.trackedValues = new Map(); // Map<Set<value>>

}

function newPropertyDescriptor(tracker, name) {

const emptySet = new Set();

function getValues() {
  return tracker.trackedValues.get(name) || emptySet;
}

const descriptor = {
  enumerable: true,
  configurable: true,
  get() {
    return tracker.resolverFunc(tracker.object, name, getValues);
  },
  set(value) {
    Object.defineProperty(tracker.object, name, {
      enumerable: true,
      configurable: true,
      writable: true,
      value
    });
  }
};

descriptor.get[IS_NAMED_PROPERTY] = true;
descriptor.set[IS_NAMED_PROPERTY] = true;
return descriptor;

}

/**

* Track a value (e.g. a Node) for a specified name.
*
* Values can be tracked eagerly, which means that not all tracked values *have* to appear in the output. The resolver
* function that was passed to the output may filter the value.
*
* Tracking the same `name` and `value` pair multiple times has no effect
*
* @param {String} name
* @param {*} value
*/

NamedPropertiesTracker.prototype.track = function (name, value) {

if (name === undefined || name === null || name === "") {
  return;
}

let valueSet = this.trackedValues.get(name);
if (!valueSet) {
  valueSet = new Set();
  this.trackedValues.set(name, valueSet);
}

valueSet.add(value);

if (name in this.object) {
  // already added our getter or it is not a named property (e.g. "addEventListener")
  return;
}

const descriptor = newPropertyDescriptor(this, name);
Object.defineProperty(this.object, name, descriptor);

};

/**

* Stop tracking a previously tracked `name` & `value` pair, see track().
*
* Untracking the same `name` and `value` pair multiple times has no effect
*
* @param {String} name
* @param {*} value
*/

NamedPropertiesTracker.prototype.untrack = function (name, value) {

if (name === undefined || name === null || name === "") {
  return;
}

const valueSet = this.trackedValues.get(name);
if (!valueSet) {
  // the value is not present
  return;
}

if (!valueSet.delete(value)) {
  // the value was not present
  return;
}

if (valueSet.size === 0) {
  this.trackedValues.delete(name);
}

if (valueSet.size > 0) {
  // other values for this name are still present
  return;
}

// at this point there are no more values, delete the property

const descriptor = Object.getOwnPropertyDescriptor(this.object, name);

if (!descriptor || !descriptor.get || descriptor.get[IS_NAMED_PROPERTY] !== true) {
  // Not defined by NamedPropertyTracker
  return;
}

// note: delete puts the object in dictionary mode.
// if this turns out to be a performance issue, maybe add:
// https://github.com/petkaantonov/bluebird/blob/3e36fc861ac5795193ba37935333eb6ef3716390/src/util.js#L177
delete this.object[name];

};