text: 'use strict';
  
  const { setProp } = require('../base/model');
  const { csnRefs } = require('../model/csnRefs');
  const { sortCsn } = require('../json/to-csn');
  
  // Low-level utility functions to work with compact CSN.
  
  /**
   * Generic Callback
   *
   * @callback genericCallback
   * @param {CSN.Artifact} art
   * @param {CSN.FQN} name Artifact Name
   * @param {string} prop Dictionary Property
   * @param {CSN.Path} path Location
   * @param {CSN.Artifact} [dictionary]
   */
  
  /**
   * @callback refCallback
   * @param {any} ref
   * @param {object} node
   * @param {CSN.Path} path
   */
  
  /**
   * @callback queryCallback
   * @param {CSN.Query} query
   * @param {CSN.Path} path
   */
  
  /**
   * Get utility functions for a given CSN.
   * @param {CSN.Model} model (Compact) CSN model
   */
  function getUtils(model) {
    const { artifactRef, inspectRef, effectiveType } = csnRefs(model);
  
    return {
      getCsnDef,
      isStructured,
      getFinalType,
      getFinalTypeDef,
      isManagedAssociationElement,
      isAssocOrComposition,
      isAssociation,
      isComposition,
      getArtifactDatabaseNameOf,
      getNamespaceOfArtifact,
      getContextOfArtifact,
      addStringAnnotationTo,
      getServiceName,
      hasBoolAnnotation,
      cloneWithTransformations,
      getFinalBaseType,
      inspectRef,
      artifactRef,
      effectiveType,
    };
  
    /**
     * Create an object to track visited objects identified by a unique string.
     * @param {string} [id] Initial entry (optional)
     */
    function createVisited(id) {
      let visited = {};
      check(id);
      return { check };
  
      /**
       * Check if an identifier has already been visited and
       * add it to the list of visited identifiers.
       * @param {string} id unique identifier
       */
      function check(id) {
        if (!id) return;
        if (visited[id]) {
          throw new Error('Circular dependency');
        }
        visited[id] = true;
      }
    }
  
    /**
     * Get the CSN definition for an artifact name.
     * @param {string} defName Absolute name of the artifact
     */
    function getCsnDef(defName) {
      if (model.definitions[defName])
        return model.definitions[defName]
      else
        throw new Error(`Nonexistent definition in the model: '${defName}'`);
    }
  
    /**
     * Returns true if an artifact is a structured type
     * or a typedef of a structured type.
     *
     * @param {CSN.Artifact} obj
     */
    function isStructured(obj) {
      return obj.elements ||
        (obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseType(obj.type).elements)));
    }
  
    /**
     * Resolves typedefs to its final typedef which is returned.
     * If the artifact for typename isn't a typedef, the name itself is returned.
     *
     * @param {string} typeName Absolute type name
     * @returns {object}
     */
    function getFinalTypeDef(typeName) {
      let visited = createVisited(typeName);
      let type = model.definitions[typeName];
      if (!type) {
        return typeName;
      }
      for (let nextType = type; nextType;) {
        type = nextType;
        visited.check(type.type);
        nextType = model.definitions[nextType.type];
      }
      return type;
    }
  
    /**
     * Resolves typedefs to its final type (name) which is returned.
     * @param {string} typeName Absolute type name
     * @returns {string}
     */
    function getFinalType(typeName) {
      let visited = createVisited(typeName);
      let type = model.definitions[typeName];
      while (type && type.type) {
        typeName = type.type;
        visited.check(typeName);
        type = model.definitions[typeName];
      }
      return typeName;
    }
  
    // Return true if 'node' is a managed association element
    // TODO: what about elements having a type, which (finally) is an assoc?
    function isManagedAssociationElement(node) {
      return node.target !== undefined && node.on === undefined && node.keys;
    }
  
    /**
     * Returns if a type is an association or a composition or a typedef
     * to any of them.
     * @param {string} typeName Absolute type name
     */
    function isAssocOrComposition(typeName) {
      if (typeName === 'cds.Association' || typeName === 'cds.Composition')
        return true;
      let visited = createVisited(typeName);
      let type = model.definitions[typeName];
      while (type) {
        if (type.type === 'cds.Association' || type.type === 'cds.Composition')
          return true;
        visited.check(type.type);
        type = model.definitions[type.type];
      }
      return false;
    }
  
    /**
     * Returns if a type is an association or a typedef to it.
     * @param {string} typeName Absolute type name
     */
    function isAssociation(typeName) {
      if (typeName === 'cds.Association')
        return true;
      let visited = createVisited(typeName);
      let type = model.definitions[typeName];
      while (type) {
        if (type.type === 'cds.Association')
          return true;
        visited.check(type.type);
        type = model.definitions[type.type];
      }
      return false;
    }
  
    /**
     * Returns if a type is an composition or a typedef to it.
     * @param {string} typeName Absolute type name
     */
    function isComposition(typeName) {
      if (typeName === 'cds.Composition')
        return true;
      let visited = createVisited(typeName);
      let type = model.definitions[typeName];
      while (type) {
        if (type.type === 'cds.Composition')
          return true;
        visited.check(type.type);
        type = model.definitions[type.type];
      }
      return false;
    }
  
    /**
     * Return the namespace part of the artifact name.
     * @param {string} name Absolute name of artifact
     */
    function getNamespaceOfArtifact(name) {
      let lastDotIdx = name.lastIndexOf('.');
      if (lastDotIdx === -1) return undefined;
      while (model.definitions[name]) {
        if (model.definitions[name].kind === 'namespace')
          return name;
        lastDotIdx = name.lastIndexOf('.');
        if (lastDotIdx === -1) return undefined;
        name = name.substring(0, lastDotIdx);
      }
      return name;
    }
  
    /**
     * Return the context part of the artifact name if any.
     * @param {string} name Absolute name of artifact
     */
    function getContextOfArtifact(name) {
      let lastDotIdx = name.lastIndexOf('.');
      while (model.definitions[name]) {
        if (model.definitions[name].kind === 'context')
          return name;
        lastDotIdx = name.lastIndexOf('.');
        if (lastDotIdx === -1) return undefined;
        name = name.substring(0, lastDotIdx);
      }
      return undefined;
    }
  
    /**
     * Add an annotation with absolute name 'absoluteName' (including the at-sign) and string value 'theValue' to 'node'
     *
     * @param {string} absoluteName Name of the annotation, including the at-sign
     * @param {any} theValue string value of the annotation
     * @param {any} node Node to add the annotation to
     */
    function addStringAnnotationTo(absoluteName, theValue, node) {
      // Sanity check
      if (!absoluteName.startsWith('@')) {
        throw Error('Annotation name should start with "@": ' + absoluteName);
      }
      // Only overwrite if undefined or null
      if(node[absoluteName] === undefined || node[absoluteName] === null) {
        // Assemble the annotation
        node[absoluteName] = theValue;
      }
    }
  
    /**
     * Return the name of the service in which the artifact is contained.
     * Returns null if the artifact doesn't live in a service.
     *
     * @param {string} artifactName Absolute name of artifact
     * @returns {string|null}
     */
    function getServiceName(artifactName) {
      for(;;) {
        let idx = artifactName.lastIndexOf('.');
        if (idx == -1) return null;
        artifactName = artifactName.substring(0, idx);
        let artifact = model.definitions[artifactName];
        if (artifact && artifact.kind === 'service') {
          return artifactName;
        }
      }
    }
  
    /**
     * Clone 'node', transforming nodes therein recursively. Object 'transformers' is expected
     * to contain a mapping of property 'key' names to transformer functions. The node's properties
     * are walked recursively, calling each transformer function on its corresponding property
     * 'key' of 'node', replacing 'value' in 'resultNode' with the function's return value
     * (returning 'undefined' will delete the property).
     * If no transformation function is found for 'key', the first letter of 'key' is tried
     * instead (this seems to be intended for handling annotations that start with '@' ?)
     *
     * Regardless of their names, transformers are never applied to dictionary elements.
     *
     * The transformer functions are called with the following signature:
     * transformer(value, node, resultNode, key)
     *
     * @param {any} node Node to transform
     * @param {any} transformers Object defining transformer functions
     * @returns {object}
     */
    function cloneWithTransformations(node, transformers) {
  
      return transformNode(node);
  
    // This general transformation function will be applied to each node recursively
      function transformNode(node) {
      // Return primitive values and null unchanged, but let objects and dictionaries through
      // (Note that 'node instanceof Object' would be false for dictionaries).
        if (node === null || typeof node !== 'object') {
          return node
        }
      // Simply return if node is to be ignored
        if (node == undefined || node._ignore)
          return undefined;
      // Transform arrays element-wise
        if (node instanceof Array) {
          return node.map(transformNode);
        }
      // Things not having 'proto' are dictionaries
        let proto = Object.getPrototypeOf(node);
      // Iterate own properties of 'node' and transform them into 'resultNode'
        let resultNode = Object.create(proto);
        for (let key of Object.keys(node)) {
        // Dictionary always use transformNode(), other objects their transformer according to key
          let transformer = (proto == undefined) ? transformNode : transformers[key] || transformers[key.charAt(0)];
        // Apply transformer, or use transformNode() if there is none
          let resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
          if (resultValue !== undefined) {
            resultNode[key] = resultValue;
          }
        }
        return resultNode;
      }
    }
  
    // Resolve to the final type of a type, that means follow type chains, references to other types or
    // elements a.s.o
    // Works for all kinds of types, strings as well as type objects. Strings need to be absolute type names.
    // Returns the final type as string (if it has a name, which is not always the case, think of embedded structures),
    // else the type object itself is returned. If a type is structured, you can navigate into it by providing a path,
    // e.g. given the following model
    //     type bar: S.foo;
    //     type s1 {
    //       s: s2;
    //     };
    //     type s2 {
    //       u: type of S.e:t;
    //     }
    //     service S {
    //       type foo: type of S.e:i.j1;
    //       entity e {
    //         key i: { j1: Integer };
    //         t: bar;
    //         v: s1;
    //         x: blutz.s.u;
    //       };
    //       type blutz: S.e.v;
    //       view V as select from e {
    //         1+1 as i: bar,
    //       };
    //       type tt: type of V:i;
    //    }
    // the following calls will all return 'cds.Integer'
    //     getFinalBaseType('S.tt')
    //     getFinalBaseType('S.e',['i','j1'])
    //     getFinalBaseType('S.e',['t'])
    //     getFinalBaseType('S.e',['x'])
    //     getFinalBaseType('S.blutz',['s', 'u'])
    // Types are always resolved as far as possible. A type name which has no further definition is simply returned.
    // Composed types (structures, entities, vies, ...) are returned as type objects, if not drilled down into
    // the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
    // no type (e.g. expr in a view without explicit type) returns 'null'
    function getFinalBaseType(type, path = [], cycleCheck = undefined) {
      if (!type)
        return type;
      if (typeof(type) === 'string') {
        if (type.startsWith('cds.') && type.indexOf('.') === type.lastIndexOf('.')) // built-in type
          return type;
        if (cycleCheck) {
          let visited = path.length? type + ':' + path.join('.') : type;
          if (cycleCheck[visited])
            throw new Error('Circular type chain on type ' + type);
          else
            cycleCheck[visited] = true;
        }
        else {
          cycleCheck = Object.create(null);
        }
        let definedType = model.definitions[type];
        if (definedType && definedType.type)
          return getFinalBaseType(definedType.type, path, cycleCheck);
        else
          return getFinalBaseType(definedType, path, cycleCheck);
      }
      else if (typeof(type) === 'object') {
        if (type.ref) {
          // assert type.ref instanceof Array && type.ref.length >= 1
          const ref = artifactRef(type);
          return getFinalBaseType(ref, path, cycleCheck);
        }
        else if (type.elements) {
          if (path.length) {
            let [e, ...p] = path;
            return getFinalBaseType(type.elements[e], p, cycleCheck);
          }
        }
        else if (type.type)
          return (getFinalBaseType(type.type, path, cycleCheck));
        else if (type.items)
          return getFinalBaseType(type.items, path, cycleCheck);
        else
          // TODO: this happens if we don't have a type, e.g. an expression in a select list
          // in a view without explicit type. Instead of returning null we might want to return
          // the object instead?
          return null;
      }
      return type;
    }
  }
  
  // Tell if a type is (directly) a builtin type
  // Note that in CSN builtins are not in the definition of the model, so we can only check against their absolute names.
  // Builtin types are "cds.<something>", i.e. they are directly in 'cds', but not for example
  // in 'cds.foundation'. Also note, that a type might be a ref object, that refers to something else,
  // so if you consider type chains don't forget first to resolve to the final type before
  function isBuiltinType(type) {
    return typeof(type) === 'string' && type.startsWith('cds.') && !type.startsWith('cds.foundation.')
  }
  
  
  /**
   * Deeply clone the given CSN model and return it.
   * @param {object} csn
   */
  function cloneCsn(csn){
    // cannot require this earlier because to-csn has a dependency on this file
    const clonedCsn = sortCsn(csn, true);
    if (csn.messages){
      setProp(clonedCsn, 'messages', csn.messages);
    }
  
    return clonedCsn;
  }
  
  /**
   * Apply function `callback` to all artifacts in dictionary
   * `model.definitions`.  See function `forEachGeneric` for details.
   * Callback will be called with artifact, artifact name, property
   * name ('definitions') and csn-path to artifact.
   *
   * @param {CSN.Model} csn
   * @param {(genericCallback|genericCallback[])} callback
   */
  function forEachDefinition( csn, callback ) {
    forEachGeneric( csn, 'definitions', callback );
  }
  
  /**
   * Apply function `callback` to all members of object `obj` (main artifact or
   * parent member).  Members are considered those in dictionaries `elements`,
   * `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
   * searched inside property `items` (array of).  See function `forEachGeneric`
   * for details.
   *
   * @param {CSN.Artifact} construct
   * @param {genericCallback|genericCallback[]} callback
   * @param {CSN.Path} [path]
   * @param {boolean} [ignoreIgnore]
   */
  function forEachMember( construct, callback, path=[], ignoreIgnore=true) {
    let obj = construct.returns || construct; // why the extra `returns` for actions?
    const obj_path = Array.from(path); // Clone the path
  
    // Allow processing _ignored elements if requested
    if(ignoreIgnore && ( construct._ignore || obj._ignore )){
      return;
    }
  
    if(construct.returns){
      obj_path.push("returns");
    }
  
    if(obj.items){
      obj_path.push("items");
    }
  
    obj = obj.items || obj;
    forEachGeneric( obj, 'elements', callback, obj_path );
    forEachGeneric( obj, 'enum', callback, obj_path );
    forEachGeneric( obj, 'foreignKeys', callback, obj_path );
    forEachGeneric( construct, 'actions', callback, path );
    forEachGeneric( construct, 'params', callback, path );
  }
  
  /**
   * Apply function `callback(member, memberName)` to each member in `construct`,
   * recursively (i.e. also for sub-elements of elements).
   *
   * @param {CSN.Artifact} construct
   * @param {genericCallback|genericCallback[]} callback
   * @param {CSN.Path} [path]
   * @param {boolean} [ignoreIgnore]
   */
  function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true) {
    forEachMember( construct, ( member, memberName, prop, subpath ) => {
      if(Array.isArray(callback))
        callback.forEach(cb => cb( member, memberName, prop, subpath, construct ));
      else
        callback( member, memberName, prop, subpath, construct );
      // Descend into nested members, too
      forEachMemberRecursively( member, callback, subpath, ignoreIgnore);
    }, path, ignoreIgnore);
  }
  
  /**
   * Apply function `callback` to all objects in dictionary `dict`, including all
   * duplicates (found under the same name).  Function `callback` is called with
   * the following arguments: the object, the name, and -if it is a duplicate-
   * the array index and the array containing all duplicates.
   *
   * @param {object} obj
   * @param {string} prop
   * @param {genericCallback|genericCallback[]} callback
   * @param {CSN.Path} path
   */
  function forEachGeneric( obj, prop, callback, path = []) {
    let dict = obj[prop];
    for (let name in dict) {
      let dictObj = dict[name];
      if (dictObj instanceof Array) // redefinitions - not in CSN!
        dictObj.forEach( o => cb( o, name ) );
      else
        cb( dictObj, name );
    }
    function cb(o, name ) {
      if(callback instanceof Array)
        callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));
      else
        callback( o, name, prop, path.concat([prop, name]))
    }
  }
  
  /**
   * For each property named 'ref' in 'node' (recursively), call callback(ref, node, path)
   *
   * @param {object} node
   * @param {refCallback|refCallback[]} callback
   * @param {CSN.Path} path
   */
  function forEachRef(node, callback, path = []) {
    if (node === null || typeof node !== 'object') {
      // Primitive node
      return;
    }
  
    if(node._ignore){
      return;
    }
  
    if(Array.isArray(node)){
      for (let i = 0; i < node.length; i++) {
        // Descend recursively
        forEachRef(node[i], callback, path.concat([i]));
      }
    } else {
      for (let name in node) {
        // If ref found within a non-dictionary, call callback
        if (name === 'ref' && Object.getPrototypeOf(node)) {
          if(callback instanceof Array)
            callback.forEach(cb => cb( node.ref, node, path ));
          else
            callback( node.ref, node, path );
        }
        // Descend recursively
        forEachRef(node[name], callback, path.concat([name]));
      }
    }
  }
  
  // Like Object.assign() but copies also non enumerable properties
  function assignAll(target, ...sources) {
    sources.forEach(source => {
      let descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
        descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
        return descriptors;
      }, {});
      // by default, Object.assign copies enumerable Symbols too
      Object.getOwnPropertySymbols(source).forEach(sym => {
        let descriptor = Object.getOwnPropertyDescriptor(source, sym);
        if (descriptor.enumerable) {
          descriptors[sym] = descriptor;
        }
      });
      Object.defineProperties(target, descriptors);
    });
    return target;
  }
  
  /**
   * @param {CSN.Query} query
   * @param {queryCallback|queryCallback[]} callback
   * @param {CSN.Path} path
   */
  function forAllQueries(query, callback, path = []){
    return traverseQuery(query, callback, path);
    function traverseQuery( q, callback, p ) {
      if (q.SELECT) {
        p.push('SELECT');
        cb( q, p );
        q = q.SELECT;
        traverseFrom( q.from, callback, p.concat(['from']) );
      }
      else if (q.SET) {
        p.push('SET');
        cb( q, p );
        q = q.SET;
      }
      for (const prop of ['args', 'xpr', 'columns', 'where', 'having']) {
        // all properties which could have sub queries (directly or indirectly)
        const expr = q[prop];
        if (expr && typeof expr === 'object') {
          if(Array.isArray(expr)){
            for(let i = 0; i < expr.length; i++){
              traverseQuery(expr[i], callback, p.concat([prop, i]));
            }
          } else {
            for(const argName of Object.keys( expr )){
              traverseQuery(expr[argName], callback, p.concat([prop, argName]))
            }
          }
        }
      }
      function cb(q, p) {
        if(callback instanceof Array)
          callback.forEach(cb => cb( q, p ));
        else
          callback( q, p );
      }
    }
  
    /**
     * @param {CSN.QueryFrom} from
     * @param {Function} callback
     * @param {CSN.Path} path
     */
    function traverseFrom( from, callback, path = [] ) {
      if (from.ref) // ignore
        return;
      else if (from.args){ // join
        for(let i = 0; i < from.args.length; i++){
          traverseFrom(from.args[i], callback, path.concat(['args', i]));
        }
      }
      else
        traverseQuery( from, callback, path ); // sub query in FROM
    }
  }
  
  /**
   * Returns true if the element has a specific annotation set to the given value.
   *
   * @param {CSN.Artifact} artifact
   * @param {string} annotationName Name of the annotation (including the at-sign)
   * @param {any} value
   * @returns {boolean}
   */
  function hasBoolAnnotation(artifact, annotationName, value = true) {
    return artifact[annotationName] === value;
  }
  
  /**
   * EDM specific check: Render (navigation) property if element is NOT ...
   * 1) ... annotated @cds.api.ignore
   * 2) ... annotated @odata.navigable: false
   * 2) ... annotated @odata.foreignKey4 and odataFormat: structured
   * function accepts EDM internal and external options
   *
   * @param {CSN.Element} elementCsn
   * @param {CSN.Options & CSN.OdataOptions} options EDM specific options
   */
  function isEdmPropertyRendered(elementCsn, options) {
    if(options.toOdata)
      options = options.toOdata;
    // FKs are rendered in
    // V2/V4 flat: always on
    // V4 struct: on/off
    const renderForeignKey = (options.version === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
    const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
    const isNavigable = elementCsn.target ?
      (elementCsn['@odata.navigable'] === undefined ||
       elementCsn['@odata.navigable'] !== undefined && (elementCsn['@odata.navigable'] === null || elementCsn['@odata.navigable'] === true)) : true;
    // Foreign Keys can be ignored
    if(elementCsn['@odata.foreignKey4'])
      return isNotIgnored && renderForeignKey;
    // ordinary elements can be ignored and isNavigable is always true for them
    // assocs cannot be ignored but not navigable
    return isNotIgnored && isNavigable;
  }
  
  
  /**
   * Return the resulting database name for (absolute) 'artifactName', depending on the current naming
   * convention.
   *
   * - For the 'hdbcds' naming convention, this means converting '.' to '::' on
   *   the border between namespace and top-level artifact.
   * - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
   * - For the 'quoted' naming convention, this is just 'artifactName'.
   *
   * @param {string} artifactName The name of the artifact
   * @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
   * @param {string} [namespace] The namespace of the artifact
   * @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming convention.
   */
  function getArtifactDatabaseNameOf(artifactName, namingConvention, namespace = undefined) {
    if (namingConvention === 'hdbcds') {
      if (namespace) {
        return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
      }
      return artifactName;
    }
    else if (namingConvention === 'plain') {
      return artifactName.replace(/\./g, '_').toUpperCase();
    }
    else if (namingConvention === 'quoted') {
      return artifactName;
    }
    else {
      throw new Error('Unknown naming convention: ' + namingConvention);
    }
  }
  
  
  /**
   *  Return the resulting database element name for 'elemName', depending on the current naming
   *  convention.
   *  - For the 'hdbcds' naming convention, this is just 'elemName'.
   *  - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
   *  - For the 'quoted' naming convention, it means converting all '.' to '_'.
   *  No other naming conventions are accepted
   *
   * @param {string} elemName Name of the element
   * @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
   * @returns {string} The resulting database element name for 'elemName', depending on the current naming convention.
   */
  function getElementDatabaseNameOf(elemName, namingConvention) {
    if (namingConvention === 'hdbcds') {
      return elemName;
    }
    else if (namingConvention === 'plain') {
      return elemName.replace(/\./g, '_').toUpperCase();
    }
    else if (namingConvention === 'quoted') {
      return elemName.replace(/\./g, '_');
    }
    else {
      throw new Error('Unknown naming convention: ' + namingConvention);
    }
  }
  
  
  /**
   * Loop through the model, applying the custom transformations on the node's matching.
   *
   * Each transformer gets:
   * - the parent having the property
   * - the name of the property
   * - the value of the property
   * - the path to the property
   *
   * @param {object} csn CSN to enrich in-place
   * @param {object} customTransformers Map of prop to transform and function to apply
   * @param {Function[]} artifactTransformers Transformations to run on the artifacts, like forEachDefinition
   * @param {Boolean} skipIgnore Wether to skip _ignore elements or not
   * @returns {object} CSN with transformations applied
   */
  function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true ) {
    const transformers = {
      elements: dictionary,
      definitions: dictionary,
      actions: dictionary,
      params: dictionary,
      enum: dictionary,
      mixin: dictionary,
      ref: pathRef,
      //type: simpleRef,
      //target: simpleRef,
      //includes: simpleRef,
    }
  
    const csnPath = [];
    if (csn.definitions)
      definitions( csn, 'definitions', csn.definitions );
    return csn;
  
    function standard( parent, prop, node ) {
      if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')) || (skipIgnore && node._ignore))
        return;
  
      csnPath.push( prop );
  
      if (node instanceof Array) {
        node.forEach( (n, i) => standard( node, i, n ) );
      }
  
      else {
        for (let name of Object.getOwnPropertyNames( node )) {
          const trans = transformers[name] || standard;
          if(customTransformers[name])
            customTransformers[name](node, name, node[name], csnPath);
  
          trans( node, name, node[name], csnPath );
        }
      }
      csnPath.pop();
    }
  
    function dictionary( node, prop, dict ) {
      csnPath.push( prop );
      for (let name of Object.getOwnPropertyNames( dict )) {
        standard( dict, name, dict[name] );
      }
      if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
        setProp(node, '$' + prop, dict);
      csnPath.pop();
    }
  
    function definitions( node, prop, dict ) {
      csnPath.push( prop );
      for (let name of Object.getOwnPropertyNames( dict )) {
        artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
        standard( dict, name, dict[name] );
      }
      if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
        setProp(node, '$' + prop, dict);
      csnPath.pop();
    }
  
    //Keep looping through the pathRef
    function pathRef( node, prop, path ) {
      csnPath.push( prop );
      path.forEach( function step( s, i ) {
        if (s && typeof s === 'object') {
          csnPath.push( i );
          if (s.args)
            standard( s, 'args', s.args );
          if (s.where)
            standard( s, 'where', s.where );
          csnPath.pop();
        }
      } );
      csnPath.pop();
    }
  }
  
  module.exports = {
    getUtils,
    cloneCsn,
    isBuiltinType,
    assignAll,
    forEachGeneric,
    forEachDefinition,
    forEachMember,
    forEachMemberRecursively,
    forEachRef,
    forAllQueries,
    hasBoolAnnotation,
    isEdmPropertyRendered,
    getArtifactDatabaseNameOf,
    getElementDatabaseNameOf,
    applyTransformations
  };
  