/**
 * 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.
 *
 * 
 * @format
 */
'use strict';

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

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

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

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

/**
 * Given a graphql`` tagged template literal, replace it with the appropriate
 * runtime artifact.
 */
function compileGraphQLTag(t, path, state, ast) {
  var mainDefinition = ast.definitions[0];

  if (mainDefinition.kind === 'FragmentDefinition') {
    var objPropName = getAssignedObjectPropertyName(t, path);

    if (objPropName) {
      if (ast.definitions.length !== 1) {
        throw new Error('BabelPluginRelay: Expected exactly one fragment in the ' + "graphql tag referenced by the property ".concat(objPropName, "."));
      }

      return replaceMemoized(t, path, createAST(t, state, path, mainDefinition));
    }

    var nodeMap = {};
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

    try {
      for (var _iterator = ast.definitions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
        var definition = _step.value;

        if (definition.kind !== 'FragmentDefinition') {
          throw new Error('BabelPluginRelay: Expected only fragments within this ' + 'graphql tag.');
        }

        var _getFragmentNameParts = getFragmentNameParts(definition.name.value),
            propName = _getFragmentNameParts[1];

        nodeMap[propName] = createAST(t, state, path, definition);
      }
    } catch (err) {
      _didIteratorError = true;
      _iteratorError = err;
    } finally {
      try {
        if (!_iteratorNormalCompletion && _iterator["return"] != null) {
          _iterator["return"]();
        }
      } finally {
        if (_didIteratorError) {
          throw _iteratorError;
        }
      }
    }

    return replaceMemoized(t, path, createObject(t, nodeMap));
  }

  if (mainDefinition.kind === 'OperationDefinition') {
    if (ast.definitions.length !== 1) {
      throw new Error('BabelPluginRelay: Expected exactly one operation ' + '(query, mutation, or subscription) per graphql tag.');
    }

    return replaceMemoized(t, path, createAST(t, state, path, mainDefinition));
  }

  throw new Error('BabelPluginRelay: Expected a fragment, mutation, query, or ' + 'subscription, got `' + mainDefinition.kind + '`.');
}

function createAST(t, state, path, graphqlDefinition) {
  var isCompatMode = Boolean(state.opts && state.opts.compat);
  var isHasteMode = Boolean(state.opts && state.opts.haste);
  var isDevVariable = state.opts && state.opts.isDevVariable;
  var artifactDirectory = state.opts && state.opts.artifactDirectory;
  var buildCommand = state.opts && state.opts.buildCommand || 'relay-compiler'; // Fallback is 'true'

  var isDevelopment = (process.env.BABEL_ENV || process.env.NODE_ENV) !== 'production';
  var modernNode = createModernNode(t, graphqlDefinition, state, {
    artifactDirectory: artifactDirectory,
    buildCommand: buildCommand,
    isDevelopment: isDevelopment,
    isHasteMode: isHasteMode,
    isDevVariable: isDevVariable
  });

  if (isCompatMode) {
    return createCompatNode(t, modernNode, createClassicNode(t, path, graphqlDefinition, state));
  }

  return modernNode;
}

function replaceMemoized(t, path, ast) {
  var topScope = path.scope;

  while (topScope.parent) {
    topScope = topScope.parent;
  }

  if (path.scope === topScope) {
    path.replaceWith(ast);
  } else {
    var id = topScope.generateDeclaredUidIdentifier('graphql');
    path.replaceWith(t.logicalExpression('||', id, t.assignmentExpression('=', id, ast)));
  }
}

function createObject(t, obj) {
  return t.objectExpression(Object.keys(obj).map(function (key) {
    return t.objectProperty(t.identifier(key), obj[key]);
  }));
}

function getAssignedObjectPropertyName(t, path) {
  var property = path;

  while (property) {
    if (t.isObjectProperty(property) && property.node.key.name) {
      return property.node.key.name;
    }

    property = property.parentPath;
  }
}

module.exports = compileGraphQLTag;