import { walk } from 'estree-walker';

const isNodeInPatternWeakSet = /* @__PURE__ */ new WeakSet();
function setIsNodeInPattern(node) {
  return isNodeInPatternWeakSet.add(node);
}
function isNodeInPattern(node) {
  return isNodeInPatternWeakSet.has(node);
}
function esmWalker(root, { onIdentifier, onImportMeta, onDynamicImport, onCallExpression }) {
  const parentStack = [];
  const varKindStack = [];
  const scopeMap = /* @__PURE__ */ new WeakMap();
  const identifiers = [];
  const setScope = (node, name) => {
    let scopeIds = scopeMap.get(node);
    if (scopeIds && scopeIds.has(name))
      return;
    if (!scopeIds) {
      scopeIds = /* @__PURE__ */ new Set();
      scopeMap.set(node, scopeIds);
    }
    scopeIds.add(name);
  };
  function isInScope(name, parents) {
    return parents.some((node) => {
      var _a;
      return node && ((_a = scopeMap.get(node)) == null ? void 0 : _a.has(name));
    });
  }
  function handlePattern(p, parentScope) {
    if (p.type === "Identifier") {
      setScope(parentScope, p.name);
    } else if (p.type === "RestElement") {
      handlePattern(p.argument, parentScope);
    } else if (p.type === "ObjectPattern") {
      p.properties.forEach((property) => {
        if (property.type === "RestElement")
          setScope(parentScope, property.argument.name);
        else
          handlePattern(property.value, parentScope);
      });
    } else if (p.type === "ArrayPattern") {
      p.elements.forEach((element) => {
        if (element)
          handlePattern(element, parentScope);
      });
    } else if (p.type === "AssignmentPattern") {
      handlePattern(p.left, parentScope);
    } else {
      setScope(parentScope, p.name);
    }
  }
  walk(root, {
    enter(node, parent) {
      if (node.type === "ImportDeclaration")
        return this.skip();
      if (parent && !(parent.type === "IfStatement" && node === parent.alternate))
        parentStack.unshift(parent);
      if (node.type === "VariableDeclaration")
        varKindStack.unshift(node.kind);
      if (node.type === "CallExpression")
        onCallExpression == null ? void 0 : onCallExpression(node);
      if (node.type === "MetaProperty" && node.meta.name === "import")
        onImportMeta == null ? void 0 : onImportMeta(node);
      else if (node.type === "ImportExpression")
        onDynamicImport == null ? void 0 : onDynamicImport(node);
      if (node.type === "Identifier") {
        if (!isInScope(node.name, parentStack) && isRefIdentifier(node, parent, parentStack)) {
          identifiers.push([node, parentStack.slice(0)]);
        }
      } else if (isFunctionNode(node)) {
        if (node.type === "FunctionDeclaration") {
          const parentScope = findParentScope(parentStack);
          if (parentScope)
            setScope(parentScope, node.id.name);
        }
        node.params.forEach((p) => {
          if (p.type === "ObjectPattern" || p.type === "ArrayPattern") {
            handlePattern(p, node);
            return;
          }
          walk(p.type === "AssignmentPattern" ? p.left : p, {
            enter(child, parent2) {
              if ((parent2 == null ? void 0 : parent2.type) === "AssignmentPattern" && (parent2 == null ? void 0 : parent2.right) === child)
                return this.skip();
              if (child.type !== "Identifier")
                return;
              if (isStaticPropertyKey(child, parent2))
                return;
              if ((parent2 == null ? void 0 : parent2.type) === "TemplateLiteral" && (parent2 == null ? void 0 : parent2.expressions.includes(child)) || (parent2 == null ? void 0 : parent2.type) === "CallExpression" && (parent2 == null ? void 0 : parent2.callee) === child)
                return;
              setScope(node, child.name);
            }
          });
        });
      } else if (node.type === "Property" && parent.type === "ObjectPattern") {
        setIsNodeInPattern(node);
      } else if (node.type === "VariableDeclarator") {
        const parentFunction = findParentScope(
          parentStack,
          varKindStack[0] === "var"
        );
        if (parentFunction)
          handlePattern(node.id, parentFunction);
      } else if (node.type === "CatchClause" && node.param) {
        handlePattern(node.param, node);
      }
    },
    leave(node, parent) {
      if (parent && !(parent.type === "IfStatement" && node === parent.alternate))
        parentStack.shift();
      if (node.type === "VariableDeclaration")
        varKindStack.shift();
    }
  });
  identifiers.forEach(([node, stack]) => {
    if (!isInScope(node.name, stack)) {
      const parent = stack[0];
      const grandparent = stack[1];
      const hasBindingShortcut = isStaticProperty(parent) && parent.shorthand && (!isNodeInPattern(parent) || isInDestructuringAssignment(parent, parentStack));
      const classDeclaration = parent.type === "PropertyDefinition" && (grandparent == null ? void 0 : grandparent.type) === "ClassBody" || parent.type === "ClassDeclaration" && node === parent.superClass;
      const classExpression = parent.type === "ClassExpression" && node === parent.id;
      onIdentifier == null ? void 0 : onIdentifier(node, {
        hasBindingShortcut,
        classDeclaration,
        classExpression
      }, stack);
    }
  });
}
function isRefIdentifier(id, parent, parentStack) {
  if (parent.type === "CatchClause" || (parent.type === "VariableDeclarator" || parent.type === "ClassDeclaration") && parent.id === id)
    return false;
  if (isFunctionNode(parent)) {
    if (parent.id === id)
      return false;
    if (parent.params.includes(id))
      return false;
  }
  if (parent.type === "MethodDefinition" && !parent.computed)
    return false;
  if (isStaticPropertyKey(id, parent))
    return false;
  if (isNodeInPattern(parent) && parent.value === id)
    return false;
  if (parent.type === "ArrayPattern" && !isInDestructuringAssignment(parent, parentStack))
    return false;
  if (parent.type === "MemberExpression" && parent.property === id && !parent.computed)
    return false;
  if (parent.type === "ExportSpecifier")
    return false;
  if (id.name === "arguments")
    return false;
  return true;
}
function isStaticProperty(node) {
  return node && node.type === "Property" && !node.computed;
}
function isStaticPropertyKey(node, parent) {
  return isStaticProperty(parent) && parent.key === node;
}
const functionNodeTypeRE = /Function(?:Expression|Declaration)$|Method$/;
function isFunctionNode(node) {
  return functionNodeTypeRE.test(node.type);
}
const blockNodeTypeRE = /^BlockStatement$|^For(?:In|Of)?Statement$/;
function isBlock(node) {
  return blockNodeTypeRE.test(node.type);
}
function findParentScope(parentStack, isVar = false) {
  return parentStack.find(isVar ? isFunctionNode : isBlock);
}
function isInDestructuringAssignment(parent, parentStack) {
  if (parent && (parent.type === "Property" || parent.type === "ArrayPattern"))
    return parentStack.some((i) => i.type === "AssignmentExpression");
  return false;
}

export { esmWalker, isFunctionNode, isInDestructuringAssignment, isNodeInPattern, isStaticProperty, isStaticPropertyKey, setIsNodeInPattern };
