"use strict";

function replaceArray(t, path) {
  const node = path.node; // arguments is taken :(

  const constructorArgs = path.get("arguments");

  if (t.isIdentifier(node.callee, {
    name: "Array"
  }) && !path.scope.getBinding("Array")) {
    if (constructorArgs.length === 0) {
      // Array() -> []
      path.replaceWith(t.arrayExpression([]));
    } else if (constructorArgs.length === 1) {
      const arg = constructorArgs[0];
      const result = arg.evaluate();

      if (result.confident) {
        if (typeof result.value === "number") {
          if (result.value >= 0 && result.value <= 6 && result.value % 1 === 0) {
            // "Array(7)" is shorter than "[,,,,,,,]"
            path.replaceWith(t.arrayExpression(Array(result.value).fill(null)));
          } else {
            dropNewIfPresent();
          }
        } else {
          // Array("Asdf"), Array(true), Array(false)
          path.replaceWith(t.arrayExpression([t.valueToNode(result.value)]));
        }
      } else {
        const transformables = ["ArrayExpression", "ObjectExpression", "FunctionExpression", "ArrowFunctionExpression", "ClassExpression"];

        if (transformables.indexOf(arg.node.type) !== -1) {
          // Array([]), Array({})
          // Array(()=>{}), Array(class{}), Array(function(){})
          path.replaceWith(t.arrayExpression([arg.node]));
        } else {
          // Array(x); Array(a.b);
          dropNewIfPresent();
        }
      }
    } else {
      // Array(2,3), Array(a,b) => [2,3], [a,b]
      path.replaceWith(t.arrayExpression(node.arguments));
    }

    return true;
  }

  function dropNewIfPresent() {
    if (path.isNewExpression()) {
      path.replaceWith(t.callExpression(node.callee, node.arguments));
    }
  }
}

function replaceObject(t, path) {
  const node = path.node;

  if (t.isIdentifier(node.callee, {
    name: "Object"
  }) && !path.scope.getBinding("Object")) {
    const isVoid0 = require("babel-helper-is-void-0")(t);

    const arg = node.arguments[0];
    const binding = arg && t.isIdentifier(arg) && path.scope.getBinding(arg.name); // Object() -> {}

    if (node.arguments.length === 0) {
      path.replaceWith(t.objectExpression([])); // Object([]) -> []
    } else if (arg.type === "ArrayExpression" || t.isFunctionExpression(arg)) {
      path.replaceWith(arg); // Object(null) -> {}
    } else if (isVoid0(arg) || arg.name === "undefined" || arg.type === "NullLiteral" || arg.type === "ObjectExpression" && arg.properties.length === 0) {
      path.replaceWith(t.objectExpression([])); // Object(localFn) -> localFn
    } else if (binding && binding.path.isFunction()) {
      path.replaceWith(arg); // Object({a:b}) -> {a:b}
    } else if (arg.type === "ObjectExpression") {
      path.replaceWith(arg); // new Object(a) -> Object(a)
    } else if (node.type === "NewExpression") {
      path.replaceWith(t.callExpression(node.callee, node.arguments));
    }

    return true;
  }
}

function defaults({
  boolean = true,
  number = true,
  string = true,
  array = true,
  object = true
} = {}) {
  return {
    boolean,
    number,
    string,
    array,
    object
  };
}

module.exports = function ({
  types: t
}) {
  return {
    name: "minify-type-constructors",
    visitor: {
      CallExpression(path) {
        const node = path.node;
        const opts = defaults(this.opts); // Boolean(foo) -> !!foo

        if (opts.boolean && t.isIdentifier(node.callee, {
          name: "Boolean"
        }) && node.arguments.length === 1 && !path.scope.getBinding("Boolean")) {
          path.replaceWith(t.unaryExpression("!", t.unaryExpression("!", node.arguments[0], true), true));
          return;
        } // Number(foo) -> +foo


        if (opts.number && t.isIdentifier(node.callee, {
          name: "Number"
        }) && node.arguments.length === 1 && !path.scope.getBinding("Number")) {
          path.replaceWith(t.unaryExpression("+", node.arguments[0], true));
          return;
        } // String(foo) -> foo + ''


        if (opts.string && t.isIdentifier(node.callee, {
          name: "String"
        }) && node.arguments.length === 1 && !path.scope.getBinding("String")) {
          path.replaceWith(t.binaryExpression("+", node.arguments[0], t.stringLiteral("")));
          return;
        } // Array() -> []


        if (opts.array && replaceArray(t, path)) {
          return;
        } // Object() -> {}


        if (opts.object && replaceObject(t, path)) {
          return;
        }
      },

      NewExpression(path) {
        const opts = defaults(this.opts); // new Array() -> []

        if (opts.array && replaceArray(t, path)) {
          return;
        } // new Object() -> {}


        if (opts.object && replaceObject(t, path)) {
          return;
        }
      }

    }
  };
};