text: // @ts-check
  // Module core/inlines
  // Process all manners of inline information. These are done together despite it being
  // seemingly a better idea to orthogonalise them. The issue is that processing text nodes
  // is harder to orthogonalise, and in some browsers can also be particularly slow.
  // Things that are recognised are <abbr>/<acronym> which when used once are applied
  // throughout the document, [[REFERENCES]]/[[!REFERENCES]], {{ IDL }} and RFC2119 keywords.
  
  import {
    InsensitiveStringSet,
    getTextNodes,
    norm,
    refTypeFromContext,
    showInlineError,
    showInlineWarning,
  } from "./utils.js";
  import hyperHTML from "hyperhtml";
  import { idlStringToHtml } from "./inline-idl-parser.js";
  import { renderInlineCitation } from "./render-biblio.js";
  
  export const name = "core/inlines";
  export const rfc2119Usage = {};
  
  // Inline `code`
  // TODO: Replace (?!`) at the end with (?:<!`) at the start when Firefox + Safari
  // add support.
  const inlineCodeRegExp = /(?:`[^`]+`)(?!`)/; // `code`
  const inlineIdlReference = /(?:{{[^}]+}})/; // {{ WebIDLThing }}
  const inlineVariable = /\B\|\w[\w\s]*(?:\s*:[\w\s&;<>]+)?\|\B/; // |var : Type|
  const inlineCitation = /(?:\[\[(?:!|\\|\?)?[A-Za-z0-9.-]+\]\])/; // [[citation]]
  const inlineExpansion = /(?:\[\[\[(?:!|\\|\?)?#?[\w-.]+\]\]\])/; // [[[expand]]]
  const inlineAnchor = /(?:\[=[^=]+=\])/; // Inline [= For/link =]
  const inlineElement = /(?:\[\^[A-Za-z]+(?:-[A-Za-z]+)?\^\])/; // Inline [^element^]
  
  /**
   * @param {string} matched
   * @return {HTMLElement}
   */
  function inlineElementMatches(matched) {
    const value = matched.slice(2, -2).trim();
    const html = hyperHTML`<code><a data-xref-type="element">${value}</a></code>`;
    return html;
  }
  
  /**
   * @param {string} matched
   * @return {HTMLElement}
   */
  function inlineRFC2119Matches(matched) {
    const value = norm(matched);
    const nodeElement = hyperHTML`<em class="rfc2119" title="${value}">${value}</em>`;
    // remember which ones were used
    rfc2119Usage[value] = true;
    return nodeElement;
  }
  
  /**
   * @param {string} matched
   * @return {HTMLElement}
   */
  function inlineRefMatches(matched) {
    // slices "[[[" at the beginning and "]]]" at the end
    const ref = matched.slice(3, -3).trim();
    if (!ref.startsWith("#")) {
      return hyperHTML`<a data-cite="${ref}"></a>`;
    }
    if (document.querySelector(ref)) {
      return hyperHTML`<a href="${ref}"></a>`;
    }
    const badReference = hyperHTML`<span>${matched}</span>`;
    showInlineError(
      badReference, // cite element
      `Wasn't able to expand ${matched} as it didn't match any id in the document.`,
      `Please make sure there is element with id ${ref} in the document.`
    );
    return badReference;
  }
  
  /**
   * @param {string} matched
   */
  function inlineXrefMatches(matched) {
    // slices "{{" at the beginning and "}}" at the end
    const ref = matched.slice(2, -2).trim();
    return ref.startsWith("\\")
      ? matched.replace("\\", "")
      : idlStringToHtml(norm(ref));
  }
  
  /**
   * @param {string} matched
   * @param {Text} txt
   * @param {Object} conf
   * @return {Iterable<string | Node>}
   */
  function inlineBibrefMatches(matched, txt, conf) {
    // slices "[[" at the start and "]]" at the end
    const ref = matched.slice(2, -2);
    if (ref.startsWith("\\")) {
      return [`[[${ref.slice(1)}]]`];
    }
    const { type, illegal } = refTypeFromContext(ref, txt.parentNode);
    const cite = renderInlineCitation(ref);
    const cleanRef = ref.replace(/^(!|\?)/, "");
    if (illegal && !conf.normativeReferences.has(cleanRef)) {
      showInlineWarning(
        cite.childNodes[1], // cite element
        "Normative references in informative sections are not allowed. " +
          `Remove '!' from the start of the reference \`[[${ref}]]\``
      );
    }
  
    if (type === "informative" && !illegal) {
      conf.informativeReferences.add(cleanRef);
    } else {
      conf.normativeReferences.add(cleanRef);
    }
    return cite.childNodes;
  }
  
  /**
   * @param {string} matched
   * @param {Text} txt
   * @param {Map<string, string>} abbrMap
   */
  function inlineAbbrMatches(matched, txt, abbrMap) {
    return txt.parentElement.tagName === "ABBR"
      ? matched
      : hyperHTML`<abbr title="${abbrMap.get(matched)}">${matched}</abbr>`;
  }
  
  /**
   * @example |varName: type| => <var data-type="type">varName</var>
   * @example |varName| => <var>varName</var>
   * @param {string} matched
   */
  function inlineVariableMatches(matched) {
    // remove "|" at the beginning and at the end, then split at an optional `:`
    const matches = matched.slice(1, -1).split(":", 2);
    const [varName, type] = matches.map(s => s.trim());
    return hyperHTML`<var data-type="${type}">${varName}</var>`;
  }
  
  function inlineAnchorMatches(matched) {
    const parts = matched
      .slice(2, -2) // Chop [= =]
      .split("/", 2)
      .map(s => s.trim());
    const [isFor, content] = parts.length === 2 ? parts : ["", parts[0]];
    const processedContent = processInlineContent(content);
    const forValue = norm(isFor);
    return hyperHTML`<a data-link-for="${forValue}" data-xref-for="${forValue}">${processedContent}</a>`;
  }
  
  function inlineCodeMatches(matched) {
    const clean = matched.slice(1, -1); // Chop ` and `
    return hyperHTML`<code>${clean}</code>`;
  }
  
  function processInlineContent(text) {
    if (inlineCodeRegExp.test(text)) {
      // We use a capture group to split, so we can process all the parts.
      return text.split(/(`[^`]+`)(?!`)/).map(part => {
        return part.startsWith("`")
          ? inlineCodeMatches(part)
          : processInlineContent(part);
      });
    }
    return document.createTextNode(text);
  }
  
  export function run(conf) {
    const abbrMap = new Map();
    document.normalize();
    if (!document.querySelector("section#conformance")) {
      // make the document informative
      document.body.classList.add("informative");
    }
    conf.normativeReferences = new InsensitiveStringSet();
    conf.informativeReferences = new InsensitiveStringSet();
  
    if (!conf.respecRFC2119) conf.respecRFC2119 = rfc2119Usage;
  
    // PRE-PROCESSING
    /** @type {NodeListOf<HTMLElement>} */
    const abbrs = document.querySelectorAll("abbr[title]");
    for (const abbr of abbrs) {
      abbrMap.set(abbr.textContent, abbr.title);
    }
    const aKeys = [...abbrMap.keys()];
    const abbrRx = aKeys.length ? `(?:\\b${aKeys.join("\\b)|(?:\\b")}\\b)` : null;
  
    // PROCESSING
    // Don't gather text nodes for these:
    const exclusions = ["#respec-ui", ".head", "pre"];
    const txts = getTextNodes(document.body, exclusions, {
      wsNodes: false, // we don't want nodes with just whitespace
    });
    const keywords = new RegExp(
      [
        "\\bMUST(?:\\s+NOT)?\\b",
        "\\bSHOULD(?:\\s+NOT)?\\b",
        "\\bSHALL(?:\\s+NOT)?\\b",
        "\\bMAY\\b",
        "\\b(?:NOT\\s+)?REQUIRED\\b",
        "\\b(?:NOT\\s+)?RECOMMENDED\\b",
        "\\bOPTIONAL\\b",
      ].join("|")
    );
    const rx = new RegExp(
      `(${[
        keywords.source,
        inlineIdlReference.source,
        inlineVariable.source,
        inlineCitation.source,
        inlineExpansion.source,
        inlineAnchor.source,
        inlineCodeRegExp.source,
        inlineElement.source,
        ...(abbrRx ? [abbrRx] : []),
      ].join("|")})`
    );
    for (const txt of txts) {
      const subtxt = txt.data.split(rx);
      if (subtxt.length === 1) continue;
      const df = document.createDocumentFragment();
      let matched = true;
      for (const t of subtxt) {
        matched = !matched;
        if (!matched) {
          df.append(t);
        } else if (t.startsWith("{{")) {
          const node = inlineXrefMatches(t);
          df.append(node);
        } else if (t.startsWith("[[[")) {
          const node = inlineRefMatches(t);
          df.append(node);
        } else if (t.startsWith("[[")) {
          const nodes = inlineBibrefMatches(t, txt, conf);
          df.append(...nodes);
        } else if (t.startsWith("|")) {
          const node = inlineVariableMatches(t);
          df.append(node);
        } else if (t.startsWith("[=")) {
          const node = inlineAnchorMatches(t);
          df.append(node);
        } else if (t.startsWith("`")) {
          const node = inlineCodeMatches(t);
          df.append(node);
        } else if (t.startsWith("[^")) {
          const node = inlineElementMatches(t);
          df.append(node);
        } else if (abbrMap.has(t)) {
          const node = inlineAbbrMatches(t, txt, abbrMap);
          df.append(node);
        } else if (keywords.test(t)) {
          const node = inlineRFC2119Matches(t);
          df.append(node);
        } else {
          // FAIL -- not sure that this can really happen
          throw new Error(
            `Found token '${t}' but it does not correspond to anything`
          );
        }
      }
      txt.replaceWith(df);
    }
  }
  