"use strict";

module.exports = ({ types: t }) => {
  const NO_MEMBER = Symbol("no member");

  const replaceVisitor = {
    ReferencedIdentifier(path) {
      const { node } = path;
      const optionsMap = this.replacements[node.name];
      if (!optionsMap) {
        return;
      }

      let options;
      if (path.parentPath.isMemberExpression({ object: node })) {
        const { property } = path.parent;
        const key = t.isIdentifier(property) && property.name;
        if (typeof key === "string") {
          options = optionsMap[key];
          path = path.parentPath;
        }
      }

      if (!options) {
        options = optionsMap[NO_MEMBER];
      }

      if (!options) {
        return;
      }

      path.replaceWith(options.node);
    }
  };

  return {
    name: "minify-replace",
    visitor: {
      Program(path) {
        /**
           Replacements is an array of objects like this:
           {
             identifierName: 'console',
             member: 'log', // optional
             replacement: {
               type: 'identifier',
               value: '',
             },
           }
        **/

        if (!this.opts.replacements) {
          // No replacements. Bail.
          return;
        }

        const map = Object.create(null);
        this.opts.replacements.forEach(
          ({ identifierName, replacement, member }) => {
            if (path.scope.globals[identifierName]) {
              // Convert to a node, we only allow identifiers and literals as replacements
              if (!replacement.type.match(/literal|identifier/i)) {
                throw new Error(
                  "Only literals and identifier are supported as replacements"
                );
              }

              const node = t[replacement.type](replacement.value);
              const options = {
                identifierName,
                node,
                member
              };

              if (!map[identifierName]) {
                map[identifierName] = {};
              }

              if (member && map[identifierName][member]) {
                throw new Error(
                  `Replacement collision ${identifierName}.${member}`
                );
              }
              map[identifierName][member || NO_MEMBER] = options;
            }
          }
        );

        path.traverse(replaceVisitor, { replacements: map });
      }
    }
  };
};
