text: 'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.DirectTypeManager = undefined;
  
  var _defineProperty = require('babel-runtime/core-js/object/define-property');
  
  var _defineProperty2 = _interopRequireDefault(_defineProperty);
  
  var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
  
  var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
  
  var _for = require('babel-runtime/core-js/symbol/for');
  
  var _for2 = _interopRequireDefault(_for);
  
  exports.DirectTypeAdd = DirectTypeAdd;
  exports.applyTags = applyTags;
  exports.Getters = Getters;
  exports.Setters = Setters;
  exports.Properties = Properties;
  
  var _GQLBase = require('../GQLBase');
  
  var _types = require('../types');
  
  var _util = require('util');
  
  var _graphql = require('graphql');
  
  var _SyntaxTree = require('../SyntaxTree');
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  /**
   * For each of the decorators, Getters, Setters, and Properties, we take a
   * list of property names used to create the appropriate accessor types. In
   * some cases, however, the instance of GQLBase's data model may have a
   * different name. Finally if the return type for the getter should be wrapped
   * in a another GQLBase class type, we will need a way to specify those things
   * too.
   *
   * The `extractBits()` takes a single argument value from the decorator as it
   * parses them and converts it into an object, properly sorted, into values that
   * allow the above described behavior.
   *
   * Examples:
   *
   * ```
   * // Create a class with a name and age property that map directly to the
   * // underlying data model
   * @Getters('name', 'age')
   * class MyType extends GQLBase {...}
   *
   * // Create a class with a name property that maps to a different property
   * // name in the underlying data model
   * @Getters(['name', '_fake_name'])
   * class MyMockType extends GQLBase {...}
   *
   * // Create a class with an employee property that returns an Employee
   * @Getters(['employee', Employee])
   * class MyRoleType extends GQLBase {...}
   *
   * // Finally create a class with an employe property that returns an Employee
   * // with data under a different name in the underlying data model.
   * @Getters(['employee', '_worker', Employee])
   * class MyMockRoleType extends GQLBase {...}
   * ```
   *
   * @memberof decorators
   * @method ⌾⠀extractBits
   * @since 2.5
   *
   * @param {String|Array<String|Function>} property name of a property, or list
   * of property names and a Class.
   * @return {Object} an object with the following format ```
   * {
   *   fieldName: name of root instance property to create
   *   modelName: name of its associated internal model property
   *   typeClass: an optional class to wrap around the results in a getter
   * }
   * ```
   */
  function extractBits(property) {
    let array = (0, _types.isArray)(property) ? property : [property, property, null];
    let reply;
  
    if (!property) {
      let error = new Error('Invalid property. Given\n  %o', (0, _util.inspect)(property, { depth: 2 }));
  
      return {
        fieldName: 'anErrorOccurred',
        modelName: 'anErrorOccurred',
        typeClass: null,
        getterMaker: function () {
          return () => error;
        },
        setterMaker: function () {
          return v => undefined;
        }
      };
    }
  
    //
    if (array.length === 3) {
      reply = {
        fieldName: array[0],
        modelName: array[1],
        typeClass: typeof array[2] === 'function' && array[2] || null
      };
    }
  
    //
    else if (array.length === 2) {
        reply = {
          fieldName: array[0],
          modelName: typeof array[1] === 'string' ? array[1] : array[0],
          typeClass: typeof array[1] === 'function' && array[1] || null
        };
      }
  
      //
      else {
          reply = {
            fieldName: array[0],
            modelName: array[0],
            typeClass: array[0]
          };
        }
  
    reply.getterMaker = function () {
      let { modelName, fieldName, typeClass } = reply;
  
      return function () {
        const thisClass = this.constructor;
        const model = this[_GQLBase.MODEL_KEY] || null;
        let val;
  
        if (!(0, _types.extendsFrom)(thisClass, _GQLBase.GQLBase)) {
          console.error(`${thisClass.name} is not derived from GQLBase`);
          return undefined;
        }
  
        if (!thisClass.SCHEMA) {
          throw new Error(`
          All GQLBase extended classes should have a defined SCHEMA. Please
          manually define a static get SCHEMA() in your class or use the
          @Schema() decorator to do so.
          `);
        }
  
        if (typeClass) {
          // If the value of the model is already the type of class we expect
          // we do not need to do any processing and we can just grab it and
          // go.
          if (model[modelName] && (0, _types.extendsFrom)(model[modelName], typeClass)) {
            val = model[modelName];
          }
  
          // Otherwise we need to return an instance of the determined typeClass
          // and pass that back instead; as requested.
          else {
              const results = _SyntaxTree.SyntaxTree.findField((0, _graphql.parse)(this.constructor.SCHEMA), this.constructor.name, modelName);
              const { meta } = results || { meta: null };
  
              let args = [model[modelName], this.requestData];
  
              if (meta && !meta.nullable && !model) {
                throw new Error(`
                Using @Getters or @Properties decorators with a null or
                undefined model when the schema states that this field
                cannot be null.
  
                Type      : ${typeClass.name}
                Field (AST data)
                  name    : ${meta.name}
                  type    : ${meta.type}
                  nullable: ${meta.nullable}
                [getter]  : ${fieldName}
                [maps to] : ${modelName}
                [model  ] : ${model}
              `);
              }
  
              // If the following is true, it means that despite allowing nulls
              // for this field in the schema, we do have a valid model and should
              // proceed.
              if (model) {
                if (extractBits.DIRECT_TYPES.includes(typeClass.name)) {
                  val = typeClass(...args);
                } else {
                  val = new typeClass(...args);
                }
  
                if (typeClass.GQL_TYPE === _graphql.GraphQLEnumType) {
                  return val.value;
                }
              }
            }
        } else {
          val = model[modelName];
        }
  
        if (val === 'undefined' || val === undefined) {
          val = null;
        }
  
        return val;
      };
    };
  
    reply.setterMaker = function () {
      let { modelName } = reply;
      return function (value) {
        this[_GQLBase.MODEL_KEY][modelName] = value;
      };
    };
  
    return reply;
  }
  
  /**
   * An array of proper class names that are used to test for cases where the
   * proper usage of instantiating an instance should preclude the use of `new`
   *
   * @memberof decorators
   * @type {Array<String>}
   */
  /** @namespace decorators */
  
  extractBits.DIRECT_TYPES = [String.name];
  
  /**
   * A small suite of functions a getter that allows easy manipulation of the
   * the DIRECT_TYPES workaround needed for some types of complex class
   * wrapping allowed by the @Getters and @Properties decorators. Namely the
   * ability to do something like @Getters('name', String) which would wrap the
   * contents of whatever is in the objects model in a String call.
   *
   * Direct types are those that need to be called without `new` in order for the
   * desired behavior to present itself.
   *
   * @memberof decorators
   * @type {Object}
   * @since 2.7.0
   */
  const DirectTypeManager = exports.DirectTypeManager = {
    /**
     * A getter that retrieves the array of direct types
     *
     * @method DirectTypeManager#types
     * @member {Array<String>} types
     *
     * @return {Array<String>} an array of class name strings.
     */
    get types() {
      return extractBits.DIRECT_TYPES;
    },
  
    /**
     * Appends the supplied class name to the list of registered direct types. If
     * a class or function is passed, rather than a String,
     *
     * @method DirectTypeManager#types
     *
     * @param {Function|string|RegExp} className the name of the class to append.
     * Typically it is best to pass the name property of the class in question
     * such as `RegExp.name` or `MyClass.name`.
     */
    add(className) {
      if (typeof className === 'function') {
        className = className.name;
      }
  
      extractBits.DIRECT_TYPES.push(className);
    },
  
    /**
     * Foricbly empties the contents of the extractBits.DIRECT_TYPES array. This
     * is not recommended as it can have unintended consequences. It is
     * recommended to use `reset` instead
     *
     * @method DirectTypeManager#clear
     *
     * @return {Array<string>} an array of class name Strings that were removed
     * when cleared.
     */
    clear() {
      return extractBits.DIRECT_TYPES.splice(0, extractBits.DIRECT_TYPES.length);
    },
  
    /**
     * The recommended way to reset the DIRECT_TYPES list. This removes all
     * changed values, returns the removed bits, and adds back in the defaults.
     *
     * @method DirectTypeManager#reset
     *
     * @return {Array<string>} an array of class name Strings that were removed
     * during the reset process.
     */
    reset() {
      return extractBits.DIRECT_TYPES.splice(0, extractBits.DIRECT_TYPES.length, String.name);
    }
  };
  
  /**
   * This decorator allows you to add a Class method to the DirectTypeManager
   * as a function that should not be invoked with the `new` keyword. For all
   * intents and purposes the function should be declared `static`.
   *
   * @method DirectTypeAdd
   * @param {Function} target [description]
   * @constructor
   */
  function DirectTypeAdd(target) {
    DirectTypeManager.add(target);
    return target;
  }
  
  /**
   * When applying multiple property getters and setters, knowing some info
   * about what was applied elsewhere can be important. "Tags" can be applied
   * that store the fieldName and descriptor applied via one of these decorators.
   *
   * Multiple "tags" are supported to allow for detecting the difference between
   * decorators applied by the developer using lattice and something auto
   * generated such as auto-props.
   *
   * @param  {GQLBase} Class an instance of GQLBase to apply the tags tp
   * @param  {Array<string|Symbol>} addTags an array of Symbols or strings to be
   * wrapped in Symbols that will be used as tag keys
   * @param  {string} fieldName the name of the field being decorated
   * @param  {Object} descriptor the JavaScript descriptor object to associate
   * with this tagged field.
   */
  function applyTags(Class, addTags, fieldName, descriptor) {
    let tags = (Array.isArray(addTags) && addTags || []).map(tag => typeof tag === 'string' && (0, _for2.default)(tag) || tag).filter(tag => typeof tag === 'symbol');
  
    tags.forEach(tag => {
      Class[_GQLBase.META_KEY][tag] = Class[_GQLBase.META_KEY][tag] || {};
      Class[_GQLBase.META_KEY][tag][fieldName] = descriptor;
    });
  }
  
  /**
   * When working with `GQLBase` instances that expose properties
   * that have a 1:1 mapping to their own model property of the
   * same name, adding the getters manually can be annoying. This
   * takes an indeterminate amount of strings representing the
   * properties for which getters should be injected.
   *
   * @function 🏷⠀Getters
   * @memberof! decorators
   *
   * @param {Array<String|Array<String>>} propertyNames if the model has 'name'
   * and 'age' as properties, then passing those two strings will result
   * in getters that surface those properties as GraphQL fields.
   * @return {Function} a class decorator method.s
   */
  function Getters(...propertyNames) {
    return function (target, addTags = []) {
      for (let property of propertyNames) {
        let { fieldName, getterMaker } = extractBits(property);
        let desc = (0, _getOwnPropertyDescriptor2.default)(target.prototype, fieldName);
        let hasImpl = desc && (desc.get || typeof desc.value === 'function');
        let tags = [_GQLBase.GETTERS].concat(Array.isArray(addTags) && addTags || []);
  
        if (!hasImpl) {
          let descriptor = {
            get: getterMaker()
          };
  
          applyTags(target, tags, fieldName, descriptor);
          (0, _defineProperty2.default)(target.prototype, fieldName, descriptor);
        } else {
          console.warn(`Skipping getter for ${target.name}.${fieldName}; already exists`);
        }
      }
  
      return target;
    };
  }
  
  /**
   * When working with `GQLBase` instances that expose properties
   * that have a 1:1 mapping to their own model property of the
   * same name, adding the setters manually can be annoying. This
   * takes an indeterminate amount of strings representing the
   * properties for which setters should be injected.
   *
   * @function 🏷⠀Setters
   * @memberof! decorators
   * @since 2.1.0
   *
   * @param {Array<String|Array<String>>} propertyNames if the model has
   * 'name' and 'age' as properties, then passing those two strings will
   * result in setters that surface those properties as GraphQL fields.
   * @return {Function} a class decorator method
   */
  function Setters(...propertyNames) {
    return function (target, addTags = []) {
      for (let property of propertyNames) {
        let { fieldName, setterMaker } = extractBits(property);
        let desc = (0, _getOwnPropertyDescriptor2.default)(target.prototype, fieldName);
        let hasImpl = desc && (desc.get || typeof desc.value === 'function');
        let tags = [_GQLBase.SETTERS].concat(Array.isArray(addTags) && addTags || []);
  
        if (!hasImpl) {
          let descriptor = {
            set: setterMaker()
          };
  
          applyTags(target, tags, fieldName, descriptor);
          (0, _defineProperty2.default)(target.prototype, fieldName, descriptor);
        } else {
          console.warn(`Skipping setter for ${target.name}.${fieldName}; already exists`);
        }
      }
  
      return target;
    };
  }
  
  /**
   * When working with `GQLBase` instances that expose properties
   * that have a 1:1 mapping to their own model property of the
   * same name, adding the getters manually can be annoying. This
   * takes an indeterminate amount of strings representing the
   * properties for which getters should be injected.
   *
   * This method creates both getters and setters
   *
   * @function 🏷⠀Properties
   * @memberof! decorators
   * @since 2.1.0
   *
   * @param {Array<String|Array<String>>} propertyNames if the model has 'name'
   * and 'age' as properties, then passing those two strings will result
   * in getters and setters that surface those properties as GraphQL fields.
   * @return {Function} a class decorator method
   */
  function Properties(...propertyNames) {
    return function (target, addTags = []) {
      for (let property of propertyNames) {
        let { fieldName, getterMaker, setterMaker } = extractBits(property);
        let desc = (0, _getOwnPropertyDescriptor2.default)(target.prototype, fieldName);
        let hasImpl = desc && (desc.get || typeof desc.value === 'function');
        let tags = [_GQLBase.PROPS].concat(Array.isArray(addTags) && addTags || []);
  
        if (!hasImpl) {
          let descriptor = {
            set: setterMaker(),
            get: getterMaker()
          };
  
          applyTags(target, tags, fieldName, descriptor);
          (0, _defineProperty2.default)(target.prototype, fieldName, descriptor);
        } else {
          console.warn(`Skipping properties for ${target.name}.${fieldName}; already exists`);
        }
      }
  
      return target;
    };
  }
  
  exports.default = Properties;
  //# sourceMappingURL=ModelProperties.js.map