import rewritePattern from "regexpu-core";
import {
  featuresKey,
  FEATURES,
  enableFeature,
  runtimeKey,
  hasFeature,
} from "./features";
import { generateRegexpuOptions } from "./util";

import pkg from "../package.json";
import { types as t } from "@babel/core";
import annotateAsPure from "@babel/helper-annotate-as-pure";

type RegExpFlags = "i" | "g" | "m" | "s" | "u" | "y";

/**
 * Remove given flag from given RegExpLiteral node
 *
 * @param {RegExpLiteral} node
 * @param {RegExpFlags} flag
 * @returns {void}
 */
function pullFlag(node, flag: RegExpFlags): void {
  node.flags = node.flags.replace(flag, "");
}

// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
//       as 70000100005. This method is easier than using a semver-parsing
//       package, but it breaks if we release x.y.z where x, y or z are
//       greater than 99_999.
const version = pkg.version.split(".").reduce((v, x) => v * 1e5 + +x, 0);
const versionKey = "@babel/plugin-regexp-features/version";

export function createRegExpFeaturePlugin({ name, feature, options = {} }) {
  return {
    name,
    pre() {
      const { file } = this;
      const features = file.get(featuresKey) ?? 0;
      let newFeatures = enableFeature(features, FEATURES[feature]);

      const { useUnicodeFlag, runtime = true } = options;
      if (useUnicodeFlag === false) {
        newFeatures = enableFeature(newFeatures, FEATURES.unicodeFlag);
      }
      if (newFeatures !== features) {
        file.set(featuresKey, newFeatures);
      }

      if (!runtime) {
        file.set(runtimeKey, false);
      }

      if (!file.has(versionKey) || file.get(versionKey) < version) {
        file.set(versionKey, version);
      }
    },

    visitor: {
      RegExpLiteral(path) {
        const { node } = path;
        const { file } = this;
        const features = file.get(featuresKey);
        const runtime = file.get(runtimeKey) ?? true;
        const regexpuOptions = generateRegexpuOptions(node, features);
        if (regexpuOptions === null) {
          return;
        }
        const namedCaptureGroups = {};
        if (regexpuOptions.namedGroup) {
          regexpuOptions.onNamedGroup = (name, index) => {
            namedCaptureGroups[name] = index;
          };
        }
        node.pattern = rewritePattern(node.pattern, node.flags, regexpuOptions);

        if (
          regexpuOptions.namedGroup &&
          Object.keys(namedCaptureGroups).length > 0 &&
          runtime &&
          !isRegExpTest(path)
        ) {
          const call = t.callExpression(this.addHelper("wrapRegExp"), [
            node,
            t.valueToNode(namedCaptureGroups),
          ]);
          annotateAsPure(call);

          path.replaceWith(call);
        }
        if (hasFeature(features, FEATURES.unicodeFlag)) {
          pullFlag(node, "u");
        }
        if (hasFeature(features, FEATURES.dotAllFlag)) {
          pullFlag(node, "s");
        }
      },
    },
  };
}

function isRegExpTest(path) {
  return (
    path.parentPath.isMemberExpression({
      object: path.node,
      computed: false,
    }) && path.parentPath.get("property").isIdentifier({ name: "test" })
  );
}
