"use strict";

var evaluate = require("babel-helper-evaluate-path");

module.exports = function (_ref) {
  var t = _ref.types,
      traverse = _ref.traverse;

  var seen = Symbol("seen");

  return {
    name: "minify-constant-folding",
    visitor: {
      // Evaluate string expressions that are next to each other
      // but are not actually a binary expression.
      // "a" + b + "c" + "d" -> "a" + b + "cd"
      BinaryExpression(path) {
        var literal = void 0,
            bin = void 0;
        if (path.get("right").isStringLiteral()) {
          literal = path.get("right");
          if (path.get("left").isBinaryExpression({ operator: "+" })) {
            bin = path.get("left");
          } else {
            return;
          }
        } else if (path.get("left").isStringLiteral()) {
          literal = path.get("left");
          if (path.get("right").isBinaryExpression({ operator: "+" })) {
            bin = path.get("right");
          } else {
            return;
          }
        } else {
          return;
        }

        var relevant = getLeaf(bin, literal.key);

        if (!relevant) {
          return;
        }

        var value = literal.key === "right" ? relevant.node.value + literal.node.value : literal.node.value + relevant.node.value;

        relevant.replaceWith(t.stringLiteral(value));
        path.replaceWith(bin.node);

        function getLeaf(path, direction) {
          if (path.isStringLiteral()) {
            return path;
          } else if (path.isBinaryExpression({ operator: "+" })) {
            return getLeaf(path.get(direction), direction);
          }
        }
      },

      // TODO: look into evaluating binding too (could result in more code, but gzip?)
      Expression(path) {
        var node = path.node;


        if (node[seen]) {
          return;
        }

        if (path.isLiteral()) {
          return;
        }

        if (!path.isPure()) {
          return;
        }

        if (traverse.hasType(node, path.scope, "Identifier", t.FUNCTION_TYPES)) {
          return;
        }

        // -0 maybe compared via dividing and then checking against -Infinity
        // Also -X will always be -X.
        if (t.isUnaryExpression(node, { operator: "-" }) && t.isNumericLiteral(node.argument)) {
          return;
        }

        // We have a transform that converts true/false to !0/!1
        if (t.isUnaryExpression(node, { operator: "!" }) && t.isNumericLiteral(node.argument)) {
          if (node.argument.value === 0 || node.argument.value === 1) {
            return;
          }
        }

        // void 0 is used for undefined.
        if (t.isUnaryExpression(node, { operator: "void" }) && t.isNumericLiteral(node.argument, { value: 0 })) {
          return;
        }

        var res = evaluate(path);
        if (res.confident) {
          // Avoid fractions because they can be longer than the original expression.
          // There is also issues with number percision?
          if (typeof res.value === "number" && !Number.isInteger(res.value)) {
            return;
          }

          // Preserve -0
          if (typeof res.value === "number" && res.value === 0) {
            if (1 / res.value === -Infinity) {
              var _node2 = t.unaryExpression("-", t.numericLiteral(0), true);
              _node2[seen] = true;
              path.replaceWith(_node2);
              return;
            }
          }

          var _node = t.valueToNode(res.value);
          _node[seen] = true;
          path.replaceWith(_node);
        }
      }
    }
  };
};