/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * 
 * @fullSyntaxTransform
 * @format
 */
'use strict';

var RelayQLPrinter = require("./RelayQLPrinter");

var invariant = require("./invariant");

var util = require("util");

var _require = require("./RelayQLAST"),
    RelayQLFragment = _require.RelayQLFragment,
    RelayQLMutation = _require.RelayQLMutation,
    RelayQLQuery = _require.RelayQLQuery,
    RelayQLSubscription = _require.RelayQLSubscription;

var _require2 = require("graphql"),
    formatError = _require2.formatError,
    parse = _require2.parse,
    Source = _require2.Source,
    validate = _require2.validate,
    FieldsOnCorrectTypeRule = _require2.FieldsOnCorrectTypeRule,
    FragmentsOnCompositeTypesRule = _require2.FragmentsOnCompositeTypesRule,
    KnownArgumentNamesRule = _require2.KnownArgumentNamesRule,
    KnownTypeNamesRule = _require2.KnownTypeNamesRule,
    PossibleFragmentSpreadsRule = _require2.PossibleFragmentSpreadsRule,
    ValuesOfCorrectTypeRule = _require2.ValuesOfCorrectTypeRule,
    VariablesInAllowedPositionRule = _require2.VariablesInAllowedPositionRule;

/**
 * Transforms a TemplateLiteral node into a RelayQLDefinition, which is then
 * transformed into a Babel AST via RelayQLPrinter.
 */
var RelayQLTransformer =
/*#__PURE__*/
function () {
  function RelayQLTransformer(schema, options) {
    this.schema = schema;
    this.options = options;
  }

  var _proto = RelayQLTransformer.prototype;

  _proto.transform = function transform(t, // Babel
  node, options) {
    var _this$processTemplate = this.processTemplateLiteral(node, options.documentName),
        substitutions = _this$processTemplate.substitutions,
        templateText = _this$processTemplate.templateText,
        variableNames = _this$processTemplate.variableNames;

    var documentText = this.processTemplateText(templateText, options);
    var definition = this.processDocumentText(documentText, options);
    var Printer = RelayQLPrinter(t, this.options);
    return new Printer(options.tagName, variableNames).print(definition, substitutions, options.enableValidation);
  };
  /**
   * Convert TemplateLiteral into a single template string with substitution
   * names, a matching array of substituted values, and a set of substituted
   * variable names.
   */


  _proto.processTemplateLiteral = function processTemplateLiteral(node, documentName) {
    var _this = this;

    var chunks = [];
    var variableNames = {};
    var substitutions = [];
    node.quasis.forEach(function (element, ii) {
      var chunk = element.value.cooked;
      chunks.push(chunk);

      if (!element.tail) {
        var name = 'RQL_' + ii;
        var value = node.expressions[ii];
        substitutions.push({
          name: name,
          value: value
        });

        if (/:\s*$/.test(chunk)) {
          !_this.options.substituteVariables ? process.env.NODE_ENV !== "production" ? invariant(false, 'You supplied a GraphQL document named `%s` that uses template ' + 'substitution for an argument value, but variable substitution ' + 'has not been enabled.', documentName) : invariant(false) : void 0;
          chunks.push('$' + name);
          variableNames[name] = undefined;
        } else {
          chunks.push('...' + name);
        }
      }
    });
    return {
      substitutions: substitutions,
      templateText: chunks.join('').trim(),
      variableNames: variableNames
    };
  };
  /**
   * Converts the template string into a valid GraphQL document string.
   */


  _proto.processTemplateText = function processTemplateText(templateText, _ref) {
    var documentName = _ref.documentName,
        propName = _ref.propName;
    var pattern = /^(fragment|mutation|query|subscription)\s*(\w*)?([\s\S]*)/;
    var matches = pattern.exec(templateText);
    !matches ? process.env.NODE_ENV !== "production" ? invariant(false, 'You supplied a GraphQL document named `%s` with invalid syntax. It ' + 'must start with `fragment`, `mutation`, `query`, or `subscription`.', documentName) : invariant(false) : void 0;
    var type = matches[1];
    var name = matches[2] || documentName;
    var rest = matches[3]; // Allow `fragment on Type {...}`.

    if (type === 'fragment' && name === 'on') {
      name = documentName + (propName ? '_' + capitalize(propName) : '') + 'RelayQL';
      rest = 'on' + rest;
    }

    var definitionName = capitalize(name);
    return type + ' ' + definitionName + ' ' + rest;
  };
  /**
   * Parses the GraphQL document string into a RelayQLDocument.
   */


  _proto.processDocumentText = function processDocumentText(documentText, _ref2) {
    var documentName = _ref2.documentName,
        enableValidation = _ref2.enableValidation;
    var document = parse(new Source(documentText, documentName));
    var validationErrors = enableValidation ? this.validateDocument(document, documentName) : null;

    if (validationErrors) {
      var error = new Error(util.format('You supplied a GraphQL document named `%s` with validation errors.', documentName));
      error.validationErrors = validationErrors;
      error.sourceText = documentText;
      throw error;
    }

    var definition = document.definitions[0];
    var context = {
      definitionName: capitalize(documentName),
      isPattern: false,
      generateID: createIDGenerator(),
      schema: this.schema
    };

    if (definition.kind === 'FragmentDefinition') {
      return new RelayQLFragment(context, definition);
    } else if (definition.kind === 'OperationDefinition') {
      if (definition.operation === 'mutation') {
        return new RelayQLMutation(context, definition);
      } else if (definition.operation === 'query') {
        return new RelayQLQuery(context, definition);
      } else if (definition.operation === 'subscription') {
        return new RelayQLSubscription(context, definition);
      } else {
        !false ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported operation: %s', definition.operation) : invariant(false) : void 0;
      }
    } else {
      !false ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unsupported definition kind: %s', definition.kind) : invariant(false) : void 0;
    }
  };

  _proto.validateDocument = function validateDocument(document, documentName) {
    !(document.definitions.length === 1) ? process.env.NODE_ENV !== "production" ? invariant(false, 'You supplied a GraphQL document named `%s` with %d definitions, but ' + 'it must have exactly one definition.', documentName, document.definitions.length) : invariant(false) : void 0;
    var validator = this.options.validator;
    var validationErrors;

    if (validator) {
      validationErrors = validator().validate(this.schema, document);
    } else {
      var rules = [FieldsOnCorrectTypeRule, FragmentsOnCompositeTypesRule, KnownArgumentNamesRule, KnownTypeNamesRule, PossibleFragmentSpreadsRule, ValuesOfCorrectTypeRule, VariablesInAllowedPositionRule];
      validationErrors = validate(this.schema, document, rules);
    }

    if (validationErrors && validationErrors.length > 0) {
      return validationErrors.map(formatError);
    }

    return null;
  };

  return RelayQLTransformer;
}();

function capitalize(string) {
  return string[0].toUpperCase() + string.slice(1);
}
/**
 * Utility to generate locally scoped auto-incrementing IDs.
 */


function createIDGenerator() {
  var _id = 0;
  return function () {
    return (_id++).toString(32);
  };
}

module.exports = RelayQLTransformer;