text: import gql from 'graphql-tag';
  
  export const Fragments = {};
  export const FragmentsExtensions = {}; // will be used on startup
  
  /*
  
  Get a fragment's name from its text
  
  */
  export const extractFragmentName = fragmentText => fragmentText.match(/fragment (.*) on/)[1];
  
  /*
  
  Get a query resolver's name from its text
  
  */
  export const extractResolverName = resolverText => resolverText.trim().substr(0, resolverText.trim().indexOf('{'));
  
  
  /*
  
  Register a fragment, including its text, the text of its subfragments, and the fragment object
  
  */
  export const registerFragment = fragmentTextSource => {
    // remove comments
    const fragmentText = fragmentTextSource.replace(/\#.*\n/g, '\n');
  
    // extract name from fragment text
    const fragmentName = extractFragmentName(fragmentText);
  
    // extract subFragments from text
    const matchedSubFragments = fragmentText.match(/\.{3}([_A-Za-z][_0-9A-Za-z]*)/g) || [];
    const subFragments = _.unique(matchedSubFragments.map(f => f.replace('...', '')));
    
    // register fragment
    Fragments[fragmentName] = {
      fragmentText
    }
  
    // also add subfragments if there are any
    if(subFragments && subFragments.length) {
      Fragments[fragmentName].subFragments = subFragments;
    }
  
  };
  
  /*
  
  Create gql fragment object from text and subfragments
  
  */
  export const getFragmentObject = (fragmentText, subFragments) => {
    // pad the literals array with line returns for each subFragments
    const literals = subFragments ? [fragmentText, ...subFragments.map(x => '\n')] : [fragmentText];
  
    // the gql function expects an array of literals as first argument, and then sub-fragments as other arguments
    const gqlArguments = subFragments ? [literals, ...subFragments.map(subFragmentName => {
      // return subfragment's gql fragment
      if (!Fragments[subFragmentName] || !Fragments[subFragmentName].fragmentObject) {
        throw new Error(`Subfragment “${subFragmentName}” of fragment “${extractFragmentName(fragmentText)}” has not been initialized yet.`);
      }
      return Fragments[subFragmentName].fragmentObject;
    })] : [literals];
  
    return gql.apply(null, gqlArguments);
  }
  
  /*
  
  Create default "dumb" gql fragment object for a given collection
  
  */
  export const getDefaultFragmentText = (collection, options = { onlyViewable: true }) => {
    const schema = collection.simpleSchema()._schema;
    const fieldNames = _.reject(_.keys(schema), fieldName => {
      /*
  
      Exclude a field from the default fragment if
      1. it has a resolver and addOriginalField is false
      2. it has $ in its name
      3. it's not viewable (if onlyViewable option is true)
  
      */
      const field = schema[fieldName];
      // OpenCRUD backwards compatibility
      return (field.resolveAs && !field.resolveAs.addOriginalField) || fieldName.indexOf('$') !== -1 || options.onlyViewable && !(field.canRead || field.viewableBy);
    });
  
    if (fieldNames.length) {
      const fragmentText = `
        fragment ${collection.options.collectionName}DefaultFragment on ${collection.typeName} {
          ${fieldNames.map(fieldName => {
            return fieldName+'\n'
          }).join('')}
        }
      `;
  
      return fragmentText;
    } else {
      return null;
    }
  
  }
  export const getDefaultFragment = collection => {
    const fragmentText = getDefaultFragmentText(collection);
    return fragmentText ? gql`${fragmentText}` : null;
  }
  /*
  
  Queue a fragment to be extended with additional properties.
  
  Note: can be used even before the fragment has been registered. 
  
  */
  export const extendFragment = (fragmentName, newProperties) => {
    FragmentsExtensions[fragmentName] = FragmentsExtensions[fragmentName] ? [...FragmentsExtensions[fragmentName], newProperties] : [newProperties];
  }
  
  /*
  
  Perform fragment extension (called from initializeFragments()
  
  Note: will call registerFragment again each time, resulting in multiple fragments
  with the same name (but duplicate fragments warning is disabled).
  
  */
  export const extendFragmentWithProperties = (fragmentName, newProperties) => {
    const fragment = Fragments[fragmentName];
    const fragmentEndPosition = fragment.fragmentText.lastIndexOf('}');
    const newFragmentText = [
      fragment.fragmentText.slice(0, fragmentEndPosition), 
      newProperties, 
      fragment.fragmentText.slice(fragmentEndPosition)
    ].join('');
    registerFragment(newFragmentText);
  }
  
  /*
  
  Remove a property from a fragment
  
  Note: can only be called *after* a fragment is registered
  
  */
  export const removeFromFragment = (fragmentName, propertyName) => {
    const fragment = Fragments[fragmentName];
    const newFragmentText = fragment.fragmentText.replace(propertyName, '');
    registerFragment(newFragmentText);  
  }
  
  /*
  
  Get fragment name from fragment object
  
  */
  export const getFragmentName = fragment => fragment && fragment.definitions[0] && fragment.definitions[0].name.value;
  
  /*
  
  Get actual gql fragment
  
  */
  export const getFragment = fragmentName => {
    if (!Fragments[fragmentName]) {
      throw new Error(`Fragment "${fragmentName}" not registered.`);
    }
    if (!Fragments[fragmentName].fragmentObject) {
      initializeFragments([fragmentName]);
    }
    // return fragment object created by gql
    return Fragments[fragmentName].fragmentObject;  
  }
  
  /*
  
  Get gql fragment text
  
  */
  export const getFragmentText = fragmentName => {
    if (!Fragments[fragmentName]) {
      throw new Error(`Fragment "${fragmentName}" not registered.`)
    }
    // return fragment object created by gql
    return Fragments[fragmentName].fragmentText;  
  }
  
  /*
  
  Get names of non initialized fragments.
  
  */
  export const getNonInitializedFragmentNames = () =>
    _.keys(Fragments).filter(name => !Fragments[name].fragmentObject);
  
  /*
  
  Perform all fragment extensions (called from routing)
  
  */
  export const initializeFragments = (fragments = getNonInitializedFragmentNames()) => {
  
    const errorFragmentKeys = [];
  
    // extend fragment texts (if extended fragment exists)
    _.forEach(FragmentsExtensions, (extensions, fragmentName) => {
      if (Fragments[fragmentName]) {
        extensions.forEach(newProperties => {
          extendFragmentWithProperties(fragmentName, newProperties);
        });
      }
    });
    
    // create fragment objects
  
    // initialize fragments *with no subfragments* first to avoid unresolved dependencies
    const keysWithoutSubFragments = _.filter(fragments, fragmentName => !Fragments[fragmentName].subFragments);
    _.forEach(keysWithoutSubFragments, fragmentName => {
      const fragment = Fragments[fragmentName];
      fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments)
    });
  
    // next, initialize fragments that *have* subfragments
    const keysWithSubFragments = _.filter(_.keys(Fragments), fragmentName => !!Fragments[fragmentName].subFragments);
    _.forEach(keysWithSubFragments, fragmentName => {
      const fragment = Fragments[fragmentName];
      try {
        fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments);
      } catch (error) {
        // if fragment initialization triggers an error, store fragment and try again later
        // common error causes include cross-dependencies
        errorFragmentKeys.push(fragmentName);
      }
    });
  
    // finally, try initializing any fragment that triggered an error again
    _.forEach(errorFragmentKeys, fragmentName => {
      const fragment = Fragments[fragmentName];
      fragment.fragmentObject = getFragmentObject(fragment.fragmentText, fragment.subFragments);
    });
  
  }