“use strict”;

Object.defineProperty(exports, “__esModule”, {

value: true

}); exports.default = void 0;

var t = _interopRequireWildcard(require(“@babel/types”));

function _getRequireWildcardCache() { if (typeof WeakMap !== “function”) return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj = obj; } } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

const referenceVisitor = {

ReferencedIdentifier(path, state) {
  if (path.isJSXIdentifier() && t.react.isCompatTag(path.node.name) && !path.parentPath.isJSXMemberExpression()) {
    return;
  }

  if (path.node.name === "this") {
    let scope = path.scope;

    do {
      if (scope.path.isFunction() && !scope.path.isArrowFunctionExpression()) {
        break;
      }
    } while (scope = scope.parent);

    if (scope) state.breakOnScopePaths.push(scope.path);
  }

  const binding = path.scope.getBinding(path.node.name);
  if (!binding) return;

  for (const violation of binding.constantViolations) {
    if (violation.scope !== binding.path.scope) {
      state.mutableBinding = true;
      path.stop();
      return;
    }
  }

  if (binding !== state.scope.getBinding(path.node.name)) return;
  state.bindings[path.node.name] = binding;
}

};

class PathHoister {

constructor(path, scope) {
  this.breakOnScopePaths = [];
  this.bindings = {};
  this.mutableBinding = false;
  this.scopes = [];
  this.scope = scope;
  this.path = path;
  this.attachAfter = false;
}

isCompatibleScope(scope) {
  for (const key of Object.keys(this.bindings)) {
    const binding = this.bindings[key];

    if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
      return false;
    }
  }

  return true;
}

getCompatibleScopes() {
  let scope = this.path.scope;

  do {
    if (this.isCompatibleScope(scope)) {
      this.scopes.push(scope);
    } else {
      break;
    }

    if (this.breakOnScopePaths.indexOf(scope.path) >= 0) {
      break;
    }
  } while (scope = scope.parent);
}

getAttachmentPath() {
  let path = this._getAttachmentPath();

  if (!path) return;
  let targetScope = path.scope;

  if (targetScope.path === path) {
    targetScope = path.scope.parent;
  }

  if (targetScope.path.isProgram() || targetScope.path.isFunction()) {
    for (const name of Object.keys(this.bindings)) {
      if (!targetScope.hasOwnBinding(name)) continue;
      const binding = this.bindings[name];

      if (binding.kind === "param" || binding.path.parentKey === "params") {
        continue;
      }

      const bindingParentPath = this.getAttachmentParentForPath(binding.path);

      if (bindingParentPath.key >= path.key) {
        this.attachAfter = true;
        path = binding.path;

        for (const violationPath of binding.constantViolations) {
          if (this.getAttachmentParentForPath(violationPath).key > path.key) {
            path = violationPath;
          }
        }
      }
    }
  }

  return path;
}

_getAttachmentPath() {
  const scopes = this.scopes;
  const scope = scopes.pop();
  if (!scope) return;

  if (scope.path.isFunction()) {
    if (this.hasOwnParamBindings(scope)) {
      if (this.scope === scope) return;
      const bodies = scope.path.get("body").get("body");

      for (let i = 0; i < bodies.length; i++) {
        if (bodies[i].node._blockHoist) continue;
        return bodies[i];
      }
    } else {
      return this.getNextScopeAttachmentParent();
    }
  } else if (scope.path.isProgram()) {
    return this.getNextScopeAttachmentParent();
  }
}

getNextScopeAttachmentParent() {
  const scope = this.scopes.pop();
  if (scope) return this.getAttachmentParentForPath(scope.path);
}

getAttachmentParentForPath(path) {
  do {
    if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) {
      return path;
    }
  } while (path = path.parentPath);
}

hasOwnParamBindings(scope) {
  for (const name of Object.keys(this.bindings)) {
    if (!scope.hasOwnBinding(name)) continue;
    const binding = this.bindings[name];
    if (binding.kind === "param" && binding.constant) return true;
  }

  return false;
}

run() {
  this.path.traverse(referenceVisitor, this);
  if (this.mutableBinding) return;
  this.getCompatibleScopes();
  const attachTo = this.getAttachmentPath();
  if (!attachTo) return;
  if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return;
  let uid = attachTo.scope.generateUidIdentifier("ref");
  const declarator = t.variableDeclarator(uid, this.path.node);
  const insertFn = this.attachAfter ? "insertAfter" : "insertBefore";
  const [attached] = attachTo[insertFn]([attachTo.isVariableDeclarator() ? declarator : t.variableDeclaration("var", [declarator])]);
  const parent = this.path.parentPath;

  if (parent.isJSXElement() && this.path.container === parent.node.children) {
    uid = t.JSXExpressionContainer(uid);
  }

  this.path.replaceWith(t.cloneNode(uid));
  return attachTo.isVariableDeclarator() ? attached.get("init") : attached.get("declarations.0.init");
}

}

exports.default = PathHoister;