text: // @ts-check
  // Module w3c/headers
  // Generate the headers material based on the provided configuration.
  // CONFIGURATION
  //  - specStatus: the short code for the specification's maturity level or type (required)
  //  - shortName: the small name that is used after /TR/ in published reports (required)
  //  - editors: an array of people editing the document (at least one is required). People
  //      are defined using:
  //          - name: the person's name (required)
  //          - url: URI for the person's home page
  //          - company: the person's company
  //          - companyURL: the URI for the person's company
  //          - mailto: the person's email
  //          - note: a note on the person (e.g. former editor)
  //  - authors: an array of people who are contributing authors of the document.
  //  - formerEditors: an array of people that had earlier edited the document but no longer edit.
  //  - subtitle: a subtitle for the specification
  //  - publishDate: the date to use for the publication, default to document.lastModified, and
  //      failing that to now. The format is YYYY-MM-DD or a Date object.
  //  - previousPublishDate: the date on which the previous version was published.
  //  - previousMaturity: the specStatus of the previous version
  //  - errata: the URI of the errata document, if any
  //  - alternateFormats: a list of alternate formats for the document, each of which being
  //      defined by:
  //          - uri: the URI to the alternate
  //          - label: a label for the alternate
  //          - lang: optional language
  //          - type: optional MIME type
  //  - logos: a list of logos to use instead of the W3C logo, each of which being defined by:
  //          - src: the URI to the logo (target of <img src=>)
  //          - alt: alternate text for the image (<img alt=>), defaults to "Logo" or "Logo 1", "Logo 2", ...
  //            if src is not specified, this is the text of the "logo"
  //          - height: optional height of the logo (<img height=>)
  //          - width: optional width of the logo (<img width=>)
  //          - url: the URI to the organization represented by the logo (target of <a href=>)
  //          - id: optional id for the logo, permits custom CSS (wraps logo in <span id=>)
  //          - each logo element must specify either src or alt
  //  - testSuiteURI: the URI to the test suite, if any
  //  - implementationReportURI: the URI to the implementation report, if any
  //  - noRecTrack: set to true if this document is not intended to be on the Recommendation track
  //  - edDraftURI: the URI of the Editor's Draft for this document, if any. Required if
  //      specStatus is set to "ED".
  //  - additionalCopyrightHolders: a copyright owner in addition to W3C (or the only one if specStatus
  //      is unofficial)
  //  - overrideCopyright: provides markup to completely override the copyright
  //  - copyrightStart: the year from which the copyright starts running
  //  - prevED: the URI of the previous Editor's Draft if it has moved
  //  - prevRecShortname: the short name of the previous Recommendation, if the name has changed
  //  - prevRecURI: the URI of the previous Recommendation if not directly generated from
  //    prevRecShortname.
  //  - wg: the name of the WG in charge of the document. This may be an array in which case wgURI
  //      and wgPatentURI need to be arrays as well, of the same length and in the same order
  //  - wgURI: the URI to the group's page, or an array of such
  //  - wgPatentURI: the URI to the group's patent information page, or an array of such. NOTE: this
  //      is VERY IMPORTANT information to provide and get right, do not just paste this without checking
  //      that you're doing it right
  //  - wgPublicList: the name of the mailing list where discussion takes place. Note that this cannot
  //      be an array as it is assumed that there is a single list to discuss the document, even if it
  //      is handled by multiple groups
  //  - charterDisclosureURI: used for IGs (when publishing IG-NOTEs) to provide a link to the IPR commitment
  //      defined in their charter.
  //  - addPatentNote: used to add patent-related information to the SotD, for instance if there's an open
  //      PAG on the document.
  //  - thisVersion: the URI to the dated current version of the specification. ONLY ever use this for CG/BG
  //      documents, for all others it is autogenerated.
  //  - latestVersion: the URI to the latest (undated) version of the specification. ONLY ever use this for CG/BG
  //      documents, for all others it is autogenerated.
  //  - prevVersion: the URI to the previous (dated) version of the specification. ONLY ever use this for CG/BG
  //      documents, for all others it is autogenerated.
  //  - subjectPrefix: the string that is expected to be used as a subject prefix when posting to the mailing
  //      list of the group.
  //  - otherLinks: an array of other links that you might want in the header (e.g., link github, twitter, etc).
  //         Example of usage: [{key: "foo", href:"https://b"}, {key: "bar", href:"https://"}].
  //         Allowed values are:
  //          - key: the key for the <dt> (e.g., "Bug Tracker"). Required.
  //          - value: The value that will appear in the <dd> (e.g., "GitHub"). Optional.
  //          - href: a URL for the value (e.g., "https://foo.com/issues"). Optional.
  //          - class: a string representing CSS classes. Optional.
  //  - license: can be one of the following
  //      - "cc-by", which is experimentally available in some groups (but likely to be phased out).
  //          Note that this is a dual licensing regime.
  //      - "cc0", an extremely permissive license. It is only recommended if you are working on a document that is
  //          intended to be pushed to the WHATWG.
  //      - "w3c-software", a permissive and attributions license (but GPL-compatible).
  //      - "w3c-software-doc", (default) the W3C Software and Document License
  //            https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
  import { ISODate, concatDate, htmlJoinAnd } from "../core/utils.js";
  import cgbgHeadersTmpl from "./templates/cgbg-headers.js";
  import cgbgSotdTmpl from "./templates/cgbg-sotd.js";
  import headersTmpl from "./templates/headers.js";
  import { hyperHTML } from "../core/import-maps.js";
  import { pub } from "../core/pubsubhub.js";
  import sotdTmpl from "./templates/sotd.js";
  
  export const name = "w3c/headers";
  
  const W3CDate = new Intl.DateTimeFormat(["en-AU"], {
    timeZone: "UTC",
    year: "numeric",
    month: "long",
    day: "2-digit",
  });
  
  const status2maturity = {
    LS: "WD",
    LD: "WD",
    FPWD: "WD",
    LC: "WD",
    FPLC: "WD",
    "FPWD-NOTE": "NOTE",
    "WD-NOTE": "WD",
    "LC-NOTE": "LC",
    "IG-NOTE": "NOTE",
    "WG-NOTE": "NOTE",
  };
  
  const status2rdf = {
    NOTE: "w3p:NOTE",
    WD: "w3p:WD",
    LC: "w3p:LastCall",
    CR: "w3p:CR",
    PR: "w3p:PR",
    REC: "w3p:REC",
    PER: "w3p:PER",
    RSCND: "w3p:RSCND",
  };
  const status2text = {
    NOTE: "Working Group Note",
    "WG-NOTE": "Working Group Note",
    "CG-NOTE": "Co-ordination Group Note",
    "IG-NOTE": "Interest Group Note",
    "Member-SUBM": "Member Submission",
    "Team-SUBM": "Team Submission",
    MO: "Member-Only Document",
    ED: "Editor's Draft",
    LS: "Living Standard",
    LD: "Living Document",
    FPWD: "First Public Working Draft",
    WD: "Working Draft",
    "FPWD-NOTE": "Working Group Note",
    "WD-NOTE": "Working Draft",
    "LC-NOTE": "Working Draft",
    FPLC: "First Public and Last Call Working Draft",
    LC: "Last Call Working Draft",
    CR: "Candidate Recommendation",
    PR: "Proposed Recommendation",
    PER: "Proposed Edited Recommendation",
    REC: "Recommendation",
    RSCND: "Rescinded Recommendation",
    unofficial: "Unofficial Draft",
    base: "Document",
    finding: "TAG Finding",
    "draft-finding": "Draft TAG Finding",
    "CG-DRAFT": "Draft Community Group Report",
    "CG-FINAL": "Final Community Group Report",
    "BG-DRAFT": "Draft Business Group Report",
    "BG-FINAL": "Final Business Group Report",
  };
  const status2long = {
    ...status2text,
    "FPWD-NOTE": "First Public Working Group Note",
    "LC-NOTE": "Last Call Working Draft",
  };
  const maybeRecTrack = ["FPWD", "WD"];
  const recTrackStatus = ["FPLC", "LC", "CR", "PR", "PER", "REC"];
  const noTrackStatus = [
    "base",
    "BG-DRAFT",
    "BG-FINAL",
    "CG-DRAFT",
    "CG-FINAL",
    "draft-finding",
    "finding",
    "MO",
    "unofficial",
  ];
  const cgbg = ["CG-DRAFT", "CG-FINAL", "BG-DRAFT", "BG-FINAL"];
  const precededByAn = ["ED", "IG-NOTE"];
  const licenses = {
    cc0: {
      name: "Creative Commons 0 Public Domain Dedication",
      short: "CC0",
      url: "https://creativecommons.org/publicdomain/zero/1.0/",
    },
    "w3c-software": {
      name: "W3C Software Notice and License",
      short: "W3C Software",
      url: "https://www.w3.org/Consortium/Legal/2002/copyright-software-20021231",
    },
    "w3c-software-doc": {
      name: "W3C Software and Document Notice and License",
      short: "W3C Software and Document",
      url:
        "https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document",
    },
    "cc-by": {
      name: "Creative Commons Attribution 4.0 International Public License",
      short: "CC-BY",
      url: "https://creativecommons.org/licenses/by/4.0/legalcode",
    },
  };
  
  const baseLogo = Object.freeze({
    id: "",
    alt: "",
    href: "",
    src: "",
    height: "48",
    width: "72",
  });
  
  /**
   * @param {*} conf
   * @param {string} prop
   * @param {string | number | Date} fallbackDate
   */
  function validateDateAndRecover(conf, prop, fallbackDate = new Date()) {
    const date = conf[prop] ? new Date(conf[prop]) : new Date(fallbackDate);
    // if date is valid
    if (Number.isFinite(date.valueOf())) {
      const formattedDate = ISODate.format(date);
      return new Date(formattedDate);
    }
    const msg =
      `[\`${prop}\`](https://github.com/w3c/respec/wiki/${prop}) ` +
      `is not a valid date: "${conf[prop]}". Expected format 'YYYY-MM-DD'.`;
    pub("error", msg);
    return new Date(ISODate.format(new Date()));
  }
  
  export function run(conf) {
    conf.isUnofficial = conf.specStatus === "unofficial";
    if (conf.isUnofficial && !Array.isArray(conf.logos)) {
      conf.logos = [];
    }
    conf.isCCBY = conf.license === "cc-by";
    conf.isW3CSoftAndDocLicense = conf.license === "w3c-software-doc";
    if (["cc-by"].includes(conf.license)) {
      let msg = `You cannot use license "\`${conf.license}\`" with W3C Specs. `;
      msg += `Please set \`respecConfig.license: "w3c-software-doc"\` instead.`;
      pub("error", msg);
    }
    conf.licenseInfo = licenses[conf.license];
    conf.isCGBG = cgbg.includes(conf.specStatus);
    conf.isCGFinal = conf.isCGBG && conf.specStatus.endsWith("G-FINAL");
    conf.isBasic = conf.specStatus === "base";
    conf.isRegular = !conf.isCGBG && !conf.isBasic;
    if (!conf.specStatus) {
      pub("error", "Missing required configuration: `specStatus`");
    }
    if (conf.isRegular && !conf.shortName) {
      pub("error", "Missing required configuration: `shortName`");
    }
    if (conf.testSuiteURI) {
      const url = new URL(conf.testSuiteURI, location.href);
      const { host, pathname } = url;
      if (
        host === "github.com" &&
        pathname.startsWith("/w3c/web-platform-tests/")
      ) {
        const msg =
          "Web Platform Tests have moved to a new Github Organization at https://github.com/web-platform-tests. " +
          "Please update your [`testSuiteURI`](https://github.com/w3c/respec/wiki/testSuiteURI) to point to the " +
          `new tests repository (e.g., https://github.com/web-platform-tests/wpt/${conf.shortName} ).`;
        pub("warn", msg);
      }
    }
    conf.title = document.title || "No Title";
    if (document.title && conf.isPreview && conf.prNumber) {
      document.title = `Preview of PR #${conf.prNumber}: ${document.title}`;
    }
    if (!conf.subtitle) conf.subtitle = "";
    conf.publishDate = validateDateAndRecover(
      conf,
      "publishDate",
      document.lastModified
    );
    conf.publishYear = conf.publishDate.getUTCFullYear();
    conf.publishHumanDate = W3CDate.format(conf.publishDate);
    conf.isNoTrack = noTrackStatus.includes(conf.specStatus);
    conf.isRecTrack = conf.noRecTrack
      ? false
      : recTrackStatus.concat(maybeRecTrack).includes(conf.specStatus);
    conf.isMemberSubmission = conf.specStatus === "Member-SUBM";
    if (conf.isMemberSubmission) {
      const memSubmissionLogo = {
        alt: "W3C Member Submission",
        href: "https://www.w3.org/Submission/",
        src: "https://www.w3.org/Icons/member_subm-v.svg",
        width: "211",
      };
      conf.logos.push({ ...baseLogo, ...memSubmissionLogo });
    }
    conf.isTeamSubmission = conf.specStatus === "Team-SUBM";
    if (conf.isTeamSubmission) {
      const teamSubmissionLogo = {
        alt: "W3C Team Submission",
        href: "https://www.w3.org/TeamSubmission/",
        src: "https://www.w3.org/Icons/team_subm-v.svg",
        width: "211",
      };
      conf.logos.push({ ...baseLogo, ...teamSubmissionLogo });
    }
    conf.isSubmission = conf.isMemberSubmission || conf.isTeamSubmission;
    conf.anOrA = precededByAn.includes(conf.specStatus) ? "an" : "a";
    conf.isTagFinding =
      conf.specStatus === "finding" || conf.specStatus === "draft-finding";
    if (!conf.edDraftURI) {
      conf.edDraftURI = "";
      if (conf.specStatus === "ED")
        pub("warn", "Editor's Drafts should set edDraftURI.");
    }
    conf.maturity = status2maturity[conf.specStatus]
      ? status2maturity[conf.specStatus]
      : conf.specStatus;
    let publishSpace = "TR";
    if (conf.specStatus === "Member-SUBM") publishSpace = "Submission";
    else if (conf.specStatus === "Team-SUBM") publishSpace = "TeamSubmission";
    if (conf.isRegular)
      conf.thisVersion = `https://www.w3.org/${publishSpace}/${conf.publishDate.getUTCFullYear()}/${
        conf.maturity
      }-${conf.shortName}-${concatDate(conf.publishDate)}/`;
    if (conf.specStatus === "ED") conf.thisVersion = conf.edDraftURI;
    if (conf.isRegular)
      conf.latestVersion = `https://www.w3.org/${publishSpace}/${conf.shortName}/`;
    if (conf.isTagFinding) {
      conf.latestVersion = `https://www.w3.org/2001/tag/doc/${conf.shortName}`;
      conf.thisVersion = `${conf.latestVersion}-${ISODate.format(
        conf.publishDate
      )}`;
    }
    if (conf.previousPublishDate) {
      if (!conf.previousMaturity && !conf.isTagFinding) {
        pub("error", "`previousPublishDate` is set, but not `previousMaturity`.");
      }
  
      conf.previousPublishDate = validateDateAndRecover(
        conf,
        "previousPublishDate"
      );
  
      const pmat = status2maturity[conf.previousMaturity]
        ? status2maturity[conf.previousMaturity]
        : conf.previousMaturity;
      if (conf.isTagFinding) {
        conf.prevVersion = `${conf.latestVersion}-${ISODate.format(
          conf.previousPublishDate
        )}`;
      } else if (conf.isCGBG) {
        conf.prevVersion = conf.prevVersion || "";
      } else if (conf.isBasic) {
        conf.prevVersion = "";
      } else {
        conf.prevVersion = `https://www.w3.org/TR/${conf.previousPublishDate.getUTCFullYear()}/${pmat}-${
          conf.shortName
        }-${concatDate(conf.previousPublishDate)}/`;
      }
    } else {
      if (
        !conf.specStatus.endsWith("NOTE") &&
        conf.specStatus !== "FPWD" &&
        conf.specStatus !== "FPLC" &&
        conf.specStatus !== "ED" &&
        !conf.noRecTrack &&
        !conf.isNoTrack &&
        !conf.isSubmission
      )
        pub(
          "error",
          "Document on track but no previous version:" +
            " Add `previousMaturity`, and `previousPublishDate` to ReSpec's config."
        );
      if (!conf.prevVersion) conf.prevVersion = "";
    }
    if (conf.prevRecShortname && !conf.prevRecURI)
      conf.prevRecURI = `https://www.w3.org/TR/${conf.prevRecShortname}`;
    const peopCheck = function(it) {
      if (!it.name) pub("error", "All authors and editors must have a name.");
      if (it.orcid) {
        try {
          it.orcid = normalizeOrcid(it.orcid);
        } catch (e) {
          pub("error", `"${it.orcid}" is not an ORCID. ${e.message}`);
          // A failed orcid link could link to something outside of orcid,
          // which would be misleading.
          delete it.orcid;
        }
      }
    };
    if (!conf.formerEditors) conf.formerEditors = [];
    if (conf.editors) {
      conf.editors.forEach(peopCheck);
      // Move any editors with retiredDate to formerEditors.
      for (let i = 0; i < conf.editors.length; i++) {
        const editor = conf.editors[i];
        if ("retiredDate" in editor) {
          conf.formerEditors.push(editor);
          conf.editors.splice(i--, 1);
        }
      }
    }
    if (!conf.editors || conf.editors.length === 0)
      pub("error", "At least one editor is required");
    if (conf.formerEditors.length) {
      conf.formerEditors.forEach(peopCheck);
    }
    if (conf.authors) {
      conf.authors.forEach(peopCheck);
    }
    conf.multipleEditors = conf.editors && conf.editors.length > 1;
    conf.multipleFormerEditors = conf.formerEditors.length > 1;
    conf.multipleAuthors = conf.authors && conf.authors.length > 1;
    (conf.alternateFormats || []).forEach(it => {
      if (!it.uri || !it.label) {
        pub("error", "All alternate formats must have a uri and a label.");
      }
    });
    conf.multipleAlternates =
      conf.alternateFormats && conf.alternateFormats.length > 1;
    conf.alternatesHTML =
      conf.alternateFormats &&
      htmlJoinAnd(conf.alternateFormats, alt => {
        const lang = alt.hasOwnProperty("lang") && alt.lang ? alt.lang : null;
        const type = alt.hasOwnProperty("type") && alt.type ? alt.type : null;
        return hyperHTML`<a rel='alternate' href='${alt.uri}' hreflang='${lang}' type='${type}'>${alt.label}</a>`;
      });
    if (conf.copyrightStart && conf.copyrightStart == conf.publishYear)
      conf.copyrightStart = "";
    conf.longStatus = status2long[conf.specStatus];
    conf.textStatus = status2text[conf.specStatus];
    if (status2rdf[conf.specStatus]) {
      conf.rdfStatus = status2rdf[conf.specStatus];
    }
    conf.showThisVersion = !conf.isNoTrack || conf.isTagFinding;
    conf.showPreviousVersion =
      conf.specStatus !== "FPWD" &&
      conf.specStatus !== "FPLC" &&
      conf.specStatus !== "ED" &&
      !conf.isNoTrack &&
      !conf.isSubmission;
    if (conf.specStatus.endsWith("NOTE") && !conf.prevVersion)
      conf.showPreviousVersion = false;
    if (conf.isTagFinding)
      conf.showPreviousVersion = conf.previousPublishDate ? true : false;
    conf.notYetRec = conf.isRecTrack && conf.specStatus !== "REC";
    conf.isRec = conf.isRecTrack && conf.specStatus === "REC";
    if (conf.isRec && !conf.errata)
      pub("error", "Recommendations must have an errata link.");
    conf.notRec = conf.specStatus !== "REC";
    conf.prependW3C = !conf.isUnofficial;
    conf.isED = conf.specStatus === "ED";
    conf.isCR = conf.specStatus === "CR";
    conf.isPR = conf.specStatus === "PR";
    conf.isPER = conf.specStatus === "PER";
    conf.isMO = conf.specStatus === "MO";
    conf.isNote = ["FPWD-NOTE", "WG-NOTE"].includes(conf.specStatus);
    conf.isIGNote = conf.specStatus === "IG-NOTE";
    conf.dashDate = ISODate.format(conf.publishDate);
    conf.publishISODate = conf.publishDate.toISOString();
    conf.shortISODate = ISODate.format(conf.publishDate);
    if (conf.hasOwnProperty("wgPatentURI") && !Array.isArray(conf.wgPatentURI)) {
      Object.defineProperty(conf, "wgId", {
        get() {
          // it's always at "pp-impl" + 1
          const urlParts = this.wgPatentURI.split("/");
          const pos = urlParts.findIndex(item => item === "pp-impl") + 1;
          return urlParts[pos] || "";
        },
      });
    } else {
      conf.wgId = conf.wgId ? conf.wgId : "";
    }
    // configuration done - yay!
  
    // insert into document
    const header = (conf.isCGBG ? cgbgHeadersTmpl : headersTmpl)(conf);
    document.body.prepend(header);
    document.body.classList.add("h-entry");
  
    // handle SotD
    const sotd =
      document.getElementById("sotd") || document.createElement("section");
    if ((conf.isCGBG || !conf.isNoTrack || conf.isTagFinding) && !sotd.id) {
      pub(
        "error",
        "A custom SotD paragraph is required for your type of document."
      );
    }
    sotd.id = sotd.id || "sotd";
    sotd.classList.add("introductory");
    // NOTE:
    //  When arrays, wg and wgURI have to be the same length (and in the same order).
    //  Technically wgURI could be longer but the rest is ignored.
    //  However wgPatentURI can be shorter. This covers the case where multiple groups
    //  publish together but some aren't used for patent policy purposes (typically this
    //  happens when one is foolish enough to do joint work with the TAG). In such cases,
    //  the groups whose patent policy applies need to be listed first, and wgPatentURI
    //  can be shorter — but it still needs to be an array.
    const wgPotentialArray = [conf.wg, conf.wgURI, conf.wgPatentURI];
    if (
      wgPotentialArray.some(item => Array.isArray(item)) &&
      !wgPotentialArray.every(item => Array.isArray(item))
    ) {
      pub(
        "error",
        "If one of '`wg`', '`wgURI`', or '`wgPatentURI`' is an array, they all have to be."
      );
    }
    if (conf.isCGBG && !conf.wg) {
      pub(
        "error",
        "[`wg`](https://github.com/w3c/respec/wiki/wg)" +
          " configuration option is required for this kind of document."
      );
    }
    if (Array.isArray(conf.wg)) {
      conf.multipleWGs = conf.wg.length > 1;
      conf.wgHTML = htmlJoinAnd(conf.wg, (wg, idx) => {
        return hyperHTML`the <a href='${conf.wgURI[idx]}'>${wg}</a>`;
      });
      const pats = [];
      for (let i = 0, n = conf.wg.length; i < n; i++) {
        pats.push(
          hyperHTML`a <a href='${conf.wgPatentURI[i]}' rel='disclosure'>public list of any patent disclosures (${conf.wg[i]})</a>`
        );
      }
      conf.wgPatentHTML = htmlJoinAnd(pats);
    } else {
      conf.multipleWGs = false;
      if (conf.wg) {
        conf.wgHTML = hyperHTML`the <a href='${conf.wgURI}'>${conf.wg}</a>`;
      }
    }
    if (conf.specStatus === "PR" && !conf.crEnd) {
      pub(
        "error",
        `\`specStatus\` is "PR" but no \`crEnd\` is specified (needed to indicate end of previous CR).`
      );
    }
  
    if (conf.specStatus === "CR" && !conf.crEnd) {
      pub(
        "error",
        `\`specStatus\` is "CR", but no \`crEnd\` is specified in Respec config.`
      );
    }
    conf.crEnd = validateDateAndRecover(conf, "crEnd");
    conf.humanCREnd = W3CDate.format(conf.crEnd);
  
    if (conf.specStatus === "PR" && !conf.prEnd) {
      pub("error", `\`specStatus\` is "PR" but no \`prEnd\` is specified.`);
    }
    conf.prEnd = validateDateAndRecover(conf, "prEnd");
    conf.humanPREnd = W3CDate.format(conf.prEnd);
  
    if (conf.specStatus === "PER" && !conf.perEnd) {
      pub("error", "Status is PER but no perEnd is specified");
    }
    conf.perEnd = validateDateAndRecover(conf, "perEnd");
    conf.humanPEREnd = W3CDate.format(conf.perEnd);
    conf.recNotExpected =
      conf.noRecTrack || conf.recNotExpected
        ? true
        : !conf.isRecTrack &&
          conf.maturity == "WD" &&
          conf.specStatus !== "FPWD-NOTE";
    if (conf.noRecTrack && recTrackStatus.includes(conf.specStatus)) {
      pub(
        "error",
        `Document configured as [\`noRecTrack\`](https://github.com/w3c/respec/wiki/noRecTrack), but its status ("${
          conf.specStatus
        }") puts it on the W3C Rec Track. Status cannot be any of: ${recTrackStatus.join(
          ", "
        )}. [More info](https://github.com/w3c/respec/wiki/noRecTrack).`
      );
    }
    if (conf.isIGNote && !conf.charterDisclosureURI) {
      pub(
        "error",
        "IG-NOTEs must link to charter's disclosure section using `charterDisclosureURI`."
      );
    }
    if (!sotd.classList.contains("override")) {
      hyperHTML.bind(sotd)`${populateSoTD(conf, sotd)}`;
    }
  
    if (!conf.implementationReportURI && conf.isCR) {
      pub(
        "error",
        "CR documents must have an [`implementationReportURI`](https://github.com/w3c/respec/wiki/implementationReportURI) " +
          "that describes [implementation experience](https://www.w3.org/2019/Process-20190301/#implementation-experience)."
      );
    }
    if (!conf.implementationReportURI && conf.isPR) {
      pub(
        "warn",
        "PR documents should include an " +
          " [`implementationReportURI`](https://github.com/w3c/respec/wiki/implementationReportURI)" +
          " that describes [implementation experience](https://www.w3.org/2019/Process-20190301/#implementation-experience)."
      );
    }
  
    // Requested by https://github.com/w3c/respec/issues/504
    // Makes a record of a few auto-generated things.
    pub("amend-user-config", {
      publishISODate: conf.publishISODate,
      generatedSubtitle: `${conf.longStatus} ${conf.publishHumanDate}`,
    });
  }
  
  /**
   * @param {*} conf
   * @param {HTMLElement} sotd
   */
  function populateSoTD(conf, sotd) {
    const options = {
      ...collectSotdContent(sotd, conf),
  
      get mailToWGPublicList() {
        return `mailto:${conf.wgPublicList}@w3.org`;
      },
      get mailToWGPublicListWithSubject() {
        const fragment = conf.subjectPrefix
          ? `?subject=${encodeURIComponent(conf.subjectPrefix)}`
          : "";
        return this.mailToWGPublicList + fragment;
      },
      get mailToWGPublicListSubscription() {
        return `mailto:${conf.wgPublicList}-request@w3.org?subject=subscribe`;
      },
    };
    const template = conf.isCGBG ? cgbgSotdTmpl : sotdTmpl;
    return template(conf, options);
  }
  
  /**
   * @param {HTMLElement} sotd
   */
  function collectSotdContent(sotd, { isTagFinding = false }) {
    const sotdClone = sotd.cloneNode(true);
    const additionalContent = document.createDocumentFragment();
    // we collect everything until we hit a section,
    // that becomes the custom content.
    while (sotdClone.hasChildNodes()) {
      if (
        isElement(sotdClone.firstChild) &&
        sotdClone.firstChild.localName === "section"
      ) {
        break;
      }
      additionalContent.appendChild(sotdClone.firstChild);
    }
    if (isTagFinding && !additionalContent.hasChildNodes()) {
      pub(
        "warn",
        "ReSpec does not support automated SotD generation for TAG findings, " +
          "please add the prerequisite content in the 'sotd' section"
      );
    }
    return {
      additionalContent,
      // Whatever sections are left, we throw at the end.
      additionalSections: sotdClone.childNodes,
    };
  }
  
  /**
   * @param {string} orcid Either an ORCID URL or just the 16-digit ID which comes after the /
   * @return {string} the full ORCID URL. Throws an error if the ID is invalid.
   */
  function normalizeOrcid(orcid) {
    const orcidUrl = new URL(orcid, "https://orcid.org/");
    if (orcidUrl.origin !== "https://orcid.org") {
      throw new Error(
        `The origin should be "https://orcid.org", not "${orcidUrl.origin}".`
      );
    }
  
    // trailing slash would mess up checksum
    const orcidId = orcidUrl.pathname.slice(1).replace(/\/$/, "");
    if (!/^\d{4}-\d{4}-\d{4}-\d{3}(\d|X)$/.test(orcidId)) {
      throw new Error(
        `ORCIDs have the format "1234-1234-1234-1234", not "${orcidId}"`
      );
    }
  
    // calculate checksum as per https://support.orcid.org/hc/en-us/articles/360006897674-Structure-of-the-ORCID-Identifier
    const lastDigit = orcidId[orcidId.length - 1];
    const remainder = orcidId
      .split("")
      .slice(0, -1)
      .filter(c => /\d/.test(c))
      .map(Number)
      .reduce((acc, c) => (acc + c) * 2, 0);
    const lastDigitInt = (12 - (remainder % 11)) % 11;
    const lastDigitShould = lastDigitInt === 10 ? "X" : String(lastDigitInt);
    if (lastDigit !== lastDigitShould) {
      throw new Error(`"${orcidId}" has an invalid checksum.`);
    }
  
    return orcidUrl.href;
  }
  
  /**
   * @param {Node} node
   * @return {node is Element}
   */
  function isElement(node) {
    return node.nodeType === Node.ELEMENT_NODE;
  }
  