text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.normalizeAttributeSelector = exports.trimAttributeSelector = exports.traverseAttributeSelector = exports.pickFromAttributeSelector = exports.iterateOverAttributeSelector = exports.removeFromAttributeSelector = exports.intersectAttributeSelectors = exports.mergeAttributeSelectors = exports.attributeSelectorIncludes = exports.attributeSelectorsAreEqual = exports.cloneAttributeSelector = exports.setWithinAttributeSelector = exports.getFromAttributeSelector = exports.createAttributeSelectorFromAttributes = exports.createAttributeSelectorFromNames = void 0;
  const tslib_1 = require("tslib");
  const core_helpers_1 = require("core-helpers");
  const omit_1 = tslib_1.__importDefault(require("lodash/omit"));
  const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep"));
  const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
  const utilities_1 = require("../utilities");
  /**
   * @typedef AttributeSelector
   *
   * An `AttributeSelector` allows you to select some attributes of a component.
   *
   * The simplest `AttributeSelector` is `true`, which means that all the attributes are selected.
  
   * Another possible `AttributeSelector` is `false`, which means that no attributes are selected.
   *
   * To select some specific attributes, you can use a plain object where:
   *
   * * The keys are the name of the attributes you want to select.
   * * The values are a boolean or a nested object to select some attributes of a nested component.
   *
   * **Examples:**
   *
   * ```
   * // Selects all the attributes
   * true
   *
   * // Excludes all the attributes
   * false
   *
   * // Selects `title`
   * {title: true}
   *
   * // Selects also `title` (`summary` is not selected)
   * {title: true, summary: false}
   *
   * // Selects `title` and `summary`
   * {title: true, summary: true}
   *
   * // Selects `title`, `movieDetails.duration`, and `movieDetails.aspectRatio`
   * {
   *   title: true,
   *   movieDetails: {
   *     duration: true,
   *     aspectRatio: true
   *   }
   * }
   * ```
   */
  /**
   * Creates an `AttributeSelector` from the specified names.
   *
   * @param names An array of strings.
   *
   * @returns An `AttributeSelector`.
   *
   * @example
   * ```
   * createAttributeSelectorFromNames(['title', 'summary']);
   * // => {title: true, summary: true}
   * ```
   *
   * @category Functions
   */
  function createAttributeSelectorFromNames(names) {
      const attributeSelector = {};
      for (const name of names) {
          attributeSelector[name] = true;
      }
      return attributeSelector;
  }
  exports.createAttributeSelectorFromNames = createAttributeSelectorFromNames;
  /**
   * Creates an `AttributeSelector` from an attribute iterator.
   *
   * @param attributes An [`Attribute`](https://layrjs.com/docs/v1/reference/attribute) iterator.
   *
   * @returns An `AttributeSelector`.
   *
   * @example
   * ```
   * createAttributeSelectorFromAttributes(Movie.prototype.getAttributes());
   * // => {title: true, summary: true, movieDetails: true}
   * ```
   *
   * @category Functions
   */
  function createAttributeSelectorFromAttributes(attributes) {
      const attributeSelector = {};
      for (const attribute of attributes) {
          attributeSelector[attribute.getName()] = true;
      }
      return attributeSelector;
  }
  exports.createAttributeSelectorFromAttributes = createAttributeSelectorFromAttributes;
  /**
   * Gets an entry of an `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param name The name of the entry to get.
   *
   * @returns An `AttributeSelector`.
   *
   * @example
   * ```
   * getFromAttributeSelector(true, 'title');
   * // => true
   *
   * getFromAttributeSelector(false, 'title');
   * // => false
   *
   * getFromAttributeSelector({title: true}, 'title');
   * // => true
   *
   * getFromAttributeSelector({title: true}, 'summary');
   * // => false
   *
   * getFromAttributeSelector({movieDetails: {duration: true}}, 'movieDetails');
   * // => {duration: true}
   * ```
   *
   * @category Functions
   */
  function getFromAttributeSelector(attributeSelector, name) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      if (typeof attributeSelector === 'boolean') {
          return attributeSelector;
      }
      return normalizeAttributeSelector(attributeSelector[name]);
  }
  exports.getFromAttributeSelector = getFromAttributeSelector;
  /**
   * Returns an `AttributeSelector` where an entry of the specified `AttributeSelector` is set with another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param name The name of the entry to set.
   * @param subattributeSelector Another `AttributeSelector`.
   *
   * @returns A new `AttributeSelector`.
   *
   * @example
   * ```
   * setWithinAttributeSelector({title: true}, 'summary', true);
   * // => {title: true, summary: true}
   *
   * setWithinAttributeSelector({title: true}, 'summary', false);
   * // => {title: true}
   *
   * setWithinAttributeSelector({title: true, summary: true}, 'summary', false);
   * // => {title: true}
   *
   * setWithinAttributeSelector({title: true}, 'movieDetails', {duration: true});
   * // => {title: true, movieDetails: {duration: true}}
   * ```
   *
   * @category Functions
   */
  function setWithinAttributeSelector(attributeSelector, name, subattributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      if (typeof attributeSelector === 'boolean') {
          return attributeSelector;
      }
      subattributeSelector = normalizeAttributeSelector(subattributeSelector);
      if (subattributeSelector === false) {
          return omit_1.default(attributeSelector, name);
      }
      return { ...attributeSelector, [name]: subattributeSelector };
  }
  exports.setWithinAttributeSelector = setWithinAttributeSelector;
  /**
   * Clones an `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   *
   * @returns A new `AttributeSelector`.
   *
   * @example
   * ```
   * cloneAttributeSelector(true);
   * // => true
   *
   * cloneAttributeSelector(false);
   * // => false
   *
   * cloneAttributeSelector({title: true, movieDetails: {duration: true});
   * // => {title: true, movieDetails: {duration: true}
   * ```
   *
   * @category Functions
   */
  function cloneAttributeSelector(attributeSelector) {
      return cloneDeep_1.default(attributeSelector);
  }
  exports.cloneAttributeSelector = cloneAttributeSelector;
  /**
   * Returns whether an `AttributeSelector` is equal to another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param otherAttributeSelector Another `AttributeSelector`.
   *
   * @returns A boolean.
   *
   * @example
   * ```
   * attributeSelectorsAreEqual({title: true}, {title: true});
   * // => true
   *
   * attributeSelectorsAreEqual({title: true, summary: false}, {title: true});
   * // => true
   *
   * attributeSelectorsAreEqual({title: true}, {summary: true});
   * // => false
   * ```
   *
   * @category Functions
   */
  function attributeSelectorsAreEqual(attributeSelector, otherAttributeSelector) {
      return (attributeSelector === otherAttributeSelector ||
          (attributeSelectorIncludes(attributeSelector, otherAttributeSelector) &&
              attributeSelectorIncludes(otherAttributeSelector, attributeSelector)));
  }
  exports.attributeSelectorsAreEqual = attributeSelectorsAreEqual;
  /**
   * Returns whether an `AttributeSelector` includes another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param otherAttributeSelector Another `AttributeSelector`.
   *
   * @returns A boolean.
   *
   * @example
   * ```
   * attributeSelectorIncludes({title: true}, {title: true});
   * // => true
   *
   * attributeSelectorIncludes({title: true, summary: true}, {title: true});
   * // => true
   *
   * attributeSelectorIncludes({title: true}, {summary: true});
   * // => false
   * ```
   *
   * @category Functions
   */
  function attributeSelectorIncludes(attributeSelector, otherAttributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      otherAttributeSelector = normalizeAttributeSelector(otherAttributeSelector);
      if (attributeSelector === otherAttributeSelector) {
          return true;
      }
      if (typeof attributeSelector === 'boolean') {
          return attributeSelector;
      }
      if (typeof otherAttributeSelector === 'boolean') {
          return !otherAttributeSelector;
      }
      for (const [name, otherSubattributeSelector] of Object.entries(otherAttributeSelector)) {
          const subattributeSelector = attributeSelector[name];
          if (!attributeSelectorIncludes(subattributeSelector, otherSubattributeSelector)) {
              return false;
          }
      }
      return true;
  }
  exports.attributeSelectorIncludes = attributeSelectorIncludes;
  /**
   * Returns an `AttributeSelector` which is the result of merging an `AttributeSelector` with another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param otherAttributeSelector Another `AttributeSelector`.
   *
   * @returns A new `AttributeSelector`.
   *
   * @example
   * ```
   * mergeAttributeSelectors({title: true}, {title: true});
   * // => {title: true}
   *
   * mergeAttributeSelectors({title: true}, {summary: true});
   * // => {title: true, summary: true}
   *
   * mergeAttributeSelectors({title: true, summary: true}, {summary: false});
   * // => {title: true}
   * ```
   *
   * @category Functions
   */
  function mergeAttributeSelectors(attributeSelector, otherAttributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      otherAttributeSelector = normalizeAttributeSelector(otherAttributeSelector);
      if (attributeSelector === true) {
          return true;
      }
      if (attributeSelector === false) {
          return otherAttributeSelector;
      }
      if (otherAttributeSelector === true) {
          return true;
      }
      if (otherAttributeSelector === false) {
          return attributeSelector;
      }
      for (const [name, otherSubattributeSelector] of Object.entries(otherAttributeSelector)) {
          const subattributeSelector = attributeSelector[name];
          attributeSelector = setWithinAttributeSelector(attributeSelector, name, mergeAttributeSelectors(subattributeSelector, otherSubattributeSelector));
      }
      return attributeSelector;
  }
  exports.mergeAttributeSelectors = mergeAttributeSelectors;
  /**
   * Returns an `AttributeSelector` which is the result of the intersection of an `AttributeSelector` with another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param otherAttributeSelector Another `AttributeSelector`.
   *
   * @returns A new `AttributeSelector`.
   *
   * @example
   * ```
   * intersectAttributeSelectors({title: true, summary: true}, {title: true});
   * // => {title: true}
   *
   * intersectAttributeSelectors({title: true}, {summary: true});
   * // => {}
   * ```
   *
   * @category Functions
   */
  function intersectAttributeSelectors(attributeSelector, otherAttributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      otherAttributeSelector = normalizeAttributeSelector(otherAttributeSelector);
      if (attributeSelector === false || otherAttributeSelector === false) {
          return false;
      }
      if (attributeSelector === true) {
          return otherAttributeSelector;
      }
      if (otherAttributeSelector === true) {
          return attributeSelector;
      }
      let intersectedAttributeSelector = {};
      for (const [name, otherSubattributeSelector] of Object.entries(otherAttributeSelector)) {
          const subattributeSelector = attributeSelector[name];
          intersectedAttributeSelector = setWithinAttributeSelector(intersectedAttributeSelector, name, intersectAttributeSelectors(subattributeSelector, otherSubattributeSelector));
      }
      return intersectedAttributeSelector;
  }
  exports.intersectAttributeSelectors = intersectAttributeSelectors;
  /**
   * Returns an `AttributeSelector` which is the result of removing an `AttributeSelector` from another `AttributeSelector`.
   *
   * @param attributeSelector An `AttributeSelector`.
   * @param otherAttributeSelector Another `AttributeSelector`.
   *
   * @returns A new `AttributeSelector`.
   *
   * @example
   * ```
   * removeFromAttributeSelector({title: true, summary: true}, {summary: true});
   * // => {title: true}
   *
   * removeFromAttributeSelector({title: true}, {title: true});
   * // => {}
   * ```
   *
   * @category Functions
   */
  function removeFromAttributeSelector(attributeSelector, otherAttributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      otherAttributeSelector = normalizeAttributeSelector(otherAttributeSelector);
      if (otherAttributeSelector === true) {
          return false;
      }
      if (otherAttributeSelector === false) {
          return attributeSelector;
      }
      if (attributeSelector === true) {
          throw new Error("Cannot remove an 'object' attribute selector from a 'true' attribute selector");
      }
      if (attributeSelector === false) {
          return false;
      }
      for (const [name, otherSubattributeSelector] of Object.entries(otherAttributeSelector)) {
          const subattributeSelector = attributeSelector[name];
          attributeSelector = setWithinAttributeSelector(attributeSelector, name, removeFromAttributeSelector(subattributeSelector, otherSubattributeSelector));
      }
      return attributeSelector;
  }
  exports.removeFromAttributeSelector = removeFromAttributeSelector;
  function iterateOverAttributeSelector(attributeSelector) {
      return {
          *[Symbol.iterator]() {
              for (const [name, subattributeSelector] of Object.entries(attributeSelector)) {
                  const normalizedSubattributeSelector = normalizeAttributeSelector(subattributeSelector);
                  if (normalizedSubattributeSelector !== false) {
                      yield [name, normalizedSubattributeSelector];
                  }
              }
          }
      };
  }
  exports.iterateOverAttributeSelector = iterateOverAttributeSelector;
  function pickFromAttributeSelector(value, attributeSelector, options = {}) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      if (attributeSelector === false) {
          throw new Error(`Cannot pick attributes from a value when the specified attribute selector is 'false'`);
      }
      const { includeAttributeNames = [] } = options;
      return _pick(value, attributeSelector, { includeAttributeNames });
  }
  exports.pickFromAttributeSelector = pickFromAttributeSelector;
  function _pick(value, attributeSelector, { includeAttributeNames }) {
      if (attributeSelector === true) {
          return value;
      }
      if (value === undefined) {
          return undefined;
      }
      if (Array.isArray(value)) {
          const array = value;
          return array.map((value) => _pick(value, attributeSelector, { includeAttributeNames }));
      }
      const isComponent = utilities_1.isComponentClassOrInstance(value);
      if (!(isComponent || core_helpers_1.isPlainObject(value))) {
          throw new Error(`Cannot pick attributes from a value that is not a component, a plain object, or an array (value type: '${core_helpers_1.getTypeOf(value)}')`);
      }
      const componentOrObject = value;
      const result = {};
      if (!isComponent) {
          for (const name of includeAttributeNames) {
              if (core_helpers_1.hasOwnProperty(componentOrObject, name)) {
                  result[name] = componentOrObject[name];
              }
          }
      }
      for (const [name, subattributeSelector] of iterateOverAttributeSelector(attributeSelector)) {
          const value = isComponent
              ? componentOrObject.getAttribute(name).getValue()
              : componentOrObject[name];
          result[name] = _pick(value, subattributeSelector, { includeAttributeNames });
      }
      return result;
  }
  function traverseAttributeSelector(value, attributeSelector, iteratee, options = {}) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      core_helpers_1.assertIsFunction(iteratee);
      const { includeSubtrees = false, includeLeafs = true } = options;
      if (attributeSelector === false) {
          return;
      }
      _traverse(value, attributeSelector, iteratee, {
          includeSubtrees,
          includeLeafs,
          _context: {},
          _isDeep: false
      });
  }
  exports.traverseAttributeSelector = traverseAttributeSelector;
  function _traverse(value, attributeSelector, iteratee, { includeSubtrees, includeLeafs, _context, _isDeep }) {
      if (attributeSelector === true || value === undefined) {
          if (includeLeafs) {
              iteratee(value, attributeSelector, _context);
          }
          return;
      }
      if (Array.isArray(value)) {
          const array = value;
          for (const value of array) {
              _traverse(value, attributeSelector, iteratee, {
                  includeSubtrees,
                  includeLeafs,
                  _context: { ..._context, isArray: true },
                  _isDeep
              });
          }
          return;
      }
      const isComponent = utilities_1.isComponentClassOrInstance(value);
      if (!(isComponent || core_helpers_1.isPlainObject(value))) {
          throw new Error(`Cannot traverse attributes from a value that is not a component, a plain object, or an array (value type: '${core_helpers_1.getTypeOf(value)}')`);
      }
      const componentOrObject = value;
      if (_isDeep && includeSubtrees) {
          iteratee(componentOrObject, attributeSelector, _context);
      }
      for (const [name, subattributeSelector] of iterateOverAttributeSelector(attributeSelector)) {
          if (isComponent && !componentOrObject.getAttribute(name).isSet()) {
              continue;
          }
          const value = componentOrObject[name];
          _traverse(value, subattributeSelector, iteratee, {
              includeSubtrees,
              includeLeafs,
              _context: { name, object: componentOrObject },
              _isDeep: true
          });
      }
  }
  function trimAttributeSelector(attributeSelector) {
      attributeSelector = normalizeAttributeSelector(attributeSelector);
      if (typeof attributeSelector === 'boolean') {
          return attributeSelector;
      }
      for (const [name, subattributeSelector] of Object.entries(attributeSelector)) {
          attributeSelector = setWithinAttributeSelector(attributeSelector, name, trimAttributeSelector(subattributeSelector));
      }
      if (isEmpty_1.default(attributeSelector)) {
          return false;
      }
      return attributeSelector;
  }
  exports.trimAttributeSelector = trimAttributeSelector;
  function normalizeAttributeSelector(attributeSelector) {
      if (attributeSelector === undefined) {
          return false;
      }
      if (typeof attributeSelector === 'boolean') {
          return attributeSelector;
      }
      if (core_helpers_1.isPlainObject(attributeSelector)) {
          return attributeSelector;
      }
      throw new Error(`Expected a valid attribute selector, but received a value of type '${core_helpers_1.getTypeOf(attributeSelector)}'`);
  }
  exports.normalizeAttributeSelector = normalizeAttributeSelector;
  //# sourceMappingURL=attribute-selector.js.map