import { arg } from "@unified-latex/unified-latex-builder";
import { parse } from "@unified-latex/unified-latex-util-argspec";
import { match } from "@unified-latex/unified-latex-util-match";
import { scan } from "@unified-latex/unified-latex-util-scan";
import { updateRenderInfo } from "@unified-latex/unified-latex-util-render-info";
import { visit } from "@unified-latex/unified-latex-util-visit";
function gobbleSingleArgument(nodes, argSpec, startPos = 0) {
  if (typeof argSpec === "string" || !argSpec.type) {
    throw new Error(
      `argSpec must be an already-parsed argument specification, not "${JSON.stringify(
        argSpec
      )}"`
    );
  }
  let argument = null;
  let currPos = startPos;
  const gobbleWhitespace = argSpec.noLeadingWhitespace ? () => {
  } : () => {
    while (currPos < nodes.length) {
      if (!match.whitespace(nodes[currPos])) {
        break;
      }
      currPos++;
    }
  };
  const openMark = argSpec.openBrace || "";
  const closeMark = argSpec.closeBrace || "";
  const acceptGroup = (argSpec.type === "mandatory" || argSpec.type === "optional") && openMark === "{" && closeMark === "}";
  gobbleWhitespace();
  const currNode = nodes[currPos];
  if (currNode == null || match.comment(currNode) || match.parbreak(currNode)) {
    const ret = {
      argument,
      nodesRemoved: 0
    };
    return ret;
  }
  switch (argSpec.type) {
    case "mandatory":
      if (acceptGroup) {
        let content = [currNode];
        if (match.group(currNode)) {
          content = currNode.content;
        }
        argument = arg(content, {
          openMark,
          closeMark
        });
        currPos++;
        break;
      } else {
        const bracePos2 = findBracePositions(
          nodes,
          currPos,
          openMark,
          closeMark
        );
        if (bracePos2) {
          argument = arg(nodes.slice(bracePos2[0] + 1, bracePos2[1]), {
            openMark,
            closeMark
          });
          currPos = bracePos2[1] + 1;
          break;
        }
      }
    // NOTE: Fallthrough is on purpose.
    // Matching a mandatory argument and an optional argument is the same for our purposes
    // because we're not going to fail to parse because of a missing argument.
    case "optional":
      if (acceptGroup && match.group(currNode)) {
        argument = arg(currNode.content, {
          openMark,
          closeMark
        });
        currPos++;
        break;
      }
      const bracePos = findBracePositions(
        nodes,
        currPos,
        openMark,
        closeMark
      );
      if (bracePos) {
        argument = arg(nodes.slice(bracePos[0] + 1, bracePos[1]), {
          openMark,
          closeMark
        });
        currPos = bracePos[1] + 1;
        break;
      }
      break;
    case "optionalStar":
    case "optionalToken": {
      const bracePos2 = findBracePositions(
        nodes,
        currPos,
        argSpec.type === "optionalStar" ? "*" : argSpec.token
      );
      if (bracePos2) {
        argument = arg(currNode, { openMark: "", closeMark: "" });
        currPos = bracePos2[0] + 1;
      }
      break;
    }
    case "until": {
      if (argSpec.stopTokens.length > 1) {
        console.warn(
          `"until" matches with multi-token stop conditions are not yet implemented`
        );
        break;
      }
      const rawToken = argSpec.stopTokens[0];
      const stopToken = rawToken === " " ? { type: "whitespace" } : rawToken;
      let bracePos2 = findBracePositions(
        nodes,
        startPos,
        void 0,
        stopToken
      );
      if (!bracePos2) {
        break;
      }
      argument = arg(nodes.slice(startPos, bracePos2[1]), {
        openMark: "",
        closeMark: rawToken
      });
      currPos = bracePos2[1];
      if (currPos < nodes.length) {
        currPos++;
      }
      break;
    }
    case "embellishment": {
      for (const token of argSpec.embellishmentTokens) {
        const bracePos2 = findBracePositions(nodes, currPos, token);
        if (!bracePos2) {
          continue;
        }
        let argNode = nodes[bracePos2[0] + 1];
        argument = arg(
          match.group(argNode) ? argNode.content : argNode,
          {
            openMark: token,
            closeMark: ""
          }
        );
        currPos = bracePos2[1] + 1;
        break;
      }
      break;
    }
    default:
      console.warn(
        `Don't know how to find an argument of argspec type "${argSpec.type}"`
      );
  }
  const nodesRemoved = argument ? currPos - startPos : 0;
  nodes.splice(startPos, nodesRemoved);
  return { argument, nodesRemoved };
}
function cloneStringNode(node, content) {
  return Object.assign({}, node, { content });
}
function findBracePositions(nodes, startPos, openMark, closeMark) {
  const currNode = nodes[startPos];
  let openMarkPos = startPos;
  let closeMarkPos = startPos;
  if (openMark) {
    if (!match.anyString(currNode)) {
      return;
    }
    const nodeContent = currNode.content;
    if (!nodeContent.startsWith(openMark)) {
      return;
    }
    openMarkPos = startPos;
    if (currNode.content.length > openMark.length) {
      const nodeContent2 = currNode.content;
      currNode.content = openMark;
      nodes.splice(
        openMarkPos + 1,
        0,
        cloneStringNode(currNode, nodeContent2.slice(openMark.length))
      );
    }
    closeMarkPos = openMarkPos + 1;
  }
  if (!closeMark) {
    const argNode = nodes[closeMarkPos];
    if (!argNode) {
      return;
    }
    if (match.anyString(argNode) && argNode.content.length > 1) {
      const argContent = argNode.content;
      argNode.content = argContent[0];
      nodes.splice(
        closeMarkPos + 1,
        0,
        cloneStringNode(argNode, argContent.slice(1))
      );
    }
    return [openMarkPos, closeMarkPos];
  }
  closeMarkPos = scan(nodes, closeMark, {
    startIndex: closeMarkPos,
    allowSubstringMatches: true
  });
  if (closeMarkPos === null) {
    return;
  }
  const closingNode = nodes[closeMarkPos];
  if (match.anyString(closingNode) && typeof closeMark === "string") {
    const closingNodeContent = closingNode.content;
    let closeMarkIndex = closingNodeContent.indexOf(closeMark);
    if (closingNodeContent.length > closeMark.length) {
      closingNode.content = closeMark;
      const prev = closingNodeContent.slice(0, closeMarkIndex);
      const next = closingNodeContent.slice(
        closeMarkIndex + closeMark.length
      );
      if (prev) {
        nodes.splice(
          closeMarkPos,
          0,
          cloneStringNode(closingNode, prev)
        );
        closeMarkPos++;
      }
      if (next) {
        nodes.splice(
          closeMarkPos + 1,
          0,
          cloneStringNode(closingNode, next)
        );
      }
    }
  }
  return [openMarkPos, closeMarkPos];
}
function gobbleArguments(nodes, argSpec, startPos = 0) {
  if (typeof argSpec === "function") {
    return argSpec(nodes, startPos);
  }
  if (typeof argSpec === "string") {
    argSpec = parse(argSpec);
  }
  const args = [];
  let nodesRemoved = 0;
  for (const spec of argSpec) {
    if (spec.type === "embellishment") {
      const remainingTokens = new Set(spec.embellishmentTokens);
      const argForToken = Object.fromEntries(
        spec.embellishmentTokens.map((t, i) => {
          var _a;
          const defaultArg = "defaultArg" in spec ? (_a = spec.defaultArg) == null ? void 0 : _a[i] : void 0;
          return [t, emptyArg(defaultArg)];
        })
      );
      let { argument, nodesRemoved: removed } = gobbleSingleArgument(
        nodes,
        embellishmentSpec(remainingTokens),
        startPos
      );
      while (argument) {
        const token = argument.openMark;
        remainingTokens.delete(token);
        argForToken[token] = argument;
        nodesRemoved += removed;
        const newSpec = embellishmentSpec(remainingTokens);
        ({ argument, nodesRemoved: removed } = gobbleSingleArgument(
          nodes,
          newSpec,
          startPos
        ));
      }
      args.push(...spec.embellishmentTokens.map((t) => argForToken[t]));
    } else {
      const { argument, nodesRemoved: removed } = gobbleSingleArgument(
        nodes,
        spec,
        startPos
      );
      const defaultArg = "defaultArg" in spec ? spec.defaultArg : void 0;
      args.push(argument || emptyArg(defaultArg));
      nodesRemoved += removed;
    }
  }
  return { args, nodesRemoved };
}
function embellishmentSpec(tokens) {
  return {
    type: "embellishment",
    embellishmentTokens: [...tokens]
  };
}
function emptyArg(defaultArg) {
  const ret = arg([], { openMark: "", closeMark: "" });
  if (defaultArg != null) {
    updateRenderInfo(ret, { defaultArg });
  }
  return ret;
}
function attachMacroArgsInArray(nodes, macros) {
  let currIndex;
  const isRelevantMacro = match.createMacroMatcher(macros);
  function gobbleUntilMacro() {
    while (currIndex >= 0 && !isRelevantMacro(nodes[currIndex])) {
      currIndex--;
    }
  }
  currIndex = nodes.length - 1;
  while (currIndex >= 0) {
    gobbleUntilMacro();
    if (currIndex < 0) {
      return;
    }
    const macroIndex = currIndex;
    const macro = nodes[macroIndex];
    const macroName = macro.content;
    const macroInfo = macros[macroName];
    updateRenderInfo(macro, macroInfo.renderInfo);
    const signatureOrParser = macroInfo.argumentParser || macroInfo.signature;
    if (signatureOrParser == null) {
      currIndex--;
      continue;
    }
    if (macro.args != null) {
      currIndex = macroIndex - 1;
      continue;
    }
    currIndex++;
    const { args } = gobbleArguments(nodes, signatureOrParser, currIndex);
    macro.args = args;
    currIndex = macroIndex - 1;
  }
}
function attachMacroArgs(tree, macros) {
  visit(
    tree,
    (nodes) => {
      attachMacroArgsInArray(nodes, macros);
    },
    { includeArrays: true, test: Array.isArray }
  );
}
const unifiedLatexAttachMacroArguments = function unifiedLatexAttachMacroArguments2(options) {
  return (tree) => {
    const { macros = {} } = options || {};
    if (Object.keys(macros).length === 0) {
      console.warn(
        "Attempting to attach macro arguments but no macros are specified."
      );
    }
    visit(
      tree,
      (nodes) => {
        attachMacroArgsInArray(nodes, macros);
      },
      { includeArrays: true, test: Array.isArray }
    );
  };
};
function getArgsContent(node) {
  if (!Array.isArray(node.args)) {
    return [];
  }
  return node.args.map((arg2) => {
    if (arg2.openMark === "" && arg2.content.length === 0) {
      return null;
    }
    return arg2.content;
  });
}
function getNamedArgsContent(node, namedArgumentsFallback = []) {
  var _a;
  const names = ((_a = node._renderInfo) == null ? void 0 : _a.namedArguments) || namedArgumentsFallback;
  if (!Array.isArray(node.args) || !Array.isArray(names) || names.length === 0) {
    return {};
  }
  const ret = {};
  node.args.forEach((arg2, i) => {
    const name = names[i];
    if (name == null) {
      return;
    }
    let val = arg2.content;
    if (arg2.openMark === "" && arg2.content.length === 0) {
      val = null;
    }
    ret[name] = val;
  });
  return ret;
}
export {
  attachMacroArgs,
  attachMacroArgsInArray,
  getArgsContent,
  getNamedArgsContent,
  gobbleArguments,
  gobbleSingleArgument,
  unifiedLatexAttachMacroArguments
};
//# sourceMappingURL=index.js.map
