UNPKG

@gmb/bitmark-parser-generator

Version:
45,823 lines 1.52 MB
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

// src/config/Config.ts
import { Enum as Enum3 } from "@ncoderz/superenum";

// src/model/enum/BitType.ts
import "@ncoderz/superenum";
var BitType2 = {
  _error: "_error",
  // Used for error handling to indicate a bit type that is not supported or a bit parse error
  _comment: "_comment",
  // Used to indicate a bit is commented out
  _standard: "_standard",
  // Not to be used as a bit, but as a base for other bit types
  advertising: "advertising",
  advertisingAdvertorial: "advertising-advertorial",
  advertisingAdvertorialPage: "advertising-advertorial-page",
  advertisingCallToAction: "advertising-call-to-action",
  advertisingCallToActionMeeting: "advertising-call-to-action-meeting",
  advertisingBanner: "advertising-banner",
  advertisingSkyscraper: "advertising-skyscraper",
  advertisingRectangle: "advertising-rectangle",
  advertisingFullPage: "advertising-full-page",
  advertisingHalfPage: "advertising-half-page",
  advertisingQuarterPage: "advertising-quarter-page",
  advertisingClassifiedPage: "advertising-classified-page",
  advertisingLandscape: "advertising-landscape",
  advertisingPortrait: "advertising-portrait",
  aiChat: "ai-chat",
  aiEditor: "ai-editor",
  aiPrompt: "ai-prompt",
  aiTutor: "ai-tutor",
  aiWbt: "ai-wbt",
  anchor: "anchor",
  appAiPrompt: "app-ai-prompt",
  appBitmarkFromEditor: "app-bitmark-from-editor",
  appBitmarkFromJavascript: "app-bitmark-from-javascript",
  appCalculatorLatex: "app-calculator-latex",
  appCodeCell: "app-code-cell",
  appCodeEditor: "app-code-editor",
  appCodeIde: "app-code-ide",
  appCreateBitsFromImage: "app-create-bits-from-image",
  appFlashcards: "app-flashcards",
  appFlashcardsLearn: "app-flashcards-learn",
  appFlashcardsQuiz: "app-flashcards-quiz",
  appGetScreenshot: "app-get-screenshot",
  appLink: "app-link",
  appRag: "app-rag",
  appSearch: "app-search",
  article: "article",
  articleAi: "article-ai",
  articleAlt: "article-alt",
  articleAttachment: "article-attachment",
  articleAuthor: "article-author",
  articleEmbed: "article-embed",
  articleLink: "article-link",
  articleResponsive: "article-responsive",
  articleResponsiveAlt: "article-responsive-alt",
  assignment: "assignment",
  assignmentList: "assignment-list",
  audio: "audio",
  audioEmbed: "audio-embed",
  audioLink: "audio-link",
  audioTranscript: "audio-transcript",
  author: "author",
  authorContentBitGenerator: "author-content-bit-generator",
  bitAlias: "bit-alias",
  bitBookEnding: "bit-book-ending",
  bitBookSummary: "bit-book-summary",
  bitmarkExample: "bitmark-example",
  blogArticle: "blog-article",
  book: "book",
  bookAcknowledgements: "book-acknowledgements",
  bookAddendum: "book-addendum",
  bookAfterword: "book-afterword",
  bookAlias: "book-alias",
  bookAppendix: "book-appendix",
  bookArticle: "book-article",
  bookAuthor: "book-author",
  bookAutherBio: "book-author-bio",
  bookBibliography: "book-bibliography",
  bookClose: "book-close",
  bookComingSoon: "book-coming-soon",
  bookConclusion: "book-conclusion",
  bookCopyright: "book-copyright",
  bookCopyrightPermissions: "book-copyright-permissions",
  bookCover: "book-cover",
  bookDedication: "book-dedication",
  bookDiff: "book-diff",
  bookEnd: "book-end",
  bookEndnotes: "book-endnotes",
  bookEpigraph: "book-epigraph",
  bookEpilogue: "book-epilogue",
  bookForword: "book-foreword",
  bookFrontispiece: "book-frontispiece",
  bookImprint: "book-imprint",
  bookIncitingIncident: "book-inciting-incident",
  bookIntroduction: "book-introduction",
  bookLink: "book-link",
  bookLinkNext: "book-link-next",
  bookLinkPrev: "book-link-prev",
  bookListOfContributors: "book-list-of-contributors",
  bookNotes: "book-notes",
  bookPostscript: "book-postscript",
  bookPreface: "book-preface",
  bookPrologue: "book-prologue",
  bookReadMore: "book-read-more",
  bookReference: "book-reference",
  bookReferenceList: "book-reference-list",
  bookRequestForABookReview: "book-request-for-a-book-review",
  bookSummary: "book-summary",
  bookTeaser: "book-teaser",
  bookTitle: "book-title",
  botActionAnnounce: "bot-action-announce",
  botActionRatingNumber: "bot-action-rating-number",
  botActionRemind: "bot-action-remind",
  botActionResponse: "bot-action-response",
  botActionSave: "bot-action-save",
  botActionSend: "bot-action-send",
  botActionTrueFalse: "bot-action-true-false",
  botInterview: "bot-interview",
  brandColor: "brand-color",
  browserImage: "browser-image",
  bug: "bug",
  bugAlt: "bug-alt",
  bugCollapsible: "bug-collapsible",
  buttonCopyText: "button-copy-text",
  catalogItemBook: "catalog-item-book",
  catalogItemExternalBook: "catalog-item-external-book",
  catalogItemProduct: "catalog-item-product",
  callToAction: "call-to-action",
  callToActionBookNow: "call-to-action-book-now",
  callToActionContact: "call-to-action-contact",
  callToActionCreateAccount: "call-to-action-create-account",
  callToActionDownload: "call-to-action-download",
  callToActionGetNow: "call-to-action-get-now",
  callToActionGetOffer: "call-to-action-get-offer",
  callToActionJoin: "call-to-action-join",
  callToActionLearnMore: "call-to-action-learn-more",
  callToActionMail: "call-to-action-mail",
  callToActionSeeMore: "call-to-action-see-more",
  callToActionShopNow: "call-to-action-shop-now",
  callToActionStartNow: "call-to-action-start-now",
  callToActionSubscribe: "call-to-action-subscribe",
  callToActionWatch: "call-to-action-watch",
  card1: "card-1",
  chapter: "chapter",
  chapterSubjectMatter: "chapter-subject-matter",
  chat: "chat",
  checklist: "checklist",
  cloze: "cloze",
  clozeAndMultipleChoiceText: "cloze-and-multiple-choice-text",
  clozeInstructionGrouped: "cloze-instruction-grouped",
  clozeList: "cloze-list",
  clozeSeveral: "cloze-several",
  clozeSolutionGrouped: "cloze-solution-grouped",
  coachAudioTranscript: "coach-audio-transcript",
  coachCallToActionChecklist: "coach-call-to-action-checklist",
  coachCallToActionCloze: "coach-call-to-action-cloze",
  coachCallToActionClozeAndMultipleChoiceText: "coach-call-to-action-cloze-and-multiple-choice-text",
  coachCallToActionEssay: "coach-call-to-action-essay",
  coachCallToActionMultipleChoiceText: "coach-call-to-action-multiple-choice-text",
  coachHomeRules: "coach-home-rules",
  coachSelfReflectionCloze: "coach-self-reflection-cloze",
  coachSelfReflectionEssay: "coach-self-reflection-essay",
  coachSelfReflectionMultipleChoice: "coach-self-reflection-multiple-choice",
  coachSelfReflectionMultipleChoice1: "coach-self-reflection-multiple-choice-1",
  coachSelfReflectionMultipleChoiceText: "coach-self-reflection-multiple-choice-text",
  coachSelfReflectionMultipleResponse: "coach-self-reflection-multiple-response",
  coachSelfReflectionMultipleResponse1: "coach-self-reflection-multiple-response-1",
  coachSelfReflectionRating: "coach-self-reflection-rating",
  coachVideoTranscript: "coach-video-transcript",
  code: "code",
  codeRuntime: "code-runtime",
  collapsible: "collapsible",
  conclusion: "conclusion",
  conclusionAlt: "conclusion-alt",
  consoleLog: "console-log",
  container: "container",
  containerAccordionTabs: "container-accordion-tabs",
  containerBits2: "container-bits-2",
  containerCards: "container-cards",
  containerCarousel: "container-carousel",
  containerCookRecipe: "container-cook-recipe",
  containerFolder: "container-folder",
  containerFolderAll: "container-folder-all",
  containerGallery: "container-gallery",
  containerGrid: "container-grid",
  containerGroup: "container-group",
  containerNewsArticle: "container-news-article",
  containerNowrap: "container-nowrap",
  containerNowrapStretch: "container-nowrap-stretch",
  containerPreview: "container-preview",
  containerScroller: "container-scroller",
  containerSlides: "container-slides",
  containerStack: "container-stack",
  containerTabs: "container-tabs",
  containerWrap: "container-wrap",
  conversationLeft1: "conversation-left-1",
  conversationLeft1Scream: "conversation-left-1-scream",
  conversationLeft1Thought: "conversation-left-1-thought",
  conversationRight1: "conversation-right-1",
  conversationRight1Scream: "conversation-right-1-scream",
  conversationRight1Thought: "conversation-right-1-thought",
  clozeAndMultipleChoiceTextConversationLeft1: "cloze-and-multiple-choice-text-conversation-left-1",
  clozeAndMultipleChoiceTextConversationLeft1Scream: "cloze-and-multiple-choice-text-conversation-left-1-scream",
  clozeAndMultipleChoiceTextConversationLeft1Thought: "cloze-and-multiple-choice-text-conversation-left-1-thought",
  clozeAndMultipleChoiceTextConversationRight1: "cloze-and-multiple-choice-text-conversation-right-1",
  clozeAndMultipleChoiceTextConversationRight1Scream: "cloze-and-multiple-choice-text-conversation-right-1-scream",
  clozeAndMultipleChoiceTextConversationRight1Thought: "cloze-and-multiple-choice-text-conversation-right-1-thought",
  cookArrangement: "cook-arrangement",
  cookIngredients: "cook-ingredients",
  cookInsert: "cook-insert",
  cookNoteOnQuantity: "cook-note-on-quantity",
  cookPersonalRecommendation: "cook-personal-recommendation",
  cookPlate: "cook-plate",
  cookPracticeAdvise: "cook-practice-advise",
  cookPreparation: "cook-preparation",
  cookRecommendation: "cook-recommendation",
  cookRemark: "cook-remark",
  cookSideDish: "cook-side-dish",
  cookSideDrink: "cook-side-drink",
  cookStep: "cook-step",
  cookTimer: "cook-timer",
  cookVariation: "cook-variation",
  correction: "correction",
  danger: "danger",
  dangerAlt: "danger-alt",
  dangerCollapsible: "danger-collapsible",
  definitionList: "definition-list",
  definitionListCollapsible: "definition-list-collapsible",
  definitionTerm: "definition-term",
  deleted: "deleted",
  details: "details",
  details1: "details-1",
  detailsImage: "details-image",
  diff: "diff",
  document: "document",
  documentDownload: "document-download",
  documentEmbed: "document-embed",
  documentLink: "document-link",
  documentUpload: "document-upload",
  editorial: "editorial",
  editorNote: "editor-note",
  essay: "essay",
  example: "example",
  exampleAlt: "example-alt",
  exampleCollapsible: "example-collapsible",
  exampleList: "example-list",
  extractorAiChat: "extractor-ai-chat",
  extractorBlock: "extractor-block",
  extractorBlueprint: "extractor-blueprint",
  extractorBlueprintConfiguration: "extractor-blueprint-configuration",
  extractorConfiguration: "extractor-configuration",
  extractorImage: "extractor-image",
  extractorImageCollapsible: "extractor-image-collapsible",
  extractorInformation: "extractor-information",
  extractorPage: "extractor-page",
  extractorPageCollapsible: "extractor-page-collapsible",
  extractorPageFooter: "extractor-page-footer",
  extractorPageFooterCollapsible: "extractor-page-footer-collapsible",
  extractorPageHeader: "extractor-page-header",
  extractorPageHeaderCollapsible: "extractor-page-header-collapsible",
  extractorPageNumber: "extractor-page-number",
  extractorPageNumberCollapsible: "extractor-page-number-collapsible",
  extractorPageWithBlocks: "extractor-page-with-blocks",
  extractorPageWithBlocksCollapsible: "extractor-page-with-blocks-collapsible",
  extractorReconcile: "extractor-reconcile",
  extractorRepeatedText: "extractor-repeated-text",
  extractorRule: "extractor-rule",
  extractorTheme: "extractor-theme",
  featured: "featured",
  feedback: "feedback",
  figure: "figure",
  flashcard: "flashcard",
  flashcard1: "flashcard-1",
  focusImage: "focus-image",
  footNote: "foot-note",
  formFreeText: "form-free-text",
  formula: "formula",
  formulaImage: "formula-image",
  gapText: "gap-text",
  gapTextInstructionGrouped: "gap-text-instruction-grouped",
  glossaryTerm: "glossary-term",
  groupBorn: "group-born",
  groupDied: "group-died",
  handInAudio: "hand-in-audio",
  handInContact: "hand-in-contact",
  handInDocument: "hand-in-document",
  handInFeedbackExpert: "hand-in-feedback-expert",
  handInFeedbackSelf: "hand-in-feedback-self",
  handInFile: "hand-in-file",
  handInFreeText: "hand-in-free-text",
  handInFreeTextExpert: "hand-in-free-text-expert",
  handInLocation: "hand-in-location",
  handInPhoto: "hand-in-photo",
  handInScan: "hand-in-scan",
  handInSubmit: "hand-in-submit",
  handInSystemMedia: "hand-in-system-media",
  handInVideo: "hand-in-video",
  handInVoice: "hand-in-voice",
  help: "help",
  helpAlt: "help-alt",
  highlightText: "highlight-text",
  hint: "hint",
  hintAlt: "hint-alt",
  hintCollapsible: "hint-collapsible",
  image: "image",
  imageBanner: "image-banner",
  imageFigure: "image-figure",
  imageFigureAlt: "image-figure-alt",
  imageLandscape: "image-landscape",
  imageLink: "image-link",
  imageMood: "image-mood",
  imageOnDevice: "image-on-device",
  imagePortrait: "image-portrait",
  imagePrototype: "image-prototype",
  imageResponsive: "image-responsive",
  imageScreenshot: "image-screenshot",
  imageSeparator: "image-separator",
  imageSeparatorAlt: "image-separator-alt",
  imagesLogoGrave: "images-logo-grave",
  imageStyled: "image-styled",
  imageSuperWide: "image-super-wide",
  imageZoom: "image-zoom",
  indexTerm: "index-term",
  info: "info",
  infoAlt: "info-alt",
  infoCollapsible: "info-collapsible",
  internalLink: "internal-link",
  interview: "interview",
  interviewInstructionGrouped: "interview-instruction-grouped",
  langAudioScript: "lang-audio-script",
  langEnablingLanguageSkills: "lang-enabling-language-skills",
  langEnglishAroundWorld: "lang-english-around-world",
  langExtraActivity: "lang-extra-activity",
  langGoodToKnow: "lang-good-to-know",
  langHomework: "lang-homework",
  langLearningGoal: "lang-learning-goal",
  langLearningOutcomes: "lang-learning-outcomes",
  langLearningStrategy: "lang-learning-strategy",
  langLevelDown: "lang-level-down",
  langLevelUp: "lang-level-up",
  langLifeSkillIcon: "lang-life-skill-icon",
  langLifeSkills: "lang-life-skills",
  langLikeALocal: "lang-like-a-local",
  langMaterial: "lang-material",
  langTeacherNote: "lang-teacher-note",
  langTeacherPronunciation: "lang-teacher-pronunciation",
  langUsefulPhrases: "lang-useful-phrases",
  langVideoScript: "lang-video-script",
  langVocabulary: "lang-vocabulary",
  learningDocumentationFeedback: "learning-documentation-feedback",
  learningPathBook: "learning-path-book",
  learningPathBotTraining: "learning-path-bot-training",
  learningPathClassroomEvent: "learning-path-classroom-event",
  learningPathClassroomTraining: "learning-path-classroom-training",
  learningPathClosing: "learning-path-closing",
  learningPathExternalLink: "learning-path-external-link",
  learningPathFeedback: "learning-path-feedback",
  learningPathLearningGoal: "learning-path-learning-goal",
  learningPathLti: "learning-path-lti",
  learningPathSign: "learning-path-sign",
  learningPathStep: "learning-path-step",
  learningPathVideoCall: "learning-path-video-call",
  leAssignment: "le-assignment",
  leClassroomEvent: "le-classroom-event",
  leCompletion: "le-completion",
  leExternalLink: "le-external-link",
  leFinishingTask: "le-finishing-task",
  leFollowUpTask: "le-follow-up-task",
  leMultiDayEvent: "le-multi-day-event",
  legend: "legend",
  leDocumentDownload: "le-document-download",
  leLearningObjectives: "le-learning-objectives",
  leLearningStep: "le-learning-step",
  leListenAudioEmbed: "le-listen-audio-embed",
  lePreparationTask: "le-preparation-task",
  leRead: "le-read",
  leReadBook: "le-read-book",
  leTask: "le-task",
  leTodo: "le-todo",
  leVideoCall: "le-video-call",
  leWatchVideoEmbed: "le-watch-video-embed",
  lifeSkillSticker: "life-skill-sticker",
  list: "list",
  listItem: "list-item",
  mark: "mark",
  match: "match",
  matchAll: "match-all",
  matchAllReverse: "match-all-reverse",
  matchAudio: "match-audio",
  matchMatrix: "match-matrix",
  matchPicture: "match-picture",
  matchReverse: "match-reverse",
  matchSolutionGrouped: "match-solution-grouped",
  message: "message",
  metalevelExplanation: "metalevel-explanation",
  metaSearchDefaultTerms: "meta-search-default-terms",
  metaSearchDefaultTopics: "meta-search-default-topics",
  milestone: "milestone",
  module: "module",
  moduleProduct: "module-product",
  multipleChoice: "multiple-choice",
  multipleChoice1: "multiple-choice-1",
  multipleChoiceText: "multiple-choice-text",
  multipleResponse: "multiple-response",
  multipleResponse1: "multiple-response-1",
  newspaperArticle: "newspaper-article",
  nonProductionPrototypeIframe: "non-production-prototype-iframe",
  note: "note",
  noteAi: "note-ai",
  noteAlt: "note-alt",
  notebookArticle: "notebook-article",
  noteCollapsible: "note-collapsible",
  output: "output",
  page: "page",
  pageAcademy: "page-academy",
  pageArticle: "page-article",
  pageArticleAlt: "page-article-alt",
  pageArticleResponsive: "page-article-responsive",
  pageBanner: "page-banner",
  pageBuyButton: "page-buy-button",
  pageBuyButtonAlt: "page-buy-button-alt",
  pageBuyButtonPromotion: "page-buy-button-promotion",
  pageCategory: "page-category",
  pageCollapsible: "page-collapsible",
  pageContainer: "page-container",
  pageContainerFolder: "page-container-folder",
  pageContainerGroup: "page-container-group",
  pageContainerNowrap: "page-container-nowrap",
  pageContainerNowrapStretch: "page-container-nowrap-stretch",
  pageContainerWrap: "page-container-wrap",
  pageCoverImage: "page-cover-image",
  pageFooter: "page-footer",
  pageHero: "page-hero",
  pageOpenBook: "page-open-book",
  pageOpenBookList: "page-open-book-list",
  printBook: "print-book",
  openBookChapter: "open-book-chapter",
  openBookChapterTeaser: "open-book-chapter-teaser",
  pagePerson: "page-person",
  pageProduct: "page-product",
  pageProductList: "page-product-list",
  pageProductVideo: "page-product-video",
  pageProductVideoList: "page-product-video-list",
  pagePromotion: "page-promotion",
  pageSectionFolder: "page-section-folder",
  pageShopInShop: "page-shop-in-shop",
  pageSpecial: "page-special",
  pageEnd: "page-end",
  pageStart: "page-start",
  pageSubpage: "page-subpage",
  pageSubscribe: "page-subscribe",
  parameters: "parameters",
  photo: "photo",
  platform: "platform",
  platformBrandTarget: "platform-brand-target",
  platformHeader: "platform-header",
  platformMain: "platform-main",
  platformMainButton: "platform-main-button",
  platformMainInput: "platform-main-input",
  platformPath: "platform-path",
  platformSection: "platform-section",
  platformSectionButton: "platform-section-button",
  platformSectionChat: "platform-section-chat",
  platformSectionHeader: "platform-section-header",
  platformSectionInput: "platform-section-input",
  platformSystemIcon: "platform-system-icon",
  preparationNote: "preparation-note",
  printThisBook: "print-this-book",
  printThisChapter: "print-this-chapter",
  printPageBreak: "print-page-break",
  pronunciationTable: "pronunciation-table",
  prototypeImages: "prototype-images",
  qAndA: "q-and-a",
  qAndACard: "q-and-a-card",
  question1: "question-1",
  quote: "quote",
  rating: "rating",
  recipe: "recipe",
  recordAudio: "record-audio",
  recordVideo: "record-video",
  releaseNote: "release-note",
  releaseNotesSummary: "release-notes-summary",
  remark: "remark",
  remarkAlt: "remark-alt",
  remarkCollapsible: "remark-collapsible",
  reviewApprovedNote: "review-approved-note",
  reviewAuthorNote: "review-author-note",
  reviewCustomerNote: "review-customer-note",
  reviewError: "review-error",
  reviewErrorAi: "review-error-ai",
  reviewErrorTranslation: "review-error-translation",
  reviewNote: "review-note",
  reviewRequestForReviewNote: "review-request-for-review-note",
  reviewReviewerNote: "review-reviewer-note",
  sampleSolution: "sample-solution",
  sandbox: "sandbox",
  sandboxOutputJson: "sandbox-output-json",
  sandboxOutputMarkup: "sandbox-output-markup",
  sandboxOutputRender: "sandbox-output-render",
  scorm: "scorm",
  screenshot: "screenshot",
  selfAssessment: "self-assessment",
  separator: "separator",
  separatorAlt: "separator-alt",
  sequence: "sequence",
  sideNote: "side-note",
  sideNoteCollapsible: "side-note-collapsible",
  smartStandardArticleNonNormative: "smart-standard-article-non-normative",
  smartStandardArticleNonNormativeCollapsible: "smart-standard-article-non-normative-collapsible",
  smartStandardArticleNormative: "smart-standard-article-normative",
  smartStandardArticleNormativeCollapsible: "smart-standard-article-normative-collapsible",
  smartStandardDefinitionListNonNormative: "smart-standard-definition-list-non-normative",
  smartStandardDefinitionListNormative: "smart-standard-definition-list-normative",
  smartStandardExampleNonNormative: "smart-standard-example-non-normative",
  smartStandardExampleNonNormativeCollapsible: "smart-standard-example-non-normative-collapsible",
  smartStandardExampleNormative: "smart-standard-example-normative",
  smartStandardExampleNormativeCollapsible: "smart-standard-example-normative-collapsible",
  smartStandardFormula: "smart-standard-formula",
  smartStandardFormulaNonNormative: "smart-standard-formula-non-normative",
  smartStandardFormulaNormative: "smart-standard-formula-normative",
  smartStandardImageFigureNonNormative: "smart-standard-image-figure-non-normative",
  smartStandardImageFigureNonNormativeCollapsible: "smart-standard-image-figure-non-normative-collapsible",
  smartStandardImageFigureNormative: "smart-standard-image-figure-normative",
  smartStandardImageFigureNormativeCollapsible: "smart-standard-image-figure-normative-collapsible",
  smartStandardLegend: "smart-standard-legend",
  smartStandardLegendNonNormative: "smart-standard-legend-non-normative",
  smartStandardLegendNormative: "smart-standard-legend-normative",
  smartStandardList: "smart-standard-list",
  smartStandardListCollapsible: "smart-standard-list-collapsible",
  smartStandardListItem: "smart-standard-list-item",
  smartStandardListItemCollapsible: "smart-standard-list-item-collapsible",
  smartStandardNoteNonNormative: "smart-standard-note-non-normative",
  smartStandardNoteNonNormativeCollapsible: "smart-standard-note-non-normative-collapsible",
  smartStandardNoteNormative: "smart-standard-note-normative",
  smartStandardNoteNormativeCollapsible: "smart-standard-note-normative-collapsible",
  smartStandardRemarkLegend: "smart-standard-remark-legend",
  smartStandardRemarkLegendNonNormative: "smart-standard-remark-legend-non-normative",
  smartStandardRemarkLegendNormative: "smart-standard-remark-legend-normative",
  smartStandardRemarkFormula: "smart-standard-remark-formula",
  smartStandardRemarkFormulaNonNormative: "smart-standard-remark-formula-non-normative",
  smartStandardRemarkFormulaNormative: "smart-standard-remark-formula-normative",
  smartStandardRemarkNonNormative: "smart-standard-remark-non-normative",
  smartStandardRemarkNonNormativeCollapsible: "smart-standard-remark-non-normative-collapsible",
  smartStandardRemarkNormative: "smart-standard-remark-normative",
  smartStandardRemarkNormativeCollapsible: "smart-standard-remark-normative-collapsible",
  smartStandardRemarkTableExtendedImageNonNormative: "smart-standard-remark-table-extended-image-non-normative",
  smartStandardRemarkTableExtendedImageNonNormativeCollapsible: "smart-standard-remark-table-extended-image-non-normative-collapsible",
  smartStandardRemarkTableExtendedImageNormative: "smart-standard-remark-table-extended-image-normative",
  smartStandardRemarkTableExtendedImageNormativeCollapsible: "smart-standard-remark-table-extended-image-normative-collapsible",
  smartStandardRemarkTableExtendedNonNormative: "smart-standard-remark-table-extended-non-normative",
  smartStandardRemarkTableExtendedNonNormativeCollapsible: "smart-standard-remark-table-extended-non-normative-collapsible",
  smartStandardRemarkTableExtendedNormative: "smart-standard-remark-table-extended-normative",
  smartStandardRemarkTableExtendedNormativeCollapsible: "smart-standard-remark-table-extended-normative-collapsible",
  smartStandardRemarkTableImageNonNormative: "smart-standard-remark-table-image-non-normative",
  smartStandardRemarkTableImageNonNormativeCollapsible: "smart-standard-remark-table-image-non-normative-collapsible",
  smartStandardRemarkTableImageNormative: "smart-standard-remark-table-image-normative",
  smartStandardRemarkTableImageNormativeCollapsible: "smart-standard-remark-table-image-normative-collapsible",
  smartStandardRemarkTableNonNormative: "smart-standard-remark-table-non-normative",
  smartStandardRemarkTableNonNormativeCollapsible: "smart-standard-remark-table-non-normative-collapsible",
  smartStandardRemarkTableNormative: "smart-standard-remark-table-normative",
  smartStandardRemarkTableNormativeCollapsible: "smart-standard-remark-table-normative-collapsible",
  smartStandardTableExtendedImageNonNormative: "smart-standard-table-extended-image-non-normative",
  smartStandardTableExtendedImageNonNormativeCollapsible: "smart-standard-table-extended-image-non-normative-collapsible",
  smartStandardTableExtendedImageNormative: "smart-standard-table-extended-image-normative",
  smartStandardTableExtendedImageNormativeCollapsible: "smart-standard-table-extended-image-normative-collapsible",
  smartStandardTableExtendedNonNormative: "smart-standard-table-extended-non-normative",
  smartStandardTableExtendedNonNormativeCollapsible: "smart-standard-table-extended-non-normative-collapsible",
  smartStandardTableExtendedNormative: "smart-standard-table-extended-normative",
  smartStandardTableExtendedNormativeCollapsible: "smart-standard-table-extended-normative-collapsible",
  smartStandardTableImageNonNormative: "smart-standard-table-image-non-normative",
  smartStandardTableImageNonNormativeCollapsible: "smart-standard-table-image-non-normative-collapsible",
  smartStandardTableImageNormative: "smart-standard-table-image-normative",
  smartStandardTableImageNormativeCollapsible: "smart-standard-table-image-normative-collapsible",
  smartStandardTableNonNormative: "smart-standard-table-non-normative",
  smartStandardTableNonNormativeCollapsible: "smart-standard-table-non-normative-collapsible",
  smartStandardTableNormative: "smart-standard-table-normative",
  smartStandardTableNormativeCollapsible: "smart-standard-table-normative-collapsible",
  standardArticleNonNormative: "standard-article-non-normative",
  standardArticleNormative: "standard-article-normative",
  standardDefinitionListNonNormative: "standard-definition-list-non-normative",
  standardDefinitionListNormative: "standard-definition-list-normative",
  standardExampleNonNormative: "standard-example-non-normative",
  standardExampleNormative: "standard-example-normative",
  standardImageFigureNonNormative: "standard-image-figure-non-normative",
  standardImageFigureNormative: "standard-image-figure-normative",
  standardList: "standard-list",
  standardListItem: "standard-list-item",
  standardNoteNonNormative: "standard-note-non-normative",
  standardNoteNormative: "standard-note-normative",
  standardRemarkNonNormative: "standard-remark-non-normative",
  standardRemarkNormative: "standard-remark-normative",
  standardRemarkTableExtendedImageNonNormative: "standard-remark-table-extended-image-non-normative",
  standardRemarkTableExtendedImageNormative: "standard-remark-table-extended-image-normative",
  standardRemarkTableExtendedNonNormative: "standard-remark-table-extended-non-normative",
  standardRemarkTableExtendedNormative: "standard-remark-table-extended-normative",
  standardRemarkTableImageNonNormative: "standard-remark-table-image-non-normative",
  standardRemarkTableImageNormative: "standard-remark-table-image-normative",
  standardRemarkTableNonNormative: "standard-remark-table-non-normative",
  standardRemarkTableNormative: "standard-remark-table-normative",
  standardTableExtendedImageNonNormative: "standard-table-extended-image-non-normative",
  standardTableExtendedImageNormative: "standard-table-extended-image-normative",
  standardTableExtendedNonNormative: "standard-table-extended-non-normative",
  standardTableExtendedNormative: "standard-table-extended-normative",
  standardTableImageNonNormative: "standard-table-image-non-normative",
  standardTableImageNormative: "standard-table-image-normative",
  standardTableNonNormative: "standard-table-non-normative",
  standardTableNormative: "standard-table-normative",
  statement: "statement",
  stdout: "stdout",
  step: "step",
  stepImageScreenshot: "step-image-screenshot",
  stepImageScreenshotWithPointer: "step-image-screenshot-with-pointer",
  sticker: "sticker",
  stickyNote: "sticky-note",
  stillImageFilm: "still-image-film",
  stillImageFilmEmbed: "still-image-film-embed",
  stillImageFilmLink: "still-image-film-link",
  summary: "summary",
  summaryAi: "summary-ai",
  survey: "survey",
  survey1: "survey-1",
  surveyAnonymous: "survey-anonymous",
  surveyAnonymous1: "survey-anonymous-1",
  surveyMatrix: "survey-matrix",
  surveyMatrixMe: "survey-matrix-me",
  surveyRating: "survey-rating",
  surveyRatingDisplay: "survey-rating-display",
  surveyRatingOnce: "survey-rating-once",
  table: "table",
  tableAlt: "table-alt",
  tableExtended: "table-extended",
  tableExtendedImage: "table-extended-image",
  tableImage: "table-image",
  tableImageAlt: "table-image-alt",
  takePicture: "take-picture",
  toc: "toc",
  tocChapter: "toc-chapter",
  tocInline: "toc-inline",
  tocResource: "toc-resource",
  tocContent: "toc-content",
  trueFalse: "true-false",
  trueFalse1: "true-false-1",
  vendorAmcharts5Chart: "vendor-amcharts-5-chart",
  vendorDatadogDashboard: "vendor-datadog-dashboard",
  vendorDatadogDashboardEmbed: "vendor-datadog-dashboard-embed",
  vendorFormbricksEmbed: "vendor-formbricks-embed",
  vendorFormbricksLink: "vendor-formbricks-link",
  vendorFormbricksEmbedAnonymous: "vendor-formbricks-embed-anonymous",
  vendorFormbricksLinkAnonymous: "vendor-formbricks-link-anonymous",
  vendorHighchartsChart: "vendor-highcharts-chart",
  vendorIframelyCard: "vendor-iframely-card",
  vendorIframelyEmbed: "vendor-iframely-embed",
  vendorIframelyPreview: "vendor-iframely-preview",
  vendorIframelyPreviewMini: "vendor-iframely-preview-mini",
  vendorJupyterCellCode: "vendor-jupyter-cell-code",
  vendorJupyterCellMarkdown: "vendor-jupyter-cell-markdown",
  vendorJupyterCellRaw: "vendor-jupyter-cell-raw",
  vendorJupyterIpynb: "vendor-jupyter-ipynb",
  vendorJupyterOutput: "vendor-jupyter-output",
  vendorLearndashEmbed: "vendor-learndash-embed",
  vendorPadletEmbed: "vendor-padlet-embed",
  vendorStripePricingTable: "vendor-stripe-pricing-table",
  vendorStripePricingTableExternal: "vendor-stripe-pricing-table-external",
  vendorStripePricingTablePrintOnRequest: "vendor-stripe-pricing-table-print-on-request",
  video: "video",
  videoEmbed: "video-embed",
  videoEmbedLandscape: "video-embed-landscape",
  videoEmbedPortrait: "video-embed-portrait",
  videoLandscape: "video-landscape",
  videoLink: "video-link",
  videoLinkLandscape: "video-link-landscape",
  videoLinkPortrait: "video-link-portrait",
  videoPortrait: "video-portrait",
  videoTranscript: "video-transcript",
  warning: "warning",
  warningAlt: "warning-alt",
  warningCollapsible: "warning-collapsible",
  websiteLink: "website-link",
  workbookArticle: "workbook-article"
};

// src/model/enum/Count.ts
import "@ncoderz/superenum";
var Count = {
  infinity: "infinity"
};

// src/model/enum/ExampleType.ts
import "@ncoderz/superenum";
var ExampleType = {
  none: "none",
  // Example is not allowed
  string: "string",
  // Example is a string value
  boolean: "boolean"
  // Example is a boolean value
};

// src/model/enum/Tag.ts
import "@ncoderz/superenum";
var tags = {
  tag_title: "#",
  tag_anchor: "\u25BC",
  tag_reference: "\u25BA",
  tag_property: "@",
  tag_item: "%",
  tag_instruction: "!",
  tag_hint: "?",
  tag_true: "+",
  tag_false: "-",
  tag_sampleSolution: "$",
  tag_gap: "_",
  tag_mark: "=",
  tag_resource: "&"
};
var Tag = tags;

// src/model/enum/TagFormat.ts
import "@ncoderz/superenum";
var TagFormat = {
  none: "none",
  // The property does not have a value
  plainText: "plainText",
  // If the value of the property is treated as a trimmed plain text string
  bitmarkText: "bitmarkText",
  // If the value of the property is treated as a bitmark+
  number: "number",
  // If the value is treated as a number
  boolean: "boolean",
  // If the value is treated as a boolean
  invertedBoolean: "invertedBoolean",
  // If the value is treated as a boolean with the value inverted (e.g. isLongAnswer ==> isShortAnswer = false)
  enumeration: "enumeration",
  // If the value is treated as an enumeration (the value is a string that must be one of a predefined set of values)
  coordinates: "coordinates"
  // Comma-separated list of 4 numbers (e.g. bounding-box x, y, x1, y1)
};

// src/model/enum/TextFormat.ts
import "@ncoderz/superenum";
var TextFormat2 = {
  // plain text
  plainText: "text",
  latex: "latex",
  // LaTeX code, alias for text
  json: "json",
  // json, alias for text
  xml: "xml",
  // xml, alias for text
  // bitmarkText
  // bitmarkText in the body is bitmark++ and bitmarkText in a tag is bitmark+
  bitmarkText: "bitmark++"
};

// src/model/config/enum/CardSetConfigKey.ts
import "@ncoderz/superenum";
var CardSetConfigKey = {
  flashcard: "flashcard",
  flashcard1: "flashcard1",
  definitionList: "definitionList",
  definitionListPlain: "definitionListPlain",
  elements: "elements",
  statements: "statements",
  quiz: "quiz",
  quizResponses: "quizResponses",
  feedback: "feedback",
  questions: "questions",
  matchPairs: "matchPairs",
  matchAudioPairs: "matchAudioPairs",
  matchImagePairs: "matchImagePairs",
  matchMatrix: "matchMatrix",
  table: "table",
  tableExtended: "tableExtended",
  pronunciationTable: "pronunciationTable",
  botActionResponses: "botActionResponses",
  exampleBitList: "exampleBitList",
  pageFooterSections: "pageFooterSections",
  clozeList: "clozeList",
  ingredients: "ingredients",
  // DEPRECATED - TO BE REMOVED IN THE FUTURE
  // captionDefinitionsList: 'captionDefinitionsList',
  bookReferenceList: "bookReferenceList"
};

// src/model/config/enum/GroupConfigType.ts
import "@ncoderz/superenum";
var GroupConfigType = {
  standard: "standard",
  // Any ordinary group
  resource: "resource",
  // A resource group
  comboResource: "comboResource"
  // A combo resource group
};

// src/model/enum/BitTagConfigKeyType.ts
import "@ncoderz/superenum";
var BitTagConfigKeyType = {
  unknown: "unknown",
  // Unknown tag type, used for error handling
  tag: "tag",
  // A standard tag
  property: "property",
  // A property tag
  resource: "resource",
  // A resource tag
  //
  group: "group"
  // A reusable group of tags
};

// src/model/config/AbstractTagConfig.ts
var AbstractTagConfig = class {
  // Deprecated version
  constructor(params) {
    __publicField(this, "type");
    __publicField(this, "configKey");
    __publicField(this, "tag");
    __publicField(this, "maxCount");
    // Default: 1
    __publicField(this, "minCount");
    // Default: 0
    __publicField(this, "chain");
    __publicField(this, "jsonKey");
    // Legacy string-form jsonKey
    __publicField(this, "exportJsonKey");
    // New JSON-pattern jsonKey (export-only)
    __publicField(this, "hasExportJsonKey");
    // True iff exportJsonKey was explicitly set
    __publicField(this, "htmlKey");
    // HTML key-pattern (export-only)
    __publicField(this, "hasHtmlKey");
    // True iff htmlKey was explicitly set
    __publicField(this, "deprecated");
    this.type = params.type;
    this.configKey = params.configKey;
    this.tag = params.tag;
    this.maxCount = params.maxCount;
    this.minCount = params.minCount;
    this.chain = params.chain;
    this.jsonKey = params.jsonKey;
    this.exportJsonKey = params.exportJsonKey;
    this.hasExportJsonKey = params.hasExportJsonKey;
    this.htmlKey = params.htmlKey;
    this.hasHtmlKey = params.hasHtmlKey;
    this.deprecated = params.deprecated;
  }
};

// src/model/config/MarkupTagConfig.ts
var MarkupTagConfig = class extends AbstractTagConfig {
  constructor(params) {
    super({
      type: BitTagConfigKeyType.tag,
      ...params
    });
    __publicField(this, "type", BitTagConfigKeyType.tag);
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = `TAG[${this.tag}`;
    if (opts.includeConfigs) {
      if (this.deprecated != null) s += `, deprecated=${this.deprecated}`;
      if (this.maxCount != null) s += `, max=${this.maxCount}`;
      if (this.minCount != null) s += `, min=${this.minCount}`;
    }
    s += "]";
    if (this.chain && opts.includeChains) {
      s += `
|__`;
      let first = true;
      for (const tag of Object.values(this.chain)) {
        if (!first) s += ` `;
        s += `${tag.toString(opts)}`;
        first = false;
      }
    }
    return s;
  }
};

// src/model/config/PropertyTagConfig.ts
var PropertyTagConfig = class extends AbstractTagConfig {
  // The default value of the property - this value can be omitted from the markup
  constructor(params) {
    super({
      type: BitTagConfigKeyType.property,
      ...params
    });
    __publicField(this, "type", BitTagConfigKeyType.property);
    __publicField(this, "array");
    // If the property is treated as an array rather than a single value
    __publicField(this, "format");
    // How the property is formatted
    __publicField(this, "values");
    // If the format is an enumeration, the possible values of the property
    __publicField(this, "defaultValue");
    this.array = params.maxCount === Count.infinity || params.maxCount > 1;
    this.format = params.format;
    this.values = params.values;
    this.defaultValue = params.defaultValue;
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = `PTY[${this.tag}`;
    if (opts.includeConfigs) {
      if (this.deprecated != null) s += `, deprecated=${this.deprecated}`;
      if (this.maxCount != null) s += `, max=${this.maxCount}`;
      if (this.minCount != null) s += `, min=${this.minCount}`;
      if (this.array != null) s += `, arr=${this.array}`;
      if (this.format != null) s += `, fmt=${this.format}`;
      if (this.values != null) s += `, vals=[${this.values.join(",")}]`;
      if (this.defaultValue != null) s += `, def=${this.defaultValue}`;
      if (this.jsonKey != null) s += `, json=${this.jsonKey}`;
    }
    s += "]";
    if (this.chain && opts.includeChains) {
      s += `
|__`;
      let first = true;
      for (const tag of Object.values(this.chain)) {
        if (!first) s += ` `;
        s += `${tag.toString(opts)}`;
        first = false;
      }
    }
    return s;
  }
};

// src/model/config/ResourceTagConfig.ts
var ResourceTagConfig = class extends AbstractTagConfig {
  constructor(params) {
    super({
      type: BitTagConfigKeyType.resource,
      ...params
    });
    __publicField(this, "type", BitTagConfigKeyType.resource);
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = `RES[${this.tag}`;
    if (opts.includeConfigs) {
      if (this.deprecated != null) s += `, deprecated=${this.deprecated}`;
      if (this.maxCount != null) s += `, max=${this.maxCount}`;
      if (this.minCount != null) s += `, min=${this.minCount}`;
      if (this.jsonKey != null) s += `, json=${this.jsonKey}`;
    }
    s += "]";
    if (this.chain && opts.includeChains) {
      s += `
|__`;
      let first = true;
      for (const tag of Object.values(this.chain)) {
        if (!first) s += ` `;
        s += `${tag.toString(opts)}`;
        first = false;
      }
    }
    return s;
  }
};

// src/model/config/CardVariantConfig.ts
var CardVariantConfig = class {
  // Body text format. Default: bitmark++
  constructor(tags2, bodyAllowed, bodyRequired, repeatCount, jsonKey, exportJsonKey, hasExportJsonKey = false, format, htmlKey, hasHtmlKey = false) {
    __publicField(this, "tags");
    __publicField(this, "bodyAllowed");
    // Default: true
    __publicField(this, "bodyRequired");
    // Default: false
    __publicField(this, "repeatCount");
    // Default: 1
    // JSON mapping fields
    __publicField(this, "jsonKey");
    // JSON path for body text (legacy)
    __publicField(this, "exportJsonKey");
    // New JSON-pattern jsonKey
    __publicField(this, "hasExportJsonKey");
    __publicField(this, "htmlKey");
    // HTML key-pattern
    __publicField(this, "hasHtmlKey");
    __publicField(this, "format");
    this.tags = tags2;
    this.bodyAllowed = bodyAllowed == null ? true : bodyAllowed;
    this.bodyRequired = bodyRequired;
    this.repeatCount = repeatCount;
    this.jsonKey = jsonKey;
    this.exportJsonKey = exportJsonKey;
    this.hasExportJsonKey = hasExportJsonKey;
    this.htmlKey = htmlKey;
    this.hasHtmlKey = hasHtmlKey;
    this.format = format;
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = "";
    const flags = [];
    if (this.bodyAllowed) flags.push("bodyAllowed");
    if (this.bodyRequired) flags.push("bodyRequired");
    if (this.repeatCount != null) flags.push(`repeatCount=${this.repeatCount}`);
    s += `[Flags]
${flags.join(", ")}`;
    s += `

[Tags]`;
    for (const tag of Object.values(this.tags)) {
      s += `
${tag.toString(opts)}`;
    }
    return s;
  }
};

// src/model/config/CardSideConfig.ts
var CardSideConfig = class {
  constructor(name, repeat, variants, jsonKey, exportJsonKey, hasExportJsonKey = false, htmlKey, hasHtmlKey = false) {
    __publicField(this, "name");
    __publicField(this, "repeat");
    __publicField(this, "jsonKey");
    __publicField(this, "exportJsonKey");
    __publicField(this, "hasExportJsonKey");
    __publicField(this, "htmlKey");
    __publicField(this, "hasHtmlKey");
    __publicField(this, "variants");
    this.name = name;
    this.repeat = repeat;
    this.jsonKey = jsonKey;
    this.exportJsonKey = exportJsonKey;
    this.hasExportJsonKey = hasExportJsonKey;
    this.htmlKey = htmlKey;
    this.hasHtmlKey = hasHtmlKey;
    this.variants = variants;
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = "";
    s += `[Side: ${this.name}]`;
    if (this.repeat) s += " (repeat)";
    let variantNo = 0;
    for (const variant of this.variants) {
      s += `
  [Variant ${variantNo}]`;
      s += `
  ${variant.toString(opts)}`;
      variantNo++;
    }
    return s;
  }
};

// src/model/config/CardSetConfig.ts
var CardSetConfig = class {
  constructor(configKey, jsonKey, exportJsonKey, hasExportJsonKey, sections, sides, htmlKey = void 0, hasHtmlKey = false) {
    __publicField(this, "configKey");
    __publicField(this, "jsonKey");
    __publicField(this, "exportJsonKey");
    __publicField(this, "hasExportJsonKey");
    __publicField(this, "htmlKey");
    __publicField(this, "hasHtmlKey");
    __publicField(this, "sections");
    __publicField(this, "sides");
    this.configKey = configKey;
    this.jsonKey = jsonKey;
    this.exportJsonKey = exportJsonKey;
    this.hasExportJsonKey = hasExportJsonKey;
    this.htmlKey = htmlKey;
    this.hasHtmlKey = hasHtmlKey;
    this.sections = sections;
    this.sides = sides;
  }
  // Legacy accessor — provides the same shape as the old `variants: CardVariantConfig[][]`
  // so downstream consumers (Config.getCardSetVariantConfig, etc.) continue to work.
  get variants() {
    return this.sides.map((side) => side.variants);
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = "";
    s += `[CardSet: ${this.configKey}]`;
    s += `
  jsonKey: ${this.jsonKey}`;
    for (const side of this.sides) {
      s += `
${side.toString(opts)}`;
    }
    return s;
  }
};

// src/model/config/BitConfig.ts
var BitConfig = class {
  constructor(config) {
    __publicField(this, "since");
    // Supported since version
    __publicField(this, "bitType");
    __publicField(this, "inheritedBitTypes");
    // Bit inheritance tree (array for order)
    __publicField(this, "inheritedBitTypesSet");
    // Bit inheritance tree (set for faster lookup)
    __publicField(this, "textFormatDefault");
    // Default text format
    __publicField(this, "tags", {});
    __publicField(this, "cardSet");
    __publicField(this, "quizBit");
    // True if the bit is a quiz bit
    __publicField(this, "deprecated");
    // Deprecated version
    __publicField(this, "bodyAllowed");
    // Default: true
    __publicField(this, "bodyRequired");
    // Default: false
    __publicField(this, "footerAllowed");
    // Default: true
    __publicField(this, "footerRequired");
    // Default: false
    __publicField(this, "resourceAttachmentAllowed");
    // Default: true
    __publicField(this, "rootExampleType");
    __publicField(this, "comboResourceConfigKey");
    const {
      since,
      bitType,
      inheritedBitTypes,
      textFormatDefault,
      tags: tags2,
      cardSet,
      quizBit,
      deprecated,
      bodyAllowed,
      bodyRequired,
      footerAllowed,
      footerRequired,
      resourceAttachmentAllowed,
      rootExampleType,
      comboResourceConfigKey
    } = config;
    this.since = since;
    this.bitType = bitType;
    this.inheritedBitTypes = inheritedBitTypes;
    this.inheritedBitTypesSet = new Set(inheritedBitTypes);
    this.textFormatDefault = textFormatDefault;
    this.tags = tags2;
    this.cardSet = cardSet;
    this.quizBit = quizBit;
    this.deprecated = deprecated;
    this.bodyAllowed = bodyAllowed == null ? true : bodyAllowed;
    this.bodyRequired = bodyRequired;
    this.footerAllowed = footerAllowed == null ? true : footerAllowed;
    this.footerRequired = footerRequired;
    this.resourceAttachmentAllowed = resourceAttachmentAllowed == null ? true : resourceAttachmentAllowed;
    this.rootExampleType = rootExampleType;
    this.comboResourceConfigKey = comboResourceConfigKey;
  }
  toString(options) {
    const opts = Object.assign({}, options);
    let s = `[Bit]
${this.bitType}`;
    if (this.inheritedBitTypes.length > 0) {
      s += `

[Inheritance]`;
      s += `
${this.inheritedBitTypes.join(" => ")}`;
    }
    s += `

[Default text format]
${this.textFormatDefault}`;
    const flags = [];
    if (this.since != null) flags.push(`since=${this.since}`);
    if (this.deprecated != null) flags.push(`deprecated=${this.deprecated}`);
    if (this.bodyAllowed) flags.push("bodyAllowed");
    if (this.bodyRequired) flags.push("bodyRequired");
    if (this.footerAllowed) flags.push("footerAllowed");
    if (this.footerRequired) flags.push("footerRequired");
    if (this.resourceAttachmentAllowed) flags.push("resourceAttachmentAllowed");
    if (this.rootExampleType != null) flags.push(`rootExampleType=${this.rootExampleType}`);
    if (this.comboResourceConfigKey != null)
      flags.push(`comboResourceConfigKey=${this.comboResourceConfigKey}`);
    s += `

[Flags]
${flags.join(", ")}`;
    s += `

[Tags]`;
    for (const tag of Object.values(this.tags)) {
      if (tag.maxCount === Count.infinity || tag.maxCount > 0) {
        s += `
${tag.toString(opts)}`;
      }
    }
    if (this.cardSet) {
      s += `

${this.cardSet.toString(opts)}`;
    }
    return s;
  }
};

// src/model/config/enum/ConfigKey.ts
import { Enum } from "@ncoderz/superenum";

// src/model/enum/GroupKey.ts
import "@ncoderz/superenum";
var groupKeys = {
  group_standardAllBits: "group_standardAllBits",
  group_standardItemLead: "group_standardItemLead",
  group_standardItemLeadInstructionHint: "group_standardItemLeadInstructionHint",
  group_standardInstructionHint: "group_standardInstructionHint",
  group_standardTags: "group_standardTags",
  group_standardTagsNoItemLead: "group_standardTagsNoItemLead",
  group_imageSource: "group_imageSource",
  group_technicalTerm: "group_technicalTerm",
  group_person: "group_person",
  group_gap: "group_gap",
  group_trueFalse: "group_trueFalse",
  group_trueFalseInlineHighlight: "group_trueFalseInlineHighlight",
  group_trueFalseInlineSelect: "group_trueFalseInlineSelect",
  group_trueFalseResponses: "group_trueFalseResponses",
  group_markConfig: "group_markConfig",
  group_mark: "group_mark",
  group_bookCommon: "group_bookCommon",
  group_learningPathCommon: "group_learningPathCommon",
  group_advertisingCommon: "group_advertisingCommon",
  group_platformStylesCommon: "group_platformStylesCommon",
  group_quizCommon: "group_quizCommon",
  group_backgroundWallpaper: "group_backgroundWallpaper",
  group_imageNoZoom: "group_imageNoZoom",
  group_resourceBitTags: "group_resourceBitTags",
  group_resourceCommon: "group_resourceCommon",
  group_resourceImageCommon: "group_resourceImageCommon",
  group_resourceImageCommonNoZoom: "group_resourceImageCommonNoZoom",
  group_resourceAudioCommon: "group_resourceAudioCommon",
  group_resourceVideoCommon: "group_resourceVideoCommon",
  group_resourceIcon: "group_resourceIcon",
  // Alias for image
  group_resourceImage: "group_resourceImage",
  group_resourceImageNoZoom: "group_resourceImageNoZoom",
  group_resourceImageEmbed: "group_resourceImageEmbed",
  group_resourceImageLink: "group_resourceImageLink",
  group_resourceImageResponsive: "group_resourceImageResponsive",
  // Combo resource
  group_resourceImagePortrait: "group_resourceImagePortrait",
  group_resourceImageLandscape: "group_resourceImageLandscape",
  group_resourceAudio: "group_resourceAudio",
  group_resourceAudioEmbed: "group_resourceAudioEmbed",
  group_resourceAudioLink: "group_resourceAudioLink",
  group_resourceVideo: "group_resourceVideo",
  group_resourceVideoEmbed: "group_resourceVideoEmbed",
  group_resourceVideoLink: "group_resourceVideoLink",
  group_resourceStillImageFilm: "group_resourceStillImageFilm",
  // Combo resource
  group_resourceStillImageFilmEmbed: "group_resourceStillImageFilmEmbed",
  group_resourceStillImageFilmLink: "group_resourceStillImageFilmLink",
  group_resourceArticle: "group_resourceArticle",
  group_resourceArticleEmbed: "group_resourceArticleEmbed",
  group_resourceArticleLink: "group_resourceArticleLink",
  group_resourceDocument: "group_resourceDocument",
  group_resourceDocumentEmbed: "group_resourceDocumentEmbed",
  group_resourceDocumentLink: "group_resourceDocumentLink",
  group_resourceDocumentDownload: "group_resourceDocumentDownload",
  group_resourceAppLink: "group_resourceAppLink",
  group_resourceWebsiteLink: "group_resourceWebsiteLink",
  group_previewImages: "group_previewImages",
  group_previewVideos: "group_previewVideos"
};

// src/model/enum/PropertyKey.ts
import "@ncoderz/superenum";
var propertyKeys = {
  property_action: "@action",
  property_activityType: "@activityType",
  property_additionalSolutions: "@additionalSolutions",
  property_advertisingClickUrl: "@advertisingClickUrl",
  property_ageRange: "@ageRange",
  property_aiGenerated: "@aiGenerated",
  property_allowedBit: "@allowedBit",
  property_allowPrint: "@allowPrint",
  property_allowSubtitles: "@allowSubtitles",
  property_alt: "@alt",
  property_altLangTag: "@altLangTag",
  property_analyticsTag: "@analyticsTag",
  property_author: "@author",
  property_authorFullName: "@authorFullName",
  property_authorJobTitle: "@authorJobTitle",
  property_authorPseudonym: "@authorPseudonym",
  property_authorTitle: "@authorTitle",
  property_autoplay: "@autoplay",
  property_availableClassifications: "@availableClassifications",
  property_brandColor: "@brandColor",
  property_brandColorName: "@brandColorName",
  property_backgroundImage: "@backgroundImage",
  property_blockId: "@blockId",
  property_book: "@book",
  property_bookAlias: "@bookAlias",
  property_bookDiff: "@bookDiff",
  property_bot: "@bot",
  property_bubbleTag: "@bubbleTag",
  property_extractorTag: "@extractorTag",
  property_extractorInternal: "@extractorInternal",
  property_bitFingerprint: "@bitFingerprint",
  property_buttonCaption: "@buttonCaption",
  property_callToActionUrl: "@callToActionUrl",
  property_caption: "@caption",
  property_categoryTag: "@categoryTag",
  property_chatWithBook: "@chatWithBook",
  property_chatWithBookBrainKey: "@chatWithBookBrainKey",
  property_currency: "@currency",
  property_bookIsbn: "@bookIsbn",
  property_bookAuthor: "@bookAuthor",
  property_bookType: "@bookType",
  property_bookBindingType: "@bookBindingType",
  property_bookNumberOfPages: "@bookNumberOfPages",
  property_bookRating: "@bookRating",
  property_bookSeriesTitle: "@bookSeriesTitle",
  property_bookSeriesId: "@bookSeriesId",
  property_bookSeriesNumber: "@bookSeriesNumber",
  property_bookLanguage: "@bookLanguage",
  property_bookPublishingDate: "@bookPublishingDate",
  property_bookPublisher: "@bookPublisher",
  property_bookTranslator: "@bookTranslator",
  property_bookOriginalTitle: "@bookOriginalTitle",
  property_gmbExternalShop: "@gmbExternalShop",
  property_gmbExternalShopItems: "@gmbExternalShopItems",
  property_citationStyle: "@citationStyle",
  property_classification: "@classification",
  property_codeLineNumbers: "@codeLineNumbers",
  property_codeMinimap: "@codeMinimap",
  property_color: "@color",
  property_colorTag: "@colorTag",
  property_computerLanguage: "@computerLanguage",
  property_content2Buy: "@content2Buy",
  property_listPrice: "@listPrice",
  property_customerProductId: "@customerProductId",
  property_customerProductShopLink: "@customerProductShopLink",
  property_copyright: "@copyright",
  property_coverColor: "@coverColor",
  property_coverImage: "@coverImage",
  property_customerExternalId: "@customerExternalId",
  property_customerId: "@customerId",
  property_date: "@date",
  property_dateEnd: "@dateEnd",
  property_decimalPlaces: "@decimalPlaces",
  property_deeplink: "@deeplink",
  property_displayName: "@displayName",
  property_diffContext: "@diffContext",
  property_diffOp: "@diffOp",
  property_diffRef: "@diffRef",
  property_diffTime: "@diffTime",
  property_diffTo: "@diffTo",
  property_disableCalculation: "@disableCalculation",
  property_disableFeedback: "@disableFeedback",
  property_duration: "@duration",
  property_isEditable: "@isEditable",
  property_needsValidation: "@needsValidation",
  property_validationDate: "@validationDate",
  property_emphasis: "@emphasis",
  property_example: "@example",
  property_externalId: "@externalId",
  property_externalLink: "@externalLink",
  property_externalLinkText: "@externalLinkText",
  property_feedbackEngine: "@feedbackEngine",
  property_feedbackType: "@feedbackType",
  property_userFeedbackTranslationUrl: "@userFeedbackTranslationUrl",
  property_userFeedbackContentUrl: "@userFeedbackContentUrl",
  property_flashcardSet: "@flashcardSet",
  property_focusX: "@focusX",
  property_focusY: "@focusY",
  property_format: "@format",
  property_formula: "@formula",
  property_fullName: "@fullName",
  property_groupTag: "@groupTag",
  property_gUri: "@gUri",
  property_handInAcceptFileType: "@handInAcceptFileType",
  property_handInInstruction: "@handInInstruction",
  property_handInRequirement: "@handInRequirement",
  property_hasBookNavigation: "@hasBookNavigation",
  property_hasMarkAsDone: "@hasMarkAsDone",
  property_height: "@height",
  property_icon: "@icon",
  property_iconTag: "@iconTag",
  property_id: "@id",
  property_iframeSrc: "@iframeSrc",
  property_iframeName: "@iframeName",
  property_imageFirst: "@imageFirst",
  property_imageSource: "@imageSource",
  property_index: "@index",
  property_internalComment: "@internalComment",
  property_internalPrintPdf: "@internalPrintPdf",
  property_hasPrintRestriction: "@hasPrintRestriction",
  property_enforceUpdateOverUserInput: "@enforceUpdateOverUserInput",
  property_extractorExtractionTimestamp: "@extractorExtractionTimestamp",
  property_isCaseSensitive: "@isCaseSensitive",
  property_isCollapsible: "@isCollapsible",
  property_isInfoOnly: "@isInfoOnly",
  property_isPublic: "@isPublic",
  property_isTemplate: "@isTemplate",
  property_isTemplateStripTheme: "@isTemplateStripTheme",
  property_isTracked: "@isTracked",
  property_jobTitle: "@jobTitle",
  property_jupyterExecutionCount: "@jupyterExecutionCount",
  property_jupyterId: "@jupyterId",
  property_kind: "@kind",
  property_label: "@label",
  property_labelFalse: "@labelFalse",
  property_labelTrue: "@labelTrue",
  property_lang: "@lang",
  property_language: "@language",
  property_layer: "@layer",
  property_layerRole: "@layerRole",
  property_levelACTFL: "@levelACTFL",
  property_levelCEFR: "@levelCEFR",
  property_levelCEFRp: "@levelCEFRp",
  property_levelILR: "@levelILR",
  property_license: "@license",
  property_list: "@list",
  property_listItemIndent: "@listItemIndent",
  property_location: "@location",
  property_machineTranslated: "@machineTranslated",
  property_translationOf: "@translationOf",
  property_translationOfBook: "@translationOfBook",
  property_spansPageBreak: "@spansPageBreak",
  property_mailingList: "@mailingList",
  property_mark: "@mark",
  property_maxCreatedBits: "@maxCreatedBits",
  property_maxDisplayLevel: "@maxDisplayLevel",
  property_maxTocChapterLevel: "@maxTocChapterLevel",
  property_mockupId: "@mockupId",
  property_mute: "@mute",
  property_padletId: "@padletId",
  property_page: "@page",
  property_printParentChapterLevel: "@printParentChapterLevel",
  property_printOnRequestCustomerId: "@printOnRequestCustomerId",
  property_printOnRequestProductId: "@printOnRequestProductId",
  property_pageNo: "@pageNo",
  property_partialAnswer: "@partialAnswer",
  property_partner: "@partner",
  // Deprecated, replaced by person
  property_path: "@path",
  property_platformBackgroundColor: "@platformBackgroundColor",
  property_platformBorderColor: "@platformBorderColor",
  property_platformBrandTarget: "@platformBrandTarget",
  property_platformButtonBackgroundColor: "@platformButtonBackgroundColor",
  property_platformButtonPrimaryColor: "@platformButtonPrimaryColor",
  property_platformMessageBackgroundColor: "@platformMessageBackgroundColor",
  property_platformName: "@platformName",
  property_platformPlaceholderColor: "@platformPlaceholderColor",
  property_platformPrimaryColor: "@platformPrimaryColor",
  property_platformScrollbarColor: "@platformScrollbarColor",
  property_platformSecondaryColor: "@platformSecondaryColor",
  property_platformSelectionColor: "@platformSelectionColor",
  property_platformSelectionTextColor: "@platformSelectionTextColor",
  property_platformBaseLayerColor: "@platformBaseLayerColor",
  property_platformMargin: "@platformMargin",
  property_platformBorderRadius: "@platformBorderRadius",
  property_platformSelectionBorderRadius: "@platformSelectionBorderRadius",
  property_platformLogoMaxHeight: "@platformLogoMaxHeight",
  property_platformNeedsShadow: "@platformNeedsShadow",
  property_platformSeparatorColor: "@platformSeparatorColor",
  property_platformTextSelectionColor: "@platformTextSelectionColor",
  property_person: "@person",
  property_pointerLeft: "@pointerLeft",
  property_pointerTop: "@pointerTop",
  property_posterImage: "@posterImage",
  property_processHandIn: "@processHandIn",
  property_processHandInLocation: "@processHandInLocation",
  property_product: "@product",
  property_productFolder: "@productFolder",
  property_productId: "@productId",
  property_productVideo: "@productVideo",
  property_progress: "@progress",
  property_pseudonym: "@pseudonym",
  property_publications: "@publications",
  property_publisher: "@publisher",
  property_publisherName: "@publisherName",
  property_quizCountItems: "@quizCountItems",
  property_quizStrikethroughSolutions: "@quizStrikethroughSolutions",
  property_quotedPerson: "@quotedPerson",
  property_ratingLevelEnd: "@ratingLevelEnd",
  property_ratingLevelSelected: "@ratingLevelSelected",
  property_ratingLevelStart: "@ratingLevelStart",
  property_reaction: "@reaction",
  property_reasonableNumOfChars: "@reasonableNumOfChars",
  property_reductionTag: "@reductionTag",
  property_refAuthor: "@refAuthor",
  property_refBookTitle: "@refBookTitle",
  property_reference: "@reference",
  property_refPublicationYear: "@refPublicationYear",
  property_refPublisher: "@refPublisher",
  property_relatedBook: "@relatedBook",
  property_releaseDate: "@releaseDate",
  property_releaseKind: "@releaseKind",
  property_releaseVersion: "@releaseVersion",
  property_reportTag: "@reportTag",
  property_resolved: "@resolved",
  property_resolvedBy: "@resolvedBy",
  property_resolvedDate: "@resolvedDate",
  property_revealSolutions: "@revealSolutions",
  property_reviewTag: "@reviewTag",
  property_reviewerTag: "@reviewerTag",
  property_rightsPolicy: "@rightsPolicy",
  property_sampleSolution: "@sampleSolution",
  property_scormSource: "@scormSource",
  property_search: "@search",
  property_searchIndex: "@searchIndex",
  property_selected: "@selected",
  property_servings: "@servings",
  property_showInIndex: "@showInIndex",
  property_showSubtitles: "@showSubtitles",
  property_siteName: "@siteName",
  property_size: "@size",
  property_slug: "@slug",
  property_sourceBB: "@sourceBB",
  property_sourceDocument: "@sourceDocument",
  property_sourceRL: "@sourceRL",
  property_spaceId: "@spaceId",
  property_src1x: "@src1x",
  property_src2x: "@src2x",
  property_src3x: "@src3x",
  property_src4x: "@src4x",
  property_srcAlt: "@srcAlt",
  property_stripePricingTableId: "@stripePricingTableId",
  property_stripePricingTableDescriptiveName: "@stripePricingTableDescriptiveName",
  property_stripePublishableKey: "@stripePublishableKey",
  property_subject: "@subject",
  property_subtype: "@subtype",
  property_tableAutoWidth: "@tableAutoWidth",
  property_tableColWidth: "@tableColWidth",
  property_tableColumnMinWidth: "@tableColumnMinWidth",
  property_tableFixedHeader: "@tableFixedHeader",
  property_tableHeaderWhitespaceNoWrap: "@tableHeaderWhitespaceNoWrap",
  property_tableHeight: "@tableHeight",
  property_tablePagination: "@tablePagination",
  property_tablePaginationLimit: "@tablePaginationLimit",
  property_tableResizableColumns: "@tableResizableColumns",
  property_tableSearch: "@tableSearch",
  property_tableSort: "@tableSort",
  property_tableWhitespaceNoWrap: "@tableWhitespaceNoWrap",
  property_tableCellType: "@tableCellType",
  property_tableRowSpan: "@tableRowSpan",
  property_tableColSpan: "@tableColSpan",
  property_tableScope: "@tableScope",
  property_tag: "@tag",
  property_target: "@target",
  property_technicalTerm: "@technicalTerm",
  property_textReference: "@textReference",
  property_theme: "@theme",
  property_rtl: "@rtl",
  property_thumbImage: "@thumbImage",
  property_title: "@title",
  property_toc: "@toc",
  property_tocContent: "@tocContent",
  property_tocResource: "@tocResource",
  property_topicTag: "@topicTag",
  property_trim: "@trim",
  property_unit: "@unit",
  property_unitAbbr: "@unitAbbr",
  property_unitAbbrPlural: "@unitAbbrPlural",
  property_unitPlural: "@unitPlural",
  property_vendorDashboardId: "@vendorDashboardId",
  property_vendorSurveyId: "@vendorSurveyId",
  property_vendorUrl: "@vendorUrl",
  property_videoCallLink: "@videoCallLink",
  property_width: "@width",
  property_x: "@x",
  property_y: "@y",
  property_zoomDisabled: "@zoomDisabled"
};
var PropertyKey = propertyKeys;

// src/model/enum/ResourceKey.ts
import "@ncoderz/superenum";
var resourceKeys = {
  resource_image: "&image",
  resource_imageResponsive: "&imageResponsive",
  resource_imagePortrait: "&imagePortrait",
  resource_imageLandscape: "&imageLandscape",
  resource_imageEmbed: "&imageEmbed",
  resource_imageLink: "&imageLink",
  resource_audio: "&audio",
  resource_audioEmbed: "&audioEmbed",
  resource_audioLink: "&audioLink",
  resource_video: "&video",
  resource_videoEmbed: "&videoEmbed",
  resource_videoLink: "&videoLink",
  resource_stillImageFilm: "&stillImageFilm",
  resource_stillImageFilmEmbed: "&stillImageFilmEmbed",
  resource_stillImageFilmLink: "&stillImageFilmLink",
  resource_article: "&article",
  resource_articleEmbed: "&articleEmbed",
  resource_articleLink: "&articleLink",
  resource_document: "&document",
  resource_documentEmbed: "&documentEmbed",
  resource_documentLink: "&documentLink",
  resource_documentDownload: "&documentDownload",
  resource_appLink: "&appLink",
  resource_websiteLink: "&websiteLink",
  resource_icon: "&icon",
  resource_platformBackgroundImage: "&platformBackgroundImage",
  resource_platformIcon: "&platformIcon",
  resource_platformLogo: "&platformLogo",
  resource_backgroundWallpaper: "&backgroundWallpaper",
  resource_imagePlaceholder: "&imagePlaceholder",
  resource_previewImage: "&previewImage",
  resource_previewVideo: "&previewVideo",
  resource_coverImage: "&coverImage",
  resource_backgroundImage: "&backgroundImage"
};

// src/model/enum/ResourceType.ts
import "@ncoderz/superenum";

// src/utils/StringUtils.ts
var StringUtils = class {
  /**
   * Check if an object is a string.
   *
   * @param obj - The object to check.
   * @returns true if the object is a string, otherwise false.
   */
  isString(obj) {
    return typeof obj === "string" || obj instanceof String;
  }
  /**
   * Pass any value and convert it to a string.
   *
   * @param str the value to convert to a string
   * @returns the string or an empty string if the value is undefined
   */
  string(str) {
    return str ? `${str}` : "";
  }
  /**
   * Pass any value and convert it to a trimmed string.
   *
   * @param str the value to convert to a trimmed string
   * @returns the trimmed string or an empty string if the value is undefined
   */
  trimmedString(str) {
    return this.string(str).trim();
  }
  /**
   * Count the occurrences of a substring in a string.
   *
   * @param str the string to search
   * @param subStr the substring to search for
   * @returns the number of occurrences of the substring in the string
   */
  countOccurrences(str, subStr) {
    return str.split(subStr).length - 1;
  }
  /**
   * Count the occurrences of a substring at the start of a string.
   *
   * @param str the string to search
   * @param subStr the substring to search for
   * @returns the number of occurrences of the substring at the start of the string
   */
  countOccurrencesAtStart(str, subStr) {
    let count = 0;
    while (str.startsWith(subStr)) {
      count++;
      str = str.substring(subStr.length);
    }
    return count;
  }
  /**
   * Count the occurrences of a substring at the end of a string.
   *
   * @param str the string to search
   * @param subStr the substring to search for
   * @returns the number of occurrences of the substring at the end of the string
   */
  countOccurrencesAtEnd(str, subStr) {
    let count = 0;
    while (str.endsWith(subStr)) {
      count++;
      str = str.substring(0, str.length - subStr.length);
    }
    return count;
  }
  /**
   * Split a string using a set of placeholders, returning an array of strings and placeholders.
   *
   * e.g.
   * str = "This {0} a {1} with lots {2} placeholders"
   * placeholders = ["{0}", "{1}", "{2}"]
   *
   * return = ["This ", "{0}", " a ", "{1}", " with lots ", "{2}", " placeholders"]
   *
   * @param str
   * @param placeholders
   */
  splitPlaceholders(str, placeholders) {
    let res = [str];
    const splitTexts = (texts, key) => {
      const newTexts = [];
      for (const text of texts) {
        const tParts = text.split(key);
        for (let i = 0, len = tParts.length; i < len; i++) {
          const tPart = tParts[i];
          const lastIndex = i === len - 1;
          newTexts.push(tPart);
          if (!lastIndex) {
            newTexts.push(key);
          }
        }
      }
      return newTexts;
    };
    for (const p of placeholders) {
      res = splitTexts(res, p);
    }
    return res;
  }
  /**
   * Return the first line of text to a specified maximum width.
   * @argument str The string to be truncated.
   * @argument width The maximum width of the wrapped text in characters.
   * @returns A string containing the first line, possibly truncated.
   */
  firstLine(str, width) {
    const result = str;
    if (width == null) width = str.length;
    if (width < 1 || str == null || str.length <= width) return result;
    const strs = str.split(`
`);
    if (strs.length > 0) {
      return strs[0].substring(0, width);
    }
    return result;
  }
  /**
   * Wraps the provided string input to a specified maximum width.
   * @argument str The string to be wrapped.
   * @argument width The maximum width of the wrapped text in characters.
   * @returns A string array containing all lines.
   */
  wordWrap(str, width) {
    const result = [];
    if (width < 1 || str == null || str.length <= width) return result;
    const len = str.length;
    const rangeMax = len - width;
    let rangeStart = 0;
    let subString;
    while (rangeStart < rangeMax) {
      let subLength;
      const rangeEnd = rangeStart + width;
      const ixNewLine = str.indexOf("\n", rangeStart);
      if (ixNewLine > -1 && ixNewLine > rangeStart && ixNewLine < rangeEnd) {
        subLength = ixNewLine - rangeStart;
        subString = str.substr(rangeStart, subLength);
        rangeStart = rangeStart + subLength + 1;
        result.push(subString);
        continue;
      }
      const ix = str.lastIndexOf(" ", rangeEnd);
      let rangeStartNext = 0;
      if (ix > -1 && ix != rangeStart - 1) {
        subLength = ix - rangeStart;
        if (subLength > 0) {
          rangeStartNext = rangeStart + subLength + 1;
        }
      } else {
        subLength = width;
        rangeStartNext = rangeStart + width;
      }
      subString = str.slice(rangeStart, rangeStart + subLength);
      rangeStart = rangeStartNext;
      result.push(subString.trim());
    }
    if (rangeStart < len) {
      subString = str.slice(rangeStart);
      result.push(subString);
    }
    return result;
  }
  /**
   * Convert a kebab-case string to camelCase.
   *
   * @param str the string to convert
   * @returns the camelCase version of the string
   */
  kebabToCamel(str) {
    if (!str) return str;
    return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
  }
  /**
   * Convert a camelCase string to kebab-case.
   *
   * @param str the string to convert
   * @returns the kebab-case version of the string
   */
  camelToKebab(str) {
    if (!str) return str;
    return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
  }
};
var stringUtils = new StringUtils();

// src/model/enum/ResourceType.ts
var ResourceType = {
  unknown: "unknown",
  image: "image",
  imageResponsive: "image-responsive",
  imagePortrait: "image-portrait",
  imageLandscape: "image-landscape",
  imageEmbed: "image-embed",
  imageLink: "image-link",
  audio: "audio",
  audioEmbed: "audio-embed",
  audioLink: "audio-link",
  video: "video",
  videoEmbed: "video-embed",
  videoLink: "video-link",
  stillImageFilm: "still-image-film",
  stillImageFilmEmbed: "still-image-film-embed",
  stillImageFilmLink: "still-image-film-link",
  article: "article",
  articleEmbed: "article-embed",
  articleLink: "article-link",
  document: "document",
  documentEmbed: "document-embed",
  documentLink: "document-link",
  documentDownload: "document-download",
  appLink: "app-link",
  websiteLink: "website-link",
  // Aliases for image
  icon: "icon",
  backgroundWallpaper: "backgroundWallpaper",
  imagePlaceholder: "imagePlaceholder",
  platformIcon: "platformIcon",
  platformLogo: "platformLogo",
  platformBackgroundImage: "platformBackgroundImage",
  previewImage: "previewImage",
  previewVideo: "previewVideo",
  coverImage: "coverImage",
  backgroundImage: "backgroundImage"
};
function resourceTypeToConfigKey(type) {
  return `&${stringUtils.kebabToCamel(type)}`;
}

// src/model/config/enum/ConfigKey.ts
var ConfigKey = {
  // Internal
  _unknown: "_unknown",
  // Tags
  ...tags,
  // Resources
  ...resourceKeys,
  // Properties
  ...propertyKeys,
  // Groups
  ...groupKeys
};
function typeFromConfigKey(tagKey) {
  if (!tagKey) return BitTagConfigKeyType.unknown;
  if (tagKey.startsWith("@")) return BitTagConfigKeyType.property;
  if (tagKey.startsWith("&")) return BitTagConfigKeyType.resource;
  if (tagKey.startsWith("group_")) return BitTagConfigKeyType.group;
  return BitTagConfigKeyType.tag;
}
function configKeyToPropertyType(configKey) {
  return configKey.replace(/^@/, "");
}
function configKeyToResourceType(configKey) {
  return Enum(ResourceType).fromKey(configKey.replace(/^&/, ""));
}

// src/model/config/ResourcesConfig.ts
var ResourcesConfig = class {
  constructor(tags2, resourceAttachmentAllowed, resourceAttachmentConfigKey, comboResourceConfigKeysMap) {
    __publicField(this, "tags");
    __publicField(this, "resourceAttachmentAllowed");
    __publicField(this, "resourceAttachmentConfigKey");
    __publicField(this, "comboResourceConfigKeysMap");
    this.tags = tags2;
    this.resourceAttachmentAllowed = resourceAttachmentAllowed ?? false;
    this.resourceAttachmentConfigKey = resourceAttachmentConfigKey;
    this.comboResourceConfigKeysMap = comboResourceConfigKeysMap;
  }
  getCountsMin() {
    const counts = /* @__PURE__ */ new Map();
    for (const r of Object.values(this.tags)) {
      if (r.minCount != null) {
        counts.set(r.configKey, r.minCount);
      }
    }
    return counts;
  }
  getCountsMax() {
    const counts = /* @__PURE__ */ new Map();
    for (const r of Object.values(this.tags)) {
      if (r.maxCount != null) {
        counts.set(r.configKey, r.maxCount);
      }
    }
    return counts;
  }
};

// src/utils/ObjectUtils.ts
var ObjectUtils = class {
  /**
   * Check if a variable is a function
   *
   * @param val
   * @returns true if a function
   */
  isFunction(val) {
    return Object.prototype.toString.call(val) === "[object Function]";
  }
  /**
   * Check if a variable is an array
   *
   * @param val
   * @returns true if a array
   */
  isArray(val) {
    return Array.isArray(val);
  }
  /**
   * Check if a variable is a date
   *
   * @param val
   * @returns true if a date
   */
  isDate(val) {
    return Object.prototype.toString.call(val) === "[object Date]";
  }
  /**
   * Check if a variable is a object
   *
   * @param val
   * @returns true if an object
   */
  isObject(val) {
    return Object.prototype.toString.call(val) === "[object Object]";
  }
  /**
   * Check if a variable is a value (i.e. a number)
   *
   * @param val
   * @returns true if a value
   */
  isValue(val) {
    return !this.isObject(val) && !this.isArray(val);
  }
  /**
   * Get the value of a property in a plain JS object
   * using a path string or array.
   *
   * @param obj the object to get a property from
   * @param path the path to the property to set as a dotted string or array or a mix of both
   * @returns the value of the property, or undefined if the property does not exist
   */
  getViaPath(obj, path2) {
    if (!obj) return void 0;
    if (!path2) return void 0;
    if (!Array.isArray(path2)) path2 = [path2];
    path2 = path2.flatMap((p) => p.split("."));
    let node = obj;
    for (const key of path2) {
      if (node == null) return void 0;
      node = node[key];
    }
    return node;
  }
  /**
   * Set the value of a property in a plain JS object
   * using a path string or array.
   *
   * @param obj the object to modify
   * @param path the path to the property to set as a dotted string or array or a mix of both
   * @param value the value to set
   * @param create - if true, create plain objects in the path if they do not exist
   * @returns true if the value was set, false if the value was not set
   */
  setViaPath(obj, path2, value, create) {
    if (!obj) return false;
    if (!path2) return false;
    if (!Array.isArray(path2)) path2 = [path2];
    path2 = path2.flatMap((p) => p.split("."));
    let node = obj;
    for (let i = 0; i < path2.length - 1; i++) {
      const key = path2[i];
      if (node[key] == void 0) {
        if (create) {
          node[key] = {};
        } else {
          return false;
        }
      }
      node = node[key];
    }
    node[path2[path2.length - 1]] = value;
    return true;
  }
  /**
   * Return an array of the objects at the end of the paths
   * specified in the path array.
   *
   * e.g.
   *
   * const obj = obj = {
   *   a: {
   *     b: {
   *       c: 1,
   *     },
   *   },
   *   d: [{ e: 2 }, { f: 3 }],
   *   g: [{ h: [{ i: 4 }] }, { h: [{ i: 5 }, { i: 6 }] }, { h: [{ i: 7 }] }],
   * }
   *
   * objectUtils.flatMapPath(obj, ['a']);
   * [{ b: { c: 1 }]
   *
   * objectUtils.flatMapPath(obj, ['a', 'b']);
   * [{ c: 1 }]
   *
   * objectUtils.flatMapPath(obj, ['a', 'b', 'c']);
   * [1]
   *
   * objectUtils.flatMapPath(obj, ['d']);
   * [{ e: 2 }, { f: 3 }]
   *
   * objectUtils.flatMapPath(obj, ['d', 'e']);
   * [2]
   *
   * objectUtils.flatMapPath(obj, ['g']);
   * [{ h: [{ i: 4 }] }, { h: [{ i: 5 }, { i: 6 }] }, { h: [{ i: 7 }] }]
   *
   * objectUtils.flatMapPath(obj, ['g', 'h']);
   * [{ i: 4 }, { i: 5 }, { i: 6 }, { i: 7 }]
   *
   * objectUtils.flatMapPath(obj, ['g', 'h', 'i']);
   * [4, 5, 6, 7]
   *
   * @param obj
   * @param path
   * @returns
   */
  flatMapPath(obj, path2) {
    if (!obj) return [];
    if (!path2) return [];
    if (!Array.isArray(path2)) path2 = [path2];
    const node = obj;
    const key = path2.shift();
    if (key == null) return node;
    const data = node[key];
    if (!data) return [];
    if (Array.isArray(data)) {
      return data.flatMap((d) => this.flatMapPath(d, path2.slice()));
    } else {
      return [this.flatMapPath(data, path2)].flat();
    }
  }
  // testFlatMapPath() {
  //   const obj = {
  //     a: {
  //       b: {
  //         c: 1,
  //       },
  //     },
  //     d: [{ e: 2 }, { f: 3 }],
  //     g: [{ h: [{ i: 4 }] }, { h: [{ i: 5 }, { i: 6 }] }, { h: [{ i: 4 }] }],
  //   };
  //   console.log(this.flatMapPath(obj, 'a')); // [{ b: { c: 1 }]
  //   console.log(this.flatMapPath(obj, ['a', 'b'])); // [{ c: 1 }]
  //   console.log(this.flatMapPath(obj, ['a', 'b', 'c'])); // [1]
  //   console.log(this.flatMapPath(obj, ['d'])); // [{ e: 2 }, { f: 3 }]
  //   console.log(this.flatMapPath(obj, ['d', 'e'])); // [2]
  //   console.log(this.flatMapPath(obj, ['g'])); // [{ h: [{ i: 4 }] }, { h: [{ i: 5 }, { i: 6 }] }, { h: [{ i: 4 }] }]
  //   console.log(this.flatMapPath(obj, ['g', 'h'])); // [{ i: 4 }, { i: 5 }, { i: 6 }, { i: 4 }]
  //   console.log(this.flatMapPath(obj, ['g', 'h', 'i'])); // [4, 5, 6, 4]
  // }
  /**
   * Order the properties of a plain JS object.
   * The original properties are removed, and re-added in the new order.
   * Any non-specified properties are dropped.
   *
   * NOTE: The JS spec does not specifiy an order for property iteration, but all major JS engines including V8 which
   * is used by NodeJS and Chrome do have a specific iteration order which is defined by the order properties are
   * added to an object. Many applications rely on this iteration order.
   *
   * @param obj - object with properties to re-order
   * @param order - array of property keys in the desired order.
   */
  orderProperties(obj, order) {
    const objAsAny = obj;
    const props = Object.assign({}, objAsAny);
    for (const k of Object.keys(objAsAny)) {
      delete objAsAny[k];
    }
    for (const k of order) {
      if (Object.prototype.hasOwnProperty.call(props, k)) {
        objAsAny[k] = props[k];
      }
    }
  }
  /**
   * Remove any keys whose value is 'undefined' from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeUndefinedProperties(obj, excludeKeys) {
    const objAsAny = obj;
    for (const [k, v] of Object.entries(objAsAny)) {
      if (excludeKeys && excludeKeys.indexOf(k) >= 0) continue;
      if (v == void 0) {
        delete objAsAny[k];
      }
    }
  }
  /**
   * Remove any keys whose value is 'false' from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeFalseProperties(obj, excludeKeys) {
    const objAsAny = obj;
    for (const [k, v] of Object.entries(objAsAny)) {
      if (excludeKeys && excludeKeys.indexOf(k) >= 0) continue;
      if (v === false) {
        delete objAsAny[k];
      }
    }
  }
  /**
   * Remove any keys whose value is '' from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeEmptyStringProperties(obj, excludeKeys) {
    const objAsAny = obj;
    for (const [k, v] of Object.entries(objAsAny)) {
      if (excludeKeys && excludeKeys.indexOf(k) >= 0) continue;
      if (v === "") {
        delete objAsAny[k];
      }
    }
  }
  /**
   * Remove any keys whose value is '[]' from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeEmptyArrayProperties(obj, excludeKeys) {
    const objAsAny = obj;
    for (const [k, v] of Object.entries(objAsAny)) {
      if (excludeKeys && excludeKeys.indexOf(k) >= 0) continue;
      if (Array.isArray(v) && v.length === 0) {
        delete objAsAny[k];
      }
    }
  }
  /**
   * Remove any keys whose value is '{}' from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeEmptyObjectProperties(obj, excludeKeys) {
    const objAsAny = obj;
    for (const [k, v] of Object.entries(objAsAny)) {
      if (excludeKeys && excludeKeys.indexOf(k) >= 0) continue;
      if (this.isObject(v) && Object.keys(v).length === 0) {
        delete objAsAny[k];
      }
    }
  }
  /**
   * Remove any unwanted properties from a plain JS object
   *
   * @param obj plain JS object
   * @param excludeKeys keys to be excluded from removal
   */
  removeUnwantedProperties(obj, options) {
    options = Object.assign({}, options);
    if (!options.ignoreAllUndefined) this.removeUndefinedProperties(obj, options.ignoreUndefined);
    if (!options.ignoreAllFalse) this.removeFalseProperties(obj, options.ignoreFalse);
    if (!options.ignoreAllEmptyString)
      this.removeEmptyStringProperties(obj, options.ignoreEmptyString);
    if (!options.ignoreAllEmptyArrays)
      this.removeEmptyArrayProperties(obj, options.ignoreEmptyArrays);
    if (!options.ignoreAllEmptyObjects)
      this.removeEmptyObjectProperties(obj, options.ignoreEmptyObjects);
  }
  /**
   * Extract a single value from a plain JS object
   *
   * If the item at the key is not an array, the item is returned.
   * If the object at the key is an array, the last item in the array is returned.
   *
   * @param obj
   * @param key
   * @returns a non-array value, or undefined if the key does not exist
   */
  extractSingleValue(obj, key) {
    if (!obj) return void 0;
    const objAsAny = obj;
    if (objAsAny[key] == void 0) return void 0;
    let value = objAsAny[key];
    if (Array.isArray(value)) {
      if (value.length === 0) return void 0;
      value = value[value.length - 1];
    }
    return value;
  }
  /**
   * Deep merge two or more objects or arrays.
   * (c) 2023 Chris Ferdinandi, MIT License, https://gomakethings.com
   * TypeScript version (c) 2023 RA Sewell
   *
   * @param   objs  The arrays or objects to merge
   * @returns The merged arrays or objects
   */
  deepMerge(...objs) {
    function _deepMerge(...objs2) {
      function getType(obj) {
        return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
      }
      function mergeObj(clone2, obj) {
        for (const [key, value] of Object.entries(obj)) {
          const type = getType(value);
          if (clone2[key] !== void 0 && getType(clone2[key]) === type && ["array", "object"].includes(type)) {
            clone2[key] = _deepMerge(clone2[key], value);
          } else {
            clone2[key] = structuredClone(value);
          }
        }
      }
      let clone = structuredClone(objs2.shift());
      for (const obj of objs2) {
        const type = getType(obj);
        if (getType(clone) !== type) {
          clone = structuredClone(obj);
          continue;
        }
        if (type === "array") {
          clone = [...clone, ...structuredClone(obj)];
        } else if (type === "object") {
          mergeObj(clone, obj);
        } else {
          clone = obj;
        }
      }
      return clone;
    }
    return _deepMerge(...objs);
  }
};
var objectUtils = new ObjectUtils();

// src/config/ConfigHydrator.ts
import { Enum as Enum2 } from "@ncoderz/superenum";

// src/config/raw/cardSets.ts
var CARDSETS = {
  //
  // flashcard
  //
  [CardSetConfigKey.flashcard]: {
    jsonKey: "cards",
    exportJsonKey: { cards: "$" },
    sides: [
      {
        name: "question",
        variants: [
          {
            jsonKey: "question.text",
            exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "question.icon|resource(type=image, key=image)",
                exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the flashcard question.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          }
        ]
      },
      {
        name: "answer",
        variants: [
          {
            jsonKey: "answer.text",
            exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "answer.icon|resource(type=image, key=image)",
                exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the flashcard answer.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          },
          {
            jsonKey: "alternativeAnswers[].text",
            exportJsonKey: { alternativeAnswers: [{ text: "$" }] },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)",
                exportJsonKey: {
                  alternativeAnswers: [{ icon: { type: "image", image: { src: "$" } } }]
                }
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // flashcard1 — single-card variant of flashcard. Same shape, but capped
  // at one card via `sections.default.maxCount = 1` (PLAN-085). Used by
  // the `flashcard-1` bit type; the multi-card `flashcard` and
  // `q-and-a-card` bit types continue to use the unbounded `flashcard`
  // cardset.
  //
  [CardSetConfigKey.flashcard1]: {
    jsonKey: "cards",
    exportJsonKey: { cards: "$" },
    sections: {
      default: {
        jsonKey: "cards",
        exportJsonKey: { cards: "$" },
        isDefault: true,
        maxCount: 1
      }
    },
    sides: [
      {
        name: "question",
        variants: [
          {
            jsonKey: "question.text",
            exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "question.icon|resource(type=image, key=image)",
                exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the flashcard question.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          }
        ]
      },
      {
        name: "answer",
        variants: [
          {
            jsonKey: "answer.text",
            exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "answer.icon|resource(type=image, key=image)",
                exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the flashcard answer.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          },
          {
            jsonKey: "alternativeAnswers[].text",
            exportJsonKey: { alternativeAnswers: [{ text: "$" }] },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the flashcard."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the flashcard."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the flashcard.",
                jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)",
                exportJsonKey: {
                  alternativeAnswers: [{ icon: { type: "image", image: { src: "$" } } }]
                }
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // definition-list
  //
  [CardSetConfigKey.definitionList]: {
    jsonKey: "definitions",
    exportJsonKey: { definitions: "$" },
    // PLAN-141 F1 (NISO import): each <def-item> is one card; the sides
    // read <term>/<def>; the enclosing <legend>'s <title> lands on the
    // term side's heading tag ([#Key] header card).
    // PLAN-141 F3: a <tbx:langSet> is ALSO one card (a terminology entry —
    // its enclosing <tbx:termEntry> is a transparent claim wrapper); its
    // children compose the term/definition sides via the side rules below.
    mappingKeys: {
      "xml-niso-iec": [
        { "@el": "def-item", "@children": "$" },
        { "@el": "tbx:langSet", "@children": "$" }
      ]
    },
    sides: [
      {
        name: "term",
        // F3: every <tbx:tig>'s <tbx:term> STACKS one-per-line onto the
        // single term side (the "$\n" template's authored separator; the
        // trailing one is trimmed) — the PDF's Maus/Mus/M/Müsli stack.
        mappingKeys: {
          "xml-niso-iec": [
            { "@el": "term", "@text": "$" },
            { "@el": "tbx:tig", "@children": { "@el": "tbx:term", "@text": "$\n" } }
          ]
        },
        variants: [
          {
            jsonKey: "term.text",
            exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the definition.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } },
                mappingKeys: { "xml-niso-iec": { "@el": "title", "@text": "$" } }
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                format: TagFormat.plainText,
                jsonKey: "term.icon|resource(type=image, key=image)",
                exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the definition term.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          }
        ]
      },
      {
        name: "definition",
        // F3: the definition side COMPOSES from the entry's fields in
        // document order, with the ES-rendered locale affixes AUTHORED as
        // value-position templates (never engine text): the subject field
        // in ⟨⟩ glues (trailing space) onto the definition line; example /
        // notes / source follow as blank-line-separated lines. The TBX
        // grammar flags (normativeAuthorization, termType, partOfSpeech,
        // grammaticalNumber) are unrendered machine-only data — dropped and
        // recorded (catalog R-2, reviewed under D7 max-preservation).
        mappingKeys: {
          "xml-niso-iec": [
            { "@el": "def", "@children": "$" },
            { "@el": "tbx:subjectField", "@text": "\u27E8$\u27E9 " },
            { "@el": "tbx:definition", "@children": "$" },
            { "@el": "tbx:example", "@children": "BEISPIEL $" },
            { "@el": "tbx:note", "@children": "Anmerkung zu Begriff: $" },
            { "@el": "tbx:source", "@text": "[QUELLE: $]" }
          ]
        },
        variants: [
          {
            jsonKey: "definition.text",
            exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the definition.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                jsonKey: "definition.icon|resource(type=image, key=image)",
                exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the definition.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          },
          {
            jsonKey: "alternativeDefinitions[].text",
            exportJsonKey: { alternativeDefinitions: [{ text: "$" }] },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the definition."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)",
                exportJsonKey: {
                  alternativeDefinitions: [{ icon: { type: "image", image: { src: "$" } } }]
                }
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // definition-list-plain
  //
  // Same shape as `definitionList`, but every variant body is rendered as
  // plain text (string), not bitmark+ AST. Used by
  // `meta-search-default-terms` / `meta-search-default-topics`, which need
  // term/definition text emitted verbatim instead of as a ProseMirror tree.
  //
  [CardSetConfigKey.definitionListPlain]: {
    jsonKey: "definitions",
    exportJsonKey: { definitions: "$" },
    sides: [
      {
        name: "term",
        variants: [
          {
            jsonKey: "term.text",
            exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the definition.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                format: TagFormat.plainText,
                jsonKey: "term.icon|resource(type=image, key=image)",
                exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the definition term.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          }
        ]
      },
      {
        name: "definition",
        variants: [
          {
            jsonKey: "definition.text",
            exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the definition.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                jsonKey: "definition.icon|resource(type=image, key=image)",
                exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
              },
              {
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the definition.",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          },
          {
            jsonKey: "alternativeDefinitions[].text",
            exportJsonKey: { alternativeDefinitions: [{ text: "$" }] },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the definition."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the definition."
              },
              {
                key: ConfigKey.group_resourceIcon,
                description: "Icon resource for the definition.",
                jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)",
                exportJsonKey: {
                  alternativeDefinitions: [{ icon: { type: "image", image: { src: "$" } } }]
                }
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // match-pairs
  //
  [CardSetConfigKey.matchPairs]: {
    jsonKey: "pairs",
    exportJsonKey: { pairs: "$" },
    sides: [
      {
        name: "key",
        variants: [
          {
            jsonKey: "key",
            exportJsonKey: { key: "$" },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the match pair."
              },
              {
                // Pair-level example: emit `isExample` alongside `example`
                // (matches OLD parser's per-pair allow-list behaviour).
                //
                // PLAN-074: a valued cascade source (`[@example:V]` anywhere
                // in the cardset) fills each pair's `example` with `V`
                // once per pair from the keys-side cascade fire (single
                // parsed_variant per pair — no per-value duplication).
                //
                // PLAN-076 NOTE: matchPairs (TAG 415) KEEPS `example: '$'`
                // at the pair-object level. BPG `parseMatchPairs`
                // (`CardContentProcessor.ts:714-723`) emits ONE example
                // per PAIR (the `exampleCard` accumulator), placed at the
                // pair object. Unlike matchMatrix (per-cell example),
                // matchPairs has no nested per-cell layer, so the pair-key
                // `[@example:V]` must double-emit as the visible field.
                // Confirmed via `chaining` fixture: `match-solution-grouped`
                // expects `example: "packen"` at pair-object level from
                // `die Koffer[%1][@example:packen]`. Original PLAN-076
                // draft applied to both keys-side tags; the matchPairs
                // half was reverted after fixture parity testing.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
                  {
                    predicates: ["@absent", { $cascade: "*" }],
                    rule: {
                      isExample: true,
                      example: "$cascade",
                      "@bit": { isExample: true }
                    }
                  },
                  { "@absent": { isExample: true } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker / value on the match pair.",
                format: TagFormat.bitmarkText,
                nullable: true
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              },
              {
                // PLAN-084: gate the key-side `@absent → $ancestor` cascade
                // synth with `@variantLacksTag=isCaseSensitive` so the rule
                // only fires when NO variant in the active card explicitly
                // carries `[@isCaseSensitive]`. When a value-side variant
                // carries the tag (e.g. `[@isCaseSensitive:false]`), the
                // synth is skipped here; the value-side explicit fire owns
                // the pair-level emission.
                key: ConfigKey.property_isCaseSensitive,
                exportJsonKey: [
                  {
                    predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
                    rule: { isCaseSensitive: "$ancestor" }
                  },
                  { isCaseSensitive: "$" }
                ],
                description: "Property to indicate if the match is case sensitive.",
                format: TagFormat.boolean
              },
              {
                key: ConfigKey.resource_audio,
                description: "Audio resource for the match pair.",
                jsonKey: "keyAudio",
                exportJsonKey: { keyAudio: "$" }
              },
              {
                key: ConfigKey.resource_image,
                description: "Image resource for the match pair.",
                jsonKey: "keyImage",
                exportJsonKey: { keyImage: "$" }
              }
            ]
          }
        ]
      },
      {
        name: "values",
        repeat: true,
        variants: [
          {
            jsonKey: "values[]",
            exportJsonKey: { values: ["$"] },
            format: TextFormat2.plainText,
            tags: [
              {
                // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
                // BPG `parseMatchPairs` does `delete tags.item; delete tags.lead;`
                // for sideIdx > 0; the schema-side equivalent is to use a
                // group_standardTags variant that omits group_standardItemLead.
                key: ConfigKey.group_standardTagsNoItemLead,
                description: "Standard tags for the match pair (values side, no item/lead)."
              },
              {
                // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.6: cascade-fired
                // `@example` from the bit header fills each pair's
                // `example` with the pair's first value (mirrors BPG's
                // `fillStringExample(pairs, __defaultExample=values[0])`).
                //
                // PLAN-074: the cascade-source-VALUE branch is handled by
                // TAG 415 (keys side) which fires once per pair — keeping
                // it here would emit `example` twice per pair (one per
                // values-side parsed_variant) and merge them into a
                // multi-paragraph array.
                //
                // PLAN-075: bare `$parent` reads the parent variant fire's
                // source `$` value (the current `++`-iteration's value
                // text). For single-value pairs this matches the old
                // `$parent.values[0]`; for multi-value pairs with
                // `[@example]` attached to a non-first value, it correctly
                // resolves to that value.
                //
                // PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
                // routes pair-key `[@example:V]` (a per-pair cascade
                // source, bound via per-card CascadeCounters scope) and
                // bit-header `[@example:V]` (cardset cascade) into the
                // pair-level `example` via `$cascade`. The aggregator in
                // `populate_variants` fires this rule ONCE per pair (BPG
                // `parseMatchPairs` `exampleCard` precedence), avoiding
                // per-iter pollution.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  // PLAN-084: bare `[@example]` on a values-side variant fires
                  // once per pair (first-wins, mirrors BPG `parseMatchPairs`
                  // `exampleCard` + `pushDownTree` set-if-null semantic).
                  // `maxEmits: 1` enforced inside the per-pair cascade-counter
                  // scope opened by `build_card_item`.
                  {
                    predicates: ["@keyonly"],
                    maxEmits: 1,
                    rule: {
                      isExample: true,
                      example: "$parent",
                      "@bit": { isExample: true }
                    }
                  },
                  {
                    predicates: ["@absent", { $cascade: "*" }],
                    rule: {
                      isExample: true,
                      example: "$cascade",
                      "@bit": { isExample: true }
                    }
                  },
                  {
                    "@absent": {
                      isExample: true,
                      example: "$parent",
                      "@bit": { isExample: true }
                    }
                  },
                  // PLAN-084: same cap applies to the explicit-value `[@example:V]`
                  // form so per-`++`-iter values-side fires also first-win.
                  {
                    predicates: [],
                    maxEmits: 1,
                    rule: { isExample: true, example: "$", "@bit": { isExample: true } }
                  }
                ],
                description: "Example marker / value on the match pair (values side).",
                format: TagFormat.bitmarkText,
                nullable: true
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              },
              {
                // PLAN-084: symmetric @variantLacksTag gating with the key-side.
                key: ConfigKey.property_isCaseSensitive,
                exportJsonKey: [
                  {
                    predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
                    rule: { isCaseSensitive: "$ancestor" }
                  },
                  { isCaseSensitive: "$" }
                ],
                description: "Property to indicate if the match is case sensitive.",
                format: TagFormat.boolean
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // match-audio-pairs
  //
  [CardSetConfigKey.matchAudioPairs]: {
    jsonKey: "pairs",
    exportJsonKey: { pairs: "$" },
    sides: [
      {
        name: "key",
        variants: [
          {
            jsonKey: "key",
            exportJsonKey: { key: "$" },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the audio match pair."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the audio match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              },
              {
                key: ConfigKey.resource_audio,
                description: "Audio resource for the match pair.",
                jsonKey: "keyAudio",
                exportJsonKey: { keyAudio: "$" }
              }
            ]
          }
        ]
      },
      {
        name: "values",
        repeat: true,
        variants: [
          {
            jsonKey: "values[]",
            exportJsonKey: { values: ["$"] },
            format: TextFormat2.plainText,
            tags: [
              {
                // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
                key: ConfigKey.group_standardTagsNoItemLead,
                description: "Standard tags for the audio match pair (values side, no item/lead)."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the audio match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              },
              {
                key: ConfigKey.resource_audio,
                description: "Audio resource for the match pair."
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // match-image-pairs
  //
  [CardSetConfigKey.matchImagePairs]: {
    jsonKey: "pairs",
    exportJsonKey: { pairs: "$" },
    sides: [
      {
        name: "key",
        variants: [
          {
            jsonKey: "key",
            exportJsonKey: { key: "$" },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the image match pair."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the image match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              },
              {
                key: ConfigKey.resource_image,
                description: "Image resource for the match pair.",
                jsonKey: "keyImage",
                exportJsonKey: { keyImage: "$" }
              }
            ]
          }
        ]
      },
      {
        name: "values",
        repeat: true,
        variants: [
          {
            jsonKey: "values[]",
            exportJsonKey: { values: ["$"] },
            format: TextFormat2.plainText,
            tags: [
              {
                // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
                key: ConfigKey.group_standardTagsNoItemLead,
                description: "Standard tags for the image match pair (values side, no item/lead)."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the image match pair.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              },
              {
                key: ConfigKey.resource_image,
                description: "Image resource for the match pair."
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // match-matrix
  //
  [CardSetConfigKey.matchMatrix]: {
    jsonKey: "matrix",
    exportJsonKey: { matrix: "$" },
    sides: [
      {
        name: "key",
        variants: [
          {
            jsonKey: "key",
            exportJsonKey: { key: "$" },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                // PLAN-076: drop `example: '$'` from valued rule — pair-key
                // `[@example:V]` is consumed as the per-pair cascade source.
                // PLAN-077 R14 follow-up: investigation showed the fixture
                // reference is INCONSISTENT — bits without a cardset-level
                // cascade (e.g. bit 3 `[@example:Y]should be...`) expect
                // pair-level `example: V` at pair scope, but bits WITH a
                // cardset cascade (e.g. bit 8 `[@example:99]` in title-row
                // PLUS pair-key `[@example:Y]`) expect NO pair-level
                // example. Emitting `example: $` here satisfies bit 3 but
                // breaks bit 8; dropping it satisfies bit 8 but breaks bit
                // 3. Conditional emission would require runtime gating on
                // the cardset cascade scope state. Left dropped pending a
                // dedicated plan — accepted divergence on bit 3's pair-
                // level emission (cells still get the per-cell `example`
                // correctly via PLAN-077 §4.2 + §4.3).
                key: ConfigKey.property_example,
                exportJsonKey: [
                  {
                    "@keyonly": {
                      isExample: true,
                      "@bit": { isExample: true },
                      "@card": { isExample: true }
                    }
                  },
                  {
                    "@absent": {
                      isExample: true,
                      "@bit": { isExample: true },
                      "@card": { isExample: true }
                    }
                  },
                  { isExample: true, "@bit": { isExample: true }, "@card": { isExample: true } }
                ],
                description: "Example text for the match matrix.",
                format: TagFormat.plainText,
                nullable: true
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the match matrix.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              },
              {
                // Axis-side `[@isCaseSensitive]` is a cascade SOURCE
                // only: it must fire (so value-side cells inherit via
                // `$ancestor`) but emit nothing itself — emission is
                // owned by the value-side's cell-nested rule
                // (`cells[$s].isCaseSensitive`, PLAN-122). The earlier
                // flat `$ancestor` rule here double-emitted at bit
                // scope and broke the `optimize(fullize(x)) ==
                // optimize(x)` law on match-matrix.
                key: ConfigKey.property_isCaseSensitive,
                exportJsonKey: {},
                description: "Property to indicate if the match is case sensitive.",
                format: TagFormat.boolean
              }
            ]
          }
        ]
      },
      {
        name: "cell",
        repeat: true,
        variants: [
          {
            jsonKey: "cells[{s}].values[]",
            exportJsonKey: [
              // PLAN-085 / R14: the positional-blank emission for an
              // effectively-absent cell side fires ONLY when the active
              // card has at least one `[#]` variant fire. This matches
              // BPG `extractHeadingCard`'s `isHeading` gate: when no
              // `[#]` exists anywhere in the title card, BPG returns
              // undefined and no `heading` is emitted. The phantom
              // `forValues:['']` placeholder is appropriate only when
              // the card is being treated as a title-row.
              {
                predicates: ["@absent", { "@variantHasTag": "#" }],
                rule: { "@bit": { heading: { forValues: [""] } } }
              },
              { cells: { $s: { values: ["$"] } } }
            ],
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                // PLAN-075: bare `$parent` reads the parent variant fire's
                // source `$` value (the current `++`-iteration's cell text).
                // Replaces `$parent.values[0]` which pinned to position 0
                // regardless of which `++`-value `[@example]` was on.
                //
                // PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
                // routes pair-key `[@example:V]` (a per-pair cascade
                // source, bound via per-card CascadeCounters scope) and
                // bit-header `[@example:V]` (cardset cascade) into the
                // per-cell `example` via `$cascade`. The aggregator in
                // `populate_variants` fires this rule ONCE per cell (BPG
                // `parseMatchMatrix` `exampleSide` precedence), avoiding
                // per-iter pollution.
                // @card scope-shift writes matrix-row container isExample.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  {
                    "@keyonly": {
                      cells: { $s: { isExample: true, example: "$parent" } },
                      "@bit": { isExample: true },
                      "@card": { isExample: true }
                    }
                  },
                  {
                    predicates: ["@absent", { $cascade: "*" }],
                    rule: {
                      cells: { $s: { isExample: true, example: "$cascade" } },
                      "@bit": { isExample: true },
                      "@card": { isExample: true }
                    }
                  },
                  {
                    "@absent": {
                      cells: { $s: { isExample: true, example: "$parent" } },
                      "@bit": { isExample: true },
                      "@card": { isExample: true }
                    }
                  },
                  {
                    cells: { $s: { isExample: true, example: "$" } },
                    "@bit": { isExample: true },
                    "@card": { isExample: true }
                  }
                ],
                description: "Example text for the match matrix.",
                format: TagFormat.bitmarkText,
                nullable: true
              },
              {
                // PLAN-085 / R14: bare `[#]` on a value-side emits the
                // empty-string positional blank into `forValues` so the
                // array stays aligned with the cell positions (matches BPG
                // `extractHeadingCard`'s "all value-side headings present"
                // shape). The `@keyonly` rule is required for two reasons:
                // 1) it provides an emission for bare `[#]` (the `$` sigil
                //    in the unconditional rule has no source for bare); and
                // 2) the presence of any `@keyonly` rule disables the
                //    optimised-mode `should_suppress_outcome` strip (see
                //    `forward_emit.rs`) so the resulting `forValues: [""]`
                //    survives the all-natural-defaults check.
                key: ConfigKey.tag_title,
                description: "Title of the match matrix.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: [
                  { "@keyonly": { "@bit": { heading: { forValues: [""] } } } },
                  { "@bit": { heading: { forValues: ["$"] } } }
                ]
              },
              {
                // matchMatrix cells are NESTED (`cells[$s]`), unlike matchPairs'
                // flat pair object — so `isCaseSensitive` must be written INTO the
                // cell, mirroring the sibling `example` rule above (`cells: { $s:
                // {...} }`). The earlier flat `{ isCaseSensitive: '$' }` (copied
                // from matchPairs) wrote at bit scope, so an authored cell-side
                // `[@isCaseSensitive:false]` (e.g. on a `++` value) was dropped
                // instead of landing on the cell. PLAN-122.
                key: ConfigKey.property_isCaseSensitive,
                exportJsonKey: [
                  { "@absent": { cells: { $s: { isCaseSensitive: "$ancestor" } } } },
                  { cells: { $s: { isCaseSensitive: "$" } } }
                ],
                description: "Property to indicate if the match is case sensitive.",
                format: TagFormat.boolean
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // statements
  //
  [CardSetConfigKey.statements]: {
    jsonKey: "statements",
    exportJsonKey: { statements: "$" },
    sides: [
      {
        name: "statement",
        variants: [
          {
            jsonKey: "statement",
            exportJsonKey: { statement: "$" },
            tags: [
              {
                key: ConfigKey.tag_true,
                description: "Tag for true statements.",
                jsonKey: "statement|set(isCorrect=true)",
                exportJsonKey: { statement: "$", isCorrect: true },
                maxCount: 1
              },
              {
                key: ConfigKey.tag_false,
                description: "Tag for false statements.",
                jsonKey: "statement|set(isCorrect=false)",
                exportJsonKey: { statement: "$", isCorrect: false },
                maxCount: 1
              },
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the statement."
              },
              {
                // PLAN-072: cascade-fired `@example` from the bit header
                // fills each statement entry's `example` with the entry's
                // own `isCorrect`, mirroring BPG's `pushExampleDownTree`
                // for the statements cardset. Per-array-item strip removes
                // `example: false` from incorrect entries so only correct
                // entries retain `example: true`.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  {
                    "@keyonly": {
                      isExample: true,
                      example: true,
                      "@bit": { isExample: true }
                    }
                  },
                  {
                    "@absent": {
                      isExample: true,
                      example: "$parent.isCorrect",
                      "@bit": { isExample: true }
                    }
                  },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker on the statement. Bare `[@example]` emits the boolean marker; valued `[@example:true|false]` emits the literal value. Cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
                format: TagFormat.boolean,
                nullable: true
              }
            ],
            bodyAllowed: false
          }
        ]
      }
    ]
  },
  //
  // quiz
  //
  [CardSetConfigKey.quiz]: {
    jsonKey: "quizzes",
    exportJsonKey: { quizzes: "$" },
    sides: [
      {
        name: "choices",
        variants: [
          {
            jsonKey: null,
            tags: [
              {
                key: ConfigKey.group_trueFalse,
                description: "Group for true/false questions."
              },
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the quiz."
              },
              {
                // ALIGN-EXAMPLE-CASCADE §3.2: variant-level cascade for
                // quiz choices is delegated to group_trueFalse's
                // chain-child `@example` (`+` chain with maxEmits:1 +
                // `@parent.isCorrect=true`). Variant-level `@absent`
                // would over-fire on subsequent correct choices and on
                // incorrect ones — drop it.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
                  { isExample: true, "@bit": { isExample: true } }
                ],
                description: "Example marker on the quiz choice (entry-local only; cascade handled by group_trueFalse chain-children).",
                format: TagFormat.boolean,
                nullable: true
              }
            ],
            bodyAllowed: false
          }
        ]
      }
    ]
  },
  //
  // quiz (responses)
  //
  [CardSetConfigKey.quizResponses]: {
    jsonKey: "quizzes",
    exportJsonKey: { quizzes: "$" },
    sides: [
      {
        name: "responses",
        variants: [
          {
            jsonKey: null,
            tags: [
              {
                key: ConfigKey.group_trueFalseResponses,
                description: "Group for true/false questions."
              },
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the quiz."
              },
              {
                // PLAN-072: cascade-fired `@example` from the bit header
                // fills each response entry's `example` with the entry's
                // own `isCorrect`, mirroring BPG's `pushExampleDownTree`
                // for the quiz-responses cardset (statements-style).
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
                  {
                    "@absent": {
                      isExample: true,
                      example: "$parent.isCorrect",
                      "@bit": { isExample: true }
                    }
                  },
                  { isExample: true, "@bit": { isExample: true } }
                ],
                description: "Example marker on the quiz response; cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
                format: TagFormat.boolean,
                nullable: true
              }
            ],
            bodyAllowed: false
          }
        ]
      }
    ]
  },
  //
  // feedback
  //
  [CardSetConfigKey.feedback]: {
    jsonKey: "feedbacks",
    exportJsonKey: { feedbacks: "$" },
    sides: [
      {
        name: "choices",
        variants: [
          {
            jsonKey: null,
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                // Feedback choices: `[+]` / `[-]` map to choice + `requireReason`
                // (NOT `isCorrect` like trueFalse). Shadow the inherited
                // `tag_true` / `tag_false` from `group_trueFalse` to emit the
                // feedback-specific shape.
                //
                // Chain @example: `[+ X][@example]` writes
                // `isExample/example` onto the X choice only. BPG's
                // `setIsExampleFlags` (Builder.ts:4041-4172) does NOT
                // iterate `cardNode.feedbacks` — so unlike multipleChoice,
                // there is no bubble to bit-level or feedbacks-level
                // isExample. Pattern omits `@bit` scope-shift accordingly.
                // No `@absent`/`@parent.isCorrect` cascade rules either:
                // BPG's `pushExampleDownTree` (Builder.ts:3777-3849) has
                // no feedback dispatch, so bit-header `[@example]` does
                // not flow into feedback choices.
                key: ConfigKey.tag_true,
                exportJsonKey: { choices: [{ choice: "$", requireReason: true }] },
                description: "Reason-required choice for feedback (`[+]`).",
                maxCount: Count.infinity,
                format: TagFormat.plainText,
                chain: [
                  {
                    key: ConfigKey.property_example,
                    exportJsonKey: [
                      { "@keyonly": { isExample: true, example: true } },
                      { isExample: true, example: "$" }
                    ],
                    description: "Per-choice example marker for feedback `[+]`.",
                    format: TagFormat.boolean,
                    maxCount: 1,
                    nullable: true
                  },
                  {
                    // `[+ X][! foo]` chains `!` (instruction) onto the
                    // choice; same for `[?]` (hint), `[%]` (item),
                    // `[%2]`/`[%3]`/`[%4]` (lead/pageNumber/marginNumber).
                    // Mirrors group_trueFalse's `+` chain shape.
                    key: ConfigKey.group_standardItemLeadInstructionHint,
                    description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
                  }
                ]
              },
              {
                key: ConfigKey.tag_false,
                exportJsonKey: { choices: [{ choice: "$", requireReason: false }] },
                description: "Reason-not-required choice for feedback (`[-]`).",
                maxCount: Count.infinity,
                format: TagFormat.plainText,
                chain: [
                  {
                    key: ConfigKey.property_example,
                    exportJsonKey: [
                      { "@keyonly": { isExample: true, example: true } },
                      { isExample: true, example: "$" }
                    ],
                    description: "Per-choice example marker for feedback `[-]`.",
                    format: TagFormat.boolean,
                    maxCount: 1,
                    nullable: true
                  },
                  {
                    key: ConfigKey.group_standardItemLeadInstructionHint,
                    description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
                  }
                ]
              },
              {
                key: ConfigKey.group_trueFalse,
                description: "Group for true/false feedback."
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the feedback.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forKeys",
                exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
              }
            ],
            bodyAllowed: false
          }
        ]
      },
      {
        name: "reason",
        variants: [
          {
            // PLAN: feedback.reason — Object-only variant pattern. Per
            // SYNTAX.md §8.1, sibling tag emissions do NOT auto-wrap
            // under `reason.*`; each tag here spells out the full path.
            // Body is a plain string (BPG `parseFeedback` uses
            // `cardBodyStr ?? ''` for `reason.text`).
            jsonKey: "reason.text",
            exportJsonKey: { reason: { text: "$" } },
            format: TextFormat2.plainText,
            tags: [
              {
                // Instruction lands under reason. Hint/item/lead/etc.
                // omitted: not exercised by any fixture and BPG's
                // `parseFeedback` spreads only the tags actually present
                // — adding the rest can be done lazily as fixtures
                // require them, with the same `{reason: {…}}` wrapper.
                key: ConfigKey.tag_instruction,
                exportJsonKey: { reason: { instruction: "$" } },
                description: "Instruction for the feedback reason.",
                format: TagFormat.bitmarkText
              },
              {
                key: ConfigKey.property_reasonableNumOfChars,
                exportJsonKey: [
                  { "@absent": { reason: { reasonableNumOfChars: "$ancestor" } } },
                  { reason: { reasonableNumOfChars: "$" } }
                ],
                description: "Property for reasonable number of characters.",
                format: TagFormat.number
              },
              {
                // ALIGN-EXAMPLE-CASCADE §3.9: `feedback.reason` is NOT in
                // BPG's `pushExampleDownTree` dispatch — no bit-header
                // cascade. Local emission only.
                //
                // Both `reason.example` and `feedbacks[].example` are
                // `TextAst` per BPG's schema — paragraph arrays. Format
                // here is `bitmarkText` so `$` coerces to a paragraph
                // array at both write sites.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  {
                    // Bare `[@example]`: reason.example is TextAst — BPG
                    // coerces the boolean default to a paragraph
                    // containing the string "true". feedbacks[].example
                    // is not TextAst, stays a boolean. Two literal writes.
                    "@keyonly": {
                      reason: {
                        isExample: true,
                        example: [{ type: "paragraph", content: [{ text: "true", type: "text" }] }]
                      },
                      isExample: true,
                      example: true,
                      "@bit": { isExample: true }
                    }
                  },
                  {
                    reason: { isExample: true, example: "$" },
                    isExample: true,
                    example: "$",
                    "@bit": { isExample: true }
                  }
                ],
                description: "Example text for the feedback reason (entry-local only; no bit-header cascade).",
                format: TagFormat.bitmarkText,
                nullable: true
              },
              {
                key: ConfigKey.tag_title,
                description: "Title of the feedback.",
                format: TagFormat.plainText,
                nullable: true,
                jsonKey: "^heading.forValues",
                exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
              }
            ],
            bodyAllowed: true
          }
        ]
      }
    ]
  },
  //
  // questions
  //
  [CardSetConfigKey.questions]: {
    jsonKey: "questions",
    exportJsonKey: { questions: "$" },
    sides: [
      {
        name: "question",
        variants: [
          {
            jsonKey: "question",
            exportJsonKey: { question: "$" },
            tags: [
              {
                key: ConfigKey.property_reasonableNumOfChars,
                exportJsonKey: [
                  { "@absent": { reasonableNumOfChars: "$ancestor" } },
                  { reasonableNumOfChars: "$" }
                ],
                description: "Property for reasonable number of characters.",
                format: TagFormat.number
              },
              {
                exportJsonKey: { sampleSolution: "$" },
                key: ConfigKey.tag_sampleSolution,
                description: "Sample solution for the question.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_sampleSolution,
                description: "Property for sample solution text.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_additionalSolutions,
                description: "Property for additional solutions.",
                format: TagFormat.plainText,
                maxCount: Count.infinity
              },
              {
                key: ConfigKey.property_partialAnswer,
                description: "Property for partial answer text.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for the question."
              },
              {
                // ALIGN-EXAMPLE-CASCADE §3.10: `questions` is NOT in BPG's
                // `pushExampleDownTree` dispatch — bit-header `[@example]`
                // does not propagate `example`/`isExample` onto question
                // entries. Only entry-local `[@example]` fires.
                //
                // BPG's `BaseBuilder.toExample` for interview questions
                // (Builder.ts:2575,2588) uses `data.__sampleSolutionAst`
                // as `defaultExample` — bare `[@example]` (which sets
                // `__isDefaultExample=true, example=undefined`) emits the
                // parsed sample-solution AST in the `example` slot. The
                // `@keyonly` rule mirrors that runtime behaviour via
                // `$parent.sampleSolution` — the chain-walker's post-walk
                // retry resolves this once the sibling `[@sampleSolution]`
                // chain has merged into the shared scope (interview's
                // `[@example]` typically precedes `[@sampleSolution]` in
                // source). Format coercion turns the resolved string
                // value into a PM tree.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  {
                    "@keyonly": {
                      isExample: true,
                      example: "$parent.sampleSolution",
                      "@bit": { isExample: true }
                    }
                  },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example marker for the question (entry-local only; no bit-header cascade).",
                format: TagFormat.bitmarkText,
                nullable: true
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // elements
  //
  [CardSetConfigKey.elements]: {
    jsonKey: null,
    sides: [
      {
        name: "element",
        repeat: true,
        variants: [
          {
            jsonKey: "elements[]",
            exportJsonKey: { elements: ["$"] },
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                // ALIGN-EXAMPLE-CASCADE §3.11: `elements` is NOT in BPG's
                // `pushExampleDownTree` dispatch — no bit-header cascade.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example text for the element (entry-local only; no bit-header cascade).",
                format: TagFormat.plainText,
                nullable: true
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // table
  //
  // PLAN-079: supports BOTH source formats and collapses to the same basic
  // output (`{ columns?, data, columnWidths? }`):
  //
  // 1. Basic format — first card carries `[#X]` markers (fire `tag_title`
  //    → `^table.columns[]`); subsequent cards are body rows. Cards have
  //    no qualifier, so they route to the `table-body` (default) section.
  //
  // 2. Extended format — `==== table-header ====` / `==== table-footer ====`
  //    qualifiers. The `table-header` section uses `@cardIndex=0` to route
  //    the first header card's cells to `columns` and demote remaining
  //    header cards (rows) into `data`. Per-cell `[@tableRowSpan]` /
  //    `[@tableScope]` / `[@tableCellType]` tags are valid-but-unmapped
  //    here (no per-cell metadata in basic shape) — declared on the cell
  //    variant below.
  //
  [CardSetConfigKey.table]: {
    jsonKey: null,
    exportJsonKey: null,
    // PLAN-109 / HTML.md §8 — idiomatic HTML mapping. The classic (non-extended)
    // table produces the same `<table>`/`<thead>`/`<tbody>`/`<tfoot>` structure as
    // table-extended, so the structural htmlKeys mirror that cardSet. NOTE: the
    // JSON side routes the FIRST header card → `columns` (header) and SUBSEQUENT
    // header cards → `data` (body); HTML uses a uniform `<thead>` for the whole
    // `table-header` section, so a multi-card header (e.g. a second header card
    // that JSON sends to `data`) renders as additional `<thead>` rows rather than
    // moving to `<tbody>`. Per-card header/body splitting needs engine-level
    // routing not yet expressible in the pattern vocabulary — see PLAN-109.
    htmlKey: { "@el": "table", "@children": "$" },
    sections: {
      "table-header": {
        // First header card's row of cells → `columns`. Subsequent header
        // cards' rows → `data` (per-card append; `[$]` shape because
        // per-card emission fires once per card, multiple fires deep_merge-
        // append). PLAN-079 + PLAN-080: predicates inside exportJsonKey
        // both ROUTE cards to this section (when no explicit `==== table-
        // header ====` qualifier) and SELECT the rule body once routed.
        // First rule = first card with any `[#]` → `columns`; second rule
        // = subsequent cards in this section → `data` (per-row append).
        jsonKey: "table.columns",
        exportJsonKey: [
          {
            predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
            rule: { "@bit": { table: { columns: "$" } } }
          },
          { "@cardIndex=0": { "@bit": { table: { columns: "$" } } } },
          { "@bit": { table: { data: ["$"] } } }
        ],
        // Header section → `<thead>` rows of `<th>`; `@tableColWidth` bubbles a
        // `width` attr onto the enclosing `<th>` (HTML-C2).
        htmlKey: { "@el": "thead", "@children": { "@el": "tr", "@children": "$" } },
        sideHtmlKey: { "@el": "th", "@children": "$" }
      },
      "table-body": {
        // Batched emission: `$` is the entire 2D array of body card rows.
        // This is the default section (no qualifier → cards land here).
        jsonKey: "table.data",
        exportJsonKey: { "@bit": { table: { data: "$" } } },
        htmlKey: { "@el": "tbody", "@children": { "@el": "tr", "@children": "$" } },
        isDefault: true
      },
      "table-footer": {
        // Batched emission: `$` is the entire 2D array of footer rows.
        jsonKey: "table.data",
        exportJsonKey: { "@bit": { table: { data: "$" } } },
        // Footer cells route to `data` (not `columns`), so they are `<td>` (no
        // sideHtmlKey override — the cell side default applies).
        htmlKey: { "@el": "tfoot", "@children": { "@el": "tr", "@children": "$" } }
      }
    },
    sides: [
      {
        name: "cell",
        repeat: true,
        // Default (body/footer) cell → `<td>`; the header section overrides to
        // `<th>` via its sideHtmlKey (HTML.md §8).
        htmlKey: { "@el": "td", "@children": "$" },
        variants: [
          {
            jsonKey: null,
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                key: ConfigKey.tag_title,
                jsonKey: "^table.columns[]",
                exportJsonKey: { "@bit": { table: { columns: ["$"] } } },
                description: "Title of the table."
              },
              {
                key: ConfigKey.property_tableCellType,
                // HTML.md §8: first-match predicate selecting `<th>` vs `<td>`.
                htmlKey: [{ "@value=th": { "@el": "th" } }, { "@absent": { "@el": "td" } }],
                description: "Table cell type (th/td).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_tableRowSpan,
                // HTML-C2: contribute a `rowspan` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { rowspan: "$" } } },
                description: "Number of rows the cell spans.",
                format: TagFormat.number
              },
              {
                key: ConfigKey.property_tableColSpan,
                // HTML-C2: contribute a `colspan` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { colspan: "$" } } },
                description: "Number of columns the cell spans.",
                format: TagFormat.number
              },
              {
                key: ConfigKey.property_tableScope,
                // HTML-C2: contribute a `scope` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { scope: "$" } } },
                description: "Scope attribute for header cells.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_tableColWidth,
                jsonKey: "^table.columnWidths[{s}]",
                exportJsonKey: { "@bit": { table: { columnWidths: { $s: "$" } } } },
                // HTML.md §8 + HTML-C2: contribute a `width` attribute onto the
                // enclosing cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { width: "$" } } },
                description: "Width for the column.",
                format: TagFormat.number
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // table-extended
  //
  [CardSetConfigKey.tableExtended]: {
    jsonKey: null,
    // PLAN-109 / HTML.md §8 — idiomatic HTML mapping for the extended table.
    // The card-set container is the `<table>` element; each section is a
    // table section element (`<thead>`/`<tbody>`/`<tfoot>`) whose children are
    // `<tr>` rows, and each cell side is a `<td>` (body) or `<th>` (header /
    // footer, via the section sideHtmlKey override).
    htmlKey: { "@el": "table", "@children": "$" },
    sections: {
      "table-header": {
        jsonKey: "table.header.rows",
        // PLAN-080: section dispatch via predicates inside exportJsonKey.
        // Cards with no explicit `==== table-header ====` qualifier
        // route here if FIRST CARD contains any `[#]` tag in any
        // variant. Explicit-qualifier cards fall through to the
        // unconditional fallback rule. Both rules use the per-card
        // append shape `rows: ['$']` — the runtime triggers per-card
        // emission whenever the pattern carries predicates, and each
        // fire appends a single-element array (multi-fire deep-merge
        // concatenates).
        exportJsonKey: [
          {
            predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
            rule: { table: { header: { rows: ["$"] } } }
          },
          { table: { header: { rows: ["$"] } } }
        ],
        htmlKey: { "@el": "thead", "@children": { "@el": "tr", "@children": "$" } },
        // PLAN-141 F2 (NISO import): the XHTML table model NISO uses — a
        // <thead> SECTION whose <tr> rows are the header cards.
        mappingKeys: {
          "xml-niso-iec": { "@el": "thead", "@children": { "@el": "tr", "@children": "$" } }
        },
        sideJsonKey: "cells[{s}]|set(title=true)",
        sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } },
        // Header cells are `<th>`. The `width` attribute is contributed by the
        // `@tableColWidth` tag, which bubbles it onto the enclosing `<th>`
        // (HTML-C2) — so it is not repeated here.
        sideHtmlKey: { "@el": "th", "@children": "$" }
      },
      "table-body": {
        jsonKey: "table.body.rows",
        exportJsonKey: { table: { body: { rows: "$" } } },
        htmlKey: { "@el": "tbody", "@children": { "@el": "tr", "@children": "$" } },
        // PLAN-141 F2 (NISO import): <tbody> rows are the (default) data cards.
        mappingKeys: {
          "xml-niso-iec": { "@el": "tbody", "@children": { "@el": "tr", "@children": "$" } }
        },
        isDefault: true
      },
      "table-footer": {
        jsonKey: "table.footer.rows",
        exportJsonKey: { table: { footer: { rows: "$" } } },
        htmlKey: { "@el": "tfoot", "@children": { "@el": "tr", "@children": "$" } },
        // PLAN-141 F2 (NISO import): NISO has no <tfoot> — the footer card's
        // source is the <table-wrap-foot> block (CONTENT-shaped: no rows; its
        // prose/notes flatten into one footer card — catalog table-wrap-foot
        // row; a contained <def-list> promotes as a sibling legend bit).
        mappingKeys: { "xml-niso-iec": { "@el": "table-wrap-foot", "@children": "$" } },
        sideJsonKey: "cells[{s}]|set(title=true)",
        sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } },
        // Footer cells render as `<th>` (mirrors the header `title: true`
        // mapping); §8 lists only the header side, so this parallels it.
        sideHtmlKey: { "@el": "th", "@children": "$" }
      }
    },
    sides: [
      {
        name: "cell",
        repeat: true,
        jsonKey: "cells[{s}]",
        exportJsonKey: { cells: { $s: "$" } },
        // Default (body) cell → `<td>`; the header/footer sections override the
        // side to `<th>` via their sideHtmlKey (HTML.md §8).
        htmlKey: { "@el": "td", "@children": "$" },
        // PLAN-141 F2 (NISO import): a <td> row child is a body cell side.
        mappingKeys: { "xml-niso-iec": { "@el": "td", "@children": "$" } },
        variants: [
          {
            jsonKey: "content",
            exportJsonKey: { content: "$" },
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                key: ConfigKey.tag_title,
                // PLAN-080: the `[#]` tag writes its text to the cell's
                // `content` field. When the card is in the `table-header`
                // section, the section's sideExportJsonKey wraps each
                // cell with `title: true` automatically.
                jsonKey: "content",
                exportJsonKey: { content: "$" },
                description: "Title text of the table cell (header rows).",
                // PLAN-141 F2 (NISO import): a <th> row child is a TITLE cell —
                // its content lands in the `[#…]` tag (an empty <th> keeps its
                // slot as a bare `[#]`).
                mappingKeys: { "xml-niso-iec": { "@el": "th", "@children": "$" } }
              },
              {
                key: ConfigKey.property_tableCellType,
                jsonKey: "title|bool(th)",
                exportJsonKey: [{ "@value=th": { title: true } }],
                // HTML.md §8: same first-match predicate machinery selecting
                // `<th>` vs `<td>` instead of `title:true` vs nothing.
                htmlKey: [{ "@value=th": { "@el": "th" } }, { "@absent": { "@el": "td" } }],
                description: "Table cell type (th/td).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_tableRowSpan,
                jsonKey: "rowspan",
                exportJsonKey: { rowspan: "$" },
                // HTML-C2: contribute a `rowspan` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { rowspan: "$" } } },
                // PLAN-141 F2 (NISO import): recover the source cell's own
                // `rowspan` attribute (element-scope attr form).
                mappingKeys: { "xml-niso-iec": { "@el": { "@attr": { rowspan: "$" } } } },
                description: "Number of rows the cell spans.",
                format: TagFormat.number
              },
              {
                key: ConfigKey.property_tableColSpan,
                jsonKey: "colspan",
                exportJsonKey: { colspan: "$" },
                // HTML-C2: contribute a `colspan` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { colspan: "$" } } },
                // PLAN-141 F2 (NISO import): recover the source cell's own
                // `colspan` attribute (element-scope attr form).
                mappingKeys: { "xml-niso-iec": { "@el": { "@attr": { colspan: "$" } } } },
                description: "Number of columns the cell spans.",
                format: TagFormat.number
              },
              {
                key: ConfigKey.property_tableScope,
                jsonKey: "scope",
                exportJsonKey: { scope: "$" },
                // HTML-C2: contribute a `scope` attribute onto the enclosing
                // cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { scope: "$" } } },
                description: "Scope attribute for header cells.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_tableColWidth,
                // PLAN-080: write to the cell's `colwidth` field.
                jsonKey: "colwidth",
                exportJsonKey: { colwidth: "$" },
                // HTML.md §8 + HTML-C2: contribute a `width` attribute onto the
                // enclosing cell element (`<th>`/`<td>`).
                htmlKey: { "@el": { "@attr": { width: "$" } } },
                // PLAN-141 F2 (NISO import): NISO carries widths as PER-COLUMN
                // <col width="…"> descriptors at container level, not on cells.
                // The matched values distribute positionally onto the first
                // (header) card's sides. Number format ⇒ only an integral
                // percentage survives ("20.0000%" → 20); a fractional one
                // (renderer-equalised "4.7619%") is dropped — catalog `col` row.
                mappingKeys: { "xml-niso-iec": { "@el": "col", "@attr": { width: "$" } } },
                description: "Width for the column.",
                format: TagFormat.number
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // pronunciation-table
  //
  [CardSetConfigKey.pronunciationTable]: {
    jsonKey: "pronunciationTable.data",
    exportJsonKey: { pronunciationTable: { data: "$" } },
    sides: [
      {
        name: "cell",
        repeat: true,
        variants: [
          {
            jsonKey: "body",
            exportJsonKey: { body: "$" },
            tags: [
              {
                key: ConfigKey.group_standardItemLeadInstructionHint,
                description: "Standard tags for lead, instruction, and hint."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the pronunciation table."
              },
              {
                key: ConfigKey.resource_audio,
                description: "Audio resource for the pronunciation table.",
                jsonKey: "audio",
                exportJsonKey: { audio: { type: "audio", audio: { src: "$" } } }
              }
            ],
            repeatCount: Count.infinity
          }
        ]
      }
    ]
  },
  //
  // bot-action-responses
  //
  [CardSetConfigKey.botActionResponses]: {
    jsonKey: "responses",
    exportJsonKey: { responses: "$" },
    sides: [
      {
        name: "response",
        variants: [
          {
            jsonKey: "feedback",
            exportJsonKey: { feedback: "$" },
            format: TextFormat2.plainText,
            tags: [
              {
                key: ConfigKey.property_reaction,
                description: "Reaction to the bot action.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.tag_item,
                jsonKey: "item",
                exportJsonKey: { item: "$" },
                format: TagFormat.plainText,
                description: "The item for the bit",
                chain: [
                  {
                    key: ConfigKey.tag_item,
                    jsonKey: "lead",
                    exportJsonKey: { lead: "$" },
                    description: "The lead for the bit",
                    maxCount: 1,
                    chain: [
                      {
                        key: ConfigKey.tag_item,
                        jsonKey: "pageNumber",
                        exportJsonKey: { pageNumber: "$" },
                        description: "The page number for the bit",
                        maxCount: 1,
                        chain: [
                          {
                            key: ConfigKey.tag_item,
                            jsonKey: "marginNumber",
                            exportJsonKey: { marginNumber: "$" },
                            description: "The margin number for the bit",
                            maxCount: 1
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                key: ConfigKey.tag_instruction,
                description: "The response label for the bot action.",
                jsonKey: "response",
                exportJsonKey: { response: "$" },
                format: TagFormat.plainText
              },
              {
                // ALIGN-EXAMPLE-CASCADE §3.12: `bot-action-responses` is
                // NOT in BPG's `pushExampleDownTree` dispatch — no bit-
                // header cascade.
                key: ConfigKey.property_example,
                exportJsonKey: [
                  { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
                  { isExample: true, example: "$", "@bit": { isExample: true } }
                ],
                description: "Example text for the bot action response (entry-local only; no bit-header cascade).",
                format: TagFormat.plainText,
                nullable: true
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // cloze-list
  //
  [CardSetConfigKey.clozeList]: {
    jsonKey: "listItems",
    exportJsonKey: { listItems: "$" },
    sides: [
      {
        name: "content",
        variants: [
          {
            jsonKey: "body",
            exportJsonKey: { body: "$" },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for cloze items."
              },
              {
                key: ConfigKey.group_gap,
                description: "Group for gap tags in cloze items."
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // example-bit-list
  //
  [CardSetConfigKey.exampleBitList]: {
    jsonKey: "listItems",
    exportJsonKey: { listItems: "$" },
    sides: [
      {
        name: "item",
        variants: [
          {
            jsonKey: "body",
            exportJsonKey: { body: "$" },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for example bits."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the example bit."
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // page-footer-sections — mirrors example-bit-list but emits cards under
  // `sections` instead of `listItems`. BPG's JsonGenerator special-cases
  // BitType.pageFooter in code; this dedicated cardSet expresses the same
  // routing as pure config so the Rust serializer can pick it up.
  //
  [CardSetConfigKey.pageFooterSections]: {
    jsonKey: "sections",
    exportJsonKey: { sections: "$" },
    sides: [
      {
        name: "item",
        variants: [
          {
            jsonKey: "body",
            exportJsonKey: { body: "$" },
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for page-footer section bits."
              },
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the page-footer section."
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // ingredients
  //
  [CardSetConfigKey.ingredients]: {
    jsonKey: "ingredients",
    exportJsonKey: { ingredients: "$" },
    sides: [
      {
        name: "ingredient",
        variants: [
          {
            jsonKey: "ingredient",
            exportJsonKey: { ingredient: "$" },
            tags: [
              {
                exportJsonKey: { title: "$" },
                key: ConfigKey.tag_title,
                description: "Title of the ingredient."
              },
              {
                key: ConfigKey.tag_true,
                description: "Checked state for the ingredient.",
                jsonKey: "|set(checked=true)",
                exportJsonKey: { checked: true, $: "$" },
                maxCount: 1
                // nullable: true,
              },
              {
                key: ConfigKey.tag_false,
                description: "Unchecked state for the ingredient.",
                jsonKey: "|set(checked=false)",
                exportJsonKey: { checked: false, $: "$" },
                maxCount: 1
                // nullable: true,
              },
              {
                key: ConfigKey.group_standardItemLead,
                description: "Item, lead, page number, and margin number for the ingredient."
              },
              {
                key: ConfigKey.tag_instruction,
                description: "The quantity of the ingredient.",
                jsonKey: "quantity",
                exportJsonKey: { quantity: "$" },
                format: TagFormat.number
              },
              {
                exportJsonKey: { hint: "$" },
                key: ConfigKey.tag_hint,
                description: "The hint for the ingredient."
              },
              {
                key: ConfigKey.property_unit,
                description: "Unit of measurement for the ingredient (singular).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_unitPlural,
                description: "Unit of measurement for the ingredient (plural).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_unitAbbr,
                description: "Abbreviation for the unit of measurement (singular).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_unitAbbrPlural,
                description: "Abbreviation for the unit of measurement (plural).",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_decimalPlaces,
                description: "Number of decimal places for the ingredient quantity.",
                format: TagFormat.number,
                defaultValue: "1"
              },
              {
                key: ConfigKey.property_disableCalculation,
                description: "Disable calculation for the ingredient.",
                format: TagFormat.boolean
              }
            ]
          }
        ]
      }
    ]
  },
  //
  // book-reference-list
  //
  [CardSetConfigKey.bookReferenceList]: {
    jsonKey: "bookReferences",
    exportJsonKey: { bookReferences: "$" },
    sides: [
      {
        name: "reference",
        variants: [
          {
            jsonKey: null,
            tags: [
              {
                key: ConfigKey.group_standardTags,
                description: "Standard tags for book references."
              },
              {
                key: ConfigKey.property_refAuthor,
                description: "Author of the book.",
                format: TagFormat.plainText,
                maxCount: Count.infinity
              },
              {
                key: ConfigKey.property_refBookTitle,
                description: "Title of the book.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_refPublisher,
                description: "Publisher of the book.",
                format: TagFormat.plainText,
                maxCount: Count.infinity
              },
              {
                key: ConfigKey.property_refPublicationYear,
                description: "Year of publication of the book.",
                format: TagFormat.plainText
              },
              {
                key: ConfigKey.property_citationStyle,
                description: "Citation style for the book reference.",
                format: TagFormat.plainText
              }
            ]
          }
        ]
      }
    ]
  }
};

// src/config/raw/groups.ts
var GROUPS = {
  [ConfigKey.group_standardAllBits]: {
    name: "Standard",
    description: "Standard tags which apply to all bits",
    type: GroupConfigType.standard,
    tags: [
      {
        key: ConfigKey.property_id,
        description: "The unique identifier(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_gUri,
        description: "Global URI for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_customerId,
        description: "The customer-specific identifier for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_externalId,
        description: "The external identifier for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        // PLAN-140 refs family (NISO import): the machine identifier of a
        // referenced standard. NESTED content slot — the tag reads (and
        // consumes) ONLY the <std-id> inside a <std> child; the remaining
        // designation/title text stays bit body. A <std> without <std-id>
        // does not fire the key.
        // PLAN-141 F5: on the [.book] bit, the DATED <std-ref> designation
        // is the document's external id. Safe rule order: <std-ref> is a
        // direct bit-element child only under <std-meta> (everywhere else
        // it nests inside <std>, which the tag spine never reaches).
        mappingKeys: {
          "xml-niso-iec": [
            { "@el": "std", "@children": { "@el": "std-id", "@text": "$" } },
            { "@el": "std-ref", "@attr": { type: "dated" }, "@text": "$" }
          ]
        }
      },
      {
        key: ConfigKey.property_sourceRL,
        description: "The original location of the information in the original source material",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_copyright,
        description: "The copyright information for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_license,
        description: "The license for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_rightsPolicy,
        description: "The rights policy or policies for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_isTemplate,
        description: "If true, the bit is a template",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_isTemplateStripTheme,
        description: "If true, the bit is a template strip theme",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_needsValidation,
        description: "If true, the bit needs validation",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_validationDate,
        description: "The date when the bit was validated",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_aiGenerated,
        description: "If true, the bit is AI-generated",
        format: TagFormat.boolean,
        nullable: true
      },
      {
        key: ConfigKey.property_machineTranslated,
        description: "If true, the bit is machine-translated",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_translationOf,
        description: "Translation source reference",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_spansPageBreak,
        description: "If true, the bit spans a page break",
        format: TagFormat.boolean,
        nullable: true
      },
      {
        key: ConfigKey.property_searchIndex,
        description: "The search index(es) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_analyticsTag,
        description: "The analytics tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_categoryTag,
        description: "The category tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_topicTag,
        description: "The topic tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_reportTag,
        description: "The report tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_altLangTag,
        description: "The alternative language tag for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_feedbackEngine,
        description: "The feedback engine for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_feedbackType,
        description: "The feedback type for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_disableFeedback,
        description: "If true, feedback is disabled for the bit",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_diffTo,
        description: "The diff to version for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_diffOp,
        description: "The diff operation for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_diffRef,
        description: "The diff reference for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_diffContext,
        description: "The diff context for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_diffTime,
        description: "The diff time for the bit",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_ageRange,
        description: "The age range for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_lang,
        description: "The language for the bit",
        format: TagFormat.plainText,
        // PLAN-141 F3 (NISO import): a term-sec's language rides
        // <tbx:termEntry><tbx:langSet xml:lang> — a nested content slot
        // whose leaf is a $-in-ATTR-position slot (the nested counterpart
        // of the value-attr slot). Only termEntry elements match, so the
        // shared tag is inert everywhere else.
        mappingKeys: {
          "xml-niso-iec": {
            "@el": "tbx:termEntry",
            "@children": { "@el": "tbx:langSet", "@attr": { "xml:lang": "$" } }
          }
        }
      },
      {
        key: ConfigKey.property_publisher,
        description: "The publisher(s) of the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_publisherName,
        description: "The name of the publisher of the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_theme,
        description: "The theme(s) for the bit",
        format: TagFormat.plainText,
        nullable: true,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_rtl,
        description: "If true, the book is right-to-left",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_target,
        description: "The target(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_tag,
        description: "The tag(s) for the bit",
        format: TagFormat.plainText,
        nullable: true,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_groupTag,
        jsonKey: "groupTag.name",
        // PLAN-063: starter-array shape with `@id` declares entity-merge
        // semantics — repeated `[@groupTag: name]` occurrences with the
        // same value fold into one entry; chained `@tag` children's
        // arrays merge as sets.
        exportJsonKey: { groupTag: [{ "@id": "name", name: "$" }] },
        description: "The group tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        chain: [
          {
            key: ConfigKey.property_tag,
            exportJsonKey: { tags: ["$"] },
            description: "The tag(s) for the group",
            format: TagFormat.plainText,
            nullable: true,
            maxCount: Count.infinity
          }
        ]
      },
      {
        key: ConfigKey.property_reviewTag,
        description: "The review tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_reviewerTag,
        description: "The reviewer tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_reductionTag,
        description: "The reduction tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_bubbleTag,
        description: "The bubble tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_extractorTag,
        description: "The extractor tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_extractorInternal,
        description: "Internal metadata used by the extractor for processing purposes",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_sourceBB,
        description: "The source bounding box(es) for the bit (x, y, x1, y1)",
        format: TagFormat.coordinates,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_bitFingerprint,
        description: "Fingerprint(s) used by the AI extractor to identify the bit (font, style, color, pixels, etc.)",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_levelCEFRp,
        description: "The CEFRp level for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_levelCEFR,
        description: "The CEFR level for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_levelILR,
        description: "The ILR level for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_levelACTFL,
        description: "The ACTFL level for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_icon,
        description: "The icon for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_iconTag,
        description: "The icon tag for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_colorTag,
        description: "The color tag(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        exportJsonKey: { anchor: "$" },
        key: ConfigKey.tag_anchor,
        name: "Anchor",
        format: TagFormat.plainText,
        description: "The anchor for the bit"
      },
      {
        key: ConfigKey.property_search,
        description: "The search text for the bit",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_showInIndex,
        description: "If true, the bit is shown in the index",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_layer,
        description: "The layer(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_layerRole,
        description: "The layer role(s) for the bit",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        // Universal @internalComment — declares in config what Config.ts also
        // injects programmatically into every bit (its push runs last and wins
        // the keyed merge, so runtime behaviour is unchanged). Declared here so
        // the config export (ConfigBuilder → assets/config → configurator →
        // bitmark.json) carries the tag for downstream parsers. Empty
        // exportJsonKey: consumed but never emitted to the bit JSON (the value
        // is routed to parser.internalComments).
        key: ConfigKey.property_internalComment,
        description: "Internal comment for the bit.",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        exportJsonKey: {}
      }
    ]
  },
  [ConfigKey.group_standardItemLead]: {
    type: GroupConfigType.standard,
    description: "Standard group for item, lead, page number, and margin number tags",
    tags: [
      {
        key: ConfigKey.tag_item,
        jsonKey: "item",
        exportJsonKey: { item: "$" },
        // PLAN-140 W3 (NISO import): the clause/note label of a mapped
        // source element ([%1], [%ANMERKUNG 1]).
        mappingKeys: { "xml-niso-iec": { "@el": "label", "@text": "$" } },
        description: "The item for the bit",
        chain: [
          {
            key: ConfigKey.tag_item,
            jsonKey: "lead",
            exportJsonKey: { lead: "$" },
            description: "The lead for the bit",
            maxCount: 1,
            chain: [
              {
                key: ConfigKey.tag_item,
                jsonKey: "pageNumber",
                exportJsonKey: { pageNumber: "$" },
                description: "The page number for the bit",
                maxCount: 1,
                chain: [
                  {
                    key: ConfigKey.tag_item,
                    jsonKey: "marginNumber",
                    exportJsonKey: { marginNumber: "$" },
                    description: "The margin number for the bit",
                    maxCount: 1
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  [ConfigKey.group_standardItemLeadInstructionHint]: {
    type: GroupConfigType.standard,
    description: "Standard group for item, lead, page number, margin number, instruction and hint tags",
    tags: [
      {
        key: ConfigKey.group_standardItemLead,
        description: "Item, lead, page number, and margin number tags"
      },
      {
        exportJsonKey: { instruction: "$" },
        key: ConfigKey.tag_instruction,
        name: "Instruction",
        description: "The instruction for the bit"
      },
      {
        exportJsonKey: { hint: "$" },
        key: ConfigKey.tag_hint,
        name: "Hint",
        description: "The hint for the bit"
      }
    ]
  },
  [ConfigKey.group_standardInstructionHint]: {
    type: GroupConfigType.standard,
    description: "Standard group for instruction and hint tags only (no item/lead/pageNumber/marginNumber chain). Used by sides where item/lead are owned by another side (e.g. matchPairs value-side, which BPG strips item/lead from at sideIdx > 0).",
    tags: [
      {
        exportJsonKey: { instruction: "$" },
        key: ConfigKey.tag_instruction,
        name: "Instruction",
        description: "The instruction for the bit"
      },
      {
        exportJsonKey: { hint: "$" },
        key: ConfigKey.tag_hint,
        name: "Hint",
        description: "The hint for the bit"
      }
    ]
  },
  [ConfigKey.group_standardTags]: {
    type: GroupConfigType.standard,
    description: "Standard tags which apply to MOST (but not all) bits",
    tags: [
      {
        key: ConfigKey.group_standardAllBits,
        description: "All standard tags which apply to all bits"
      },
      {
        key: ConfigKey.group_standardItemLeadInstructionHint,
        description: "Item, lead, page number, margin number, instruction and hint tags"
      },
      {
        // Bit-level example tag (default — non-`isTopLevelExample` bits):
        // - bare `[@example]`: consume only (records cascade marker, emits
        //   nothing at bit-level — OLD parser scrubs bit-level isExample
        //   for bits NOT in its `isTopLevelExample` allow-list).
        // - synthesized (descendant cascade): still emits `isExample: true`
        //   at the descendant scope.
        // - valued `[@example:text]`: emit the example value only; bit-level
        //   `isExample` is left to bubble from descendants.
        // Bits in the OLD parser's allow-list (flashcard, definitionList,
        // sequence, multipleChoice, trueFalse, cloze*, mark, interview,
        // matchMatrix, match, essay, etc.) override this entry with the
        // legacy emit-isExample shape.
        key: ConfigKey.property_example,
        exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [ConfigKey.group_standardTagsNoItemLead]: {
    type: GroupConfigType.standard,
    description: "Standard tags mirror of group_standardTags but excluding the item/lead/pageNumber/marginNumber chain. Used by sides where item/lead are owned by another side (e.g. matchPairs value-side \u2014 BPG `parseMatchPairs` strips tags.item / tags.lead for sideIdx > 0).",
    tags: [
      {
        key: ConfigKey.group_standardAllBits,
        description: "All standard tags which apply to all bits"
      },
      {
        key: ConfigKey.group_standardInstructionHint,
        description: "Instruction and hint tags only (no item/lead chain)."
      },
      {
        // Mirror of group_standardTags property_example entry.
        key: ConfigKey.property_example,
        exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [ConfigKey.group_imageSource]: {
    type: GroupConfigType.standard,
    description: "Image source chain",
    tags: [
      {
        key: ConfigKey.property_imageSource,
        jsonKey: "imageSource.url",
        exportJsonKey: { imageSource: { url: "$" } },
        description: "The source of an image",
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.property_mockupId,
            description: "The mockup ID for the image",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_size,
            description: "The size of the image",
            format: TagFormat.number,
            nullable: true
          },
          {
            key: ConfigKey.property_format,
            description: "The format of the image",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_trim,
            description: "If true, the image is trimmed",
            format: TagFormat.boolean
          }
        ]
      }
    ]
  },
  [ConfigKey.group_technicalTerm]: {
    type: GroupConfigType.standard,
    description: "Technical term chain",
    tags: [
      {
        key: ConfigKey.property_technicalTerm,
        jsonKey: "technicalTerm.technicalTerm",
        exportJsonKey: { technicalTerm: { technicalTerm: "$" } },
        description: "The technical term",
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.property_lang,
            description: "The language of the technical term",
            format: TagFormat.plainText
          }
        ]
      }
    ]
  },
  [ConfigKey.group_person]: {
    type: GroupConfigType.standard,
    description: "Person chain",
    tags: [
      {
        key: ConfigKey.property_person,
        jsonKey: "person.name",
        exportJsonKey: { person: { name: "$" } },
        description: "A person",
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.property_title,
            description: "The person's title",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_resourceImage,
            jsonKey: "avatarImage|resource(type=image, key=image)",
            exportJsonKey: { avatarImage: { type: "image", image: { src: "$" } } },
            description: "The image of the person"
          }
        ]
      },
      {
        // Deprecated (parter renamed to person)
        key: ConfigKey.property_partner,
        jsonKey: "person.name",
        exportJsonKey: { person: { name: "$" } },
        description: "A partner",
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.property_title,
            description: "The partner's title",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_resourceImage,
            jsonKey: "avatarImage|resource(type=image, key=image)",
            exportJsonKey: { avatarImage: { type: "image", image: { src: "$" } } },
            description: "The image of the partner"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_gap]: {
    type: GroupConfigType.standard,
    description: "Gap chain",
    tags: [
      {
        exportJsonKey: {
          "@body-inline": { type: "gap", attrs: { solutions: ["$"] } }
        },
        key: ConfigKey.tag_gap,
        description: "The value of a gap in the content",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_gap,
            jsonKey: "solutions[]",
            exportJsonKey: { solutions: ["$"] },
            description: "Alternative values for the gaps in the content",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // BPG ExampleTagContentProcessor.ts:91-95 reads
            // `__solutionsAst[length - 1]` — the solution closest to the
            // `[@example]` tag at parse time. Our chain accumulator is built
            // incrementally (scope.rs `parent.merge` after each tag), so at
            // the moment `[@example]` fires, `solutions` only contains the
            // preceding `[_x]` entries — `[-1]` resolves to the closest one.
            // E.g. `[_cloze][_gap][@example]` → solutions=["cloze","gap"],
            // `[-1]`="gap"; `[_solution][@example][_problem]` →
            // solutions=["solution"], `[-1]`="solution".
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.solutions[-1]",
                  "@bit": { isExample: true }
                }
              },
              {
                "@absent": {
                  isExample: true,
                  example: "$parent.solutions[-1]",
                  "@bit": { isExample: true }
                }
              },
              { isExample: true, example: "$", "@bit": { isExample: true } }
            ],
            description: "An example for the gap",
            format: TagFormat.bitmarkText,
            nullable: true
          },
          {
            key: ConfigKey.property_isCaseSensitive,
            jsonKey: "isCaseSensitive",
            exportJsonKey: [
              { "@absent": { isCaseSensitive: "$ancestor" } },
              { isCaseSensitive: "$" }
            ],
            description: "If true, the gap text is case sensitive",
            format: TagFormat.boolean,
            defaultValue: "true"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_trueFalse]: {
    type: GroupConfigType.standard,
    description: "True/False chain",
    tags: [
      {
        exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
        key: ConfigKey.tag_true,
        description: "True value for a true/false statement/question",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            jsonKey: "choices[]|set(isCorrect=true)",
            exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
            description: "True values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            jsonKey: "choices[]|set(isCorrect=false)",
            exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
            description: "False values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2, corrected by
            // bpg 5.31 probing: cascade-fired `@example` decorates only
            // the FIRST correct choice (`example: true` via the
            // `@parent.isCorrect`-gated `@absent` rule, `maxEmits:1` —
            // BPG `fillBooleanExample(..., firstCorrectOnly=true)`).
            // There is deliberately NO plain `@absent` rule: an ancestor
            // bare example must leave the remaining choices untouched
            // (a plain `@absent` decoration broke the
            // `optimize(fullize(x)) == optimize(x)` law — the
            // synthesized `isExample` keys survived re-optimization).
            // The `serialize_cards` cascade-counter scope holds the cap
            // across the whole cardset.
            // @card scope-shift writes choice-container isExample (e.g.
            // `quizzes[i].isExample`) — was previously provided by the
            // universal `bubble_is_example` post-walk, which was removed
            // in favour of explicit per-tag config. SYNTAX.md §8.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                predicates: ["@absent", { "@parent.isCorrect": true }],
                maxEmits: 1,
                rule: {
                  isExample: true,
                  example: true,
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@card": { isExample: true }
              }
            ],
            description: "An example for the true/false statement/question",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      },
      {
        exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
        key: ConfigKey.tag_false,
        description: "False value for a true/false statement/question",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            jsonKey: "choices[]|set(isCorrect=true)",
            exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
            description: "True values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            jsonKey: "choices[]|set(isCorrect=false)",
            exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
            description: "False values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2, corrected by
            // bpg 5.31 probing: an ancestor bare `@example` leaves
            // incorrect choices UNTOUCHED — no cascade decoration at
            // all (no `@absent` rule; a plain `@absent → isExample`
            // decoration broke the `optimize(fullize(x)) ==
            // optimize(x)` law). Only an explicit `[@example]` on the
            // choice itself emits.
            // @card scope-shift writes choice-container isExample.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@card": { isExample: true }
              }
            ],
            description: "An example for the true/false statement/question (incorrect entry \u2014 no cascade decoration: an ancestor bare example leaves incorrect choices untouched, bpg 5.31)",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      }
    ]
  },
  [ConfigKey.group_trueFalseResponses]: {
    type: GroupConfigType.standard,
    description: "True/False Responses chain",
    tags: [
      {
        exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
        key: ConfigKey.tag_true,
        description: "True value for a true/false statement/question",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            jsonKey: "responses[]|set(isCorrect=true)",
            exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
            description: "True values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            jsonKey: "responses[]|set(isCorrect=false)",
            exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
            description: "False values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // @card scope-shift writes response-container isExample.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                "@absent": {
                  isExample: "$parent.isCorrect",
                  example: "$parent.isCorrect",
                  "@bit": { isExample: "$parent.isCorrect" },
                  "@card": { isExample: "$parent.isCorrect" }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@card": { isExample: true }
              }
            ],
            description: "An example for the true/false statement/question",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      },
      {
        exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
        key: ConfigKey.tag_false,
        description: "False value for a true/false statement/question",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            jsonKey: "responses[]|set(isCorrect=true)",
            exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
            description: "True values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            jsonKey: "responses[]|set(isCorrect=false)",
            exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
            description: "False values for a true/false statement/question",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // @card scope-shift writes response-container isExample.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                "@absent": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@card": { isExample: true }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@card": { isExample: true }
              }
            ],
            description: "An example for the true/false statement/question",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      }
    ]
  },
  [ConfigKey.group_trueFalseInlineSelect]: {
    type: GroupConfigType.standard,
    description: "True/False chain composed inline (select body-bit). +/- tags are consume-only at bit level.",
    tags: [
      {
        exportJsonKey: {
          "@body-inline": {
            type: "select",
            attrs: { options: [{ text: "$", isCorrect: true }] }
          }
        },
        key: ConfigKey.tag_true,
        description: "True value for an inline select body bit",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            exportJsonKey: {},
            description: "True values for an inline select body bit",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            exportJsonKey: {},
            description: "False values for an inline select body bit",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-083: cascade-fired `@example` from bit header fires
            // `isExample:true, example:true` on the FIRST CORRECT option
            // only (BPG `fillBooleanExample(select.options,
            // firstCorrectOnly=true)`). Mirrors group_trueFalseInlineHighlight.
            // Every rule also bubbles `@bit:{isExample:true}` (idempotent
            // OR — any cascade ⇒ bit marked) and
            // `@body-inline:{attrs:{isExample:true}}` (node-level marker
            // on the select node).
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                predicates: ["@absent", { "@parent.isCorrect": true }],
                maxEmits: 1,
                rule: {
                  isExample: true,
                  example: true,
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@body-inline": { attrs: { isExample: true } }
              }
            ],
            description: "An example for the inline select option (first correct only)",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      },
      {
        exportJsonKey: {
          "@body-inline": {
            type: "select",
            attrs: { options: [{ text: "$", isCorrect: false }] }
          }
        },
        key: ConfigKey.tag_false,
        description: "False value for an inline select body bit",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            exportJsonKey: {},
            description: "True values for an inline select body bit",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            exportJsonKey: {},
            description: "False values for an inline select body bit",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-083: identical to the `+` lead's chain @example —
            // cascade reaches the first correct option regardless of
            // which lead (+/-) opens the chain. Every firing rule bubbles
            // `@bit:{isExample:true}` and `@body-inline:{attrs:{isExample:true}}`.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                predicates: ["@absent", { "@parent.isCorrect": true }],
                maxEmits: 1,
                rule: {
                  isExample: true,
                  example: true,
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@body-inline": { attrs: { isExample: true } }
              }
            ],
            description: "An example for the inline select option (first correct only)",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      }
    ]
  },
  [ConfigKey.group_trueFalseInlineHighlight]: {
    type: GroupConfigType.standard,
    description: 'True/False chain composed inline (highlight body-bit). +/- tags emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}` inline body nodes. Used by highlight-text bit.',
    tags: [
      {
        exportJsonKey: {
          "@body-inline": {
            type: "highlight",
            attrs: { texts: [{ text: "$", isCorrect: true }] }
          }
        },
        key: ConfigKey.tag_true,
        description: "Highlighted span in the body (correct)",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            exportJsonKey: {},
            description: "Highlighted span values (correct)",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            exportJsonKey: {},
            description: "Highlighted span values (incorrect)",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
            // `@example` from the bit header fires `example: true` +
            // `isExample: true` on the FIRST correct highlight only
            // (BPG `fillBooleanExample(highlight.texts,
            // firstCorrectOnly=true)`). Incorrect highlights get
            // nothing (unlike choices, the highlight-text fixture
            // shows no `isExample` on incorrect entries).
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                predicates: ["@absent", { "@parent.isCorrect": true }],
                maxEmits: 1,
                rule: {
                  isExample: true,
                  example: true,
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@body-inline": { attrs: { isExample: true } }
              }
            ],
            description: "An example for the highlighted span (first correct only)",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      },
      {
        // PLAN-083 follow-up (2026-05-22): mirror the select `-` lead shape
        // by writing `isCorrect: false`. Without it, `$parent.isCorrect` in
        // chain-child @example rules resolves to ParentPathMissing for `-`
        // entries (e.g. `[-fish][@example]`), so the @keyonly rule falls
        // through and nothing fires. The natural-default strip removes
        // `isCorrect: false` from the final texts entries (matches fixture).
        exportJsonKey: {
          "@body-inline": {
            type: "highlight",
            attrs: { texts: [{ text: "$", isCorrect: false }] }
          }
        },
        key: ConfigKey.tag_false,
        description: "Highlighted span in the body (incorrect)",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.tag_true,
            exportJsonKey: {},
            description: "Highlighted span values (correct)",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.tag_false,
            exportJsonKey: {},
            description: "Highlighted span values (incorrect)",
            maxCount: Count.infinity,
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.group_standardItemLeadInstructionHint,
            description: "Item, lead, page number, margin number, instruction and hint tags"
          },
          {
            // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
            // `@example` on a `-` (incorrect highlight) emits nothing
            // (highlight-text fixture has no `isExample` on incorrect
            // entries; firstCorrectOnly cascade skips them entirely).
            // No `@absent` rule.
            // PLAN-083: identical to the `+` lead's chain @example —
            // cascade reaches the first correct option regardless of
            // which lead (+/-) opens the chain.
            key: ConfigKey.property_example,
            jsonKey: "example",
            exportJsonKey: [
              {
                "@keyonly": {
                  isExample: true,
                  example: "$parent.isCorrect",
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                predicates: ["@absent", { "@parent.isCorrect": true }],
                maxEmits: 1,
                rule: {
                  isExample: true,
                  example: true,
                  "@bit": { isExample: true },
                  "@body-inline": { attrs: { isExample: true } }
                }
              },
              {
                isExample: true,
                example: "$",
                "@bit": { isExample: true },
                "@body-inline": { attrs: { isExample: true } }
              }
            ],
            description: "An example for the highlighted span (first correct only)",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      }
    ]
  },
  [ConfigKey.group_markConfig]: {
    type: GroupConfigType.standard,
    description: "Mark configuration chain",
    tags: [
      {
        key: ConfigKey.property_mark,
        jsonKey: "marks.mark",
        // `emphasis: 'underline'` is the per-entry default for mark
        // configs — mirrors the hardcoded default in BPG
        // `MarkConfigChainContentProcessor.ts:55-58`. Chained
        // `[@emphasis:…]` overrides via deep-merge into `marks[-1]`.
        exportJsonKey: { marks: [{ mark: "$", emphasis: "underline" }] },
        description: "The mark configuration",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        chain: [
          {
            key: ConfigKey.property_color,
            description: "The color of the mark",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_emphasis,
            description: "The emphasis of the mark",
            format: TagFormat.plainText
          }
        ]
      }
    ]
  },
  [ConfigKey.group_mark]: {
    type: GroupConfigType.standard,
    description: "Mark chain",
    tags: [
      {
        exportJsonKey: {
          "@body-inline": { type: "mark", attrs: { solution: "$" } }
        },
        key: ConfigKey.tag_mark,
        description: "The marked content",
        maxCount: Count.infinity,
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.property_mark,
            jsonKey: "mark",
            exportJsonKey: { mark: "$" },
            description: "The mark configuration",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_example,
            jsonKey: "example",
            // Per-mark `@example` is a boolean toggle. BPG's runtime
            // `ExampleTagContentProcessor.handleDefaultOnlyExample`
            // (lines 147-161) normalises ANY `[@example]` or
            // `[@example:V]` on a mark body-bit to
            // `__isDefaultExample=true, example=undefined`; the
            // downstream `toExample(_, _, defaultExample=true)` in
            // `BaseBuilder.ts:80-87` then emits `example: true`
            // regardless of source. Declare a single unconditional
            // boolean-shape rule so the exported schema matches that
            // runtime semantic instead of the misleading `example: $`
            // shape (which would only fire if `handleDefaultOnlyExample`
            // were removed). `@bit: {isExample: true}` cascades the
            // marker — but NOT the `example` value — to bit root.
            exportJsonKey: { isExample: true, example: true, "@bit": { isExample: true } },
            description: "An example for the marked content",
            format: TagFormat.boolean,
            nullable: true
          }
        ]
      }
    ]
  },
  [ConfigKey.group_bookCommon]: {
    type: GroupConfigType.standard,
    description: "Common book tags",
    tags: [
      {
        key: ConfigKey.property_language,
        description: "The language of the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        // PLAN-141 F5 (NISO import): only the PRIMARY (first)
        // <content-language> survives — the maxEmits cap realises the
        // catalog's document-language rule (extras drop, R-4).
        mappingKeys: {
          "xml-niso-iec": [
            { predicates: [], maxEmits: 1, rule: { "@el": "content-language", "@text": "$" } }
          ]
        }
      },
      {
        key: ConfigKey.property_displayName,
        description: "The display name of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_customerExternalId,
        description: "The customer-specific external identifier for the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_spaceId,
        description: "The space ID for the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_kind,
        description: "The kind of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_hasMarkAsDone,
        description: 'If true, the book has a "Mark as done" feature',
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_processHandIn,
        description: "If true, the book will be processed when upon hand-in",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_processHandInLocation,
        description: "The location where the book was handed-in",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_isPublic,
        description: "If true, the book is public",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_isTemplate,
        description: "If true, the book is a template",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_isTemplateStripTheme,
        description: "If true, the book is a template strip theme",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_isEditable,
        description: "If true, the book is editable",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_chatWithBook,
        description: "If true, the user can chat about the book with an AI assistant",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_chatWithBookBrainKey,
        description: "The BookBrain key for the book chat",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.tag_title,
        name: "Title",
        description: "The title of the book",
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        // PLAN-141 F5 (NISO import): the PRIMARY (first) <title-wrap>'s
        // assembled <full> title → [#]; the per-language extras drop (R-4).
        mappingKeys: {
          "xml-niso-iec": [
            {
              predicates: [],
              maxEmits: 1,
              rule: { "@el": "title-wrap", "@children": { "@el": "full", "@text": "$" } }
            }
          ]
        }
      },
      {
        key: ConfigKey.property_subtype,
        description: "The subtype of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_coverImage,
        description: "The cover image(s) of the book",
        format: TagFormat.plainText,
        nullable: true,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.resource_coverImage,
        jsonKey: "coverImage|resource(type=image, key=image)",
        exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
        description: "The cover image of the book",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the cover image resource"
          }
        ]
      },
      {
        key: ConfigKey.property_backgroundImage,
        description: "The background image(s) of the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.resource_backgroundImage,
        jsonKey: "backgroundImage|resource(type=image, key=image)",
        exportJsonKey: { backgroundImage: { type: "image", image: { src: "$" } } },
        description: "The background image of the book",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the background image resource"
          }
        ]
      },
      {
        key: ConfigKey.property_coverColor,
        description: "The cover color of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_subject,
        description: "The subject(s) of the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_author,
        description: "The author(s) of the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_publications,
        description: "The publication(s) of the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_relatedBook,
        description: "Books related to this book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_translationOfBook,
        description: "External Id of the translated book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_userFeedbackTranslationUrl,
        description: "URL to an external feedback form for book translation feedback",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_userFeedbackContentUrl,
        description: "URL to an external feedback form for book content feedback",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_duration,
        description: "The duration of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_maxTocChapterLevel,
        description: "The maximum table of contents chapter level",
        format: TagFormat.number,
        defaultValue: "-1"
      },
      {
        key: ConfigKey.property_sourceDocument,
        description: "Url of the source document for the book (for example, a PDF file)",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_internalPrintPdf,
        description: "Url of the internal print PDF for the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_allowPrint,
        description: "If true, the book allows printing",
        format: TagFormat.enumeration,
        values: ["enforceFalse", "enforceTrue", "useSpaceConfiguration"],
        defaultValue: "useSpaceConfiguration"
      },
      {
        key: ConfigKey.property_hasPrintRestriction,
        description: "If true, the book has print restrictions",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_enforceUpdateOverUserInput,
        description: "If true, prioritize new content over legacy content from the instance API",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_extractorExtractionTimestamp,
        description: "Extraction timestamps for book conversion",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [ConfigKey.group_learningPathCommon]: {
    type: GroupConfigType.standard,
    description: "Common learning path tags",
    tags: [
      {
        key: ConfigKey.property_action,
        description: "The action for the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_duration,
        description: "The duration of the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_date,
        description: "The date of the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_dateEnd,
        description: "The end date of the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_location,
        description: "The location of the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_list,
        description: "The list of items in the learning path",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_textReference,
        description: "The text reference for the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_isTracked,
        description: "If true, the learning path is tracked",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_isInfoOnly,
        description: "If true, the learning path is info only",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_deeplink,
        description: "The deeplink(s) for the learning path",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption for the button of the learning path",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_book,
        description: "The book(s) associated with the learning path",
        format: TagFormat.plainText,
        chain: [
          {
            exportJsonKey: { reference: "$" },
            key: ConfigKey.tag_reference,
            format: TagFormat.plainText,
            description: "The reference for the book(s) in the learning path",
            maxCount: 1,
            chain: [
              {
                key: ConfigKey.tag_reference,
                jsonKey: "referenceEnd",
                exportJsonKey: { referenceEnd: "$" },
                format: TagFormat.plainText,
                description: "The referenceEnd for the book(s) in the learning path",
                maxCount: 1
              }
            ]
          }
        ]
      }
    ]
  },
  [ConfigKey.group_advertisingCommon]: {
    type: GroupConfigType.standard,
    description: "Common advertising tags",
    tags: [
      {
        key: ConfigKey.property_advertisingClickUrl,
        description: "The URL to which the advertisement should link",
        format: TagFormat.plainText
      }
    ]
  },
  [ConfigKey.group_platformStylesCommon]: {
    type: GroupConfigType.standard,
    description: "Common platform styles",
    tags: [
      {
        key: ConfigKey.property_platformPrimaryColor,
        description: "The platform system primary color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformSecondaryColor,
        description: "The platform section secondary color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformBackgroundColor,
        description: "The platform system background color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformButtonPrimaryColor,
        description: "The platform section header button primary color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformButtonBackgroundColor,
        description: "The platform section header button background color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformScrollbarColor,
        description: "The platform main scrollbar color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformSelectionColor,
        description: "The platform main selection color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformTextSelectionColor,
        description: "The platform main input text selection color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformSeparatorColor,
        description: "The platform main separator color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformPlaceholderColor,
        description: "The platform main input placeholder color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformMessageBackgroundColor,
        description: "The platform section chat message background color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformBorderColor,
        description: "The platform border color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformSelectionTextColor,
        description: "The platform selection text color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformBaseLayerColor,
        description: "The platform base layer color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_platformMargin,
        description: "The platform margin",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_platformBorderRadius,
        description: "The platform border radius",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_platformSelectionBorderRadius,
        description: "The platform selection border radius",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_platformNeedsShadow,
        description: "If true, the platform needs shadow",
        format: TagFormat.boolean,
        nullable: true
      }
    ]
  },
  [ConfigKey.group_quizCommon]: {
    type: GroupConfigType.standard,
    description: "Common quiz tags",
    tags: [
      {
        key: ConfigKey.property_revealSolutions,
        description: "If true, the quiz solutions are revealed",
        format: TagFormat.boolean,
        nullable: true
        // defaultValue: 'false',
      },
      {
        key: ConfigKey.property_additionalSolutions,
        description: "Additional solutions",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [ConfigKey.group_backgroundWallpaper]: {
    type: GroupConfigType.standard,
    description: "Background wallpaper tags",
    tags: [
      {
        key: ConfigKey.resource_backgroundWallpaper,
        exportJsonKey: { backgroundWallpaper: { type: "image", image: { src: "$" } } },
        description: "Background wallpaper for the image, used to set a background for the image",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common resource image tags for images"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_imageNoZoom]: {
    type: GroupConfigType.standard,
    description: "Image bit tags",
    tags: [
      {
        key: ConfigKey.group_backgroundWallpaper,
        description: "Background wallpaper tags for images, used to define background properties for images"
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for images, used to define additional properties for images"
      },
      {
        key: ConfigKey.group_resourceImageNoZoom,
        description: "Resource image tags for images, used to attach images to the bit",
        minCount: 1
      }
    ]
  },
  //
  // Resource groups
  //
  [ConfigKey.group_resourceBitTags]: {
    type: GroupConfigType.standard,
    description: "Resource bit tags",
    tags: [
      {
        key: ConfigKey.resource_imagePlaceholder,
        jsonKey: "imagePlaceholder|resource(type=image, key=image)",
        exportJsonKey: { imagePlaceholder: { type: "image", image: { src: "$" } } },
        description: "Placeholder image for the resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the placeholder image"
          }
        ],
        maxCount: 1
      }
    ]
  },
  //
  // Common resource properties
  //
  [ConfigKey.group_resourceCommon]: {
    type: GroupConfigType.standard,
    description: "Common resource properties",
    tags: [
      {
        key: ConfigKey.property_license,
        description: "The license for the resource",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_copyright,
        description: "The copyright information for the resource",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_caption,
        description: "The caption for the resource",
        format: TagFormat.bitmarkText,
        // PLAN-141 F1 (NISO import): a figure's <caption><title> — the
        // nested content slot reads the title text; the caption rides the
        // resource chain ([&image:…][@caption:…], catalog D5).
        mappingKeys: {
          "xml-niso-iec": { "@el": "caption", "@children": { "@el": "title", "@text": "$" } }
        }
      },
      {
        key: ConfigKey.property_showInIndex,
        description: "If true, the resource is shown in the index",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_search,
        description: "The search text for the resource",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_selected,
        description: "If true, the resource is selected",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_srcAlt,
        description: "An alternative source for the resource",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [ConfigKey.group_resourceImageCommon]: {
    type: GroupConfigType.standard,
    description: "Common properties for image resources",
    tags: [
      {
        key: ConfigKey.group_resourceCommon,
        description: "Common resource properties"
      },
      {
        key: ConfigKey.property_src1x,
        description: "The source URL for the image at 1x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src2x,
        description: "The source URL for the image at 2x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src3x,
        description: "The source URL for the image at 3x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src4x,
        description: "The source URL for the image at 4x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_width,
        description: "The width of the image",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_height,
        description: "The height of the image",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_alt,
        description: "The alternative text for the image",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_zoomDisabled,
        description: "If true, zooming is disabled for the image",
        format: TagFormat.boolean
      }
    ]
  },
  [ConfigKey.group_resourceImageCommonNoZoom]: {
    type: GroupConfigType.standard,
    description: "Common properties for image resources where @zoomDisabled defaults to true",
    tags: [
      {
        key: ConfigKey.group_resourceCommon,
        description: "Common resource properties"
      },
      {
        key: ConfigKey.property_src1x,
        description: "The source URL for the image at 1x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src2x,
        description: "The source URL for the image at 2x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src3x,
        description: "The source URL for the image at 3x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_src4x,
        description: "The source URL for the image at 4x resolution",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_width,
        description: "The width of the image",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_height,
        description: "The height of the image",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_alt,
        description: "The alternative text for the image",
        format: TagFormat.plainText
      },
      // @zoomDisabled with a true default
      {
        key: ConfigKey.property_zoomDisabled,
        description: "If true, zooming is disabled for the image (defaults to true)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [ConfigKey.group_resourceAudioCommon]: {
    type: GroupConfigType.standard,
    description: "Common properties for audio resources",
    tags: [
      {
        key: ConfigKey.group_resourceCommon,
        description: "Common resource properties"
      },
      {
        key: ConfigKey.property_duration,
        description: "The duration of the audio resource",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_mute,
        description: "If true, the audio is muted",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_autoplay,
        description: "If true, the audio plays automatically",
        format: TagFormat.boolean
      }
    ]
  },
  [ConfigKey.group_resourceVideoCommon]: {
    type: GroupConfigType.standard,
    description: "Common properties for video resources",
    tags: [
      {
        key: ConfigKey.group_resourceCommon,
        description: "Common resource properties"
      },
      {
        key: ConfigKey.property_width,
        description: "The width of the video",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_height,
        description: "The height of the video",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_duration,
        description: "The duration of the video",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_mute,
        description: "If true, the video is muted",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_autoplay,
        description: "If true, the video plays automatically",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_allowSubtitles,
        description: "If true, subtitles are allowed for the video",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_showSubtitles,
        description: "If true, subtitles are shown in the video",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_alt,
        description: "The alternative text for the video",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_posterImage,
        jsonKey: "posterImage.src",
        exportJsonKey: { posterImage: { src: "$" } },
        description: "The poster image for the video",
        format: TagFormat.plainText,
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the poster image"
          }
        ]
      },
      {
        key: ConfigKey.property_src1x,
        description: "The source URL for the video at 1x resolution",
        format: TagFormat.plainText,
        exportJsonKey: { thumbnails: [{ src: "$" }] }
      },
      {
        key: ConfigKey.property_src2x,
        description: "The source URL for the video at 2x resolution",
        format: TagFormat.plainText,
        exportJsonKey: { thumbnails: [{ src: "$" }] }
      },
      {
        key: ConfigKey.property_src3x,
        description: "The source URL for the video at 3x resolution",
        format: TagFormat.plainText,
        exportJsonKey: { thumbnails: [{ src: "$" }] }
      },
      {
        key: ConfigKey.property_src4x,
        description: "The source URL for the video at 4x resolution",
        format: TagFormat.plainText,
        exportJsonKey: { thumbnails: [{ src: "$" }] }
      }
    ]
  },
  //
  // Single resources
  //
  [ConfigKey.group_resourceIcon]: {
    type: GroupConfigType.standard,
    description: "Common properties for icon resources",
    tags: [
      {
        key: ConfigKey.resource_icon,
        exportJsonKey: { resource: { type: "icon", icon: { src: "$" } } },
        description: "The icon resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the icon resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImage]: {
    type: GroupConfigType.resource,
    description: "Common properties for image resources",
    tags: [
      {
        key: ConfigKey.resource_image,
        exportJsonKey: { resource: { type: "image", image: { src: "$" } } },
        // PLAN-141 F1 (NISO import): the image URL rides the `$`-in-attr
        // value slot — a <graphic xlink:href> child of the mapped bit
        // element roots the [&image:URL] chain.
        mappingKeys: { "xml-niso-iec": { "@el": "graphic", "@attr": { "xlink:href": "$" } } },
        description: "The image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImageNoZoom]: {
    type: GroupConfigType.resource,
    description: "Image resource where @zoomDisabled defaults to true",
    tags: [
      {
        key: ConfigKey.resource_image,
        exportJsonKey: { resource: { type: "image", image: { src: "$" } } },
        description: "The image resource (no-zoom default)",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommonNoZoom,
            description: "Common image properties with @zoomDisabled defaulting to true"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImagePortrait]: {
    type: GroupConfigType.resource,
    description: "Portrait image resource",
    tags: [
      {
        key: ConfigKey.resource_imagePortrait,
        exportJsonKey: { resource: { type: "image-portrait", imagePortrait: { src: "$" } } },
        description: "The portrait image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the portrait image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImageLandscape]: {
    type: GroupConfigType.resource,
    description: "Landscape image resource",
    tags: [
      {
        key: ConfigKey.resource_imageLandscape,
        exportJsonKey: { resource: { type: "image-landscape", imageLandscape: { src: "$" } } },
        description: "The landscape image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the landscape image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImageEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded image resource",
    tags: [
      {
        key: ConfigKey.resource_imageEmbed,
        exportJsonKey: { resource: { type: "image-embed", imageEmbed: { src: "$" } } },
        description: "The embedded image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the embedded image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceImageLink]: {
    type: GroupConfigType.resource,
    description: "Link to an image resource",
    tags: [
      {
        key: ConfigKey.resource_imageLink,
        exportJsonKey: { resource: { type: "image-link", imageLink: { url: "$" } } },
        description: "The link to the image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the linked image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceAudio]: {
    type: GroupConfigType.resource,
    description: "Audio resource",
    tags: [
      {
        key: ConfigKey.resource_audio,
        exportJsonKey: { resource: { type: "audio", audio: { src: "$" } } },
        description: "The audio resource",
        chain: [
          {
            key: ConfigKey.group_resourceAudioCommon,
            description: "Common audio properties for the audio resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceAudioEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded audio resource",
    tags: [
      {
        key: ConfigKey.resource_audioEmbed,
        exportJsonKey: { resource: { type: "audio-embed", audioEmbed: { src: "$" } } },
        description: "The embedded audio resource",
        chain: [
          {
            key: ConfigKey.group_resourceAudioCommon,
            description: "Common audio properties for the embedded audio resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceAudioLink]: {
    type: GroupConfigType.resource,
    description: "Link to an audio resource",
    tags: [
      {
        key: ConfigKey.resource_audioLink,
        exportJsonKey: { resource: { type: "audio-link", audioLink: { url: "$" } } },
        description: "The link to the audio resource",
        chain: [
          {
            key: ConfigKey.group_resourceAudioCommon,
            description: "Common audio properties for the linked audio resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceVideo]: {
    type: GroupConfigType.resource,
    description: "Video resource",
    tags: [
      {
        key: ConfigKey.resource_video,
        exportJsonKey: { resource: { type: "video", video: { src: "$" } } },
        description: "The video resource",
        chain: [
          {
            key: ConfigKey.group_resourceVideoCommon,
            description: "Common video properties for the video resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceVideoEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded video resource",
    tags: [
      {
        key: ConfigKey.resource_videoEmbed,
        exportJsonKey: { resource: { type: "video-embed", videoEmbed: { url: "$" } } },
        description: "The embedded video resource",
        chain: [
          {
            key: ConfigKey.group_resourceVideoCommon,
            description: "Common video properties for the embedded video resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceVideoLink]: {
    type: GroupConfigType.resource,
    description: "Link to a video resource",
    tags: [
      {
        key: ConfigKey.resource_videoLink,
        exportJsonKey: { resource: { type: "video-link", videoLink: { url: "$" } } },
        description: "The link to the video resource",
        chain: [
          {
            key: ConfigKey.group_resourceVideoCommon,
            description: "Common video properties for the linked video resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceStillImageFilmEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded still image film resource",
    tags: [
      {
        key: ConfigKey.resource_stillImageFilmEmbed,
        exportJsonKey: {
          resource: { type: "still-image-film-embed", stillImageFilmEmbed: { url: "$" } }
        },
        description: "The embedded still image film resource",
        chain: [
          {
            key: ConfigKey.group_resourceVideoCommon,
            description: "Common video properties for the embedded still image film resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceStillImageFilmLink]: {
    type: GroupConfigType.resource,
    description: "Link to a still image film resource",
    tags: [
      {
        key: ConfigKey.resource_stillImageFilmLink,
        exportJsonKey: {
          resource: { type: "still-image-film-link", stillImageFilmLink: { url: "$" } }
        },
        description: "The link to the still image film resource",
        chain: [
          {
            key: ConfigKey.group_resourceVideoCommon,
            description: "Common video properties for the linked still image film resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceArticle]: {
    type: GroupConfigType.resource,
    description: "Article resource",
    tags: [
      {
        key: ConfigKey.resource_article,
        exportJsonKey: { resource: { type: "article", article: { body: "$" } } },
        description: "The article resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the article resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceArticleEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded article resource",
    tags: [
      {
        key: ConfigKey.resource_articleEmbed,
        exportJsonKey: { resource: { type: "article-embed", articleEmbed: { url: "$" } } },
        description: "The embedded article resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the embedded article resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceArticleLink]: {
    type: GroupConfigType.resource,
    description: "Link to an article resource",
    tags: [
      {
        key: ConfigKey.resource_articleLink,
        exportJsonKey: { resource: { type: "article-link", articleLink: { url: "$" } } },
        description: "The link to the article resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the linked article resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceDocument]: {
    type: GroupConfigType.resource,
    description: "Document resource",
    tags: [
      {
        key: ConfigKey.resource_document,
        exportJsonKey: { resource: { type: "document", document: { url: "$" } } },
        description: "The document resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the document resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceDocumentEmbed]: {
    type: GroupConfigType.resource,
    description: "Embedded document resource",
    tags: [
      {
        key: ConfigKey.resource_documentEmbed,
        exportJsonKey: { resource: { type: "document-embed", documentEmbed: { url: "$" } } },
        description: "The embedded document resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the embedded document resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceDocumentLink]: {
    type: GroupConfigType.resource,
    description: "Link to a document resource",
    tags: [
      {
        key: ConfigKey.resource_documentLink,
        exportJsonKey: { resource: { type: "document-link", documentLink: { url: "$" } } },
        description: "The link to the document resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the linked document resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceDocumentDownload]: {
    type: GroupConfigType.resource,
    description: "Downloadable document resource",
    tags: [
      {
        key: ConfigKey.resource_documentDownload,
        exportJsonKey: { resource: { type: "document-download", documentDownload: { url: "$" } } },
        description: "The downloadable document resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the downloadable document resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceAppLink]: {
    type: GroupConfigType.resource,
    description: "Link to an app resource",
    tags: [
      {
        key: ConfigKey.resource_appLink,
        exportJsonKey: { resource: { type: "app-link", appLink: { url: "$" } } },
        description: "The link to the app resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the linked app resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_resourceWebsiteLink]: {
    type: GroupConfigType.resource,
    description: "Link to a website resource",
    tags: [
      {
        key: ConfigKey.resource_websiteLink,
        exportJsonKey: { resource: { type: "website-link", websiteLink: { url: "$" } } },
        description: "The link to the website resource",
        chain: [
          {
            key: ConfigKey.group_resourceCommon,
            description: "Common resource properties for the linked website resource"
          }
        ]
      }
    ]
  },
  //
  // Combo resources - these are resources made up of a combination of multiple resources.
  //
  [ConfigKey.group_resourceStillImageFilm]: {
    type: GroupConfigType.comboResource,
    description: "Resource for still image film",
    comboResourceConfigKey: ConfigKey.resource_stillImageFilm,
    tags: [
      {
        key: ConfigKey.group_resourceImage,
        description: "The image resource for the still image film",
        maxCount: 1,
        minCount: 1
      },
      {
        key: ConfigKey.group_resourceAudio,
        description: "The audio resource for the still image film",
        maxCount: 1,
        minCount: 1
      }
    ]
  },
  [ConfigKey.group_resourceImageResponsive]: {
    type: GroupConfigType.comboResource,
    description: "Responsive image resource",
    comboResourceConfigKey: ConfigKey.resource_imageResponsive,
    tags: [
      {
        key: ConfigKey.group_resourceImagePortrait,
        description: "The portrait image resource",
        maxCount: 1,
        minCount: 1
      },
      {
        key: ConfigKey.group_resourceImageLandscape,
        description: "The landscape image resource",
        maxCount: 1,
        minCount: 1
      }
    ]
  },
  [ConfigKey.group_previewImages]: {
    type: GroupConfigType.resource,
    description: "Array of preview images for catalog items",
    tags: [
      {
        key: ConfigKey.resource_previewImage,
        jsonKey: "previewImage|resource(type=image, key=image)",
        exportJsonKey: { previewImage: { type: "image", image: { src: "$" } } },
        description: "The preview image resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the image resource"
          }
        ]
      }
    ]
  },
  [ConfigKey.group_previewVideos]: {
    type: GroupConfigType.resource,
    description: "Array of preview videos for catalog items",
    tags: [
      {
        key: ConfigKey.resource_previewVideo,
        jsonKey: "previewVideo|resource(type=video, key=video)",
        exportJsonKey: { previewVideo: { type: "video", video: { src: "$" } } },
        description: "The preview video resource",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the image resource"
          }
        ]
      }
    ]
  }
};

// src/config/ConfigHydrator.ts
var MAX_COUNT_DEFAULT = 1;
var MIN_COUNT_DEFAULT = 0;
var ConfigHydrator = class {
  hydrateTagsConfig(_tags) {
    const tagsWithInfo = {
      tags: {}
    };
    for (const t of _tags) {
      let expandedTags;
      const type = typeFromConfigKey(t.key);
      switch (type) {
        case BitTagConfigKeyType.tag:
          expandedTags = this.hydrateTagConfig(t);
          break;
        case BitTagConfigKeyType.property:
          expandedTags = this.hydratePropertyTagConfig(t);
          break;
        case BitTagConfigKeyType.resource:
          expandedTags = this.hydrateResourceTagConfig(t);
          break;
        case BitTagConfigKeyType.group:
          expandedTags = this.hydrateTagGroupConfig(t);
          break;
        default:
          throw new Error(`Unknown tag type for config key '${t.key}'`);
      }
      if (expandedTags) {
        tagsWithInfo.tags = {
          ...tagsWithInfo.tags,
          ...expandedTags.tags
        };
        tagsWithInfo.info = Object.assign(tagsWithInfo.info ?? {}, expandedTags.info);
      }
    }
    return tagsWithInfo;
  }
  hydrateCardSetConfig(_cardSet) {
    if (!_cardSet) return void 0;
    const _cardSetConfig = CARDSETS[_cardSet];
    if (!_cardSetConfig) throw new Error(`No config found for card set config key '${_cardSet}'`);
    const sides = [];
    for (const _side of _cardSetConfig.sides) {
      const variantsOfSide = [];
      for (const _variant of _side.variants) {
        const v = this.hydrateCardVariantConfig(_variant);
        variantsOfSide.push(v);
      }
      const sideConfig = new CardSideConfig(
        _side.name,
        _side.repeat ?? false,
        variantsOfSide,
        _side.jsonKey,
        _side.exportJsonKey,
        Object.prototype.hasOwnProperty.call(_side, "exportJsonKey"),
        _side.htmlKey,
        Object.prototype.hasOwnProperty.call(_side, "htmlKey")
      );
      sides.push(sideConfig);
    }
    const cardSetConfig = new CardSetConfig(
      _cardSet,
      _cardSetConfig.jsonKey,
      _cardSetConfig.exportJsonKey,
      Object.prototype.hasOwnProperty.call(_cardSetConfig, "exportJsonKey"),
      this.hydrateCardSections(_cardSetConfig.sections),
      sides,
      _cardSetConfig.htmlKey,
      Object.prototype.hasOwnProperty.call(_cardSetConfig, "htmlKey")
    );
    return cardSetConfig;
  }
  hydrateCardSections(sections) {
    if (!sections) return void 0;
    const out = {};
    for (const [k, v] of Object.entries(sections)) {
      out[k] = {
        jsonKey: v.jsonKey,
        exportJsonKey: v.exportJsonKey,
        hasExportJsonKey: Object.prototype.hasOwnProperty.call(v, "exportJsonKey"),
        htmlKey: v.htmlKey,
        hasHtmlKey: Object.prototype.hasOwnProperty.call(v, "htmlKey"),
        isDefault: v.isDefault,
        sideJsonKey: v.sideJsonKey,
        sideExportJsonKey: v.sideExportJsonKey,
        hasSideExportJsonKey: Object.prototype.hasOwnProperty.call(v, "sideExportJsonKey"),
        sideHtmlKey: v.sideHtmlKey,
        hasSideHtmlKey: Object.prototype.hasOwnProperty.call(v, "sideHtmlKey"),
        // PLAN-085: cardinality fields pass through; defaults applied
        // downstream by the config consumer (Rust treats `0` / undefined
        // as unbounded).
        minCount: v.minCount,
        maxCount: v.maxCount
      };
    }
    return out;
  }
  hydrateTagConfig(_tag) {
    const {
      key: _configKey,
      maxCount,
      minCount,
      chain: _chain,
      deprecated,
      jsonKey,
      exportJsonKey,
      htmlKey
    } = _tag;
    const configKey = Enum2(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
    if (!configKey) throw new Error(`No tag key found for config key '${configKey}'`);
    const tag = Enum2(Tag).fromValue(configKey);
    if (!tag) throw new Error(`No tag found for tag config key '${configKey}'`);
    let chain;
    if (_chain) {
      chain = this.hydrateTagsConfig(_chain).tags;
    }
    const hydratedTag = new MarkupTagConfig({
      configKey,
      tag,
      maxCount: maxCount ?? MAX_COUNT_DEFAULT,
      minCount: minCount ?? MIN_COUNT_DEFAULT,
      chain,
      jsonKey,
      exportJsonKey,
      hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
      htmlKey,
      hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
      deprecated
    });
    return {
      tags: {
        [configKey]: hydratedTag
      }
    };
  }
  hydratePropertyTagConfig(_tag) {
    const {
      key: _configKey,
      maxCount,
      minCount,
      chain: _chain,
      deprecated,
      format,
      values,
      defaultValue,
      jsonKey,
      exportJsonKey,
      htmlKey
    } = _tag;
    const configKey = Enum2(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
    if (!configKey) throw new Error(`No property key found for config key '${configKey}'`);
    const tag = _configKey.substring(1);
    let chain;
    if (_chain) {
      chain = this.hydrateTagsConfig(_chain).tags;
    }
    const hydratedTag = new PropertyTagConfig({
      configKey,
      tag,
      maxCount: maxCount ?? MAX_COUNT_DEFAULT,
      minCount: minCount ?? MIN_COUNT_DEFAULT,
      chain,
      jsonKey,
      exportJsonKey,
      hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
      htmlKey,
      hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
      format,
      values,
      defaultValue,
      deprecated
    });
    return {
      tags: {
        [configKey]: hydratedTag
      }
    };
  }
  hydrateResourceTagConfig(_tag) {
    const {
      key: _configKey,
      maxCount,
      minCount,
      chain: _chain,
      deprecated,
      jsonKey,
      exportJsonKey,
      htmlKey
    } = _tag;
    const configKey = Enum2(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
    if (!configKey) throw new Error(`No resource key found for config key '${configKey}'`);
    const tag = _configKey.substring(1);
    let chain;
    if (_chain) {
      chain = this.hydrateTagsConfig(_chain).tags;
    }
    const hydratedTag = new ResourceTagConfig({
      configKey,
      tag,
      maxCount: maxCount ?? MAX_COUNT_DEFAULT,
      minCount: minCount ?? MIN_COUNT_DEFAULT,
      chain,
      jsonKey,
      exportJsonKey,
      hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
      htmlKey,
      hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
      deprecated
    });
    return {
      tags: {
        [configKey]: hydratedTag
      }
    };
  }
  hydrateTagGroupConfig(_tag) {
    const { key: _configKey } = _tag;
    const configKey = Enum2(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
    if (!configKey) throw new Error(`No group key found for config key '${configKey}'`);
    const _groupConfig = GROUPS[configKey];
    if (!_groupConfig) throw new Error(`No config found for group config key '${_configKey}'`);
    const { tags: _tags } = _groupConfig;
    const tags2 = this.hydrateTagsConfig(_tags).tags;
    const tagValues = Object.values(tags2);
    if (tagValues.length > 0) {
      const firstTag = tagValues[0];
      const firstTagAny = firstTag;
      firstTagAny.maxCount = _tag.maxCount ?? firstTag.maxCount;
      firstTagAny.minCount = _tag.minCount ?? firstTag.minCount;
    }
    return {
      tags: tags2,
      info: {
        comboResourceConfigKey: _groupConfig.comboResourceConfigKey
      }
    };
  }
  hydrateCardVariantConfig(_variant) {
    const {
      tags: _tags,
      bodyAllowed,
      bodyRequired,
      repeatCount,
      jsonKey,
      exportJsonKey,
      htmlKey,
      format
    } = _variant;
    const tags2 = this.hydrateTagsConfig(_tags);
    const cardSetConfig = new CardVariantConfig(
      tags2.tags,
      bodyAllowed,
      bodyRequired,
      repeatCount,
      jsonKey,
      exportJsonKey,
      Object.prototype.hasOwnProperty.call(_variant, "exportJsonKey"),
      format,
      htmlKey,
      Object.prototype.hasOwnProperty.call(_variant, "htmlKey")
    );
    return cardSetConfig;
  }
};
var instance = new ConfigHydrator();

// src/config/raw/bits.ts
var BITS = {
  [BitType2._error]: {
    since: "1.3.0",
    description: "An error bit, rendered when the bit cannot be interpreted"
  },
  [BitType2._comment]: {
    since: "1.4.12",
    description: "A comment bit, used when a bit is commented out by adding | between the . and the bit name, e.g [.|article]"
  },
  [BitType2._standard]: {
    since: "3.2.0",
    description: "The standard bit type, used as a base for other bits, should not be used directly",
    tags: [
      {
        key: ConfigKey.group_standardTags,
        description: "Standard tags for (almost) all bits"
      }
    ]
  },
  [BitType2.appFlashcards]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "App flashcards bit, used for flashcard quizzes in the app",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      },
      {
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        key: ConfigKey.tag_title,
        description: "The title of the flashcard quiz"
      },
      {
        key: ConfigKey.property_flashcardSet,
        description: "The flashcard set to use for the app flashcards",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        // Override inherited @example from group_standardTags: string root
        // example. Bare/synthesized emits `example: "true"` (a text literal
        // that field-coerces to a paragraph "true" in bitmark+ contexts);
        // valued [@example:text] emits the user's text via `$`.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.appFlashcardsQuiz]: {
    since: "1.3.0",
    baseBitType: BitType2.appFlashcards,
    description: "App flashcards quiz bit"
  },
  [BitType2.appFlashcardsLearn]: {
    since: "1.3.0",
    baseBitType: BitType2.appFlashcards,
    description: "App flashcards learn bit"
  },
  [BitType2.appLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "App link bit, used to link to other bits in the app",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceAppLink,
        description: "Tags for the app link resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.appRag]: {
    since: "5.14.0",
    baseBitType: BitType2.article,
    description: "App RAG (Retrieval-Augmented Generation) bit, equal to article"
  },
  [BitType2.appSearch]: {
    since: "5.14.0",
    baseBitType: BitType2.article,
    description: "App search bit, equal to article"
  },
  [BitType2.author]: {
    since: "4.2.0",
    baseBitType: BitType2._standard,
    description: "Represents an author of something",
    tags: [
      {
        key: ConfigKey.property_fullName,
        description: "Full name of the author"
      },
      {
        key: ConfigKey.property_pseudonym,
        description: "A pseudonym of the author, if any"
      },
      {
        key: ConfigKey.property_title,
        description: 'The title of the author, e.g. "Dr.", "Prof.", etc.',
        jsonKey: "title"
      },
      {
        key: ConfigKey.property_jobTitle,
        description: 'The job title of the author, e.g. "Software Engineer", "Professor", etc.'
      }
    ]
  },
  [BitType2.bookAuthor]: {
    since: "4.2.0",
    baseBitType: BitType2.author,
    description: "Represents an author of a book"
  },
  [BitType2.articleAuthor]: {
    since: "4.2.0",
    baseBitType: BitType2.author,
    description: "Represents an author of a book"
  },
  [BitType2.article]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Article bit, used for articles / paragraphs",
    tags: [
      {
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        // PLAN-140 W3 (NISO import): the title of a mapped source element.
        mappingKeys: { "xml-niso-iec": { "@el": "title", "@text": "$" } },
        key: ConfigKey.tag_title,
        description: "The title of the article"
      }
    ]
  },
  [BitType2.articleAlt]: {
    since: "1.15.0",
    baseBitType: BitType2.article,
    description: "Alternative article bit"
  },
  [BitType2.articleResponsive]: {
    since: "1.21.0",
    baseBitType: BitType2.article,
    description: "Responsive article bit, used for articles that adapt to the screen size",
    tags: [
      {
        key: ConfigKey.property_imageFirst,
        description: "If the image should be displayed first in the responsive article",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.articleResponsiveAlt]: {
    since: "2.0.0",
    baseBitType: BitType2.articleResponsive,
    description: "Alternative responsive article bit"
  },
  [BitType2.standardArticleNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.article,
    description: "Standard normative article bit"
  },
  [BitType2.standardArticleNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.article,
    description: "Standard non-normative article bit"
  },
  [BitType2.smartStandardArticleNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardArticleNormative,
    description: "Smart standard normative article bit",
    // PLAN-140 W3 (NISO import, tranche 1): one article bit per NISO
    // paragraph (catalog D1). CONTENT keys (@children): the subtree is the
    // bit's body; nothing inside it promotes. (Ancestor-context normativity
    // is the non-normative sibling's @ancestor key — plan Q4, resolved.)
    //
    // Second rule (catalog D1 push-down): a NUMBERED-PARAGRAPH sec is an
    // article, not a chapter — the sec-type literal outranks the chapter
    // key's plain sec|app rule (most-specific-wins). Content key: the sec's
    // label rides [%] via the label tag key, its inner <p> is the body, its
    // id is the bit's customerId, and the anchor slot marks it
    // anchor-bearing.
    mappingKeys: {
      "xml-niso-iec": [
        { "@el": "p", "@attr": { id: "$customerId" }, "@children": "$" },
        {
          "@el": "sec",
          "@attr": { "sec-type": "numbered-paragraph", id: "$customerId" },
          "@children": "$",
          "\u25BC": "$anchor"
        }
      ]
    }
  },
  [BitType2.smartStandardArticleNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardArticleNonNormative,
    description: "Smart standard non-normative article bit",
    // PLAN-140 Q4 (normativity inheritance): a <p> is non-normative because
    // of an ANCESTOR element — a foreword/intro sec or an informative annex.
    // @ancestor is a reverse-match-only constraint (forward emission ignores
    // it); the rules are alternatives, first-satisfied wins, and the
    // @ancestor literals give this key higher specificity than the plain
    // normative-article <p> key wherever the context applies.
    mappingKeys: {
      "xml-niso-iec": [
        {
          "@el": "p",
          "@attr": { id: "$customerId" },
          "@ancestor": { "@attr": { "sec-type": "foreword|intro" } },
          "@children": "$"
        },
        {
          "@el": "p",
          "@attr": { id: "$customerId" },
          "@ancestor": { "@attr": { "content-type": "informative" } },
          "@children": "$"
        }
      ]
    }
  },
  [BitType2.smartStandardArticleNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardArticleNormative,
    description: "Smart standard normative article bit that is collapsible",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardArticleNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardArticleNonNormative,
    description: "Smart standard non-normative article bit that is collapsible",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.statement]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Statement bit, used for statements in documents"
  },
  [BitType2.pageArticle]: {
    since: "1.15.0",
    baseBitType: BitType2.article,
    description: "Page article bit"
  },
  [BitType2.pageArticleAlt]: {
    since: "1.15.0",
    baseBitType: BitType2.article,
    description: "Alternative page article bit"
  },
  [BitType2.pageArticleResponsive]: {
    since: "1.21.0",
    baseBitType: BitType2.article,
    description: "Responsive page article bit, used for articles that adapt to the screen size",
    tags: [
      {
        key: ConfigKey.property_imageFirst,
        description: "If the image should be displayed first in the responsive article",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.buttonCopyText]: {
    since: "1.4.3",
    baseBitType: BitType2.article,
    description: "Button copy text bit, used to create a button that copies text to the clipboard",
    tags: [
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption of the button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.catalogItemBook]: {
    since: "4.15.0",
    baseBitType: BitType2._standard,
    description: "Catalog item book bit, used to represent a book product in a catalog with cover and description",
    tags: [
      {
        key: ConfigKey.tag_title,
        description: "Title and subtitle of the catalog item",
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
      },
      {
        key: ConfigKey.property_coverImage,
        description: "Cover image of the catalog item",
        format: TagFormat.plainText,
        nullable: true
      },
      {
        key: ConfigKey.resource_coverImage,
        jsonKey: "coverImage|resource(type=image, key=image)",
        exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
        description: "Cover image of the catalog item",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the cover image resource"
          }
        ]
      },
      {
        key: ConfigKey.property_content2Buy,
        description: "Content to buy identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_listPrice,
        description: "List price of the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the buy button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_customerProductId,
        description: "Customer-specific product identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_currency,
        description: "Currency for the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookIsbn,
        description: "ISBN for iBook version",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookAuthor,
        description: "Author of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookType,
        description: "Type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookBindingType,
        description: "Binding type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookNumberOfPages,
        description: "Number of pages in the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookRating,
        description: "Rating of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesTitle,
        description: "Title of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesId,
        description: "ID of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesNumber,
        description: "Number in the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookLanguage,
        description: "Language of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublishingDate,
        description: "Publishing date of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublisher,
        description: "Publisher of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookTranslator,
        description: "Translator of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookOriginalTitle,
        description: "Original title of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShop,
        description: "External shop identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShopItems,
        description: "Array of external shop items",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_imageSource,
        description: "Image source chain"
      },
      {
        key: ConfigKey.group_previewImages,
        description: "Array of preview images for the catalog item",
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_previewVideos,
        description: "Array of preview videos for the catalog item",
        maxCount: Count.infinity
      },
      {
        // Override inherited @example from group_standardTags: string root example.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.catalogItemExternalBook]: {
    since: "4.15.0",
    baseBitType: BitType2._standard,
    description: "Catalog item external book bit, used to represent an external book product in a catalog with link to external shop",
    tags: [
      {
        key: ConfigKey.tag_title,
        description: "Title and subtitle of the catalog item",
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
      },
      {
        key: ConfigKey.property_coverImage,
        description: "Cover image of the catalog item",
        format: TagFormat.plainText,
        nullable: true
      },
      {
        key: ConfigKey.resource_coverImage,
        jsonKey: "coverImage|resource(type=image, key=image)",
        exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
        description: "Cover image of the catalog item",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the cover image resource"
          }
        ]
      },
      {
        key: ConfigKey.property_customerProductShopLink,
        description: "Link to external shop for the product",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_listPrice,
        description: "List price of the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the buy button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_customerProductId,
        description: "Customer-specific product identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_currency,
        description: "Currency for the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookIsbn,
        description: "ISBN for iBook version",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookAuthor,
        description: "Author of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookType,
        description: "Type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookBindingType,
        description: "Binding type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookNumberOfPages,
        description: "Number of pages in the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookRating,
        description: "Rating of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesTitle,
        description: "Title of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesId,
        description: "ID of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesNumber,
        description: "Number in the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookLanguage,
        description: "Language of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublishingDate,
        description: "Publishing date of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublisher,
        description: "Publisher of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookTranslator,
        description: "Translator of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookOriginalTitle,
        description: "Original title of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShop,
        description: "External shop identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShopItems,
        description: "Array of external shop items",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_imageSource,
        description: "Image source chain"
      },
      {
        key: ConfigKey.group_previewImages,
        description: "Array of preview images for the catalog item",
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_previewVideos,
        description: "Array of preview videos for the catalog item",
        maxCount: Count.infinity
      },
      {
        // Override inherited @example from group_standardTags: string root example.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.catalogItemProduct]: {
    since: "4.15.0",
    baseBitType: BitType2._standard,
    description: "Catalog item product bit, used to represent a general product in a catalog with cover and description",
    tags: [
      {
        key: ConfigKey.tag_title,
        description: "Title and subtitle of the catalog item",
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
      },
      {
        key: ConfigKey.property_coverImage,
        description: "Cover image of the catalog item",
        format: TagFormat.plainText,
        nullable: true
      },
      {
        key: ConfigKey.property_content2Buy,
        description: "Content to buy identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_listPrice,
        description: "List price of the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the buy button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_customerProductId,
        description: "Customer-specific product identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_currency,
        description: "Currency for the catalog item",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookIsbn,
        description: "ISBN for iBook version",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookAuthor,
        description: "Author of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookType,
        description: "Type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookBindingType,
        description: "Binding type of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookNumberOfPages,
        description: "Number of pages in the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookRating,
        description: "Rating of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesTitle,
        description: "Title of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesId,
        description: "ID of the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookSeriesNumber,
        description: "Number in the book series",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookLanguage,
        description: "Language of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublishingDate,
        description: "Publishing date of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookPublisher,
        description: "Publisher of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookTranslator,
        description: "Translator of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_bookOriginalTitle,
        description: "Original title of the book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShop,
        description: "External shop identifier",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_gmbExternalShopItems,
        description: "Array of external shop items",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_imageSource,
        description: "Image source chain"
      },
      {
        key: ConfigKey.group_previewImages,
        description: "Array of preview images for the catalog item",
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_previewVideos,
        description: "Array of preview videos for the catalog item",
        maxCount: Count.infinity
      },
      {
        // Override inherited @example from group_standardTags: string root example.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.callToAction]: {
    since: "1.15.0",
    baseBitType: BitType2._standard,
    description: "Call to action bit, used to create a call to action button",
    tags: [
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption of the call to action button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_callToActionUrl,
        description: "The URL to navigate to when the call to action button is clicked",
        format: TagFormat.plainText,
        minCount: 1
      }
    ]
  },
  [BitType2.callToActionSubscribe]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action subscribe bit"
  },
  [BitType2.callToActionContact]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action contact bit"
  },
  [BitType2.callToActionJoin]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action join bit"
  },
  [BitType2.callToActionMail]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action mail bit"
  },
  [BitType2.callToActionLearnMore]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action learn more bit"
  },
  [BitType2.callToActionSeeMore]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action see more bit"
  },
  [BitType2.callToActionWatch]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action watch bit"
  },
  [BitType2.callToActionStartNow]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action start now bit"
  },
  [BitType2.callToActionGetOffer]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action get offer bit"
  },
  [BitType2.callToActionBookNow]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action book now bit"
  },
  [BitType2.callToActionShopNow]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action shop now bit"
  },
  [BitType2.callToActionGetNow]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action get now bit"
  },
  [BitType2.callToActionDownload]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action download bit"
  },
  [BitType2.callToActionCreateAccount]: {
    since: "1.15.0",
    baseBitType: BitType2.callToAction,
    description: "Call to action create account bit"
  },
  [BitType2.appBitmarkFromJavascript]: {
    since: "1.4.5",
    baseBitType: BitType2._standard,
    description: "App bitmark from JavaScript, used to create bits from JavaScript in the app",
    tags: [
      {
        key: ConfigKey.property_maxCreatedBits,
        description: "The maximum number of bits that can be created from this bit",
        format: TagFormat.number,
        nullable: true
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.appBitmarkFromEditor]: {
    since: "1.4.5",
    baseBitType: BitType2.appBitmarkFromJavascript,
    description: "App bitmark from editor, used to create bits from the editor in the app"
  },
  [BitType2.articleEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Article embed bit, used to embed articles from other sources",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceArticleEmbed,
        description: "Tags for the article embed resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.articleLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Article link bit, used to link to articles from other sources",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceArticleLink,
        description: "Tags for the article link resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.audio]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Audio bit, used for audio files",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceAudio,
        description: "Tags for the audio resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.audioEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Audio embed bit, used to embed audio files from other sources",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceAudioEmbed,
        description: "Tags for the audio embed resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.audioLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Audio link bit, used to link to audio files from other sources",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for the resource bit"
      },
      {
        key: ConfigKey.group_resourceAudioLink,
        description: "Tags for the audio link resource",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.bitAlias]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bit alias, used to create an alias for a bit",
    tags: [
      {
        exportJsonKey: { reference: "$" },
        key: ConfigKey.tag_reference,
        format: TagFormat.plainText,
        description: "The reference to the bit that this alias points to"
      },
      {
        exportJsonKey: { anchor: "$" },
        key: ConfigKey.tag_anchor,
        format: TagFormat.plainText,
        description: "The anchor for the bit alias, used for linking"
      }
    ]
  },
  [BitType2.diff]: {
    since: "3.13.0",
    baseBitType: BitType2._standard,
    description: "Diff bit, used to show differences between two bits",
    tags: [
      {
        key: ConfigKey.property_diffTo,
        description: "The reference to the bit that this diff compares to",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.book]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Book bit, used to represent a book",
    // PLAN-141 F5 (NISO import): the <std-meta> front-matter container is
    // the [.book] bit (STRUCTURAL, first in document order): primary
    // title-wrap → [#], primary content-language → [@language], dated
    // std-ref → [@externalId] (tag keys with maxEmits caps — the NISO
    // convention puts the document language first). std-meta has no id, so
    // no anchor/customerId. Remaining leaves drop as recorded loss (R-4).
    mappingKeys: { "xml-niso-iec": { "@el": "std-meta" } },
    tags: [
      {
        key: ConfigKey.group_bookCommon,
        description: "Common tags for book bits"
      }
    ]
  },
  [BitType2.bookEnd]: {
    since: "3.27.0",
    baseBitType: BitType2.article,
    description: "End of book bit"
  },
  [BitType2.bookAcknowledgements]: {
    since: "1.17.0",
    baseBitType: BitType2.article,
    description: "Acknowledgements section of a book"
  },
  [BitType2.bookAddendum]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Addendum section of a book"
  },
  [BitType2.bookAfterword]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Afterword section of a book"
  },
  [BitType2.bookAppendix]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Appendix section of a book"
  },
  [BitType2.bookArticle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Article section of a book"
  },
  [BitType2.bookAutherBio]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Author biography section of a book"
  },
  [BitType2.bookBibliography]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Bibliography section of a book"
  },
  [BitType2.bookComingSoon]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Coming soon section of a book"
  },
  [BitType2.bookConclusion]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Conclusion section of a book"
  },
  [BitType2.bookCopyright]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Copyright section of a book"
  },
  [BitType2.bookCopyrightPermissions]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Copyright permissions section of a book"
  },
  [BitType2.bookDedication]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Dedication section of a book"
  },
  [BitType2.bookEndnotes]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Endnotes section of a book"
  },
  [BitType2.bookEpigraph]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Epigraph section of a book"
  },
  [BitType2.bookEpilogue]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Epilogue section of a book"
  },
  [BitType2.bookForword]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Foreword section of a book"
  },
  [BitType2.bookFrontispiece]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Frontispiece section of a book"
  },
  [BitType2.bookImprint]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Imprint section of a book"
  },
  [BitType2.bookIncitingIncident]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Inciting incident section of a book"
  },
  [BitType2.bookIntroduction]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Introduction section of a book"
  },
  [BitType2.bookListOfContributors]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "List of contributors section of a book"
  },
  [BitType2.bookNotes]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Notes section of a book"
  },
  [BitType2.bookPostscript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Postscript section of a book"
  },
  [BitType2.bookPreface]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Preface section of a book"
  },
  [BitType2.bookPrologue]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Prologue section of a book"
  },
  [BitType2.bookReadMore]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Read more section of a book"
  },
  [BitType2.bookReferenceList]: {
    //
    since: "3.2.0",
    baseBitType: BitType2._standard,
    description: "Book reference list, used to list references in a book",
    cardSet: CardSetConfigKey.bookReferenceList
  },
  [BitType2.bookRequestForABookReview]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Request for a book review section of a book"
  },
  [BitType2.bookSummary]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Summary section of a book"
  },
  [BitType2.bookTeaser]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Teaser section of a book"
  },
  [BitType2.bookTitle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Title section of a book"
  },
  [BitType2.bookCover]: {
    since: "3.27.0",
    baseBitType: BitType2.image,
    description: "Cover image of a book",
    tags: [
      {
        key: ConfigKey.tag_title,
        description: "The title of the book cover",
        maxCount: 2,
        // title & subtitle
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
      },
      {
        key: ConfigKey.property_coverColor,
        description: "The color of the book cover",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.bookAlias]: {
    since: "1.4.3",
    baseBitType: BitType2.book,
    description: "Book alias, used to create an alias for a book",
    tags: [
      {
        key: ConfigKey.property_bookAlias,
        description: "The alias for the book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.bookDiff]: {
    since: "3.10.0",
    baseBitType: BitType2.book,
    description: "Book diff, used to show differences between two book versions",
    tags: [
      {
        key: ConfigKey.property_bookDiff,
        description: "The reference to the book that this diff compares to",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.bookClose]: {
    since: "1.18.0",
    baseBitType: BitType2.article,
    description: "Close section of a book, used to close the book",
    tags: [
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption of the close button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.bookReference]: {
    since: "2.2.0",
    baseBitType: BitType2.article,
    description: "Book reference, used to reference a book in another book",
    tags: [
      {
        key: ConfigKey.property_refAuthor,
        description: "The author of the referenced book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_refBookTitle,
        description: "The title of the referenced book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_refPublisher,
        description: "The publisher of the referenced book",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_refPublicationYear,
        description: "The publication year of the referenced book",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_citationStyle,
        description: "The citation style to use for the book reference",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.botActionResponse]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action response bit, used to define responses for bot actions",
    cardSet: CardSetConfigKey.botActionResponses
  },
  [BitType2.botActionSend]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action send bit, used to define actions for sending messages",
    tags: [
      {
        key: ConfigKey.property_date,
        description: "The date when the message should be sent",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.browserImage]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Browser image bit, used to display images in the browser",
    tags: [
      {
        key: ConfigKey.property_focusX,
        description: "The X coordinate for focusing the image in the browser",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_focusY,
        description: "The Y coordinate for focusing the image in the browser",
        format: TagFormat.number,
        nullable: true
      }
    ]
  },
  [BitType2.card1]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Card bit, used for cards in quizzes",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      }
    ]
  },
  [BitType2.question1]: {
    since: "1.3.0",
    baseBitType: BitType2.card1,
    description: "Question card bit"
  },
  [BitType2.survey1]: { since: "1.3.0", baseBitType: BitType2.card1, description: "Survey card bit" },
  [BitType2.surveyAnonymous1]: {
    since: "1.3.0",
    baseBitType: BitType2.card1,
    description: "Anonymous survey card bit"
  },
  [BitType2.chapter]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Chapter bit, used to define chapters in books or articles",
    // PLAN-140 W3 (NISO import, tranche 1): recover a chapter from a NISO
    // section or annex. STRUCTURAL key (no @children content slot): the
    // element carries no body of its own — tag children (label/title) map
    // via tag keys, every mapped descendant promotes to a following sibling
    // bit, and the chapter level derives from nesting depth (catalog D1).
    // The source id lands as [@customerId] (two-id model, D2).
    mappingKeys: {
      "xml-niso-iec": { "@el": "sec|app", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
    },
    tags: [
      {
        exportJsonKey: { anchor: "$" },
        key: ConfigKey.tag_anchor,
        format: TagFormat.plainText,
        description: "The anchor for the chapter, used for linking"
      },
      {
        key: ConfigKey.tag_title,
        description: "The title of the chapter",
        jsonKey: "title|setMulti(level)",
        exportJsonKey: { title: "$", level: "$level" },
        // PLAN-140 W3 (NISO import): the section/annex title; the heading
        // level rides the normalizer-derived nesting depth (plan Q2).
        mappingKeys: { "xml-niso-iec": { "@el": "title", "@text": "$" } }
      },
      {
        key: ConfigKey.property_toc,
        description: "If the chapter should be included in the table of contents",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_progress,
        description: "The progress of the chapter, used for tracking reading progress",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.clozeAndMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Cloze and multiple choice text bit, used for quizzes with cloze and multiple choice questions",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      },
      {
        key: ConfigKey.group_gap,
        description: "Tags for gaps in cloze and multiple choice text bits"
      },
      {
        key: ConfigKey.group_trueFalseInlineSelect,
        description: "Tags for true/false questions in cloze and multiple choice text bits"
      },
      {
        // Bit-level @isCaseSensitive — empty exportJsonKey (no bit-level emission);
        // defaultValue 'true' phantom-fires onto the ancestor stack so the gap chain's
        // `@absent: $ancestor` rule resolves to true. Mirrors BPG Builder.ts:1643-1652
        // `pushDownTree(... 'isCaseSensitive', data.isCaseSensitive ?? true)` for cloze bits.
        key: ConfigKey.property_isCaseSensitive,
        exportJsonKey: {},
        description: "If the cloze answers are case sensitive",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.coachCallToActionClozeAndMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType2.clozeAndMultipleChoiceText,
    description: "Coach call to action cloze and multiple choice text bit"
  },
  [BitType2.cloze]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Cloze bit, used for fill-in-the-blank questions in quizzes",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      },
      {
        key: ConfigKey.property_isCaseSensitive,
        exportJsonKey: {},
        description: "If the cloze answers are case sensitive",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_quizCountItems,
        description: "The number of items in the cloze quiz",
        format: TagFormat.boolean,
        nullable: true
      },
      {
        key: ConfigKey.property_quizStrikethroughSolutions,
        description: "If the cloze solutions should be strikethrough",
        format: TagFormat.boolean,
        nullable: true
      },
      {
        key: ConfigKey.group_gap,
        description: "Tags for gaps in cloze bits"
      },
      {
        // Override inherited @example: cloze is in OLD parser's
        // `isTopLevelExample` allow-list, so [@example] at bit-header
        // emits `isExample: true`.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.clozeInstructionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType2.cloze,
    description: "Cloze instruction grouped bit, used for cloze questions with grouped instructions",
    tags: [
      {
        key: ConfigKey.property_quizCountItems,
        description: "The number of items in the cloze quiz (instruction-grouped default: true)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
      // Note: quizStrikethroughSolutions defaults to false for instruction-grouped per OLD
      // JsonGenerator.ts:1689-1690, which equals the Boolean natural default → no override needed.
    ]
  },
  [BitType2.clozeSolutionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType2.cloze,
    description: "Cloze solution grouped bit, used for cloze questions with grouped solutions",
    tags: [
      {
        key: ConfigKey.property_quizCountItems,
        description: "The number of items in the cloze quiz (solution-grouped default: true)",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_quizStrikethroughSolutions,
        description: "If the cloze solutions should be strikethrough (solution-grouped default: true)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.clozeSeveral]: {
    since: "3.5.0",
    baseBitType: BitType2.cloze,
    description: "Cloze several bit, used for cloze questions with multiple answers"
  },
  [BitType2.gapText]: {
    since: "1.5.15",
    baseBitType: BitType2.cloze,
    description: "Gap text bit, used for fill-in-the-blank questions in quizzes with text gaps"
  },
  [BitType2.gapTextInstructionGrouped]: {
    since: "1.5.15",
    baseBitType: BitType2.clozeInstructionGrouped,
    description: "Gap text instruction grouped bit, used for fill-in-the-blank questions with grouped instructions"
  },
  [BitType2.coachSelfReflectionCloze]: {
    since: "1.3.0",
    baseBitType: BitType2.cloze,
    description: "Coach self-reflection cloze bit, used for self-reflection questions in coaching"
  },
  [BitType2.coachCallToActionCloze]: {
    since: "1.3.0",
    baseBitType: BitType2.cloze,
    description: "Coach call to action cloze bit, used for call to action questions in coaching"
  },
  [BitType2.clozeList]: {
    since: "1.4.13",
    baseBitType: BitType2._standard,
    description: "Cloze list bit, used for lists of cloze questions in quizzes",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      },
      {
        // PLAN-084 (R12): bit-level @isCaseSensitive — empty exportJsonKey
        // (no bit-level emission); defaultValue 'true' phantom-fires onto
        // the ancestor stack so each listItem's gap chain `@absent: $ancestor`
        // rule resolves to true. Mirrors the cloze bit (bits.ts:1326-1336).
        key: ConfigKey.property_isCaseSensitive,
        exportJsonKey: {},
        description: "If the cloze answers are case sensitive",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ],
    cardSet: CardSetConfigKey.clozeList
  },
  [BitType2.code]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Code bit, used for code snippets in articles or documents",
    // PLAN-141 F4 (NISO import): a <code> block is a CONTENT bit with a
    // literal text body; @language recovers as [@computerLanguage] (absent
    // attr → no tag). Anchor = the source id (D2 revised).
    mappingKeys: {
      "xml-niso-iec": {
        "@el": "code",
        "@attr": { id: "$customerId", language: "$computerLanguage" },
        "\u25BC": "$anchor",
        "@text": "$"
      }
    },
    tags: [
      {
        key: ConfigKey.property_computerLanguage,
        description: "The programming language of the code snippet",
        format: TagFormat.plainText,
        // PLAN-141 F4 (NISO import): the bit key's `language` sigil recovers
        // a present attr; this `@absent` rule supplies the AUTHORED default
        // when the source carries none (catalog code row — an untyped block
        // is `[@computerLanguage:text]`). Fires only on foreign imports,
        // never the carrier round-trip.
        mappingKeys: { "xml-niso-iec": [{ "@absent": "text" }] }
      },
      {
        key: ConfigKey.property_codeLineNumbers,
        description: "If line numbers should be displayed for the code snippet",
        format: TagFormat.boolean
      },
      {
        key: ConfigKey.property_codeMinimap,
        description: "If a minimap should be displayed for the code snippet",
        format: TagFormat.boolean
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.formula]: {
    since: "1.35.0",
    baseBitType: BitType2._standard,
    description: "Formula bit, used for mathematical formulas in articles or documents",
    textFormatDefault: TextFormat2.latex
  },
  [BitType2.formulaImage]: {
    since: "5.23.0",
    baseBitType: BitType2.formula,
    description: "Formula image bit, used for mathematical formulas rendered as images"
  },
  [BitType2.smartStandardFormula]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard formula bit, used for mathematical formulas in smart standards"
  },
  [BitType2.smartStandardFormulaNonNormative]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard non-normative formula bit, used for mathematical formulas in smart standards that are non-normative"
  },
  [BitType2.smartStandardFormulaNormative]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard normative formula bit, used for mathematical formulas in smart standards that are normative",
    // PLAN-141 F4 (NISO import): a <disp-formula> is a CONTENT bit whose
    // latex-format body carries the <mml:math> subtree VERBATIM (G1 — no
    // MathML↔LaTeX transform; ids stripped); <label> → [%]; a contained
    // <legend> PROMOTES as a sibling legend bit (cards cannot nest in a
    // body). Anchor = the source id (D2 revised).
    mappingKeys: {
      "xml-niso-iec": {
        "@el": "disp-formula",
        "@attr": { id: "$customerId" },
        "\u25BC": "$anchor",
        "@children": "$"
      }
    }
  },
  [BitType2.smartStandardRemarkFormula]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard remark formula bit, used for mathematical formulas in smart standards remarks"
  },
  [BitType2.smartStandardRemarkFormulaNonNormative]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard non-normative remark formula bit, used for mathematical formulas in smart standards remarks that are non-normative"
  },
  [BitType2.smartStandardRemarkFormulaNormative]: {
    since: "3.11.0",
    baseBitType: BitType2.formula,
    description: "Smart standard normative remark formula bit, used for mathematical formulas in smart standards remarks that are normative"
  },
  [BitType2.appCodeCell]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "App code cell, used to create code cells in the app editor"
  },
  [BitType2.appCodeEditor]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "App code editor, used to create code editors in the app editor"
  },
  [BitType2.appCodeIde]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "App code IDE, used to create code IDEs in the app editor"
  },
  [BitType2.codeRuntime]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "Code runtime, used to define the runtime environment for code snippets"
  },
  [BitType2.consoleLog]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "Console log, used to log messages to the console in code snippets"
  },
  [BitType2.output]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "Output bit, used to display output from code snippets"
  },
  [BitType2.stdout]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "Standard output bit, used to display standard output from code snippets"
  },
  [BitType2.sandbox]: {
    since: "4.10.0",
    baseBitType: BitType2.code,
    description: "A sandbox bit, used to create a sandboxed environment for code execution"
  },
  [BitType2.sandboxOutputJson]: {
    since: "4.10.0",
    baseBitType: BitType2.code,
    description: "A sandbox output JSON bit, used to display JSON output from sandboxed code"
  },
  [BitType2.sandboxOutputMarkup]: {
    since: "4.10.0",
    baseBitType: BitType2.code,
    description: "A sandbox output markup bit, used to display markup output from sandboxed code"
  },
  [BitType2.sandboxOutputRender]: {
    since: "4.10.0",
    baseBitType: BitType2.code,
    description: "A sandbox output render bit, used to display rendered output from sandboxed code"
  },
  [BitType2.step]: {
    since: "1.5.1",
    baseBitType: BitType2.article,
    description: "Step bit, used to define steps in a process or guide"
  },
  [BitType2.stepImageScreenshot]: {
    since: "1.5.1",
    baseBitType: BitType2.image,
    description: "Step image screenshot bit, used to display screenshots in steps"
  },
  [BitType2.stepImageScreenshotWithPointer]: {
    since: "1.5.1",
    baseBitType: BitType2.image,
    description: "Step image screenshot with pointer bit, used to display screenshots with pointers in steps",
    tags: [
      {
        key: ConfigKey.property_pointerTop,
        description: "The top position of the pointer in the screenshot",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      },
      {
        key: ConfigKey.property_pointerLeft,
        description: "The left position of the pointer in the screenshot",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      }
    ]
  },
  [BitType2.milestone]: {
    since: "1.20.0",
    baseBitType: BitType2.step,
    description: "Milestone bit, used to define milestones in a process or guide"
  },
  [BitType2.conversationLeft1]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Conversation left bit, used to create a conversation on the left side of the screen",
    tags: [
      {
        key: ConfigKey.group_person,
        exportJsonKey: { partner: { name: "$" } },
        description: "Tags for the person in the conversation"
      }
    ]
  },
  [BitType2.conversationLeft1Scream]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Conversation left scream bit, used to create a conversation with a scream on the left side of the screen"
  },
  [BitType2.conversationLeft1Thought]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Conversation left thought bit, used to create a conversation with a thought on the left side of the screen"
  },
  [BitType2.conversationRight1]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Conversation right bit, used to create a conversation on the right side of the screen"
  },
  [BitType2.conversationRight1Scream]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Conversation right scream bit, used to create a conversation with a scream on the right side of the screen"
  },
  [BitType2.conversationRight1Thought]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Conversation right thought bit, used to create a conversation with a thought on the right side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationLeft1]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation left bit, used for conversations with cloze and multiple choice text on the left side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationLeft1Scream]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation left scream bit, used for conversations with cloze and multiple choice text and a scream on the left side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationLeft1Thought]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation left thought bit, used for conversations with cloze and multiple choice text and a thought on the left side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationRight1]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation right bit, used for conversations with cloze and multiple choice text on the right side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationRight1Scream]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation right scream bit, used for conversations with cloze and multiple choice text and a scream on the right side of the screen"
  },
  [BitType2.clozeAndMultipleChoiceTextConversationRight1Thought]: {
    since: "1.3.0",
    baseBitType: BitType2.conversationLeft1,
    description: "Cloze and multiple choice text conversation right thought bit, used for conversations with cloze and multiple choice text and a thought on the right side of the screen"
  },
  [BitType2.advertising]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "General advertising content bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingAdvertorial]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "Advertorial content bit (sponsored article)",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingCallToAction]: {
    since: "4.2.0",
    baseBitType: BitType2.callToAction,
    description: "Call-to-action for advertising",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingCallToActionMeeting]: {
    since: "4.2.0",
    baseBitType: BitType2.callToAction,
    description: "Call-to-action for meeting or appointment",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingBanner]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Banner advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingSkyscraper]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Skyscraper (vertical) advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingRectangle]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Rectangle advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingFullPage]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Full-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingHalfPage]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Half-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingQuarterPage]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Quarter-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingClassifiedPage]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Classifieds advertisement page bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingLandscape]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Landscape format advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.advertisingPortrait]: {
    since: "4.2.0",
    baseBitType: BitType2.image,
    description: "Portrait format advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType2.cookPreparation]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook preparation bit"
  },
  [BitType2.cookStep]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook step bit"
  },
  [BitType2.cookIngredients]: {
    since: "1.5.16",
    baseBitType: BitType2._standard,
    description: "Cook ingredients bit, used to define ingredients for recipes",
    tags: [
      {
        key: ConfigKey.group_technicalTerm,
        description: "Tags for technical terms related to ingredients"
      },
      {
        key: ConfigKey.property_servings,
        jsonKey: "servings.servings",
        exportJsonKey: { servings: { servings: "$" } },
        description: "The number of servings for the ingredients",
        format: TagFormat.number,
        chain: [
          {
            key: ConfigKey.property_unit,
            description: "The unit of measurement for the ingredients (singular)",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_unitPlural,
            description: "The unit of measurement for the ingredients (plural)",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_unitAbbr,
            description: "The abbreviation for the unit of measurement (singular)",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_unitAbbrPlural,
            description: "The abbreviation for the unit of measurement (plural)",
            format: TagFormat.plainText
          },
          {
            key: ConfigKey.property_decimalPlaces,
            description: "The number of decimal places for the ingredient quantity",
            format: TagFormat.number,
            defaultValue: "1"
          },
          {
            key: ConfigKey.property_disableCalculation,
            description: "If the ingredient quantity calculation should be disabled",
            format: TagFormat.boolean
          },
          {
            exportJsonKey: { hint: "$" },
            key: ConfigKey.tag_hint,
            description: "Hint for the ingredient, used to provide additional information",
            format: TagFormat.plainText
          }
        ]
      }
    ],
    cardSet: CardSetConfigKey.ingredients
  },
  [BitType2.recipe]: {
    since: "1.5.24",
    baseBitType: BitType2.cookIngredients,
    description: "Recipe bit, used to define a recipe"
  },
  [BitType2.cookRemark]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook remark bit, used to add remarks to recipes"
  },
  [BitType2.cookVariation]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook variation bit, used to define variations of recipes"
  },
  [BitType2.cookInsert]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook insert bit, used to insert additional content in recipes"
  },
  [BitType2.cookNoteOnQuantity]: {
    since: "3.27.0",
    baseBitType: BitType2.article,
    description: "Cook note on quantity bit, used to add notes on ingredient quantities in recipes"
  },
  [BitType2.cookArrangement]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook arrangement bit, used to arrange ingredients or steps in recipes"
  },
  [BitType2.cookPracticeAdvise]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook practice advice bit, used to provide practical advice in recipes"
  },
  [BitType2.cookPlate]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook plate bit, used to define the presentation of a dish"
  },
  [BitType2.cookRecommendation]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook recommendation bit, used to recommend dishes or ingredients"
  },
  [BitType2.cookPersonalRecommendation]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook personal recommendation bit, used to provide personal recommendations for dishes or ingredients"
  },
  [BitType2.cookSideDrink]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook side drink bit, used to define side drinks for dishes"
  },
  [BitType2.cookSideDish]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook side dish bit, used to define side dishes for main courses"
  },
  [BitType2.cookTimer]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Cook timer bit, used to set timers for cooking steps"
  },
  [BitType2.document]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Document bit, used to define documents in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for resource bits, used to categorize documents"
      },
      {
        key: ConfigKey.group_resourceDocument,
        description: "Tags for document resources, used to define documents in articles or books",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.documentDownload]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Document download bit, used to define downloadable documents in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for resource bits, used to categorize downloadable documents"
      },
      {
        key: ConfigKey.group_resourceDocumentDownload,
        description: "Tags for downloadable document resources, used to define downloadable documents in articles or books",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.leDocumentDownload]: {
    since: "3.18.0",
    baseBitType: BitType2.documentDownload,
    description: "LE Document download bit, used to define downloadable documents in learning experiences"
  },
  [BitType2.documentEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Document embed bit, used to embed documents in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for resource bits, used to categorize embedded documents"
      },
      {
        key: ConfigKey.group_resourceDocumentEmbed,
        description: "Tags for embedded document resources, used to define embedded documents in articles or books",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.documentLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Document link bit, used to link to documents in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Tags for resource bits, used to categorize document links"
      },
      {
        key: ConfigKey.group_resourceDocumentLink,
        description: "Tags for document link resources, used to define links to documents in articles or books",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.essay]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Essay bit, used to define essays in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      },
      {
        key: ConfigKey.property_reasonableNumOfChars,
        description: "The reasonable number of characters for the essay",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_sampleSolution,
        description: "Sample solution for the essay, used as a reference answer",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_partialAnswer,
        description: "Partial answer for the essay, used to indicate incomplete answers",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_reference,
        description: "Reference for the essay, used to link to external resources",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        // Override inherited @example from group_standardTags: emits both
        // `isExample` (bool) AND `example` (paragraph array). Declared as
        // bitmarkText because BPG `JsonGenerator.toExample` renders the
        // root-example value through the bit's textFormat (not this tag's
        // declared format) — `TagFormat.plainText` here would be ignored
        // at runtime and is misleading.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the essay bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.coachSelfReflectionEssay]: {
    since: "1.3.0",
    baseBitType: BitType2.essay,
    description: "Coach self-reflection essay bit"
  },
  [BitType2.coachCallToActionEssay]: {
    since: "1.3.0",
    baseBitType: BitType2.essay,
    description: "Coach call to action essay bit"
  },
  [BitType2.example]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Example bit, used to provide examples in articles or books",
    tags: [
      {
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        key: ConfigKey.tag_title,
        description: "The title of the example"
      },
      {
        // Override inherited @example from group_standardTags: string root example.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the example bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.exampleAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.example,
    description: "Alternative example bit, used to provide alternative examples in articles or books"
  },
  [BitType2.standardExampleNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.example,
    description: "Standard normative example bit, used to provide normative examples in articles or books"
  },
  [BitType2.standardExampleNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.example,
    description: "Standard non-normative example bit, used to provide non-normative examples in articles or books"
  },
  [BitType2.smartStandardExampleNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardExampleNormative,
    description: "Smart standard normative example bit, used to provide normative examples in smart standards"
  },
  [BitType2.smartStandardExampleNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardExampleNonNormative,
    description: "Smart standard non-normative example bit, used to provide non-normative examples in smart standards"
  },
  [BitType2.smartStandardExampleNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardExampleNormative,
    description: "Smart standard normative example collapsible bit, used to provide normative examples in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardExampleNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardExampleNonNormative,
    description: "Smart standard non-normative example collapsible bit, used to provide non-normative examples in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.authorContentBitGenerator]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "Body contains the content of one or more bits to be created by the bit generator",
    tags: [
      {
        key: ConfigKey.property_classification,
        description: "Classification for the created bits",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.appAiPrompt]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "App AI prompt bit, used to create AI prompts in the app editor"
  },
  [BitType2.aiPrompt]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "AI prompt bit, used to create AI prompts in articles or books"
  },
  [BitType2.aiChat]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "AI chat bit, used to create an AI chat"
  },
  [BitType2.aiEditor]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "AI editor bit, used to create an AI editor"
  },
  [BitType2.aiTutor]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "AI tutor bit, used to create an AI tutor"
  },
  [BitType2.aiWbt]: {
    since: "4.2.0",
    baseBitType: BitType2.article,
    description: "AI WBT bit, used to create an AI WBT (Web-Based Training)"
  },
  [BitType2.articleAi]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Article AI bit, used to create AI-generated articles",
    tags: [
      {
        // Shadows the inherited @aiGenerated from the standard group: this
        // bit type forces aiGenerated:true regardless of source value or
        // absence. Literal `true` in the export pattern ignores `$`.
        key: ConfigKey.property_aiGenerated,
        description: "AI-generated flag, forced true for article-ai bits",
        format: TagFormat.boolean,
        defaultValue: "true",
        exportJsonKey: { aiGenerated: true }
      }
    ]
  },
  [BitType2.articleAttachment]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Article attachment bit, used to attach files to articles"
  },
  [BitType2.assignment]: {
    since: "1.3.0",
    baseBitType: BitType2.essay,
    description: "Assignment bit, used to define assignments in articles or books"
  },
  [BitType2.audioTranscript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Audio transcript bit, used to provide transcripts for audio files in articles or books"
  },
  [BitType2.bitmarkExample]: {
    since: "1.3.0",
    baseBitType: BitType2.example,
    description: "Bitmark example bit, used to provide examples for bitmarks in articles or books"
  },
  [BitType2.blogArticle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Blog article bit, used to define blog articles in articles or books"
  },
  [BitType2.bug]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Bug bit, used to report bugs in articles or books"
  },
  [BitType2.bugAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.bug,
    description: "Alternative bug bit, used to report bugs in articles or books with alternative styling"
  },
  [BitType2.checklist]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Checklist bit, used to create checklists in articles or books"
  },
  [BitType2.coachAudioTranscript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Coach audio transcript bit, used to provide transcripts for audio files in coaching articles or books"
  },
  [BitType2.coachCallToActionChecklist]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Coach call to action checklist bit, used to create checklists in coaching articles or books"
  },
  [BitType2.coachHomeRules]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Coach home rules bit, used to define home rules in coaching articles or books"
  },
  [BitType2.coachVideoTranscript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Coach video transcript bit, used to provide transcripts for video files in coaching articles or books"
  },
  [BitType2.correction]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Correction bit, used to provide corrections in articles or books"
  },
  [BitType2.danger]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Danger bit, used to highlight dangerous or critical information in articles or books"
  },
  [BitType2.dangerAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.article,
    description: "Alternative danger bit, used to highlight dangerous or critical information in articles or books with alternative styling"
  },
  [BitType2.definitionTerm]: {
    since: "1.34.0",
    baseBitType: BitType2.article,
    description: "Definition term bit, used to define terms in articles or books"
  },
  [BitType2.deleted]: {
    since: "3.9.0",
    baseBitType: BitType2.article,
    description: "Deleted bit, used to indicate deleted content in articles or books"
  },
  [BitType2.details1]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Details bit, used to provide additional details in articles or books"
  },
  [BitType2.details]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Details bit, used to provide additional details in articles or books"
  },
  [BitType2.qAndA]: {
    since: "3.5.0",
    baseBitType: BitType2.article,
    description: "Q&A bit, used to create question and answer sections in articles or books"
  },
  [BitType2.editorial]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Editorial bit, used to provide editorial content in articles or books"
  },
  [BitType2.editorNote]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Editor note bit, used to provide notes from the editor in articles or books"
  },
  [BitType2.featured]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Featured bit, used to highlight featured content in articles or books"
  },
  [BitType2.glossaryTerm]: {
    since: "1.33.0",
    baseBitType: BitType2.article,
    description: "Glossary term bit, used to define glossary terms in articles or books"
  },
  [BitType2.help]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Help bit, used to provide help or support information in articles or books"
  },
  [BitType2.helpAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.help,
    description: "Alternative help bit, used to provide help or support information in articles or books with alternative styling"
  },
  [BitType2.hint]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Hint bit, used to provide hints or tips in articles or books"
  },
  [BitType2.hintAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.hint,
    description: "Alternative hint bit, used to provide hints or tips in articles or books with alternative styling"
  },
  [BitType2.indexTerm]: {
    since: "1.33.0",
    baseBitType: BitType2.article,
    description: "Index term bit, used to define index terms in articles or books"
  },
  [BitType2.info]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Info bit, used to provide informational content in articles or books"
  },
  [BitType2.infoAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.info,
    description: "Alternative info bit, used to provide informational content in articles or books with alternative styling"
  },
  [BitType2.langLearningOutcomes]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language learning outcomes bit, used to define learning outcomes in language articles or books"
  },
  [BitType2.langEnablingLanguageSkills]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language enabling skills bit, used to define enabling language skills in language articles or books"
  },
  [BitType2.langLifeSkills]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language life skills bit, used to define life skills in language articles or books"
  },
  [BitType2.langEnglishAroundWorld]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language English around the world bit, used to provide information about English usage around the world in language articles or books"
  },
  [BitType2.langGoodToKnow]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language good to know bit, used to provide useful information in language articles or books"
  },
  [BitType2.langLearningGoal]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language learning goal bit, used to define learning goals in language articles or books"
  },
  [BitType2.langLearningStrategy]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language learning strategy bit, used to define learning strategies in language articles or books"
  },
  [BitType2.langLikeALocal]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language like a local bit, used to provide tips for speaking like a local in language articles or books"
  },
  [BitType2.langMaterial]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language material bit, used to provide language learning materials in language articles or books"
  },
  [BitType2.langUsefulPhrases]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language useful phrases bit, used to provide useful phrases in language articles or books"
  },
  [BitType2.langLevelDown]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language level down bit, used to provide information about lower language levels in language articles or books"
  },
  [BitType2.langLevelUp]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language level up bit, used to provide information about higher language levels in language articles or books"
  },
  [BitType2.langExtraActivity]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language extra activity bit, used to provide additional activities in language articles or books"
  },
  [BitType2.langVideoScript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language video script bit, used to provide scripts for language learning videos in articles or books"
  },
  [BitType2.langAudioScript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language audio script bit, used to provide scripts for language learning audio files in articles or books"
  },
  [BitType2.langVocabulary]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language vocabulary bit, used to provide vocabulary lists in language articles or books"
  },
  [BitType2.langHomework]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language homework bit, used to define homework assignments in language articles or books"
  },
  [BitType2.langTeacherNote]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language teacher note bit, used to provide notes for teachers in language articles or books"
  },
  [BitType2.langTeacherPronunciation]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Language teacher pronunciation bit, used to provide pronunciation guidance for teachers in language articles or books"
  },
  [BitType2.list]: {
    since: "1.22.0",
    baseBitType: BitType2.article,
    description: "List bit, used to create lists in articles or books"
  },
  [BitType2.standardList]: {
    since: "1.22.0",
    baseBitType: BitType2.article,
    description: "Standard list bit, used to create standard lists in articles or books"
  },
  [BitType2.smartStandardList]: {
    since: "1.28.0",
    baseBitType: BitType2.standardList,
    description: "Smart standard list bit, used to create smart standard lists in articles or books",
    // PLAN-140 refs family (NISO import): a reference list is a STRUCTURAL
    // bit (no @children — it takes a path ordinal anchor and its <ref>
    // children promote to sibling list-item bits); its <title> rides [#]
    // via the title tag key.
    mappingKeys: {
      "xml-niso-iec": { "@el": "ref-list", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
    }
  },
  [BitType2.smartStandardListCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardList,
    description: "Smart standard list collapsible bit, used to create collapsible smart standard lists in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.message]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Message bit, used to create messages in articles or books"
  },
  [BitType2.newspaperArticle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Newspaper article bit, used to define newspaper articles in articles or books"
  },
  [BitType2.note]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Note bit, used to provide notes in articles or books"
  },
  [BitType2.noteAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.note,
    description: "Alternative note bit, used to provide notes in articles or books with alternative styling"
  },
  [BitType2.standardNoteNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.note,
    description: "Standard normative note bit, used to provide normative notes in articles or books"
  },
  [BitType2.standardNoteNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.note,
    description: "Standard non-normative note bit, used to provide non-normative notes in articles or books"
  },
  [BitType2.smartStandardNoteNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardNoteNormative,
    description: "Smart standard normative note bit, used to provide normative notes in smart standards"
  },
  [BitType2.smartStandardNoteNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardNoteNonNormative,
    description: "Smart standard non-normative note bit, used to provide non-normative notes in smart standards",
    // PLAN-140 W3 (NISO import, tranche 1). CONTENT key (@children): a
    // note's inner paragraphs are note body, never separate bits.
    mappingKeys: {
      "xml-niso-iec": {
        "@el": "non-normative-note",
        "@attr": { id: "$customerId" },
        "@children": "$",
        "\u25BC": "$anchor"
      }
    }
  },
  [BitType2.smartStandardNoteNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardNoteNormative,
    description: "Smart standard normative note collapsible bit, used to provide normative notes in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardNoteNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardNoteNonNormative,
    description: "Smart standard non-normative note collapsible bit, used to provide non-normative notes in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.noteAi]: {
    since: "1.3.0",
    baseBitType: BitType2.note,
    description: "Note AI bit, used to create AI-generated notes in articles or books",
    tags: [
      {
        // Shadows the inherited @aiGenerated from the standard group: this
        // bit type forces aiGenerated:true regardless of source value or
        // absence. Literal `true` in the export pattern ignores `$`.
        key: ConfigKey.property_aiGenerated,
        description: "AI-generated flag, forced true for article-ai bits",
        format: TagFormat.boolean,
        defaultValue: "true",
        exportJsonKey: { aiGenerated: true }
      }
    ]
  },
  [BitType2.notebookArticle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Notebook article bit, used to define notebook articles in articles or books"
  },
  [BitType2.preparationNote]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Preparation note bit, used to provide preparation notes in articles or books"
  },
  [BitType2.printThisBook]: {
    since: "5.4.0",
    baseBitType: BitType2._standard,
    description: "Print this book bit, used to create a button that prints the entire book",
    tags: [
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption of the print button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.printThisChapter]: {
    since: "5.4.0",
    baseBitType: BitType2._standard,
    description: "Print this chapter bit, used to create a button that prints the current chapter",
    tags: [
      {
        key: ConfigKey.property_buttonCaption,
        description: "The caption of the print button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_printParentChapterLevel,
        description: "The parent chapter level to print",
        format: TagFormat.number,
        defaultValue: "-1"
      }
    ]
  },
  [BitType2.printPageBreak]: {
    since: "5.4.0",
    baseBitType: BitType2.separator,
    description: "Print page break bit, used to create page breaks for printing in articles or books"
  },
  [BitType2.releaseNotesSummary]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Release notes summary bit, used to summarize release notes in articles or books"
  },
  [BitType2.remark]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Remark bit, used to provide remarks in articles or books"
  },
  [BitType2.remarkAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.remark,
    description: "Alternative remark bit, used to provide remarks in articles or books with alternative styling"
  },
  [BitType2.standardRemarkNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.article,
    description: "Standard normative remark bit, used to provide normative remarks in articles or books"
  },
  [BitType2.standardRemarkNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.article,
    description: "Standard non-normative remark bit, used to provide non-normative remarks in articles or books"
  },
  [BitType2.smartStandardRemarkNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkNormative,
    description: "Smart standard normative remark bit, used to provide normative remarks in smart standards"
  },
  [BitType2.smartStandardRemarkNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkNonNormative,
    description: "Smart standard non-normative remark bit, used to provide non-normative remarks in smart standards",
    // PLAN-140 Q4 rider: a warning/caution note routes to the remark bit —
    // the same element as the note key, distinguished by the content-type
    // attr literal (alias value: forward emits the first alternative,
    // reverse accepts any). Most-specific-wins beats the plain note key.
    mappingKeys: {
      "xml-niso-iec": {
        "@el": "non-normative-note",
        "@attr": { "content-type": "warning|caution", id: "$customerId" },
        "@children": "$",
        "\u25BC": "$anchor"
      }
    }
  },
  [BitType2.smartStandardRemarkNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkNormative,
    description: "Smart standard normative remark collapsible bit, used to provide normative remarks in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkNonNormative,
    description: "Smart standard non-normative remark collapsible bit, used to provide non-normative remarks in smart standards that can be collapsed",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.selfAssessment]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Self-assessment bit, used to create self-assessment sections in articles or books"
  },
  [BitType2.separator]: {
    since: "1.4.15",
    baseBitType: BitType2.article,
    description: "Separator bit, used to create visual separators in articles or books"
  },
  [BitType2.separatorAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.separator,
    description: "Alternative separator bit, used to create visual separators in articles or books with alternative styling"
  },
  [BitType2.sticker]: {
    since: "1.5.28",
    baseBitType: BitType2.article,
    description: "Sticker bit, used to create stickers in articles or books"
  },
  [BitType2.sideNote]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Side note bit, used to provide side notes in articles or books"
  },
  [BitType2.summary]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Summary bit, used to provide summaries in articles or books"
  },
  [BitType2.summaryAi]: {
    since: "1.3.0",
    baseBitType: BitType2.summary,
    description: "AI-generated summary bit",
    tags: [
      {
        // Shadows the inherited @aiGenerated from the standard group: this
        // bit type forces aiGenerated:true regardless of source value or
        // absence. Literal `true` in the export pattern ignores `$`.
        key: ConfigKey.property_aiGenerated,
        description: "AI-generated flag, forced true for article-ai bits",
        format: TagFormat.boolean,
        defaultValue: "true",
        exportJsonKey: { aiGenerated: true }
      }
    ]
  },
  [BitType2.videoTranscript]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Video transcript bit, used to provide transcripts for video files in articles or books"
  },
  [BitType2.warning]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Warning bit, used to highlight warnings in articles or books"
  },
  [BitType2.warningAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.warning,
    description: "Alternative warning bit, used to highlight warnings in articles or books with alternative styling"
  },
  [BitType2.workbookArticle]: {
    since: "1.3.0",
    baseBitType: BitType2.article,
    description: "Workbook article bit, used to define workbook articles in articles or books"
  },
  [BitType2.collapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.article,
    description: "Collapsible bit, used to create collapsible sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.sideNoteCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.sideNote,
    description: "Side note collapsible bit, used to create collapsible side notes in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.infoCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.info,
    description: "Info collapsible bit, used to create collapsible informational sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.remarkCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.remark,
    description: "Remark collapsible bit, used to create collapsible remarks in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.warningCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.warning,
    description: "Warning collapsible bit, used to create collapsible warnings in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.dangerCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.danger,
    description: "Danger collapsible bit, used to create collapsible danger sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.noteCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.note,
    description: "Note collapsible bit, used to create collapsible notes in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.exampleCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.example,
    description: "Example collapsible bit, used to create collapsible examples in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.hintCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.hint,
    description: "Hint collapsible bit, used to create collapsible hints in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.bugCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.bug,
    description: "Bug collapsible bit, used to create collapsible bug reports in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.platformPath]: {
    since: "3.14.1",
    baseBitType: BitType2._standard,
    description: "Platform path bit, used to define paths in the platform",
    tags: [
      {
        key: ConfigKey.property_path,
        description: "The path for the platform, used to define navigation paths",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.appCalculatorLatex]: {
    since: "4.10.0",
    baseBitType: BitType2.article,
    description: "A LaTeX calculator bit, used to create a calculator that can interpret LaTeX expressions",
    tags: [
      {
        key: ConfigKey.property_formula,
        description: "The LaTeX formula for the calculator, used to define the calculation logic",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.container]: {
    since: "1.9.0",
    baseBitType: BitType2.article,
    description: "Container bit, used to group bits together in articles or books",
    tags: [
      {
        key: ConfigKey.property_allowedBit,
        description: "Allowed bits in the container, used to define which bits can be placed inside",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.containerWrap]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Container wrap bit, used to wrap content in a container"
  },
  [BitType2.containerNowrap]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Container nowrap bit, used to prevent wrapping of content in a container"
  },
  [BitType2.containerNowrapStretch]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Container nowrap stretch bit, used to stretch content without wrapping in a container"
  },
  [BitType2.containerGroup]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Container group bit, used to group multiple containers together"
  },
  [BitType2.containerFolder]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Container folder bit, used to create folders for organizing bits"
  },
  [BitType2.containerCarousel]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container carousel bit, used to create carousels for displaying bits"
  },
  [BitType2.containerCards]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container cards bit, used to create card layouts for displaying bits"
  },
  [BitType2.containerGrid]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container grid bit, used to create grid layouts for displaying bits"
  },
  [BitType2.containerStack]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container stack bit, used to create stacked layouts for displaying bits"
  },
  [BitType2.containerSlides]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container slides bit, used to create slide presentations for displaying bits"
  },
  [BitType2.containerGallery]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container gallery bit, used to create galleries for displaying bits"
  },
  [BitType2.containerScroller]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container scroller bit, used to create scrolling sections for displaying bits"
  },
  [BitType2.containerTabs]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container tabs bit, used to create tabbed sections for displaying bits"
  },
  [BitType2.containerAccordionTabs]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container accordion tabs bit, used to create accordion-style tabbed sections for displaying bits"
  },
  [BitType2.containerFolderAll]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container folder all bit, used to create folders that can contain all types of bits"
  },
  [BitType2.containerBits2]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container bits 2 bit, used to create a container for bits with additional features"
  },
  [BitType2.containerCookRecipe]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container cook recipe bit, used to create recipe containers for displaying cooking recipes"
  },
  [BitType2.containerNewsArticle]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container news article bit, used to create containers for news articles"
  },
  [BitType2.containerPreview]: {
    since: "1.11.0",
    baseBitType: BitType2.container,
    description: "Container preview bit, used to create preview sections for displaying bits"
  },
  [BitType2.pageContainer]: {
    since: "1.9.0",
    baseBitType: BitType2.container,
    description: "Page container bit, used to create containers for pages in articles or books"
  },
  [BitType2.pageContainerWrap]: {
    since: "1.9.0",
    baseBitType: BitType2.pageContainer,
    description: "Page container wrap bit, used to wrap content in a page container"
  },
  [BitType2.pageContainerNowrap]: {
    since: "1.9.0",
    baseBitType: BitType2.pageContainer,
    description: "Page container nowrap bit, used to prevent wrapping of content in a page container"
  },
  [BitType2.pageContainerNowrapStretch]: {
    since: "1.9.0",
    baseBitType: BitType2.pageContainer,
    description: "Page container nowrap stretch bit, used to stretch content without wrapping in a page container"
  },
  [BitType2.pageContainerFolder]: {
    since: "1.9.0",
    baseBitType: BitType2.pageContainer,
    description: "Page container folder bit, used to create folders for organizing bits in a page container"
  },
  [BitType2.pageContainerGroup]: {
    since: "1.9.0",
    baseBitType: BitType2.pageContainer,
    description: "Page container group bit, used to group multiple page containers together"
  },
  [BitType2.metalevelExplanation]: {
    since: "1.10.0",
    baseBitType: BitType2.article,
    description: "Metalevel explanation bit, used to provide explanations for metalevels in articles or books"
  },
  [BitType2.module]: {
    since: "1.5.26",
    baseBitType: BitType2.article,
    description: "Module bit, used to define modules in articles or books",
    tags: [
      {
        key: ConfigKey.property_hasBookNavigation,
        description: "Indicates if the module has book navigation",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_productId,
        description: "Product ID for the module, used to link to a specific product",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_duration,
        description: "Duration of the module, used to indicate how long it takes to complete",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.moduleProduct]: {
    since: "1.9.0",
    baseBitType: BitType2.module,
    description: "Module product bit, used to define products in modules",
    tags: [
      {
        key: ConfigKey.property_productId,
        description: "Product ID for the module product, used to link to a specific product",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      }
    ]
  },
  [BitType2.exampleList]: {
    since: "1.4.13",
    baseBitType: BitType2.example,
    description: "Example list bit, used to create lists of examples in articles or books",
    cardSet: CardSetConfigKey.exampleBitList,
    rootExampleType: ExampleType.string
  },
  [BitType2.extractorPage]: {
    since: "1.5.17",
    baseBitType: BitType2.image,
    description: "Extractor page bit, used to extract pages from images"
  },
  [BitType2.extractorPageCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorPage,
    description: "Collapsible extractor page bit, used to extract pages from images with collapsible functionality",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.extractorPageWithBlocks]: {
    since: "1.5.21",
    baseBitType: BitType2.image,
    description: "Extractor page with blocks bit, used to extract pages with blocks from images"
  },
  [BitType2.extractorPageWithBlocksCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorPageWithBlocks,
    description: "Collapsible extractor page with blocks bit, used to extract pages with blocks from images with collapsible functionality",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.extractorConfiguration]: {
    since: "1.7.1",
    baseBitType: BitType2._standard,
    description: "Extractor configuration bit, used to configure extractors in articles or books",
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.extractorBlueprintConfiguration]: {
    since: "5.11.0",
    baseBitType: BitType2._standard,
    description: "Extractor blueprint configuration bit, used to specify extractor blueprint configuration when extracting from blueprints",
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.extractorImage]: {
    since: "4.3.0",
    baseBitType: BitType2._standard,
    description: "Extractor images bit, used for images extracted from PDFs",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for logo grave images, used to define additional properties"
      },
      {
        // Image resource — fully-expanded shape so each `&image` emission
        // produces a complete `{type, image: {src}}` object inside the
        // `images[]` array. Chain attrs like `[@zoomDisabled]` fold under
        // `images[].image` via the `[]` last-element-merge semantic.
        key: ConfigKey.group_resourceImageNoZoom,
        description: "Resource image tags for logo grave images, used to attach images",
        exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
        minCount: 1,
        maxCount: Count.infinity
      }
    ],
    resourceAttachmentAllowed: false,
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.extractorImageCollapsible]: {
    since: "4.3.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorImage,
    description: "Collapsible extractor images bit, used for  images extracted from PDFs",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.extractorBlueprint]: {
    since: "5.12.0",
    baseBitType: BitType2._standard,
    description: "Extractor blueprint bit, used to provide blueprint information about extractors",
    textFormatDefault: TextFormat2.json,
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for the blueprint reference image"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Optional uncropped reference page image, used so authoring tools can compute crops against the original page dimensions"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.extractorInformation]: {
    since: "3.8.0",
    baseBitType: BitType2._standard,
    description: "Extractor information bit, used to provide information about extractors in articles or books",
    textFormatDefault: TextFormat2.json
  },
  [BitType2.extractorTheme]: {
    since: "5.7.0",
    baseBitType: BitType2._standard,
    description: "Extractor theme bit, used to store design/theme JSON extracted from documents",
    textFormatDefault: TextFormat2.json
  },
  [BitType2.extractorReconcile]: {
    since: "5.35.0",
    baseBitType: BitType2._standard,
    description: "Extractor reconcile bit, used to store PDF-compare reconciliation decisions (config and issues) in JSON",
    textFormatDefault: TextFormat2.json
  },
  [BitType2.extractorAiChat]: {
    since: "3.19.0",
    baseBitType: BitType2._standard,
    description: "Extractor AI chat bit, used to create AI chat interfaces for extractors in articles or books"
  },
  [BitType2.extractorBlock]: {
    since: "1.5.16",
    baseBitType: BitType2._standard,
    description: "Extractor block bit, used to define blocks within extractor pages",
    tags: [
      {
        key: ConfigKey.property_blockId,
        description: "Unique identifier for the block, used to reference the block in extractor pages",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_pageNo,
        description: "Page number for the block, used to indicate the page on which the block appears",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_x,
        description: "X-coordinate for the block, used to position the block on the page",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_y,
        description: "Y-coordinate for the block, used to position the block on the page",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_width,
        description: "Width of the block, used to define the size of the block on the page",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_height,
        description: "Height of the block, used to define the size of the block on the page",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_index,
        description: "Index of the block, used to order blocks within the extractor page",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_classification,
        description: "Classification for the block, used to categorize the block within the extractor",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_availableClassifications,
        description: "Available classifications for the block, used to define possible classifications",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image for the block, used to attach images to the block",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.extractorRepeatedText]: {
    since: "1.5.21",
    baseBitType: BitType2.article,
    description: "Extractor repeated text bit, used to define repeated text in extractor pages"
  },
  [BitType2.extractorRule]: {
    since: "5.17.0",
    baseBitType: BitType2._standard,
    description: "Extractor rule bit, used to define extraction rules with reference images and instructions",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for extraction rule images"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image tags for the rule reference image",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false,
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.extractorPageNumber]: {
    since: "1.5.21",
    baseBitType: BitType2.article,
    description: "Extractor page number bit, used to define page numbers in extractor pages"
  },
  [BitType2.extractorPageNumberCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorPageNumber,
    description: "Collapsible extractor page number bit, used to define page numbers in extractor pages with collapsible functionality",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.extractorPageHeader]: {
    since: "1.5.21",
    baseBitType: BitType2.article,
    description: "Extractor page header bit, used to define headers in extractor pages"
  },
  [BitType2.extractorPageHeaderCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorPageHeader,
    description: "Collapsible extractor page header bit, used to define headers in extractor pages with collapsible functionality",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.extractorPageFooter]: {
    since: "1.5.21",
    baseBitType: BitType2.article,
    description: "Extractor page footer bit, used to define footers in extractor pages"
  },
  [BitType2.extractorPageFooterCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.extractorPageFooter,
    description: "Collapsible extractor page footer bit, used to define footers in extractor pages with collapsible functionality",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.pageOpenBook]: {
    since: "1.5.10",
    baseBitType: BitType2.article,
    description: "Page open book bit, used to create pages that open books in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the book, used to identify the book in the system",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_book,
        description: "Book reference for the page, used to link to a specific book",
        format: TagFormat.plainText,
        chain: [
          {
            exportJsonKey: { reference: "$" },
            key: ConfigKey.tag_reference,
            format: TagFormat.plainText,
            description: "Reference tag for the book, used to link to the book in the system",
            maxCount: 1,
            chain: [
              {
                key: ConfigKey.tag_reference,
                jsonKey: "referenceEnd",
                exportJsonKey: { referenceEnd: "$" },
                format: TagFormat.plainText,
                description: "End reference tag for the book, used to specify a range",
                maxCount: 1
              }
            ]
          }
        ]
      },
      {
        /* Allow incorrectly chained reference tag */
        exportJsonKey: { reference: "$" },
        key: ConfigKey.tag_reference,
        format: TagFormat.plainText,
        description: "Reference tag for the book, used to link to the book in the system"
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button, used to define the text displayed on the button"
      },
      {
        key: ConfigKey.property_authorFullName,
        description: "Full name of the author",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_authorPseudonym,
        description: "A pseudonym of the author, if any",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_authorTitle,
        description: 'The title of the author, e.g. "Dr.", "Prof.", etc.',
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_authorJobTitle,
        description: 'The job title of the author, e.g. "Software Engineer", "Professor", etc.',
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.printBook]: {
    since: "5.5.0",
    baseBitType: BitType2.article,
    description: "Print book bit, used to create pages that open books without author information in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the book, used to identify the book in the system",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_book,
        description: "Book reference for the page, used to link to a specific book",
        format: TagFormat.plainText,
        chain: [
          {
            exportJsonKey: { reference: "$" },
            key: ConfigKey.tag_reference,
            format: TagFormat.plainText,
            description: "Reference tag for the book, used to link to the book in the system",
            maxCount: 1,
            chain: [
              {
                key: ConfigKey.tag_reference,
                jsonKey: "referenceEnd",
                exportJsonKey: { referenceEnd: "$" },
                format: TagFormat.plainText,
                description: "End reference tag for the book, used to specify a range",
                maxCount: 1
              }
            ]
          }
        ]
      },
      {
        /* Allow incorrectly chained reference tag */
        exportJsonKey: { reference: "$" },
        key: ConfigKey.tag_reference,
        format: TagFormat.plainText,
        description: "Reference tag for the book, used to link to the book in the system",
        maxCount: 1,
        chain: [
          {
            key: ConfigKey.tag_reference,
            jsonKey: "referenceEnd",
            exportJsonKey: { referenceEnd: "$" },
            format: TagFormat.plainText,
            description: "End reference tag for the book, used to specify a range",
            maxCount: 1
          }
        ]
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button, used to define the text displayed on the button"
      }
    ]
  },
  [BitType2.openBookChapter]: {
    since: "4.16.0",
    baseBitType: BitType2.pageOpenBook,
    description: "Open book chapter bit, derived from page-open-book, used to create chapter pages that open books"
  },
  [BitType2.openBookChapterTeaser]: {
    since: "4.16.0",
    baseBitType: BitType2.openBookChapter,
    description: "Open book chapter teaser bit, equal to open-book-chapter, used to create teaser chapter pages that open books"
  },
  [BitType2.pageOpenBookList]: {
    since: "2.1.0",
    baseBitType: BitType2.article,
    description: "Page open book list bit, used to create pages that open lists of books in articles or books",
    tags: [
      {
        key: ConfigKey.property_book,
        // Starter-array shape: each `[@book:value]` chain appends a fresh
        // `{ book, reference }` entry. The chained `►` (tag_reference) lands
        // inside the most-recently-appended entry as `reference: $`.
        exportJsonKey: { book: [{ book: "$" }] },
        description: "Book reference for the page, used to link to a specific book",
        maxCount: Count.infinity,
        chain: [
          {
            exportJsonKey: { reference: "$" },
            key: ConfigKey.tag_reference,
            format: TagFormat.plainText,
            description: "Reference tag for the book, used to link to the book in the system",
            maxCount: 1,
            chain: [
              {
                key: ConfigKey.tag_reference,
                jsonKey: "referenceEnd",
                exportJsonKey: { referenceEnd: "$" },
                format: TagFormat.plainText,
                description: "End reference tag for the book, used to specify a range",
                maxCount: 1
              }
            ]
          }
        ]
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button, used to define the text displayed on the button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageSubscribe]: {
    since: "1.5.10",
    baseBitType: BitType2.article,
    description: "Page subscribe bit, used to create pages that allow users to subscribe to content",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the subscription page, used to identify the page in the system",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the subscription button, used to define the text displayed on the button",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_mailingList,
        description: "Mailing list for the subscription, used to link to a specific mailing list",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.assignmentList]: {
    since: "1.4.13",
    baseBitType: BitType2.exampleList,
    description: "Assignment list bit, used to create lists of assignments in articles or books"
  },
  [BitType2.pageFooter]: {
    since: "1.4.13",
    baseBitType: BitType2.article,
    description: "Page footer bit, used to create footers in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the footer, used to identify the footer in the system",
        format: TagFormat.plainText
      }
    ],
    // page-footer emits cards under `sections` (vs `listItems` for the
    // sibling exampleList / assignmentList bits) — see JsonGenerator.ts.
    cardSet: CardSetConfigKey.pageFooterSections
  },
  [BitType2.legend]: {
    since: "3.12.0",
    baseBitType: BitType2._standard,
    description: "Legend bit, used to provide legends in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType2.smartStandardLegend]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard legend bit, used to provide smart standard legends in articles or books"
  },
  [BitType2.smartStandardLegendNonNormative]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard non-normative legend bit, used to provide non-normative smart standard legends in articles or books"
  },
  [BitType2.smartStandardLegendNormative]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard normative legend bit, used to provide normative smart standard legends in articles or books",
    // PLAN-141 F2 (NISO import): a free-standing <legend> (a formula's) or
    // bare <def-list> (a table footer's — no <legend> wrapper there) that is
    // NOT the claimed card content of its enclosing bit is a legend bit:
    // STRUCTURAL-shaped, its <title> and <def-item>s build the
    // definition-list cards via the card set's authored keys. A <legend>
    // inside a figure/table bit stays that bit's claimed card content and
    // never reaches this key. Anchor = the source id (D2 revised).
    mappingKeys: {
      "xml-niso-iec": [
        { "@el": "legend", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" },
        { "@el": "def-list", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
      ]
    }
  },
  [BitType2.smartStandardRemarkLegend]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard remark legend bit, used to provide smart standard remarks in legends"
  },
  [BitType2.smartStandardRemarkLegendNonNormative]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard non-normative remark legend bit, used to provide non-normative smart standard remarks in legends"
  },
  [BitType2.smartStandardRemarkLegendNormative]: {
    since: "3.12.0",
    baseBitType: BitType2.legend,
    description: "Smart standard normative remark legend bit, used to provide normative smart standard remarks in legends"
  },
  [BitType2.definitionList]: {
    since: "1.34.0",
    baseBitType: BitType2._standard,
    description: "Definition list bit, used to create lists of definitions in articles or books",
    tags: [
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType2.definitionListCollapsible]: {
    since: "5.25.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.definitionList,
    description: "Collapsible definition list bit, used to create collapsible lists of definitions in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.standardDefinitionListNormative]: {
    since: "5.24.0",
    baseBitType: BitType2.definitionList,
    description: "Standard normative definition list bit, used to create normative standard definition lists in articles or books"
  },
  [BitType2.standardDefinitionListNonNormative]: {
    since: "5.24.0",
    baseBitType: BitType2.definitionList,
    description: "Standard non-normative definition list bit, used to create non-normative standard definition lists in articles or books"
  },
  [BitType2.smartStandardDefinitionListNormative]: {
    since: "5.24.0",
    baseBitType: BitType2.standardDefinitionListNormative,
    description: "Smart standard normative definition list bit, used to create normative smart standard definition lists in articles or books",
    // PLAN-141 F3 (NISO import): a terms-and-definitions clause. STRUCTURAL-
    // shaped: <label> → [%], the tbx:termEntry/langSet subtree builds the
    // definition-list card via the card set's authored keys (all tig terms
    // stacked on the term side; definition/example/notes/source composed on
    // the definition side with authored locale affixes), langSet @xml:lang →
    // [@lang] (nested attr slot on the tag key). Anchor = the source id.
    mappingKeys: {
      "xml-niso-iec": { "@el": "term-sec", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
    }
  },
  [BitType2.smartStandardDefinitionListNonNormative]: {
    since: "5.24.0",
    baseBitType: BitType2.standardDefinitionListNonNormative,
    description: "Smart standard non-normative definition list bit, used to create non-normative smart standard definition lists in articles or books"
  },
  [BitType2.metaSearchDefaultTerms]: {
    since: "3.12.0",
    baseBitType: BitType2._standard,
    description: "Meta search default terms bit, used to define default search terms in articles",
    cardSet: CardSetConfigKey.definitionListPlain
  },
  [BitType2.metaSearchDefaultTopics]: {
    since: "3.12.0",
    baseBitType: BitType2.metaSearchDefaultTerms,
    description: "Meta search default topics bit, used to define default search topics in articles"
  },
  [BitType2.flashcard]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Flashcard bit, used to create flashcards in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for flashcards"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.flashcard
  },
  [BitType2.flashcard1]: {
    since: "1.3.0",
    baseBitType: BitType2.flashcard,
    description: "Flashcard 1 bit",
    // PLAN-085: use the flashcard1 cardset (sections.default.maxCount=1)
    // instead of inheriting `flashcard`'s unbounded cardset, since this
    // bit type structurally allows only a single card.
    cardSet: CardSetConfigKey.flashcard1
  },
  [BitType2.qAndACard]: {
    since: "3.25.0",
    baseBitType: BitType2.flashcard1,
    description: "Q&A card bit, used to create question and answer cards in articles or books",
    // PLAN-085: q-and-a-card permits multiple cards (some fixtures carry
    // two). Override the inherited `flashcard1` (max=1) cardset back to
    // the unbounded `flashcard` cardset.
    cardSet: CardSetConfigKey.flashcard
  },
  [BitType2.focusImage]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Focus image bit, used to create images with focus points in articles or books",
    tags: [
      {
        key: ConfigKey.property_focusX,
        description: "X-coordinate for the focus point, used to define the focus area in the image",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_focusY,
        description: "Y-coordinate for the focus point, used to define the focus area in the image",
        format: TagFormat.number,
        nullable: true
      }
    ]
  },
  [BitType2.highlightText]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Highlight text bit, used to create highlighted text in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for highlighted text"
      },
      {
        // Dedicated highlight inline-select chain: tag_true / tag_false
        // emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}`
        // inline body nodes via @body-inline. Bit shares no tag IDs with
        // multiple-choice / multiple-choice-1 (which use group_trueFalse).
        key: ConfigKey.group_trueFalseInlineHighlight,
        description: "True/False quiz tags for highlighted text (inline body nodes)"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.image]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Image bit, used to create images in articles or books",
    tags: [
      {
        key: ConfigKey.group_backgroundWallpaper,
        description: "Background wallpaper tags for images, used to define background properties for images"
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for images, used to define additional properties for images"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image tags for images, used to attach images to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.appCreateBitsFromImage]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "App create bits from image bit, used to create bits from images in the app"
  },
  [BitType2.appGetScreenshot]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "App get screenshot bit, used to capture screenshots in the app"
  },
  [BitType2.detailsImage]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Details image bit, used to create detailed images in articles or books"
  },
  [BitType2.figure]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Figure bit, used to create figures in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType2.imageBanner]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image banner bit, used to create banners in articles or books"
  },
  [BitType2.imageFigure]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image figure bit, used to create figures with images in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType2.imageFigureAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.imageFigure,
    description: "Alternative image figure bit, used to create figures with alternative images in articles or books"
  },
  [BitType2.standardImageFigureNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.imageFigure,
    description: "Standard normative image figure bit, used to create normative image figures in articles or books"
  },
  [BitType2.standardImageFigureNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.imageFigure,
    description: "Standard non-normative image figure bit, used to create non-normative image figures in articles or books"
  },
  [BitType2.smartStandardImageFigureNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardImageFigureNormative,
    description: "Smart standard normative image figure bit, used to create smart standard normative image figures in articles or books",
    // PLAN-141 F1 (NISO import): a figure is STRUCTURAL-shaped (no
    // @children — no body): <label> → [%], <caption>/<graphic> ride the
    // [&image] resource chain, the <legend> def-list becomes the bit's
    // definition-list cards, and an inner note PROMOTES as a sibling bit.
    // Anchor = the source id (D2 revised).
    mappingKeys: {
      "xml-niso-iec": { "@el": "fig", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
    }
  },
  [BitType2.smartStandardImageFigureNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardImageFigureNonNormative,
    description: "Smart standard non-normative image figure bit, used to create smart standard non-normative image figures in articles or books"
  },
  [BitType2.smartStandardImageFigureNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardImageFigureNormative,
    description: "Collapsible smart standard normative image figure bit, used to create collapsible smart standard normative image figures in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardImageFigureNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardImageFigureNonNormative,
    description: "Collapsible smart standard non-normative image figure bit, used to create collapsible smart standard non-normative image figures in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.imageLandscape]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image landscape bit, used to create landscape images in articles or books"
  },
  [BitType2.imageMood]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image mood bit, used to create mood images in articles or books"
  },
  [BitType2.imagePortrait]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image portrait bit, used to create portrait images in articles or books"
  },
  [BitType2.imagePrototype]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image prototype bit, used to create prototype images in articles or books"
  },
  [BitType2.imageSeparator]: {
    since: "1.4.15",
    baseBitType: BitType2._standard,
    description: "Image separator bit, used to create separators in articles or books",
    tags: [
      {
        key: ConfigKey.group_imageNoZoom,
        description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.imageSeparatorAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.imageSeparator,
    description: "Alternative image separator bit, used to create alternative separators in articles or books"
  },
  [BitType2.imageScreenshot]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image screenshot bit, used to create screenshots in articles or books"
  },
  [BitType2.imageStyled]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image styled bit, used to create styled images in articles or books"
  },
  [BitType2.imageSuperWide]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image super wide bit, used to create super wide images in articles or books"
  },
  [BitType2.imageZoom]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Image zoom bit, used to create zoomable images in articles or books"
  },
  [BitType2.langLifeSkillIcon]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Language life skill icon bit, used to create icons for life skills in articles or books"
  },
  [BitType2.lifeSkillSticker]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Life skill sticker bit, used to create stickers for life skills in articles or books"
  },
  [BitType2.pageBanner]: {
    since: "1.4.3",
    baseBitType: BitType2._standard,
    description: "Page banner bit, used to create banners for pages in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the page banner, used to identify the banner in the system",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.group_imageNoZoom,
        description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.pageHero]: {
    since: "1.11.0",
    baseBitType: BitType2.pageBanner,
    description: "Page hero bit, used to create hero sections in articles or books"
  },
  [BitType2.screenshot]: {
    since: "1.3.0",
    deprecated: "1.4.0",
    baseBitType: BitType2.image,
    description: "Screenshot bit, used to create screenshots in articles or books"
  },
  [BitType2.tableImage]: {
    since: "1.5.15",
    baseBitType: BitType2.tableExtended,
    description: "Table image bit, used to create images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_caption,
        description: "Caption for the table image, used to provide a description for the image",
        format: TagFormat.bitmarkText,
        // HTML-10 / HTML-C2: render as the table's `<caption>` (see table bit).
        htmlKey: { "@el": "caption", "@children": "$" }
      },
      {
        key: ConfigKey.group_backgroundWallpaper,
        description: "Background wallpaper tags for images, used to define background properties for images"
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for images, used to define additional properties for images"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image tags for images, used to attach images to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.tableImageAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.tableImage,
    description: "Alternative table image bit, used to create alternative images in tables in articles or books"
  },
  [BitType2.standardTableImageNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableImage,
    description: "Standard normative table image bit, used to create normative images in tables in articles or books"
  },
  [BitType2.standardTableImageNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableImage,
    description: "Standard non-normative table image bit, used to create non-normative images in tables in articles or books"
  },
  [BitType2.standardRemarkTableImageNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableImage,
    description: "Standard normative remark table image bit, used to create normative remark images in tables in articles or books"
  },
  [BitType2.standardRemarkTableImageNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableImage,
    description: "Standard non-normative remark table image bit, used to create non-normative remark images in tables in articles or books"
  },
  [BitType2.standardTableExtendedImageNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableExtendedImage,
    description: "Standard normative extended table image bit, used to create normative extended images in tables in articles or books"
  },
  [BitType2.standardTableExtendedImageNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableExtendedImage,
    description: "Standard non-normative extended table image bit, used to create non-normative extended images in tables in articles or books"
  },
  [BitType2.standardRemarkTableExtendedImageNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableExtendedImage,
    description: "Standard normative remark extended table image bit, used to create normative remark extended images in tables in articles or books"
  },
  [BitType2.standardRemarkTableExtendedImageNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableExtendedImage,
    description: "Standard non-normative remark extended table image bit, used to create non-normative remark extended images in tables in articles or books"
  },
  [BitType2.smartStandardTableImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableImageNormative,
    description: "Smart standard normative table image bit, used to create smart standard normative images in tables in articles or books"
  },
  [BitType2.smartStandardTableImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableImageNonNormative,
    description: "Smart standard non-normative table image bit, used to create smart standard non-normative images in tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableImageNormative,
    description: "Smart standard normative remark table image bit, used to create smart standard normative remark images in tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableImageNonNormative,
    description: "Smart standard non-normative remark table image bit, used to create smart standard non-normative remark images in tables in articles or books"
  },
  [BitType2.smartStandardTableExtendedImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableExtendedImageNormative,
    description: "Smart standard normative extended table image bit, used to create smart standard normative extended images in tables in articles or books"
  },
  [BitType2.smartStandardTableExtendedImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableExtendedImageNonNormative,
    description: "Smart standard non-normative extended table image bit, used to create smart standard non-normative extended images in tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableExtendedImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableExtendedImageNormative,
    description: "Smart standard normative remark extended table image bit, used to create smart standard normative remark extended images in tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableExtendedImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableExtendedImageNonNormative,
    description: "Smart standard non-normative remark extended table image bit, used to create smart standard non-normative remark extended images in tables in articles or books"
  },
  [BitType2.smartStandardTableImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableImageNormative,
    description: "Collapsible smart standard normative table image bit, used to create collapsible smart standard normative images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableImageNonNormative,
    description: "Collapsible smart standard non-normative table image bit, used to create collapsible smart standard non-normative images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableImageNormative,
    description: "Collapsible smart standard normative remark table image bit, used to create collapsible smart standard normative remark images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableImageNonNormative,
    description: "Collapsible smart standard non-normative remark table image bit, used to create collapsible smart standard non-normative remark images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableExtendedImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableExtendedImageNormative,
    description: "Collapsible smart standard normative extended table image bit, used to create collapsible smart standard normative extended images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableExtendedImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableExtendedImageNonNormative,
    description: "Collapsible smart standard non-normative extended table image bit, used to create collapsible smart standard non-normative extended images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableExtendedImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableExtendedImageNormative,
    description: "Collapsible smart standard normative remark extended table image bit, used to create collapsible smart standard normative remark extended images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableExtendedImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableExtendedImageNonNormative,
    description: "Collapsible smart standard non-normative remark extended table image bit, used to create collapsible smart standard non-normative remark extended images in tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.imageLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Image link bit, used to create links with images in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for image links, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceImageLink,
        description: "Resource image link tags, used to attach images to the link",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.imageOnDevice]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Image on device bit, used to create images stored on the device in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for images on device, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image tags for images on device, used to attach images"
      },
      {
        key: ConfigKey.group_imageSource,
        description: "Image source tags for images on device, used to define the source of the image"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.imageResponsive]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Responsive image bit, used to create responsive images in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for responsive images, used to define additional properties"
      },
      {
        // Combo resource
        key: ConfigKey.group_resourceImageResponsive,
        description: "Resource image responsive tags, used to attach responsive images"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.imagesLogoGrave]: {
    since: "1.5.11",
    baseBitType: BitType2._standard,
    description: "Logo grave images bit, used to create logo grave images in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for logo grave images, used to define additional properties"
      },
      {
        // Image resource — fully-expanded shape (see BitType.extractorImage).
        key: ConfigKey.group_resourceImageNoZoom,
        description: "Resource image tags for logo grave images, used to attach images",
        exportJsonKey: { logos: [{ type: "image", image: { src: "$" } }] },
        minCount: 1,
        maxCount: Count.infinity
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.prototypeImages]: {
    since: "1.6.1",
    baseBitType: BitType2.imagesLogoGrave,
    description: "Prototype images bit, used to create prototype images in articles or books",
    tags: [
      {
        // Override inherited `logos` wrapper with `images` for this bit type.
        key: ConfigKey.group_resourceImageNoZoom,
        description: "Resource image tags for prototype images",
        exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
        minCount: 1,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.internalLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Internal link bit, used to create links to other bits in articles or books",
    tags: [
      {
        exportJsonKey: { reference: "$" },
        key: ConfigKey.tag_reference,
        format: TagFormat.plainText,
        description: "Reference tag for the internal link, used to link to the target bit"
      }
    ]
  },
  [BitType2.interview]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Interview bit, used to create interviews in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for interviews"
      },
      {
        key: ConfigKey.property_reasonableNumOfChars,
        exportJsonKey: {},
        description: "Reasonable number of characters for the interview, used to limit input size",
        format: TagFormat.number
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.questions
  },
  [BitType2.interviewInstructionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType2.interview,
    description: "Grouped interview instruction bit"
  },
  [BitType2.botInterview]: {
    since: "1.3.0",
    baseBitType: BitType2.interview,
    description: "Bot interview bit, used to create bot interviews in articles or books"
  },
  [BitType2.brandColor]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Brand color bit, used to define brand colors",
    tags: [
      {
        key: ConfigKey.property_brandColor,
        description: "The brand color",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_brandColorName,
        description: "The brand color name",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.learningPathBook]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Learning path book bit, used to create learning paths in articles or books",
    tags: [
      {
        key: ConfigKey.group_learningPathCommon,
        description: "Common tags for learning path books, used to define properties of learning paths"
      }
    ]
  },
  [BitType2.bookLink]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Book link bit, used to create links to books in learning paths"
  },
  [BitType2.bookLinkNext]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Next book link bit, used to create links to the next book in learning paths"
  },
  [BitType2.bookLinkPrev]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Previous book link bit, used to create links to the previous book in learning paths"
  },
  [BitType2.learningPathClassroomEvent]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path classroom event bit, used to create classroom events in learning paths"
  },
  [BitType2.learningPathClassroomTraining]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path classroom training bit, used to create classroom training in learning paths"
  },
  [BitType2.learningPathClosing]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path closing bit, used to close learning paths"
  },
  [BitType2.learningPathFeedback]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path feedback bit, used to provide feedback in learning paths"
  },
  [BitType2.learningPathLearningGoal]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path learning goal bit, used to define learning goals in learning paths"
  },
  [BitType2.learningPathLti]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path LTI bit, used to integrate LTI tools in learning paths"
  },
  [BitType2.learningPathSign]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path sign bit, used to create signs in learning paths"
  },
  [BitType2.learningPathStep]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path step bit, used to create steps in learning paths"
  },
  [BitType2.learningPathBotTraining]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path bot training bit, used to create bot training in learning paths",
    tags: [
      {
        key: ConfigKey.property_bot,
        description: "Bot reference for the training, used to link to a specific bot",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.learningPathExternalLink]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path external link bit, used to create external links in learning paths",
    tags: [
      {
        key: ConfigKey.property_externalLink,
        description: "External link for the learning path, used to link to external resources",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_externalLinkText,
        description: "Text for the external link, used to define the link text displayed",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.learningPathVideoCall]: {
    since: "1.3.0",
    baseBitType: BitType2.learningPathBook,
    description: "Learning path video call bit, used to create video calls in learning paths",
    tags: [
      {
        key: ConfigKey.property_videoCallLink,
        description: "Video call link for the learning path, used to link to video calls",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leLearningObjectives]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathLearningGoal,
    description: "Learning objectives bit, used to define learning objectives in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the learning objectives, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leVideoCall]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathVideoCall,
    description: "Video call bit, used to create video calls in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the video call, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leClassroomEvent]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathClassroomEvent,
    description: "Classroom event bit, used to create classroom events in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the classroom event, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leMultiDayEvent]: {
    since: "3.32.0",
    baseBitType: BitType2.leClassroomEvent,
    description: "Multi-day event bit, used to create multi-day events in learning paths"
  },
  [BitType2.leCompletion]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathClosing,
    description: "Completion bit, used to mark the completion of learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the completion, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leExternalLink]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathExternalLink,
    description: "External link bit, used to create external links in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the external link, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leReadBook]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathBook,
    description: "Read book bit, used to create reading tasks in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the reading task, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.leLearningStep]: {
    since: "1.25.0",
    baseBitType: BitType2.learningPathStep,
    description: "Learning step bit, used to create steps in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the learning step, used to categorize the activity",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.lePreparationTask]: {
    since: "1.26.0",
    baseBitType: BitType2.learningPathBook,
    description: "Preparation task bit, used to create preparation tasks in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        format: TagFormat.plainText,
        description: "Activity type for the preparation task, used to categorize the activity"
      }
    ]
  },
  [BitType2.leRead]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Read task bit, used to create reading tasks in learning paths"
  },
  [BitType2.leTask]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Task bit, used to create tasks in learning paths"
  },
  [BitType2.leTodo]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Todo bit, used to create todo tasks in learning paths"
  },
  [BitType2.leFollowUpTask]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Follow-up task bit, used to create follow-up tasks in learning paths"
  },
  [BitType2.leFinishingTask]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Finishing task bit, used to create finishing tasks in learning paths"
  },
  [BitType2.leAssignment]: {
    since: "1.27.0",
    baseBitType: BitType2.lePreparationTask,
    description: "Assignment bit, used to create assignments in learning paths"
  },
  [BitType2.leWatchVideoEmbed]: {
    since: "1.27.0",
    baseBitType: BitType2.learningPathBook,
    description: "Watch video embed bit, used to embed videos in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the video embed, used to categorize the activity",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for video embeds, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceVideoEmbed,
        description: "Resource video embed tags, used to attach video embeds to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.leListenAudioEmbed]: {
    since: "1.27.0",
    baseBitType: BitType2.learningPathBook,
    description: "Listen audio embed bit, used to embed audio in learning paths",
    tags: [
      {
        key: ConfigKey.property_activityType,
        description: "Activity type for the audio embed, used to categorize the activity",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for audio embeds, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceAudioEmbed,
        description: "Resource audio embed tags, used to attach audio embeds to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.listItem]: {
    since: "1.22.0",
    baseBitType: BitType2.article,
    description: "List item bit, used to create list items in articles or books",
    tags: [
      {
        key: ConfigKey.property_listItemIndent,
        description: "Indentation level for the list item, used to define the hierarchy of the list",
        format: TagFormat.number,
        defaultValue: "0"
      }
    ]
  },
  [BitType2.standardListItem]: {
    since: "1.22.0",
    baseBitType: BitType2.listItem,
    description: "Standard list item bit, used to create standard list items in articles or books"
  },
  [BitType2.smartStandardListItem]: {
    since: "1.28.0",
    baseBitType: BitType2.standardListItem,
    description: "Smart standard list item bit, used to create smart standard list items in articles or books",
    // PLAN-140 refs family (NISO import): one list-item bit per <ref> —
    // CONTENT key (its citation subtree is the body), counter anchor scoped
    // to the containing list, <label> → [%], nested <std-id> → [@externalId]
    // (the externalId tag key's nested content slot).
    mappingKeys: {
      "xml-niso-iec": {
        "@el": "ref",
        "@attr": { id: "$customerId" },
        "@children": "$",
        "\u25BC": "$anchor"
      }
    }
  },
  [BitType2.smartStandardListItemCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardListItem,
    description: "Collapsible smart standard list item bit, used to create collapsible smart standard list items in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.mark]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Mark bit, used to create marks in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for marks"
      },
      {
        key: ConfigKey.group_markConfig,
        description: "Configuration tags for marks, used to define properties of marks"
      },
      {
        key: ConfigKey.group_mark,
        description: "Mark tags, used to define the content of marks"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.match]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Match bit, used to create matching pairs in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for matching pairs"
      },
      {
        key: ConfigKey.property_isCaseSensitive,
        exportJsonKey: {},
        description: "Case sensitivity for matching pairs, used to define if matches are case-sensitive",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list
        // (covers match, matchAll, matchReverse, matchAllReverse,
        // matchSolutionGrouped, matchMatrix, matchAudio, matchPicture).
        // Bit-level fires emit only `isExample`; the @example value (when
        // provided) is recorded in the ancestor stack and consumed by
        // per-pair/per-cell variant overrides — not surfaced at bit scope.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.matchPairs
  },
  [BitType2.matchAll]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match all bit, used to create matching pairs with all options in articles or books"
  },
  [BitType2.matchReverse]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match reverse bit, used to create matching pairs with reversed options in articles or books"
  },
  [BitType2.matchAllReverse]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match all reverse bit, used to create matching pairs with all options and reversed in articles or books"
  },
  [BitType2.matchSolutionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Grouped match solution bit, used to create grouped matching solutions in articles or books"
  },
  [BitType2.matchMatrix]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match matrix bit, used to create matching pairs in a matrix format in articles or books",
    cardSet: CardSetConfigKey.matchMatrix
  },
  [BitType2.matchAudio]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match audio bit, used to create matching pairs with audio in articles or books",
    cardSet: CardSetConfigKey.matchAudioPairs
  },
  [BitType2.matchPicture]: {
    since: "1.3.0",
    baseBitType: BitType2.match,
    description: "Match picture bit, used to create matching pairs with pictures in articles or books",
    cardSet: CardSetConfigKey.matchImagePairs
  },
  [BitType2.feedback]: {
    since: "3.13.0",
    baseBitType: BitType2._standard,
    description: "Feedback bit, used to provide feedback in articles or books",
    tags: [
      {
        key: ConfigKey.property_reasonableNumOfChars,
        exportJsonKey: {},
        description: "Reasonable number of characters for feedback, used to limit input size",
        format: TagFormat.number
      }
    ],
    cardSet: CardSetConfigKey.feedback
  },
  [BitType2.learningDocumentationFeedback]: {
    since: "3.13.0",
    baseBitType: BitType2.feedback,
    description: "Feedback for learning documentation"
  },
  [BitType2.handInFeedbackExpert]: {
    since: "3.30.0",
    baseBitType: BitType2.feedback,
    description: "Feedback for expert hand-in"
  },
  [BitType2.handInFeedbackSelf]: {
    since: "3.30.0",
    baseBitType: BitType2.feedback,
    description: "Self-feedback for hand-in"
  },
  [BitType2.multipleChoice1]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Multiple choice 1 bit, used to create single-choice questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for multiple choice 1 questions"
      },
      {
        key: ConfigKey.group_trueFalse,
        description: "True/False tags for multiple choice 1 questions"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.coachSelfReflectionMultipleChoice1]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleChoice1,
    description: "Coach self-reflection multiple choice 1 bit, used for self-reflection in coaching"
  },
  [BitType2.multipleChoice]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Multiple choice bit, used to create multiple-choice questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for multiple choice questions"
      },
      {
        key: ConfigKey.group_trueFalse,
        description: "True/False tags for multiple choice questions"
      },
      // This is actually for multiple-choice-1, but we support it here as well (as many bits are wrong)
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.quiz
  },
  [BitType2.coachSelfReflectionMultipleChoice]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleChoice,
    description: "Coach self-reflection multiple choice bit, used for self-reflection in coaching"
  },
  [BitType2.multipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Multiple choice text bit, used to create multiple-choice questions with text in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for multiple choice text questions"
      },
      {
        key: ConfigKey.group_trueFalseInlineSelect,
        description: "True/False tags for multiple choice text questions"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.coachCallToActionMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleChoiceText,
    description: "Coach call to action multiple choice text bit, used for self-reflection in coaching"
  },
  [BitType2.coachSelfReflectionMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleChoiceText,
    description: "Coach self-reflection multiple choice text bit, used for self-reflection in coaching"
  },
  [BitType2.multipleResponse1]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Multiple response 1 bit, used to create multiple-response questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for multiple response 1 questions"
      },
      {
        key: ConfigKey.group_trueFalseResponses,
        description: "True/False tags for multiple response 1 questions"
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ]
  },
  [BitType2.coachSelfReflectionMultipleResponse1]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleResponse1,
    description: "Coach self-reflection multiple response 1 bit, used for self-reflection in coaching"
  },
  [BitType2.multipleResponse]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Multiple response bit, used to create multiple-response questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for multiple response questions"
      },
      {
        key: ConfigKey.group_trueFalseResponses,
        description: "True/False tags for multiple response questions"
      },
      // This is actually for multiple-response-1, but we support it here as well (as many bits are wrong)
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.quizResponses
  },
  [BitType2.coachSelfReflectionMultipleResponse]: {
    since: "1.3.0",
    baseBitType: BitType2.multipleResponse,
    description: "Coach self-reflection multiple response bit, used for self-reflection in coaching"
  },
  [BitType2.page]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Page bit, used to create pages in articles or books",
    tags: [
      {
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        key: ConfigKey.tag_title,
        description: "Title of the page, used to display the page title"
      },
      {
        key: ConfigKey.property_thumbImage,
        description: "Thumbnail image for the page, used to display a small image representation",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.page,
    description: "Collapsible page bit, used to create collapsible sections in pages",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.pageCoverImage]: {
    since: "1.22.0",
    baseBitType: BitType2.page,
    description: "Page cover image bit, used to create cover images for pages"
  },
  [BitType2.advertisingAdvertorialPage]: {
    since: "4.5.0",
    baseBitType: BitType2.page,
    description: "Advertorial Page bit, used to create advertorial pages"
  },
  [BitType2.pageBuyButton]: {
    since: "1.4.3",
    baseBitType: BitType2._standard,
    description: "Page buy button bit, used to create buy buttons on pages",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the page, used to identify the page in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_content2Buy,
        description: "Content to buy for the page, used to define what is being sold",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the buy button, used to define the text displayed on the button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageBuyButtonAlt]: {
    since: "1.31.0",
    baseBitType: BitType2.pageBuyButton,
    description: "Alternative page buy button bit, used to create alternative buy buttons on pages"
  },
  [BitType2.pageBuyButtonPromotion]: {
    since: "1.5.11",
    baseBitType: BitType2.pageBuyButton,
    description: "Page buy button for promotions, used to create buy buttons on promotional pages"
  },
  [BitType2.pageSubpage]: {
    since: "1.6.6",
    baseBitType: BitType2._standard,
    description: "Page subpage bit, used to create subpages in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the subpage, used to identify the subpage in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_page,
        description: "Page reference for the subpage, used to link to the parent page",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageShopInShop]: {
    since: "1.6.6",
    baseBitType: BitType2.pageSubpage,
    description: "Page shop-in-shop bit, used to create shop-in-shop pages in articles or books"
  },
  [BitType2.pageCategory]: {
    since: "1.6.6",
    baseBitType: BitType2.pageSubpage,
    description: "Page category bit, used to create category pages in articles or books"
  },
  [BitType2.pageAcademy]: {
    since: "1.6.6",
    baseBitType: BitType2.pageSubpage,
    description: "Page academy bit, used to create academy pages in articles or books"
  },
  [BitType2.pagePromotion]: {
    since: "1.6.6",
    baseBitType: BitType2.pageSubpage,
    description: "Page promotion bit, used to create promotional pages in articles or books"
  },
  [BitType2.pageSpecial]: {
    since: "1.6.6",
    baseBitType: BitType2.pageSubpage,
    description: "Page special bit, used to create special pages in articles or books"
  },
  [BitType2.pageEnd]: {
    since: "5.33.0",
    baseBitType: BitType2.article,
    description: "Page end bit, used to mark the end of a page section in articles or books"
  },
  [BitType2.pageStart]: {
    since: "5.33.0",
    baseBitType: BitType2.article,
    description: "Page start bit, used to mark the start of a page section in articles or books"
  },
  [BitType2.pagePerson]: {
    since: "1.5.16",
    baseBitType: BitType2._standard,
    description: "Page person bit, used to create person pages in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the person page, used to identify the person in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.group_person,
        description: "Person tags, used to define properties of the person"
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button on the person page, used to define the text displayed",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageProduct]: {
    since: "1.4.17",
    baseBitType: BitType2._standard,
    description: "Page product bit, used to create product pages in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the product page, used to identify the product in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_product,
        description: "Product reference for the product page, used to link to the product",
        format: TagFormat.plainText,
        nullable: true
      }
    ]
  },
  [BitType2.pageProductList]: {
    since: "1.4.17",
    baseBitType: BitType2._standard,
    description: "Page product list bit, used to create product lists in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the product list page, used to identify the product list in URLs",
        format: TagFormat.plainText
      },
      // NOTE: Only one of productVideo or productVideoList should be used, not both - TODO: fix when know which one!
      {
        key: ConfigKey.property_product,
        description: "Product reference for the product list, used to link to the product",
        format: TagFormat.plainText,
        nullable: true,
        maxCount: Count.infinity
      }
      // {
      //   key: ConfigKey.property_productList,
      //   format: TagFormat.plainText,
      //   maxCount: Count.infinity,
      // },
    ]
  },
  [BitType2.pageProductVideo]: {
    since: "1.4.17",
    baseBitType: BitType2._standard,
    description: "Page product video bit, used to create product video pages in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the product video page, used to identify the product video in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_productVideo,
        description: "Product video reference for the product video page, used to link to the video",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pageProductVideoList]: {
    since: "1.4.17",
    baseBitType: BitType2._standard,
    description: "Page product video list bit, used to create product video lists in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the product video list page, used to identify the product video list in URLs",
        format: TagFormat.plainText
      },
      // NOTE: Only one of productVideo or productVideoList should be used, not both - TODO: fix when know which one!
      {
        key: ConfigKey.property_productVideo,
        description: "Product video reference for the product video list, used to link to the video",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
      // {
      //   key: ConfigKey.property_productVideoList,
      //   format: TagFormat.plainText,
      //   maxCount: Count.infinity,
      // },
    ]
  },
  [BitType2.pageSectionFolder]: {
    since: "1.4.17",
    baseBitType: BitType2._standard,
    description: "Page section folder bit, used to create section folders in articles or books",
    tags: [
      {
        key: ConfigKey.property_slug,
        description: "Slug for the section folder page, used to identify the section in URLs",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_productFolder,
        description: "Product folder reference for the section folder page, used to link to the folder",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.photo]: {
    since: "1.3.0",
    baseBitType: BitType2.image,
    description: "Photo bit, used to create photo bits in articles or books"
  },
  [BitType2.platform]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform bit, used to define platform properties",
    tags: [
      {
        key: ConfigKey.property_platformName,
        description: "The platform name",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.resource_platformIcon,
        jsonKey: "platformIcon|resource(type=image, key=image)",
        exportJsonKey: { platformIcon: { type: "image", image: { src: "$" } } },
        description: "The platform icon",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the platform icon resource"
          }
        ],
        maxCount: 1
      },
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformBrandTarget]: {
    since: "4.15.0",
    baseBitType: BitType2._standard,
    description: "Platform brand target bit, used to define the brand target for publishing",
    tags: [
      {
        key: ConfigKey.property_platformBrandTarget,
        description: 'The platform brand target (typically "light", "dark" or "none")',
        format: TagFormat.plainText,
        defaultValue: "none"
      }
    ]
  },
  [BitType2.platformHeader]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform header bit, used to define platform header properties",
    tags: [
      {
        key: ConfigKey.resource_platformLogo,
        jsonKey: "platformLogo|resource(type=image, key=image)",
        exportJsonKey: { platformLogo: { type: "image", image: { src: "$" } } },
        description: "The platform logo",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the platform logo resource"
          }
        ],
        maxCount: 1
      },
      {
        key: ConfigKey.property_platformLogoMaxHeight,
        description: "Maximum height for the platform logo",
        format: TagFormat.number,
        defaultValue: "40"
      },
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformMain]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform main bit, used to define platform main properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformMainButton]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform main button bit, used to define platform main button properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformMainInput]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform main input bit, used to define platform main input properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformSection]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform section bit, used to define platform section properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformSectionButton]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform section button bit, used to define platform section button properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformSectionChat]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform section chat bit, used to define platform section chat properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      },
      {
        key: ConfigKey.resource_platformBackgroundImage,
        jsonKey: "platformBackgroundImage|resource(type=image, key=image)",
        exportJsonKey: { platformBackgroundImage: { type: "image", image: { src: "$" } } },
        description: "The platform section chat background image",
        chain: [
          {
            key: ConfigKey.group_resourceImageCommon,
            description: "Common image properties for the platform background image resource"
          }
        ]
      }
    ]
  },
  [BitType2.platformSectionHeader]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform section header bit, used to define platform section header properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformSectionInput]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform section input bit, used to define platform section input properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.platformSystemIcon]: {
    since: "4.12.0",
    baseBitType: BitType2._standard,
    description: "Platform system icon bit, used to define platform system icon properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType2.quote]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Quote bit, used to create quotes in articles or books",
    tags: [
      {
        key: ConfigKey.property_quotedPerson,
        description: "Quoted person for the quote, used to define who is being quoted",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.rating]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Rating bit, used to create rating bits in articles or books"
  },
  [BitType2.coachSelfReflectionRating]: {
    since: "1.3.0",
    baseBitType: BitType2.rating,
    description: "Coach self-reflection rating bit, used for self-reflection in coaching"
  },
  [BitType2.releaseNote]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Release note bit, used to create release notes in articles or books",
    tags: [
      {
        key: ConfigKey.property_releaseVersion,
        description: "Release version for the release note, used to define the version of the release",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_releaseKind,
        description: "Release kind for the release note, used to define the type of release",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_releaseDate,
        description: "Release date for the release note, used to define when the release was made",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.reviewNote]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Review note bit, used to create review notes in articles or books",
    tags: [
      {
        maxCount: 2,
        jsonKey: "title|multi(count=2, key=subtitle)",
        exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }],
        key: ConfigKey.tag_title,
        description: "Title of the review note, used to display the note title"
      },
      {
        key: ConfigKey.property_resolved,
        description: "Resolved status for the review note, used to indicate if the note is resolved",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_resolvedDate,
        description: "Resolved date for the review note, used to define when the note was resolved",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_resolvedBy,
        description: "Resolved by for the review note, used to define who resolved the note",
        format: TagFormat.plainText
      },
      {
        // Override inherited @example from group_standardTags: string root example.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: "true" } },
          { "@absent": { isExample: true, example: "true" } },
          { isExample: true, example: "$" }
        ],
        description: "The example text for the review note bit",
        format: TagFormat.plainText,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.string
  },
  [BitType2.reviewAuthorNote]: {
    since: "1.3.0",
    baseBitType: BitType2.reviewNote,
    description: "Author review note bit, used to create author notes in reviews"
  },
  [BitType2.reviewCustomerNote]: {
    //
    since: "3.5.0",
    baseBitType: BitType2.reviewNote,
    description: "Customer review note bit, used to create customer notes in reviews"
  },
  [BitType2.reviewError]: {
    since: "5.8.0",
    baseBitType: BitType2.reviewNote,
    description: "Review error note bit, used to create error notes in reviews"
  },
  [BitType2.reviewErrorAi]: {
    since: "5.8.0",
    baseBitType: BitType2.reviewNote,
    description: "Review error AI note bit, used to create AI error notes in reviews"
  },
  [BitType2.reviewErrorTranslation]: {
    since: "5.8.0",
    baseBitType: BitType2.reviewNote,
    description: "Review error translation note bit, used to create translation error notes in reviews"
  },
  [BitType2.reviewReviewerNote]: {
    since: "1.3.0",
    baseBitType: BitType2.reviewNote,
    description: "Reviewer review note bit, used to create reviewer notes in reviews"
  },
  [BitType2.reviewRequestForReviewNote]: {
    since: "1.3.0",
    baseBitType: BitType2.reviewNote,
    description: "Request for review note bit, used to create notes requesting reviews"
  },
  [BitType2.reviewApprovedNote]: {
    since: "1.3.0",
    baseBitType: BitType2.reviewNote,
    description: "Approved review note bit, used to create notes for approved reviews"
  },
  [BitType2.sampleSolution]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Sample solution bit, used to create sample solutions in articles or books",
    tags: [
      // Not sure if these are actually valid here, but include them as they are in the test bit.
      {
        exportJsonKey: { anchor: "$" },
        key: ConfigKey.tag_anchor,
        format: TagFormat.plainText,
        description: "Anchor for the sample solution, used to link to the solution"
      },
      {
        exportJsonKey: { reference: "$" },
        key: ConfigKey.tag_reference,
        format: TagFormat.plainText,
        description: "Reference for the sample solution, used to link to the source of the solution"
      }
    ]
  },
  [BitType2.sequence]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Sequence bit, used to create sequences in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for sequences"
      },
      {
        // Override inherited @example from group_standardTags: this bit has
        // a boolean root example (rootExampleType: boolean), so bit-level
        // [@example] writes `example: true` in addition to `isExample: true`.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: true } },
          { "@absent": { isExample: true, example: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example flag for the bit (boolean)",
        format: TagFormat.boolean,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.elements,
    rootExampleType: ExampleType.boolean
  },
  [BitType2.stillImageFilm]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Still image film bit, used to create still image films in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for still image films, used to define additional properties"
      },
      {
        // Combo resource
        key: ConfigKey.group_resourceStillImageFilm,
        description: "Resource still image film tags, used to attach still image films to the bit"
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.stillImageFilmEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Still image film embed bit, used to embed still image films in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for still image films, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceStillImageFilmEmbed,
        description: "Resource still image film embed tags, used to attach still image films to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.stillImageFilmLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Still image film link bit, used to create still image film links in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for still image films, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceStillImageFilmLink,
        description: "Resource still image film link tags, used to attach still image films to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.surveyAnonymous]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Anonymous survey bit, used to create anonymous surveys in articles or books"
  },
  [BitType2.survey]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Survey bit, used to create surveys in articles or books"
  },
  [BitType2.surveyMatrix]: {
    since: "1.6.2",
    baseBitType: BitType2.image,
    description: "Survey matrix bit, used to create survey matrices in articles or books",
    tags: [
      {
        key: ConfigKey.property_pointerTop,
        description: "Pointer for the top of the survey matrix, used to define the top label",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      },
      {
        key: ConfigKey.property_pointerLeft,
        description: "Pointer for the left of the survey matrix, used to define the left label",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button in the survey matrix, used to define the button text",
        format: TagFormat.plainText,
        minCount: 1,
        maxCount: 1
      }
    ]
  },
  [BitType2.surveyMatrixMe]: {
    since: "1.6.2",
    baseBitType: BitType2.surveyMatrix,
    description: "Survey matrix me bit, used to create personal survey matrices in articles or books"
  },
  [BitType2.surveyRating]: {
    since: "1.6.0",
    baseBitType: BitType2._standard,
    description: "Survey rating bit, used to create rating surveys in articles or books",
    tags: [
      {
        key: ConfigKey.property_ratingLevelStart,
        jsonKey: "ratingLevelStart.level",
        exportJsonKey: { ratingLevelStart: { level: "$" } },
        description: "Start level for the rating survey, used to define the lowest rating",
        format: TagFormat.number,
        chain: [
          {
            key: ConfigKey.property_label,
            description: "Label for the start level of the rating survey, used to define the text displayed",
            format: TagFormat.bitmarkText
          }
        ]
      },
      {
        key: ConfigKey.property_ratingLevelEnd,
        jsonKey: "ratingLevelEnd.level",
        exportJsonKey: { ratingLevelEnd: { level: "$" } },
        description: "End level for the rating survey, used to define the highest rating",
        format: TagFormat.number,
        chain: [
          {
            key: ConfigKey.property_label,
            description: "Label for the end level of the rating survey, used to define the text displayed",
            format: TagFormat.bitmarkText
          }
        ]
      },
      {
        key: ConfigKey.property_ratingLevelSelected,
        description: "Selected level for the rating survey, used to define the default rating",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button in the rating survey, used to define the button text",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.surveyRatingOnce]: {
    since: "1.6.0",
    baseBitType: BitType2.surveyRating,
    description: "Survey rating once bit, used to create one-time rating surveys in articles or books"
  },
  [BitType2.surveyRatingDisplay]: {
    since: "1.6.0",
    baseBitType: BitType2.surveyRating,
    description: "Survey rating display bit, used to display rating surveys in articles or books"
  },
  [BitType2.scorm]: {
    since: "1.5.11",
    baseBitType: BitType2._standard,
    description: "SCORM bit, used to embed SCORM content in articles or books",
    tags: [
      {
        key: ConfigKey.property_scormSource,
        description: "Source for the SCORM content, used to define the SCORM package URL",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_posterImage,
        description: "Poster image for the SCORM content, used to display a preview image",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.pronunciationTable]: {
    since: "3.1.0",
    baseBitType: BitType2._standard,
    description: "Pronunciation table bit, used to create pronunciation tables in articles or books",
    cardSet: CardSetConfigKey.pronunciationTable
  },
  [BitType2.table]: {
    since: "1.5.19",
    baseBitType: BitType2._standard,
    description: "Table bit, used to create tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_caption,
        description: "Caption for the table, used to define the title of the table",
        format: TagFormat.bitmarkText,
        // HTML-10 / HTML-C2: a bit-level caption renders as the table's
        // `<caption>` — placed inside the idiomatic `<table>` container as
        // its first child. Outside a table context it falls back to the
        // residual carrier.
        htmlKey: { "@el": "caption", "@children": "$" },
        // PLAN-141 F2 (NISO import): a table-wrap's <caption><title> — the
        // nested content slot reads the title text (catalog D5: the caption
        // is a TOP-LEVEL bit property on table bits).
        mappingKeys: {
          "xml-niso-iec": { "@el": "caption", "@children": { "@el": "title", "@text": "$" } }
        }
      },
      {
        key: ConfigKey.property_tableFixedHeader,
        description: "Fixed header for the table, used to keep the header visible while scrolling",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tableHeaderWhitespaceNoWrap,
        description: "No wrap for table header whitespace, used to prevent header text wrapping",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tableSearch,
        description: "Search functionality for the table, used to enable searching within the table",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tableSort,
        description: "Sorting functionality for the table, used to enable sorting of table columns",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tablePagination,
        description: "Pagination for the table, used to limit the number of rows displayed at once",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tablePaginationLimit,
        description: "Pagination limit for the table, used to define the number of rows per page",
        format: TagFormat.number,
        defaultValue: "0"
      },
      {
        key: ConfigKey.property_tableHeight,
        description: "Height for the table, used to define the height of the table in pixels",
        format: TagFormat.number,
        defaultValue: "0"
      },
      {
        key: ConfigKey.property_tableWhitespaceNoWrap,
        description: "No wrap for table whitespace, used to prevent text wrapping in table cells",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tableAutoWidth,
        description: "Auto width for table columns, used to automatically adjust column widths",
        format: TagFormat.boolean,
        defaultValue: "true"
      },
      {
        key: ConfigKey.property_tableResizableColumns,
        description: "Resizable columns for the table, used to allow users to resize table columns",
        format: TagFormat.boolean,
        defaultValue: "false"
      },
      {
        key: ConfigKey.property_tableColumnMinWidth,
        description: "Minimum width for table columns, used to define the minimum width of columns",
        format: TagFormat.number,
        defaultValue: "0"
      }
    ],
    cardSet: CardSetConfigKey.table
  },
  [BitType2.tableExtended]: {
    since: "4.14.0",
    baseBitType: BitType2.table,
    description: "Extended table bit, used to create complex tables with all HTML table features",
    cardSet: CardSetConfigKey.tableExtended
  },
  [BitType2.tableExtendedImage]: {
    since: "5.13.0",
    baseBitType: BitType2.tableExtended,
    description: "Extended table image bit, used to create complex tables with images and all HTML table features",
    tags: [
      {
        key: ConfigKey.property_caption,
        description: "Caption for the table image, used to provide a description for the image",
        format: TagFormat.bitmarkText,
        // HTML-10 / HTML-C2: render as the table's `<caption>` (see table bit).
        htmlKey: { "@el": "caption", "@children": "$" }
      },
      {
        key: ConfigKey.group_backgroundWallpaper,
        description: "Background wallpaper tags for images, used to define background properties for images"
      },
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for images, used to define additional properties for images"
      },
      {
        key: ConfigKey.group_resourceImage,
        description: "Resource image tags for images, used to attach images to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.tableAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.table,
    description: "Alternative table bit, used to create alternative tables in articles or books"
  },
  [BitType2.standardTableNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.table,
    description: "Standard normative table bit, used to create standard normative tables in articles or books"
  },
  [BitType2.standardTableNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.table,
    description: "Standard non-normative table bit, used to create standard non-normative tables in articles or books"
  },
  [BitType2.standardRemarkTableNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.table,
    description: "Standard normative remark table bit, used to create standard normative remark tables in articles or books"
  },
  [BitType2.standardRemarkTableNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.table,
    description: "Standard non-normative remark table bit, used to create standard non-normative remark tables in articles or books"
  },
  [BitType2.standardTableExtendedNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableExtended,
    description: "Standard normative extended table bit, used to create standard normative extended tables in articles or books"
  },
  [BitType2.standardTableExtendedNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType2.tableExtended,
    description: "Standard non-normative extended table bit, used to create standard non-normative extended tables in articles or books"
  },
  [BitType2.standardRemarkTableExtendedNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableExtended,
    description: "Standard normative remark extended table bit, used to create standard normative remark extended tables in articles or books"
  },
  [BitType2.standardRemarkTableExtendedNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType2.tableExtended,
    description: "Standard non-normative remark extended table bit, used to create standard non-normative remark extended tables in articles or books"
  },
  [BitType2.smartStandardTableNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableNormative,
    description: "Smart standard normative table bit, used to create smart standard normative tables in articles or books"
  },
  [BitType2.smartStandardTableNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableNonNormative,
    description: "Smart standard non-normative table bit, used to create smart standard non-normative tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableNormative,
    description: "Smart standard normative remark table bit, used to create smart standard normative remark tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableNonNormative,
    description: "Smart standard non-normative remark table bit, used to create smart standard non-normative remark tables in articles or books"
  },
  [BitType2.smartStandardTableExtendedNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableExtendedNormative,
    description: "Smart standard normative extended table bit, used to create smart standard normative extended tables in articles or books",
    // PLAN-141 F2 (NISO import): a table-wrap is STRUCTURAL-shaped (no
    // @children — no body): <label> → [%], <caption><title> → the bit-level
    // [@caption] (D5), the inner <table>/<table-wrap-foot> build the
    // table-extended card grid via the card set's authored keys, and a
    // footer <def-list> PROMOTES as a sibling legend bit. Anchor = the
    // source id (D2 revised).
    mappingKeys: {
      "xml-niso-iec": { "@el": "table-wrap", "@attr": { id: "$customerId" }, "\u25BC": "$anchor" }
    }
  },
  [BitType2.smartStandardTableExtendedNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardTableExtendedNonNormative,
    description: "Smart standard non-normative extended table bit, used to create smart standard non-normative extended tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableExtendedNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableExtendedNormative,
    description: "Smart standard normative remark extended table bit, used to create smart standard normative remark extended tables in articles or books"
  },
  [BitType2.smartStandardRemarkTableExtendedNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType2.standardRemarkTableExtendedNonNormative,
    description: "Smart standard non-normative remark extended table bit, used to create smart standard non-normative remark extended tables in articles or books"
  },
  [BitType2.smartStandardTableNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableNormative,
    description: "Collapsible smart standard normative table bit, used to create collapsible smart standard normative tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableNonNormative,
    description: "Collapsible smart standard non-normative table bit, used to create collapsible smart standard non-normative tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableNormative,
    description: "Collapsible smart standard normative remark table bit, used to create collapsible smart standard normative remark tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableNonNormative,
    description: "Collapsible smart standard non-normative remark table bit, used to create collapsible smart standard non-normative remark tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableExtendedNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableExtendedNormative,
    description: "Collapsible smart standard normative extended table bit, used to create collapsible smart standard normative extended tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardTableExtendedNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardTableExtendedNonNormative,
    description: "Collapsible smart standard non-normative extended table bit, used to create collapsible smart standard non-normative extended tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableExtendedNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableExtendedNormative,
    description: "Collapsible smart standard normative remark extended table bit, used to create collapsible smart standard normative remark extended tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.smartStandardRemarkTableExtendedNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType2.smartStandardRemarkTableExtendedNonNormative,
    description: "Collapsible smart standard non-normative remark extended table bit, used to create collapsible smart standard non-normative remark extended tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_isCollapsible,
        description: "If true, the bit is collapsible (defaults true for this deprecated bit type)",
        format: TagFormat.boolean,
        defaultValue: "true"
      }
    ]
  },
  [BitType2.parameters]: {
    since: "1.18.0",
    baseBitType: BitType2.table,
    description: "Parameters bit, used to create parameter tables in articles or books"
  },
  [BitType2.toc]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Table of contents bit, used to create a table of contents in articles or books"
  },
  [BitType2.tocChapter]: {
    since: "1.5.5",
    baseBitType: BitType2.toc,
    description: "Table of contents chapter bit, used to create chapters in the table of contents",
    tags: [
      {
        key: ConfigKey.property_maxDisplayLevel,
        description: "Maximum display level for the chapter in the table of contents",
        format: TagFormat.number,
        nullable: true
      }
    ]
  },
  [BitType2.tocInline]: {
    since: "3.24.0",
    baseBitType: BitType2.toc,
    description: "Inline table of contents bit, used to create inline tables of contents in articles or books",
    tags: [
      {
        key: ConfigKey.property_maxTocChapterLevel,
        description: "Maximum chapter level for the inline table of contents",
        format: TagFormat.number,
        nullable: true
      }
    ]
  },
  [BitType2.tocResource]: {
    since: "3.31.0",
    baseBitType: BitType2.toc,
    description: "Table of contents resource bit, used to create resources in the table of contents",
    tags: [
      {
        key: ConfigKey.property_tocResource,
        description: "Resource content for the table of contents, used to define the resource",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button in the table of contents resource, used to define the text displayed on the button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.tocContent]: {
    since: "3.31.0",
    baseBitType: BitType2.toc,
    description: "Table of contents content bit, used to create content sections in the table of contents",
    tags: [
      {
        key: ConfigKey.property_tocContent,
        description: "Content for the table of contents, used to define the content section",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button in the table of contents content, used to define the text displayed on the button",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.anchor]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Anchor bit, used to create anchors in articles or books"
  },
  [BitType2.bitBookEnding]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bit book ending bit, used to create endings in articles or books"
  },
  [BitType2.bitBookSummary]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bit book summary bit, used to create summaries in articles or books"
  },
  [BitType2.botActionAnnounce]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action announce bit, used to create announcements in articles or books"
  },
  [BitType2.botActionRatingNumber]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action rating number bit, used to create rating number actions in articles or books"
  },
  [BitType2.botActionRemind]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action remind bit, used to create reminders in articles or books"
  },
  [BitType2.botActionSave]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action save bit, used to create save actions in articles or books"
  },
  [BitType2.botActionTrueFalse]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Bot action true/false bit, used to create true/false actions in articles or books"
  },
  [BitType2.chapterSubjectMatter]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Chapter subject matter bit, used to create subject matter chapters in articles or books"
  },
  [BitType2.chat]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Chat bit, used to create chat sections in articles or books"
  },
  [BitType2.conclusion]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Conclusion bit, used to create conclusions in articles or books"
  },
  [BitType2.conclusionAlt]: {
    since: "1.16.0",
    baseBitType: BitType2.conclusion,
    description: "Alternative conclusion bit, used to create alternative conclusions in articles or books"
  },
  [BitType2.documentUpload]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Document upload bit, used to create document upload sections in articles or books"
  },
  [BitType2.footNote]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Footnote bit, used to create footnotes in articles or books"
  },
  [BitType2.formFreeText]: {
    since: "5.3.0",
    baseBitType: BitType2.interview,
    description: "Form free text bit, used to create free text forms in articles or books"
  },
  [BitType2.groupBorn]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Group born bit, used to create group born sections in articles or books"
  },
  [BitType2.groupDied]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Group died bit, used to create group died sections in articles or books"
  },
  [BitType2.recordAudio]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Record audio bit, used to create audio recording sections in articles or books"
  },
  [BitType2.recordVideo]: {
    since: "1.5.24",
    baseBitType: BitType2._standard,
    description: "Record video bit, used to create video recording sections in articles or books"
  },
  [BitType2.stickyNote]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Sticky note bit, used to create sticky notes in articles or books"
  },
  [BitType2.takePicture]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Take picture bit, used to create picture taking sections in articles or books"
  },
  [BitType2.handInAudio]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in audio bit, used to create audio submission sections in articles or books"
  },
  [BitType2.handInContact]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in contact bit, used to create contact submission sections in articles or books"
  },
  [BitType2.handInDocument]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in document bit, used to create document submission sections in articles or books"
  },
  [BitType2.handInFile]: {
    //
    since: "3.2.0",
    baseBitType: BitType2._standard,
    description: "Hand in file bit, used to create file submission sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_handInAcceptFileType,
        description: "Accepted file types for the hand-in file, used to define allowed file formats",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.handInFreeText]: {
    since: "5.3.0",
    baseBitType: BitType2.interview,
    description: "Hand in free text bit, used to create free text submission sections in articles or books"
  },
  [BitType2.handInFreeTextExpert]: {
    since: "5.3.0",
    baseBitType: BitType2.interview,
    description: "Hand in free text expert bit, used to create free text expert submission sections in articles or books"
  },
  [BitType2.handInLocation]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in location bit, used to create location submission sections in articles or books"
  },
  [BitType2.handInPhoto]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in photo bit, used to create photo submission sections in articles or books"
  },
  [BitType2.handInScan]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in scan bit, used to create scan submission sections in articles or books"
  },
  [BitType2.handInSystemMedia]: {
    //
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in system media bit, used to create system media submission sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_handInAcceptFileType,
        description: "Accepted file types for the hand-in system media, used to define allowed file formats",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      }
    ]
  },
  [BitType2.handInSubmit]: {
    //
    since: "3.2.0",
    baseBitType: BitType2._standard,
    description: "Hand in submit bit, used to create submission sections in articles or books",
    tags: [
      {
        key: ConfigKey.property_handInRequirement,
        description: "Requirement for the hand-in submission, used to define what is needed",
        format: TagFormat.plainText,
        maxCount: Count.infinity
      },
      {
        key: ConfigKey.property_handInInstruction,
        description: "Instruction for the hand-in submission, used to guide the user",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_buttonCaption,
        description: "Caption for the button in the hand-in submission, used to define the button text",
        format: TagFormat.plainText
      }
    ]
  },
  [BitType2.handInVideo]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in video bit, used to create video submission sections in articles or books"
  },
  [BitType2.handInVoice]: {
    since: "1.5.15",
    baseBitType: BitType2._standard,
    description: "Hand in voice bit, used to create voice submission sections in articles or books"
  },
  [BitType2.trueFalse1]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "True/False bit, used to create true/false questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for true/false questions"
      },
      {
        key: ConfigKey.property_labelTrue,
        description: "Label for the true option in the true/false question",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_labelFalse,
        description: "Label for the false option in the true/false question",
        format: TagFormat.plainText
      },
      {
        // PLAN-067 Edit #1: true-false-1's `+` / `-` emit a flat
        // `{ statement, isCorrect }` at bit root (single-statement bit),
        // not a `choices[]` array. Format is bitmarkText so the statement
        // body renders as a ProseMirror tree, matching reference fixtures.
        exportJsonKey: { statement: "$", isCorrect: true },
        key: ConfigKey.tag_true,
        description: "Tag for the true option in the true/false question",
        format: TagFormat.bitmarkText
      },
      {
        exportJsonKey: { statement: "$", isCorrect: false },
        key: ConfigKey.tag_false,
        description: "Tag for the false option in the true/false question",
        format: TagFormat.bitmarkText
      },
      {
        // Override inherited @example from group_standardTags: boolean root
        // example. Note: the legacy reference parser ties the bare-form
        // example value to `statement.isCorrect`, but the statement is not
        // yet emitted when this tag fires at bit-header. The pattern below
        // emits a literal `true` for bare/synthesized and `$` for valued —
        // sufficient for `[+...]` (correct) statements which dominate the
        // fixtures; `[-...]` (incorrect) bits with bare `[@example]` would
        // need either a runtime extension or an explicit `[@example:false]`.
        //
        // Format is `boolean` so `[@example:false]` coerces to JSON `false`
        // (a natural default that gets stripped in optimised mode, matching
        // BPG's `rootExampleType: boolean`). PlainText would emit the
        // STRING `"false"` which doesn't strip.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true, example: true } },
          { "@absent": { isExample: true, example: true } },
          // `[@example:false]`: emit `isExample: true` only — `example: false`
          // is the natural default for boolean, stripped in optimised mode.
          // Mirrors BPG `removeUnwantedProperties` `ignoreAllFalse: true`.
          { "@value=false": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example flag for the bit (boolean)",
        format: TagFormat.boolean,
        nullable: true
      }
    ],
    rootExampleType: ExampleType.boolean
  },
  [BitType2.trueFalse]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "True/False bit, used to create true/false questions in articles or books",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common quiz tags for true/false questions"
      },
      {
        key: ConfigKey.property_labelTrue,
        description: "Label for the true option in the true/false question",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_labelFalse,
        description: "Label for the false option in the true/false question",
        format: TagFormat.plainText
      },
      {
        // Override inherited @example: in `isTopLevelExample` allow-list.
        key: ConfigKey.property_example,
        exportJsonKey: [
          { "@keyonly": { isExample: true } },
          { "@absent": { isExample: true } },
          { isExample: true, example: "$" }
        ],
        description: "The example(s) for the bit",
        format: TagFormat.bitmarkText,
        nullable: true
      }
    ],
    cardSet: CardSetConfigKey.statements
  },
  [BitType2.vendorAmcharts5Chart]: {
    since: "1.5.8",
    baseBitType: BitType2.code,
    description: "AmCharts 5 chart bit, used to embed AmCharts 5 charts in articles or books",
    textFormatDefault: TextFormat2.json
  },
  [BitType2.vendorDatadogDashboard]: {
    since: "5.2.0",
    baseBitType: BitType2.nonProductionPrototypeIframe,
    description: "A Datadog dashboard bit, used to embed Datadog dashboards"
  },
  [BitType2.vendorDatadogDashboardEmbed]: {
    since: "3.12.0",
    baseBitType: BitType2.code,
    description: "Datadog dashboard embed bit, used to embed Datadog dashboards in articles or books",
    tags: [
      {
        key: ConfigKey.property_vendorDashboardId,
        description: "Dashboard ID for the Datadog dashboard, used to define which dashboard to embed",
        format: TagFormat.plainText
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorFormbricksEmbed]: {
    since: "3.8.0",
    baseBitType: BitType2.code,
    description: "Formbricks embed bit, used to embed Formbricks surveys in articles or books",
    tags: [
      {
        key: ConfigKey.property_vendorSurveyId,
        description: "Survey ID for the Formbricks survey, used to define which survey to embed",
        format: TagFormat.plainText
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorFormbricksEmbedAnonymous]: {
    since: "3.9.0",
    baseBitType: BitType2.vendorFormbricksEmbed,
    description: "Anonymous Formbricks embed bit, used to embed anonymous Formbricks surveys in articles or books"
  },
  [BitType2.vendorFormbricksLink]: {
    since: "3.8.0",
    baseBitType: BitType2.code,
    description: "Formbricks link bit, used to create links to Formbricks surveys in articles or books",
    tags: [
      {
        key: ConfigKey.property_vendorSurveyId,
        description: "Survey ID for the Formbricks survey, used to define which survey to link to",
        format: TagFormat.plainText
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorFormbricksLinkAnonymous]: {
    since: "3.9.0",
    baseBitType: BitType2.vendorFormbricksLink,
    description: "Anonymous Formbricks link bit, used to create links to anonymous Formbricks surveys in articles or books"
  },
  [BitType2.vendorHighchartsChart]: {
    since: "1.5.28",
    baseBitType: BitType2.vendorAmcharts5Chart,
    description: "Highcharts chart bit, used to embed Highcharts charts in articles or books"
  },
  [BitType2.vendorIframelyEmbed]: {
    since: "1.5.10",
    baseBitType: BitType2.code,
    description: "Iframely embed bit, used to embed content from various sources in articles or books",
    tags: [
      {
        key: ConfigKey.property_width,
        // Same as image
        description: "Width for the embedded content, used to define the width in pixels",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_height,
        // Same as image
        description: "Height for the embedded content, used to define the height in pixels",
        format: TagFormat.number,
        nullable: true
      },
      {
        key: ConfigKey.property_vendorUrl,
        description: "URL for the content to be embedded, used to define the source URL",
        format: TagFormat.plainText
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorIframelyCard]: {
    since: "1.5.10",
    baseBitType: BitType2.vendorIframelyEmbed,
    description: "Iframely card bit, used to create cards for embedded content in articles or books"
  },
  [BitType2.vendorIframelyPreview]: {
    since: "1.5.10",
    baseBitType: BitType2.vendorIframelyEmbed,
    description: "Iframely preview bit, used to create previews for embedded content in articles or books"
  },
  [BitType2.vendorIframelyPreviewMini]: {
    since: "1.5.10",
    baseBitType: BitType2.vendorIframelyEmbed,
    description: "Iframely preview mini bit, used to create mini previews for embedded content in articles or books"
  },
  [BitType2.vendorJupyterOutput]: {
    since: "1.4.3",
    baseBitType: BitType2.code,
    description: "Jupyter output bit, used to embed Jupyter notebook outputs in articles or books",
    tags: [
      {
        key: ConfigKey.property_jupyterId,
        description: "Jupyter ID for the notebook, used to define which notebook to embed",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_jupyterExecutionCount,
        description: "Execution count for the Jupyter output, used to define the execution order",
        format: TagFormat.number,
        nullable: true
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorJupyterCellCode]: {
    since: "1.4.3",
    baseBitType: BitType2.vendorJupyterOutput,
    description: "Jupyter cell code bit, used to embed Jupyter notebook cell code in articles or books"
  },
  [BitType2.vendorJupyterCellMarkdown]: {
    since: "1.4.3",
    baseBitType: BitType2.vendorJupyterOutput,
    description: "Jupyter cell markdown bit, used to embed Jupyter notebook cell markdown in articles or books"
  },
  [BitType2.vendorJupyterCellRaw]: {
    since: "1.4.3",
    baseBitType: BitType2.vendorJupyterOutput,
    description: "Jupyter cell raw bit, used to embed Jupyter notebook cell raw content in articles or books"
  },
  [BitType2.vendorJupyterIpynb]: {
    since: "1.4.3",
    baseBitType: BitType2.vendorJupyterOutput,
    description: "Jupyter IPYNB bit, used to embed Jupyter notebook files in articles or books"
  },
  [BitType2.vendorPadletEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2.code,
    description: "Padlet embed bit, used to embed Padlet boards in articles or books",
    tags: [
      {
        key: ConfigKey.property_padletId,
        description: "Padlet ID for the board, used to define which Padlet to embed",
        format: TagFormat.plainText
      }
    ],
    textFormatDefault: TextFormat2.plainText
  },
  [BitType2.vendorStripePricingTable]: {
    since: "1.20.0",
    baseBitType: BitType2.article,
    description: "Stripe pricing table bit, used to embed Stripe pricing tables in articles or books",
    tags: [
      {
        key: ConfigKey.property_stripePricingTableId,
        description: "Stripe pricing table ID, used to define which pricing table to embed",
        format: TagFormat.plainText,
        minCount: 1
      },
      {
        key: ConfigKey.property_stripePublishableKey,
        description: "Stripe publishable key, used to authenticate the Stripe pricing table",
        format: TagFormat.plainText,
        minCount: 1
      }
    ]
  },
  [BitType2.vendorStripePricingTableExternal]: {
    since: "3.13.0",
    baseBitType: BitType2.vendorStripePricingTable,
    description: "External Stripe pricing table bit, used to embed external Stripe pricing tables in articles or books"
  },
  [BitType2.vendorStripePricingTablePrintOnRequest]: {
    since: "5.21.0",
    baseBitType: BitType2.vendorStripePricingTable,
    description: "Stripe pricing table bit with print-on-request customer/product IDs, used to embed Stripe pricing tables tied to a specific print-on-request customer and product",
    tags: [
      {
        key: ConfigKey.property_stripePricingTableDescriptiveName,
        description: "Descriptive name for the Stripe pricing table",
        format: TagFormat.plainText
      },
      {
        key: ConfigKey.property_printOnRequestCustomerId,
        description: "Print-on-request customer ID, used to identify the customer for the print-on-request flow",
        format: TagFormat.plainText,
        minCount: 1
      },
      {
        key: ConfigKey.property_printOnRequestProductId,
        description: "Print-on-request product ID, used to identify the product for the print-on-request flow",
        format: TagFormat.plainText,
        minCount: 1
      }
    ]
  },
  [BitType2.video]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Video bit, used to embed videos in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for videos, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceVideo,
        description: "Resource video tags, used to attach videos to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.videoLandscape]: {
    since: "1.3.0",
    baseBitType: BitType2.video,
    description: "Landscape video bit, used to embed landscape videos in articles or books"
  },
  [BitType2.videoPortrait]: {
    since: "1.3.0",
    baseBitType: BitType2.video,
    description: "Portrait video bit, used to embed portrait videos in articles or books"
  },
  [BitType2.videoEmbed]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Video embed bit, used to embed videos from external sources in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for video embeds, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceVideoEmbed,
        description: "Resource video embed tags, used to attach video embeds to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.videoEmbedLandscape]: {
    since: "1.3.0",
    baseBitType: BitType2.videoEmbed,
    description: "Landscape video embed bit, used to embed landscape videos from external sources in articles or books"
  },
  [BitType2.videoEmbedPortrait]: {
    since: "1.3.0",
    baseBitType: BitType2.videoEmbed,
    description: "Portrait video embed bit, used to embed portrait videos from external sources in articles or books"
  },
  [BitType2.videoLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Video link bit, used to create links to videos in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for video links, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceVideoLink,
        description: "Resource video link tags, used to attach video links to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.videoLinkLandscape]: {
    since: "1.3.0",
    baseBitType: BitType2.videoLink,
    description: "Landscape video link bit, used to create links to landscape videos in articles or books"
  },
  [BitType2.videoLinkPortrait]: {
    since: "1.3.0",
    baseBitType: BitType2.videoLink,
    description: "Portrait video link bit, used to create links to portrait videos in articles or books"
  },
  [BitType2.websiteLink]: {
    since: "1.3.0",
    baseBitType: BitType2._standard,
    description: "Website link bit, used to create links to websites in articles or books",
    tags: [
      {
        key: ConfigKey.group_resourceBitTags,
        description: "Resource bit tags for website links, used to define additional properties"
      },
      {
        key: ConfigKey.group_resourceWebsiteLink,
        description: "Resource website link tags, used to attach website links to the bit",
        minCount: 1
      }
    ],
    resourceAttachmentAllowed: false
  },
  [BitType2.nonProductionPrototypeIframe]: {
    since: "4.20.0",
    baseBitType: BitType2._standard,
    description: "A non-production prototype iframe bit, used to embed prototype iframes",
    tags: [
      {
        key: ConfigKey.property_iframeSrc,
        description: "Iframe source URL, used to define the source of the iframe",
        minCount: 1
      },
      {
        key: ConfigKey.property_iframeName,
        description: "Iframe name, used to define the name of the iframe"
      }
    ]
  },
  [BitType2.vendorLearndashEmbed]: {
    since: "5.0.0",
    baseBitType: BitType2.nonProductionPrototypeIframe,
    description: "Learndash embed bit, used to embed Learndash content in an iframe"
  }
};

// src/config/Config.ts
var COLLAPSIBLE_BIT_TYPE_SUFFIX = "-collapsible";
var Config = class {
  constructor() {
    __publicField(this, "bitLevelMin", 1);
    __publicField(this, "bitLevelMax", 7);
    __publicField(this, "bitCache", /* @__PURE__ */ new Map());
    __publicField(this, "migratedBitCache", /* @__PURE__ */ new Map());
    __publicField(this, "allResourcesCache");
    __publicField(this, "comboResourcesCache", /* @__PURE__ */ new Map());
  }
  /**
   * Return the bitType given a bit type that may be invalid
   *
   * If the bit type is not found, the _error bit type will be returned.
   *
   * @param bitType bit type in (may be invalid)
   * @returns valid bitType, or _error if the bit type is invalid
   */
  getBitType(bitType) {
    if (bitType?.startsWith("|")) bitType = bitType.substring(1);
    return Enum3(BitType2).fromValue(bitType) ?? BitType2._error;
  }
  /**
   * PLAN-017: Return the migration target for a deprecated `*-collapsible` bit type.
   *
   * Every `*-collapsible` bit type declares its non-collapsible cousin as its `baseBitType`, so the
   * target is read from the config rather than a hand-maintained table — a new `*-collapsible` bit
   * type is covered automatically.
   *
   * `BitType.collapsible` is the one exclusion: it has no non-collapsible cousin (its `baseBitType`
   * is `article`, which is not a migration target).
   *
   * @param bitType the bit type
   * @returns the bit type to migrate to, or undefined if the bit type does not migrate
   */
  getMigratedBitType(bitType) {
    const cached = this.migratedBitCache.get(bitType);
    if (cached !== void 0 || this.migratedBitCache.has(bitType)) return cached;
    let target;
    if (bitType !== BitType2.collapsible && bitType.endsWith(COLLAPSIBLE_BIT_TYPE_SUFFIX)) {
      target = BITS[bitType]?.baseBitType;
    }
    this.migratedBitCache.set(bitType, target);
    return target;
  }
  /**
   * Check if a bit type is and instance of the given root bit type.
   *
   * @param bitType the bit type
   * @param baseBitType the root bit type or types to check
   */
  isOfBitType(bitType, baseBitType) {
    if (!bitType) return false;
    if (bitType === baseBitType) return true;
    const bitConfig = this.getBitConfig(bitType);
    if (!bitConfig) return false;
    if (Array.isArray(baseBitType)) {
      for (const bt of baseBitType) {
        if (bitConfig.inheritedBitTypesSet.has(bt)) return true;
      }
      return false;
    }
    return bitConfig.inheritedBitTypesSet.has(baseBitType);
  }
  /**
   * Return true if the bit type is commented.
   *
   * @param bitType bit type in (may be invalid)
   * @returns true if the bit type is commented
   */
  isBitTypeCommented(bitType) {
    return !!bitType?.startsWith("|");
  }
  /**
   * Get the configuration for a bit.
   *
   * @param bitType
   * @returns the bit configuration
   */
  getBitConfig(bitType) {
    let bitConfig = this.bitCache.get(bitType);
    if (!bitConfig) {
      const throwNotFoundError = () => {
        throw new Error(`No config found for bit: ${bitType}`);
      };
      const inheritedBitTypes = [];
      const _bitConfigTree = [];
      let bitTypeCurrent = bitType;
      while (bitTypeCurrent) {
        const _bitConfig = BITS[bitTypeCurrent];
        if (!_bitConfig) throwNotFoundError();
        inheritedBitTypes.push(bitTypeCurrent);
        _bitConfigTree.push(_bitConfig);
        bitTypeCurrent = _bitConfig.baseBitType;
      }
      _bitConfigTree.reverse();
      const _mergedBitConfig = _bitConfigTree.reduce((acc, cur) => {
        acc = objectUtils.deepMerge(acc, cur);
        return acc;
      }, {});
      const {
        since,
        textFormatDefault,
        quizBit,
        tags: _tags,
        cardSet: _cardSet,
        deprecated,
        bodyAllowed,
        bodyRequired,
        footerAllowed,
        footerRequired,
        resourceAttachmentAllowed,
        rootExampleType
      } = _mergedBitConfig;
      _tags?.push({
        key: ConfigKey.property_internalComment,
        description: "Internal comment for the bit.",
        format: TagFormat.plainText,
        maxCount: Count.infinity,
        minCount: 0
      });
      const zeroCountAllResourcesTags = this.getAllResourcesTagsWithZeroCounts();
      const bitTags = instance.hydrateTagsConfig(_tags ?? []);
      const cardSet = instance.hydrateCardSetConfig(_cardSet);
      const comboResourceConfigKey = bitTags.info?.comboResourceConfigKey;
      const tags2 = {
        ...bitTags.tags
      };
      for (const [k, v] of Object.entries(zeroCountAllResourcesTags)) {
        if (!tags2[k]) tags2[k] = v;
      }
      bitConfig = new BitConfig({
        since,
        bitType,
        inheritedBitTypes,
        textFormatDefault: textFormatDefault ?? TextFormat2.bitmarkText,
        tags: tags2,
        cardSet,
        quizBit,
        deprecated,
        bodyAllowed,
        bodyRequired,
        footerAllowed,
        footerRequired,
        resourceAttachmentAllowed,
        rootExampleType,
        comboResourceConfigKey
      });
      this.bitCache.set(bitType, bitConfig);
    }
    return bitConfig;
  }
  /**
   * Look up the tag configuration by tag (rather than by config key).
   *
   * @param bitType
   * @param key the configKey to look up - if undefined, will return undefined
   * @returns
   */
  getTagConfigForTag(tagsConfig, key) {
    if (!tagsConfig) return void 0;
    return tagsConfig[key];
  }
  /**
   * Look up the tag configuration for a cardSet variant.
   *
   * @param bitType
   * @param sideNo
   * @param variantNo
   * @param tag
   * @param parentTagConfig
   * @returns
   */
  getTagsConfigForCardSet(bitType, sideNo, variantNo) {
    const variantConfig = this.getCardSetVariantConfig(bitType, sideNo, variantNo);
    if (!variantConfig) return void 0;
    return variantConfig.tags;
  }
  /**
   * Get the configuration for a particular card side and variant
   * (checking for infinitely repeating variants)
   *
   * @param config all variant configurations
   * @param sideNo side index
   * @param variantNo variant index
   *
   * @returns the config if found, otherwise undefined
   */
  getCardSetVariantConfig(bitType, sideNo, variantNo) {
    let ret;
    const bitConfig = this.getBitConfig(bitType);
    if (!bitConfig) return void 0;
    const variants = bitConfig.cardSet?.variants;
    if (!variants) return void 0;
    const sideIdx = Math.min(sideNo, variants.length - 1);
    const variant = variants[sideIdx];
    const maxVariantIndex = variant.length - 1;
    if (variantNo > maxVariantIndex) {
      ret = variant[maxVariantIndex];
    } else {
      ret = variant[variantNo];
    }
    return ret;
  }
  /**
   * Get the resource configuration for a bit.
   *
   * The configuration returned potentially depends on the resourceTypeAttachment which can change the allowed
   * count of the resource (or comboResource) it matches.
   *
   * The function is a bit complicated, but it is actually just manipulating the configuration into a format that
   * the rest of the app can use easily.
   *
   * @param bitType
   * @param resourceTypeAttachment the resource type specified in the bit header
   * @returns the definitive resource configuration for the bit
   */
  // TODO - this will need fixing.
  getBitResourcesConfig(bitType, resourceTypeAttachment) {
    let finalResourceTags = {};
    const comboResourceConfigKeysMap = /* @__PURE__ */ new Map();
    const configKeyResourceAttachment = resourceTypeAttachment ? resourceTypeToConfigKey(resourceTypeAttachment) : void 0;
    const bitConfig = this.getBitConfig(bitType);
    const resourceTags = {};
    for (const [k, v] of Object.entries(bitConfig.tags)) {
      if (v.type === BitTagConfigKeyType.resource) {
        resourceTags[k] = v;
      }
    }
    const comboResourceConfigKey = bitConfig.comboResourceConfigKey ?? (bitConfig.resourceAttachmentAllowed ? configKeyResourceAttachment : void 0);
    if (comboResourceConfigKey) {
      const comboResourcesMap = /* @__PURE__ */ new Map();
      const comboResource = this.getComboResource(comboResourceConfigKey);
      if (comboResource) {
        const comboResourceTagConfigs = Object.values(comboResource).filter((t) => {
          return t.type === BitTagConfigKeyType.resource;
        });
        const comboResourceTypes = comboResourceTagConfigs.map((t) => t.configKey);
        comboResourceTypes.forEach((type) => {
          const tags2 = comboResourceTagConfigs.find((t) => t.configKey === type);
          if (tags2) comboResourcesMap.set(type, tags2);
        });
        const comboResourceConfigKeys = this.getComboResourceConfigKeys(comboResourceConfigKey);
        if (comboResourceConfigKeys && comboResourceConfigKeys.length > 0) {
          comboResourceConfigKeysMap.set(comboResourceConfigKey, comboResourceConfigKeys);
        }
      }
      if (resourceTypeAttachment) {
        for (const [k, tag] of Object.entries(resourceTags)) {
          const singleTagMatch = comboResourceConfigKey === tag.configKey;
          const comboTagMatch = comboResourcesMap ? comboResourcesMap.has(tag.configKey) : false;
          if (singleTagMatch) {
            const newTag = new ResourceTagConfig({
              configKey: tag.configKey,
              tag: tag.tag,
              minCount: 1,
              maxCount: 1,
              chain: tag.chain,
              jsonKey: tag.jsonKey,
              exportJsonKey: tag.exportJsonKey,
              hasExportJsonKey: tag.hasExportJsonKey,
              htmlKey: tag.htmlKey,
              hasHtmlKey: tag.hasHtmlKey,
              deprecated: tag.deprecated
            });
            finalResourceTags[k] = newTag;
          } else if (comboTagMatch) {
            if (comboResourcesMap) {
              const newTag = comboResourcesMap.get(tag.configKey);
              if (newTag) finalResourceTags[k] = newTag;
            }
          } else {
            finalResourceTags[k] = tag;
          }
        }
      } else {
        finalResourceTags = resourceTags;
      }
    } else {
      finalResourceTags = resourceTags;
    }
    const resourcesConfig = new ResourcesConfig(
      finalResourceTags,
      bitConfig.resourceAttachmentAllowed,
      configKeyResourceAttachment,
      comboResourceConfigKeysMap
    );
    return resourcesConfig;
  }
  /**
   * Get the resourceType tags for a combo resource.
   *
   * @param configKeyResourceAttachment
   * @returns resourceTypes for the combo resource (or undefined if not a combo resource)
   */
  getComboResourceConfigKeys(configKeyResourceAttachment) {
    const comboResource = this.getComboResource(configKeyResourceAttachment);
    if (comboResource) {
      const comboResourceTypes = Object.values(comboResource).filter((t) => {
        return t.type === BitTagConfigKeyType.resource;
      }).map((t) => t.configKey);
      return comboResourceTypes;
    }
    return void 0;
  }
  /**
   * Get the tags for a combo resource.
   *
   * Combo resources are resources that contain more than one resource, such as still-image-film.
   * These resources require special handling in the code and therefore also special configuration.
   *
   * @returns TagsConfig for the combo resource or undefined if not found
   */
  getComboResource(resourceConfigKey) {
    if (!resourceConfigKey) return void 0;
    let comboResourcesTags = this.comboResourcesCache.get(resourceConfigKey);
    if (!comboResourcesTags) {
      comboResourcesTags = {};
      Object.values(GROUPS).filter(
        (g) => g.type === GroupConfigType.comboResource && g.comboResourceConfigKey === resourceConfigKey
      ).forEach((g) => {
        comboResourcesTags = {
          ...comboResourcesTags,
          ...instance.hydrateTagsConfig(g.tags).tags
        };
      });
      if (Object.keys(comboResourcesTags).length === 0) comboResourcesTags = void 0;
      this.comboResourcesCache.set(resourceConfigKey, comboResourcesTags);
    }
    return comboResourcesTags;
  }
  /**
   * Get the tags for all resources with 0 max/min counts.
   *
   * This is required so that any bit can parse all resources correctly. It is necessary to parse all resources
   * for any bit because of the requirement to add excess resources to the 'parser' output.
   *
   * NOTE: These are not used for validation, only for parsing. They will be overridden for individual bits for
   * validation.
   *
   * @returns TagsConfig for all resources
   */
  getAllResourcesTagsWithZeroCounts() {
    let allResourcesTags = this.allResourcesCache;
    if (!allResourcesTags) {
      allResourcesTags = {};
      Object.values(GROUPS).filter((g) => g.type === GroupConfigType.resource).forEach((g) => {
        allResourcesTags = {
          ...allResourcesTags,
          ...instance.hydrateTagsConfig(g.tags).tags
        };
      });
      for (const tag of Object.values(allResourcesTags)) {
        const tagAny = tag;
        tagAny.maxCount = 0;
        tagAny.minCount = 0;
      }
      this.allResourcesCache = allResourcesTags;
    }
    return allResourcesTags;
  }
};
var instance2 = new Config();

// src/generated/package_info.ts
var PACKAGE_INFO = {
  "name": "@gmb/bitmark-parser-generator",
  "version": "5.37.0",
  "author": "Get More Brain Ltd",
  "license": "ISC",
  "description": "A bitmark parser and generator using Peggy.js"
};

// src/utils/env/Environment.ts
import "@ncoderz/superenum";
var Environment = {
  unknown: "",
  node: "node",
  chrome: "chrome",
  safari: "safari",
  firefox: "firefox",
  edge: "edge",
  ie: "ie"
};

// src/utils/env/Os.ts
import "@ncoderz/superenum";
var Os = {
  unknown: "",
  macos: "macos",
  windows: "windows",
  linux: "linux",
  android: "android",
  ios: "ios"
};

// src/utils/env/userAgent.ts
function parseUserAgent() {
  if (typeof window === "undefined" || !window.navigator || !window.navigator.userAgent)
    return void 0;
  const uai = {
    browser: Environment.unknown,
    os: Os.unknown
  };
  const userAgent = window.navigator.userAgent;
  const tua = tokenizeUserAgent(userAgent);
  const containsFirefox = tua.keys.has("firefox");
  const containsChrome = tua.keys.has("chrome");
  const containsChromium = tua.keys.has("chromium");
  const containsSafari = tua.keys.has("safari");
  const containsMacintosh = tua.keys.has("macintosh");
  const isSafari = containsSafari && !containsChrome && !containsChromium;
  const isChrome = containsChrome;
  const isFirefox = containsFirefox;
  const isMacOS = containsMacintosh;
  if (isSafari) {
    uai.browser = Environment.safari;
    uai.browserVersion = tua.versions.version;
  } else if (isChrome) {
    uai.browser = Environment.chrome;
    uai.browserVersion = tua.versions.chrome;
  } else if (isFirefox) {
    uai.browser = Environment.firefox;
    uai.browserVersion = tua.versions.firefox;
  }
  if (isMacOS) {
    uai.os = Os.macos;
    uai.osVersion = tua.versions.x;
  }
  return uai;
}
function tokenizeUserAgent(userAgent) {
  const rawKeys = userAgent.toLowerCase().split(new RegExp("[ /,;()]", "g")).reduce((acc, val) => {
    if (val) acc.push(val);
    return acc;
  }, []);
  const tua = {
    keys: /* @__PURE__ */ new Set(),
    versions: {}
  };
  let prevRawKey = "";
  for (let i = 0, len = rawKeys.length; i < len; i++) {
    const rawKey = rawKeys[i];
    let isNumber = false;
    for (let i2 = 0; i2 <= 9; i2++) {
      if (rawKey.startsWith(`${i2}`)) {
        isNumber = true;
        break;
      }
    }
    if (isNumber) {
      tua.versions[prevRawKey] = rawKey.replace("_", ".");
    } else {
      tua.keys.add(rawKey);
    }
    prevRawKey = rawKey;
  }
  return tua;
}

// src/utils/env/Version.ts
var EMPTY_VERSION = {
  full: "",
  major: "",
  minor: "",
  patch: "",
  build: ""
};

// src/utils/env/Env.ts
var Env = class {
  constructor() {
    __publicField(this, "bootTimestamp");
    __publicField(this, "app");
    __publicField(this, "appVersion");
    __publicField(this, "environment");
    __publicField(this, "environmentVersion");
    __publicField(this, "os");
    __publicField(this, "osVersion");
    __publicField(this, "isBrowser");
    __publicField(this, "isNode");
    __publicField(this, "isCI");
    __publicField(this, "NODE_ENV");
    // public username: string;
    // public usergroup: string;
    // public uid: number;
    // public gid: number;
    // public homedir: string;
    // public shell: string;
    __publicField(this, "userAgentInfo");
    Object.defineProperty(this, "userAgentInfo", {
      value: parseUserAgent(),
      enumerable: false
    });
    this.bootTimestamp = Date.now();
    this.app = "";
    this.appVersion = EMPTY_VERSION;
    this.environment = this.getEnvironment();
    this.environmentVersion = this.getEnvironmentVersion();
    this.isBrowser = this.getIsBrowser();
    this.isNode = this.environment === Environment.node;
    this.isCI = this.getIsCI();
    this.NODE_ENV = this.getNodeEnv();
    this.os = this.getOs();
    this.osVersion = this.getOsVersion();
  }
  init(app, version) {
    this.app = app;
    this.appVersion = this.parseMMBVersion(version);
  }
  get upTimestamp() {
    return Date.now() - this.bootTimestamp;
  }
  getEnvironment() {
    if (this.userAgentInfo) {
      return this.userAgentInfo.browser;
    } else if (typeof process !== "undefined") {
      if (process.versions !== void 0) {
        if (process.versions.node) return Environment.node;
      }
    }
    return Environment.unknown;
  }
  getEnvironmentVersion() {
    if (this.userAgentInfo) {
      return this.parseMMBVersion(this.userAgentInfo.browserVersion);
    } else if (typeof process !== "undefined") {
      return this.parseMMBVersion(process.version);
    }
    return EMPTY_VERSION;
  }
  getOs() {
    if (this.userAgentInfo) {
      return this.userAgentInfo.os;
    } else if (typeof process !== "undefined") {
      if (process.versions !== void 0) {
        if (process.versions.node) return Os.unknown;
      }
    }
    return Os.unknown;
  }
  getOsVersion() {
    if (this.userAgentInfo) {
      return this.parseMMBVersion(this.userAgentInfo.osVersion);
    } else if (typeof process !== "undefined") {
      return this.parseMMBVersion("");
    }
    return EMPTY_VERSION;
  }
  getIsBrowser() {
    return !!(typeof window !== "undefined" && typeof document !== "undefined" && typeof navigator !== "undefined" && navigator.userAgent);
  }
  getIsCI() {
    return !!(typeof process !== "undefined" && process.env && process.env.CI);
  }
  getNodeEnv() {
    return typeof process !== "undefined" && process.env && process.env.NODE_ENV || "";
  }
  /**
   * TODO:
   * Parse Senver 2.0 ?? https://semver.org/
   * and X.Y.Z.B using regex
   *
   * @param version
   */
  parseMMBVersion(version) {
    if (!version) return EMPTY_VERSION;
    if (version.startsWith("v")) {
      version = version.substring(1);
    }
    const s = version.split(".", 1e3);
    let major = "";
    let minor = "";
    let build = "";
    let patch = "";
    for (let i = 0, len = s.length; i < len; i++) {
      const v = s[i];
      if (i === 0) major = v;
      else if (i === 1) minor = v;
      else if (i === 2) patch = v;
      else if (i === 3) build = v;
    }
    return {
      full: version,
      major,
      minor,
      patch,
      build
    };
  }
};
var env = new Env();
var initEnv = env.init.bind(env);

// src/init/init.ts
var initialised = false;
function init() {
  if (initialised) return;
  initEnv(PACKAGE_INFO.name, PACKAGE_INFO.version);
  initialised = true;
}

// src/ast/Ast.ts
import { Enum as Enum4 } from "@ncoderz/superenum";

// src/model/enum/BodyBitType.ts
import "@ncoderz/superenum";
var BodyBitTypeRaw = {
  text: "text",
  gap: "gap",
  mark: "mark",
  select: "select",
  highlight: "highlight"
};
var BodyBitType = BodyBitTypeRaw;

// src/model/enum/TextMarkType.ts
import "@ncoderz/superenum";
var TextMarkType = {
  // Styles
  bold: "bold",
  light: "light",
  italic: "italic",
  highlight: "highlight",
  highlightOrange: "highlightOrange",
  highlightYellow: "highlightYellow",
  highlightGreen: "highlightGreen",
  highlightBlue: "highlightBlue",
  highlightPurple: "highlightPurple",
  highlightPink: "highlightPink",
  highlightBrown: "highlightBrown",
  highlightBlack: "highlightBlack",
  highlightWhite: "highlightWhite",
  highlightGray: "highlightGray",
  // Inline only styles
  strike: "strike",
  subscript: "subscript",
  superscript: "superscript",
  ins: "ins",
  del: "del",
  underline: "underline",
  doubleUnderline: "doubleUnderline",
  smallcaps: "smallcaps",
  circle: "circle",
  languageEmRed: "languageEmRed",
  languageEmOrange: "languageEmOrange",
  languageEmYellow: "languageEmYellow",
  languageEmGreen: "languageEmGreen",
  languageEmBlue: "languageEmBlue",
  languageEmPurple: "languageEmPurple",
  languageEmPink: "languageEmPink",
  languageEmBrown: "languageEmBrown",
  languageEmBlack: "languageEmBlack",
  languageEmWhite: "languageEmWhite",
  languageEmGray: "languageEmGray",
  languageEm: "languageEm",
  userUnderline: "userUnderline",
  userDoubleUnderline: "userDoubleUnderline",
  userStrike: "userStrike",
  userCircle: "userCircle",
  userHighlight: "userHighlight",
  notranslate: "notranslate",
  // Attribute chain
  link: "link",
  ref: "ref",
  xref: "xref",
  extref: "extref",
  footnote: "footnote",
  footnoteStar: "footnote*",
  symbol: "symbol",
  var: "var",
  code: "code",
  timer: "timer",
  duration: "duration",
  // legacy: no longer produced by the parser (>= 8.41.1), accepted as generator input
  color: "color",
  // legacy: no longer produced by the parser (>= 8.41.1, now textStyle), accepted as generator input
  textStyle: "textStyle",
  colorPicker: "colorPicker",
  comment: "comment"
};

// src/model/enum/TextNodeType.ts
import "@ncoderz/superenum";
var TextNodeType = {
  // Text
  text: "text",
  // Blocks
  hardBreak: "hardBreak",
  paragraph: "paragraph",
  heading: "heading",
  section: "section",
  noBulletList: "noBulletList",
  bulletList: "bulletList",
  orderedList: "orderedList",
  orderedListRoman: "orderedListRoman",
  orderedListRomanLower: "orderedListRomanLower",
  letteredList: "letteredList",
  letteredListLower: "letteredListLower",
  taskList: "taskList",
  listItem: "listItem",
  taskItem: "taskItem",
  image: "image",
  imageInline: "imageInline",
  codeBlock: "codeBlock",
  // Inline blocks
  latex: "latex",
  // Body bits
  gap: "gap",
  select: "select",
  highlight: "highlight",
  mark: "mark"
};

// src/model/ast/NodeType.ts
import "@ncoderz/superenum";
var NodeType = {
  unknown: "unknown",
  // unknown
  __isDefaultExample: "__isDefaultExample",
  __typeAlias: "__typeAlias",
  action: "action",
  actionValue: "actionValue",
  activityType: "activityType",
  activityTypeValue: "activityTypeValue",
  additionalSolutions: "additionalSolutions",
  additionalSolutionsValue: "additionalSolutionsValue",
  advertisingClickUrl: "advertisingClickUrl",
  advertisingClickUrlValue: "advertisingClickUrlValue",
  ageRange: "ageRange",
  ageRangeValue: "ageRangeValue",
  aiGenerated: "aiGenerated",
  aiGeneratedValue: "aiGeneratedValue",
  alias: "alias",
  // bit type (alias)
  alignment: "alignment",
  allowedBit: "allowedBit",
  allowedBitValue: "allowedBitValue",
  allowPrint: "allowPrint",
  allowPrintValue: "allowPrintValue",
  alt: "alt",
  alternativeAnswers: "alternativeAnswers",
  alternativeAnswersValue: "alternativeAnswersValue",
  alternativeDefinitions: "alternativeDefinitions",
  alternativeDefinitionsValue: "alternativeDefinitionsValue",
  altLangTag: "altLangTag",
  altLangTagValue: "altLangTagValue",
  analyticsTag: "analyticsTag",
  analyticsTagValue: "analyticsTagValue",
  anchor: "anchor",
  answer: "answer",
  appLink: "appLink",
  attrs: "attrs",
  audio: "audio",
  audioEmbed: "audioEmbed",
  audioLink: "audioLink",
  author: "author",
  authorFullName: "authorFullName",
  authorFullNameValue: "authorFullNameValue",
  authorJobTitle: "authorJobTitle",
  authorJobTitleValue: "authorJobTitleValue",
  authorPseudonym: "authorPseudonym",
  authorPseudonymValue: "authorPseudonymValue",
  authorTitle: "authorTitle",
  authorTitleValue: "authorTitleValue",
  authorValue: "authorValue",
  availableClassifications: "availableClassifications",
  availableClassificationsValue: "availableClassificationsValue",
  avatarImage: "avatarImage",
  backgroundWallpaper: "backgroundWallpaper",
  backgroundWallpaperValue: "backgroundWallpaperValue",
  backgroundImage: "backgroundImage",
  backgroundImageValue: "backgroundImageValue",
  bitLevel: "bitLevel",
  // bit level
  bitmarkAst: "bitmarkAst",
  // bitmarkAst
  bitmarkVersion: "bitmarkVersion",
  bits: "bits",
  // bits
  bitsValue: "bitsValue",
  // bit
  bitType: "bitType",
  // bit type
  bitFingerprint: "bitFingerprint",
  bitFingerprintValue: "bitFingerprintValue",
  blockId: "blockId",
  blockIdValue: "blockIdValue",
  body: "body",
  bodyBit: "bodyBit",
  bodyBits: "bodyBits",
  bodyJson: "bodyJson",
  bodyParts: "bodyParts",
  bodyPartsValue: "bodyPartsValue",
  bodyPartText: "bodyPartText",
  bodyText: "bodyText",
  bodyValue: "bodyValue",
  book: "book",
  bookAlias: "bookAlias",
  bookAliasValue: "bookAliasValue",
  bookAuthor: "bookAuthor",
  bookBindingType: "bookBindingType",
  bookDiff: "bookDiff",
  bookDiffValue: "bookDiffValue",
  bookIsbn: "bookIsbn",
  bookLanguage: "bookLanguage",
  bookNumberOfPages: "bookNumberOfPages",
  bookOriginalTitle: "bookOriginalTitle",
  bookPublisher: "bookPublisher",
  bookPublishingDate: "bookPublishingDate",
  bookRating: "bookRating",
  bookSeriesId: "bookSeriesId",
  bookSeriesNumber: "bookSeriesNumber",
  bookSeriesTitle: "bookSeriesTitle",
  bookTranslator: "bookTranslator",
  bookType: "bookType",
  bookValue: "bookValue",
  bot: "bot",
  botResponses: "botResponses",
  botResponsesValue: "botResponsesValue",
  botValue: "botValue",
  brandColor: "brandColor",
  brandColorName: "brandColorName",
  brandColorNameValue: "brandColorNameValue",
  brandColorValue: "brandColorValue",
  bubbleTag: "bubbleTag",
  bubbleTagValue: "bubbleTagValue",
  buttonCaption: "buttonCaption",
  buttonCaptionValue: "buttonCaptionValue",
  callToActionUrl: "callToActionUrl",
  callToActionUrlValue: "callToActionUrlValue",
  caption: "caption",
  captionDefinitionList: "captionDefinitionList",
  captionValue: "captionValue",
  cardBits: "cardBits",
  cardBitsValue: "cardBitsValue",
  cardNode: "cardNode",
  categoryTag: "categoryTag",
  categoryTagValue: "categoryTagValue",
  cells: "cells",
  cellsValue: "cellsValue",
  chatWithBook: "chatWithBook",
  chatWithBookBrainKey: "chatWithBookBrainKey",
  chatWithBookBrainKeyValue: "chatWithBookBrainKeyValue",
  chatWithBookValue: "chatWithBookValue",
  checked: "checked",
  choice: "choice",
  choices: "choices",
  choicesValue: "choicesValue",
  citationStyle: "citationStyle",
  citationStyleValue: "citationStyleValue",
  class: "class",
  classification: "classification",
  classificationValue: "classificationValue",
  codeLineNumbers: "codeLineNumbers",
  codeLineNumbersValue: "codeLineNumbersValue",
  codeMinimap: "codeMinimap",
  codeMinimapValue: "codeMinimapValue",
  color: "color",
  colorTag: "colorTag",
  colorTagValue: "colorTagValue",
  column: "column",
  columns: "columns",
  columnsValue: "columnsValue",
  comment: "comment",
  commentedBitType: "commentedBitType",
  computerLanguage: "computerLanguage",
  computerLanguageValue: "computerLanguageValue",
  content: "contentValue",
  content2Buy: "content2Buy",
  contentValue: "contentValue",
  contentValueValue: "contentValueValue",
  copyright: "copyright",
  coverColor: "coverColor",
  coverColorValue: "coverColorValue",
  coverImage: "coverImage",
  coverImageValue: "coverImageValue",
  currency: "currency",
  customerExternalId: "customerExternalId",
  customerId: "customerId",
  customerIdValue: "customerIdValue",
  customerProductId: "customerProductId",
  customerProductShopLink: "customerProductShopLink",
  data: "data",
  dataValue: "dataValue",
  dataValueValue: "dataValueValue",
  date: "date",
  dateEnd: "dateEnd",
  dateEndValue: "dateEndValue",
  dateValue: "dateValue",
  decimalPlaces: "decimalPlaces",
  deeplink: "deeplink",
  deeplinkValue: "deeplinkValue",
  definition: "definition",
  definitions: "definitions",
  definitionsValue: "definitionsValue",
  displayName: "displayName",
  displayNameValue: "displayNameValue",
  diffContext: "diffContext",
  diffContextValue: "diffContextValue",
  diffOp: "diffOp",
  diffOpValue: "diffOpValue",
  diffRef: "diffRef",
  diffRefValue: "diffRefValue",
  diffTime: "diffTime",
  diffTimeValue: "diffTimeValue",
  diffTo: "diffTo",
  diffToValue: "diffToValue",
  disableCalculation: "disableCalculation",
  disableFeedback: "disableFeedback",
  disableFeedbackValue: "disableFeedbackValue",
  document: "document",
  documentDownload: "documentDownload",
  documentEmbed: "documentEmbed",
  documentLink: "documentLink",
  duration: "duration",
  durationValue: "durationValue",
  elements: "elements",
  elementsValue: "elementsValue",
  elementsValueValue: "elementsValueValue",
  emphasis: "emphasis",
  end: "end",
  enforceUpdateOverUserInput: "enforceUpdateOverUserInput",
  enforceUpdateOverUserInputValue: "enforceUpdateOverUserInputValue",
  errors: "errors",
  errorsValue: "errorsValue",
  example: "example",
  exampleValue: "exampleValue",
  externalId: "externalId",
  externalIdValue: "externalIdValue",
  externalLink: "externalLink",
  externalLinkText: "externalLinkText",
  extractorExtractionTimestamp: "extractorExtractionTimestamp",
  extractorExtractionTimestampValue: "extractorExtractionTimestampValue",
  extractorTag: "extractorTag",
  extractorTagValue: "extractorTagValue",
  extractorInternal: "extractorInternal",
  extractorInternalValue: "extractorInternalValue",
  extraProperties: "extraProperties",
  feedback: "feedback",
  feedbackEngine: "feedbackEngine",
  feedbackEngineValue: "feedbackEngineValue",
  feedbacks: "feedbacks",
  feedbacksValue: "feedbacksValue",
  feedbackType: "feedbackType",
  flashcards: "flashcards",
  flashcardSet: "flashcardSet",
  flashcardSetValue: "flashcardSetValue",
  flashcardsValue: "flashcardsValue",
  focusX: "focusX",
  focusXValue: "focusXValue",
  focusY: "focusY",
  focusYValue: "focusYValue",
  footer: "footer",
  footerText: "footerText",
  footerValue: "footerValue",
  forKeys: "forKeys",
  format: "format",
  formula: "formula",
  formulaValue: "formulaValue",
  forValues: "forValues",
  forValuesValue: "forValuesValue",
  fullName: "fullName",
  fullNameValue: "fullNameValue",
  gap: "gap",
  gmbExternalShop: "gmbExternalShop",
  gmbExternalShopItems: "gmbExternalShopItems",
  groupTag: "groupTag",
  groupTagValue: "groupTagValue",
  gUri: "gUri",
  gUriValue: "gUriValue",
  handInAcceptFileType: "handInAcceptFileType",
  handInAcceptFileTypeValue: "handInAcceptFileTypeValue",
  handInInstruction: "handInInstruction",
  handInInstructionValue: "handInInstructionValue",
  handInRequirement: "handInRequirement",
  handInRequirementValue: "handInRequirementValue",
  hasBookNavigation: "hasBookNavigation",
  hasBookNavigationValue: "hasBookNavigationValue",
  hasMarkAsDone: "hasMarkAsDone",
  hasMarkAsDoneValue: "hasMarkAsDoneValue",
  hasPrintRestriction: "hasPrintRestriction",
  hasPrintRestrictionValue: "hasPrintRestrictionValue",
  head: "head",
  heading: "heading",
  height: "height",
  heightValue: "heightValue",
  highlight: "highlight",
  hint: "hint",
  hintValue: "hintValue",
  href: "href",
  icon: "icon",
  iconTag: "iconTag",
  iconTagValue: "iconTagValue",
  iconValue: "iconValue",
  id: "id",
  idValue: "idValue",
  iframeName: "iframeName",
  iframeSrc: "iframeSrc",
  image: "image",
  imageFirst: "imageFirst",
  imageFirstValue: "imageFirstValue",
  imageLandscape: "imageLandscape",
  imageLink: "imageLink",
  imagePlaceholder: "imagePlaceholder",
  imagePlaceholderValue: "imagePlaceholderValue",
  imagePortrait: "imagePortrait",
  imageSource: "imageSource",
  index: "index",
  indexValue: "indexValue",
  ingredients: "ingredients",
  ingredientsValue: "ingredientsValue",
  ingredient: "ingredient",
  ingredientValue: "ingredientValue",
  instruction: "instruction",
  instructionValue: "instructionValue",
  internalComment: "internalComment",
  internalCommentValue: "internalCommentValue",
  internalPrintPdf: "internalPrintPdf",
  internalPrintPdfValue: "internalPrintPdfValue",
  isCaseSensitive: "isCaseSensitive",
  isCollapsible: "isCollapsible",
  isCollapsibleValue: "isCollapsibleValue",
  isCommented: "isCommented",
  isCorrect: "isCorrect",
  isEditable: "isEditable",
  isEditableValue: "isEditableValue",
  isExample: "isExample",
  isInfoOnly: "isInfoOnly",
  isInfoOnlyValue: "isInfoOnlyValue",
  isPlain: "isPlain",
  isPublic: "isPublic",
  isPublicValue: "isPublicValue",
  isTemplate: "isTemplate",
  isTemplateStripTheme: "isTemplateStripTheme",
  isTemplateStripThemeValue: "isTemplateStripThemeValue",
  isTemplateValue: "isTemplateValue",
  isTracked: "isTracked",
  isTrackedValue: "isTrackedValue",
  item: "item",
  itemLead: "itemLead",
  itemValue: "itemValue",
  jobTitle: "jobTitle",
  jobTitleValue: "jobTitleValue",
  jupyterExecutionCount: "jupyterExecutionCount",
  jupyterExecutionCountValue: "jupyterExecutionCountValue",
  jupyterId: "jupyterId",
  jupyterIdValue: "jupyterIdValue",
  key: "key",
  keyAudio: "keyAudio",
  keyImage: "keyImage",
  kind: "kind",
  kindValue: "kindValue",
  label: "label",
  labelFalse: "labelFalse",
  labelFalseValue: "labelFalseValue",
  labelTrue: "labelTrue",
  labelTrueValue: "labelTrueValue",
  lang: "lang",
  language: "language",
  languageValue: "languageValue",
  langValue: "langValue",
  layer: "layer",
  layerRole: "layerRole",
  layerRoleValue: "layerRoleValue",
  layerValue: "layerValue",
  lead: "lead",
  leadValue: "leadValue",
  level: "level",
  levelACTFL: "levelACTFL",
  levelACTFLValue: "levelACTFLValue",
  levelCEFR: "levelCEFR",
  levelCEFRp: "levelCEFRp",
  levelCEFRpValue: "levelCEFRpValue",
  levelCEFRValue: "levelCEFRValue",
  levelILR: "levelILR",
  levelILRValue: "levelILRValue",
  license: "license",
  line: "line",
  list: "list",
  listItemIndent: "listItemIndent",
  listItemIndentValue: "listItemIndentValue",
  listPrice: "listPrice",
  listValue: "listValue",
  location: "location",
  locationValue: "locationValue",
  machineTranslated: "machineTranslated",
  machineTranslatedValue: "machineTranslatedValue",
  mailingList: "mailingList",
  marginNumber: "marginNumber",
  marginNumberValue: "marginNumberValue",
  mark: "mark",
  markConfig: "markConfig",
  markConfigValue: "markConfigValue",
  marks: "marks",
  marksValue: "marksValue",
  markup: "markup",
  // bitmark markup
  matrix: "matrix",
  matrixValue: "matrixValue",
  maxCreatedBits: "maxCreatedBits",
  maxCreatedBitsValue: "maxCreatedBitsValue",
  maxDisplayLevel: "maxDisplayLevel",
  maxDisplayLevelValue: "maxDisplayLevelValue",
  maxTocChapterLevel: "maxTocChapterLevel",
  maxTocChapterLevelValue: "maxTocChapterLevelValue",
  message: "message",
  mockupId: "mockupId",
  name: "name",
  needsValidation: "needsValidation",
  needsValidationValue: "needsValidationValue",
  offset: "offset",
  options: "options",
  optionsValue: "optionsValue",
  original: "original",
  padletId: "padletId",
  padletIdValue: "padletIdValue",
  page: "page",
  pageNo: "pageNo",
  pageNoValue: "pageNoValue",
  pageNumber: "pageNumber",
  pageNumberValue: "pageNumberValue",
  pageValue: "pageValue",
  pairs: "pairs",
  pairsValue: "pairsValue",
  parent: "parent",
  parser: "parser",
  partialAnswer: "partialAnswer",
  partialAnswerValue: "partialAnswerValue",
  path: "path",
  pathValue: "pathValue",
  person: "person",
  platformBackgroundColor: "platformBackgroundColor",
  platformBackgroundColorValue: "platformBackgroundColorValue",
  platformBackgroundImage: "platformBackgroundImage",
  platformBackgroundImageValue: "platformBackgroundImageValue",
  platformBaseLayerColor: "platformBaseLayerColor",
  platformBaseLayerColorValue: "platformBaseLayerColorValue",
  platformBorderColor: "platformBorderColor",
  platformBorderColorValue: "platformBorderColorValue",
  platformBorderRadius: "platformBorderRadius",
  platformBorderRadiusValue: "platformBorderRadiusValue",
  platformBrandTarget: "platformBrandTarget",
  platformBrandTargetValue: "platformBrandTargetValue",
  platformButtonBackgroundColor: "platformButtonBackgroundColor",
  platformButtonBackgroundColorValue: "platformButtonBackgroundColorValue",
  platformButtonPrimaryColor: "platformButtonPrimaryColor",
  platformButtonPrimaryColorValue: "platformButtonPrimaryColorValue",
  platformIcon: "platformIcon",
  platformIconValue: "platformIconValue",
  platformLogo: "platformLogo",
  platformLogoMaxHeight: "platformLogoMaxHeight",
  platformLogoMaxHeightValue: "platformLogoMaxHeightValue",
  platformLogoValue: "platformLogoValue",
  platformMargin: "platformMargin",
  platformMarginValue: "platformMarginValue",
  platformMessageBackgroundColor: "platformMessageBackgroundColor",
  platformMessageBackgroundColorValue: "platformMessageBackgroundColorValue",
  platformName: "platformName",
  platformNameValue: "platformNameValue",
  platformNeedsShadow: "platformNeedsShadow",
  platformNeedsShadowValue: "platformNeedsShadowValue",
  platformPlaceholderColor: "platformPlaceholderColor",
  platformPlaceholderColorValue: "platformPlaceholderColorValue",
  platformPrimaryColor: "platformPrimaryColor",
  platformPrimaryColorValue: "platformPrimaryColorValue",
  platformScrollbarColor: "platformScrollbarColor",
  platformScrollbarColorValue: "platformScrollbarColorValue",
  platformSecondaryColor: "platformSecondaryColor",
  platformSecondaryColorValue: "platformSecondaryColorValue",
  platformSelectionBorderRadius: "platformSelectionBorderRadius",
  platformSelectionBorderRadiusValue: "platformSelectionBorderRadiusValue",
  platformSelectionColor: "platformSelectionColor",
  platformSelectionColorValue: "platformSelectionColorValue",
  platformSelectionTextColor: "platformSelectionTextColor",
  platformSelectionTextColorValue: "platformSelectionTextColorValue",
  platformSeparatorColor: "platformSeparatorColor",
  platformSeparatorColorValue: "platformSeparatorColorValue",
  platformTextSelectionColor: "platformTextSelectionColor",
  platformTextSelectionColorValue: "platformTextSelectionColorValue",
  pointerLeft: "pointerLeft",
  pointerLeftValue: "pointerLeftValue",
  pointerTop: "pointerTop",
  pointerTopValue: "pointerTopValue",
  posterImage: "posterImage",
  posterImageValue: "posterImageValue",
  postfix: "postfix",
  prefix: "prefix",
  previewImage: "previewImage",
  previewImageValue: "previewImageValue",
  previewVideo: "previewVideo",
  previewVideoValue: "previewVideoValue",
  printParentChapterLevel: "printParentChapterLevel",
  printParentChapterLevelValue: "printParentChapterLevelValue",
  printOnRequestCustomerId: "printOnRequestCustomerId",
  printOnRequestCustomerIdValue: "printOnRequestCustomerIdValue",
  printOnRequestProductId: "printOnRequestProductId",
  printOnRequestProductIdValue: "printOnRequestProductIdValue",
  processHandIn: "processHandIn",
  processHandInLocation: "processHandInLocation",
  processHandInLocationValue: "processHandInLocationValue",
  processHandInValue: "processHandInValue",
  product: "product",
  productFolder: "productFolder",
  productFolderValue: "productFolderValue",
  productId: "productId",
  productIdValue: "productIdValue",
  productList: "productList",
  productListValue: "productListValue",
  productValue: "productValue",
  productVideo: "productVideo",
  productVideoList: "productVideoList",
  productVideoListValue: "productVideoListValue",
  productVideoValue: "productVideoValue",
  progress: "progress",
  progressValue: "progressValue",
  pronunciationTable: "pronunciationTable",
  properties: "properties",
  property: "property",
  propertyKey: "propertyKey",
  propertyValue: "propertyValue",
  propertyValues: "propertyValues",
  provider: "provider",
  pseudonym: "pseudonym",
  pseudonymValue: "pseudonymValue",
  publications: "publications",
  publicationsValue: "publicationsValue",
  publisher: "publisher",
  publisherName: "publisherName",
  publisherNameValue: "publisherNameValue",
  publisherValue: "publisherValue",
  quantity: "quantity",
  question: "question",
  questions: "questions",
  questionsValue: "questionsValue",
  questionValue: "questionValue",
  quizCountItems: "quizCountItems",
  quizCountItemsValue: "quizCountItemsValue",
  quizStrikethroughSolutions: "quizStrikethroughSolutions",
  quizStrikethroughSolutionsValue: "quizStrikethroughSolutionsValue",
  quizzes: "quizzes",
  quizzesValue: "quizzesValue",
  quotedPerson: "quotedPerson",
  ratingLevelEnd: "ratingLevelEnd",
  ratingLevelSelected: "ratingLevelSelected",
  ratingLevelSelectedValue: "ratingLevelSelectedValue",
  ratingLevelStart: "ratingLevelStart",
  reaction: "reaction",
  reason: "reason",
  reasonableNumOfChars: "reasonableNumOfChars",
  reasonableNumOfCharsValue: "reasonableNumOfCharsValue",
  reductionTag: "reductionTag",
  reductionTagValue: "reductionTagValue",
  refAuthor: "refAuthor",
  refBookTitle: "refBookTitle",
  reference: "reference",
  referenceEnd: "referenceEnd",
  referenceProperty: "referenceProperty",
  referencePropertyValue: "referencePropertyValue",
  refPublicationYear: "refPublicationYear",
  refPublisher: "refPublisher",
  relatedBook: "relatedBook",
  relatedBookValue: "relatedBookValue",
  translationOfBook: "translationOfBook",
  translationOfBookValue: "translationOfBookValue",
  userFeedbackTranslationUrl: "userFeedbackTranslationUrl",
  userFeedbackTranslationUrlValue: "userFeedbackTranslationUrlValue",
  userFeedbackContentUrl: "userFeedbackContentUrl",
  userFeedbackContentUrlValue: "userFeedbackContentUrlValue",
  releaseDate: "releaseDate",
  releaseDateValue: "releaseDateValue",
  releaseKind: "releaseKind",
  releaseKindValue: "releaseKindValue",
  releaseVersion: "releaseVersion",
  releaseVersionValue: "releaseVersionValue",
  reportTag: "reportTag",
  reportTagValue: "reportTagValue",
  resolved: "resolved",
  resolvedBy: "resolvedBy",
  resolvedByValue: "resolvedByValue",
  resolvedDate: "resolvedDate",
  resolvedDateValue: "resolvedDateValue",
  resolvedValue: "resolvedValue",
  resources: "resources",
  resourcesValue: "resourcesValue",
  resourceType: "resourceType",
  response: "response",
  responses: "responses",
  responsesValue: "responsesValue",
  revealSolutions: "revealSolutions",
  reviewTag: "reviewTag",
  reviewTagValue: "reviewTagValue",
  reviewerTag: "reviewerTag",
  reviewerTagValue: "reviewerTagValue",
  rightsPolicy: "rightsPolicy",
  rightsPolicyValue: "rightsPolicyValue",
  root: "root",
  // bit type (root)
  rows: "rows",
  rowsValue: "rowsValue",
  rtl: "rtl",
  rtlValue: "rtlValue",
  rubric: "rubric",
  rubricValue: "rubricValue",
  sampleSolution: "sampleSolution",
  sampleSolutionValue: "sampleSolutionValue",
  scormSource: "scormSource",
  scormSourceValue: "scormSourceValue",
  search: "search",
  searchIndex: "searchIndex",
  searchIndexValue: "searchIndexValue",
  searchValue: "searchValue",
  section: "section",
  select: "select",
  selected: "selected",
  servings: "servings",
  servingsValue: "servingsValue",
  showInIndex: "showInIndex",
  showInIndexValue: "showInIndexValue",
  size: "size",
  slug: "slug",
  slugValue: "slugValue",
  solution: "solution",
  solutions: "solutions",
  solutionsValue: "solutionsValue",
  sourceBB: "sourceBB",
  sourceBBValue: "sourceBBValue",
  sourceDocument: "sourceDocument",
  sourceDocumentValue: "sourceDocumentValue",
  sourceRL: "sourceRL",
  sourceRLValue: "sourceRLValue",
  spaceId: "spaceId",
  spaceIdValue: "spaceIdValue",
  spansPageBreak: "spansPageBreak",
  spansPageBreakValue: "spansPageBreakValue",
  src: "src",
  src1x: "src1x",
  src2x: "src2x",
  src3x: "src3x",
  src4x: "src4x",
  srcAlt: "srcAlt",
  srcAltValue: "srcAltValue",
  start: "start",
  statement: "statement",
  statementValue: "statementValue",
  statements: "statements",
  statementsValue: "statementsValue",
  statementText: "statementText",
  stillImageFilmEmbed: "stillImageFilmEmbed",
  stillImageFilmLink: "stillImageFilmLink",
  stripePricingTableId: "stripePricingTableId",
  stripePricingTableIdValue: "stripePricingTableIdValue",
  stripePricingTableDescriptiveName: "stripePricingTableDescriptiveName",
  stripePricingTableDescriptiveNameValue: "stripePricingTableDescriptiveNameValue",
  stripePublishableKey: "stripePublishableKey",
  stripePublishableKeyValue: "stripePublishableKeyValue",
  subject: "subject",
  subjectValue: "subjectValue",
  subtitle: "subtitle",
  subtitleValue: "subtitleValue",
  subtype: "subtype",
  subtypeValue: "subtypeValue",
  table: "table",
  tableAutoWidth: "tableAutoWidth",
  tableAutoWidthValue: "tableAutoWidthValue",
  tableColWidth: "tableColWidth",
  tableColWidthValue: "tableColWidthValue",
  tableColumnMinWidth: "tableColumnMinWidth",
  tableColumnMinWidthValue: "tableColumnMinWidthValue",
  tableFixedHeader: "tableFixedHeader",
  tableFixedHeaderValue: "tableFixedHeaderValue",
  tableHeaderWhitespaceNoWrap: "tableHeaderWhitespaceNoWrap",
  tableHeaderWhitespaceNoWrapValue: "tableHeaderWhitespaceNoWrapValue",
  tableHeight: "tableHeight",
  tableHeightValue: "tableHeightValue",
  tablePagination: "tablePagination",
  tablePaginationLimit: "tablePaginationLimit",
  tablePaginationLimitValue: "tablePaginationLimitValue",
  tablePaginationValue: "tablePaginationValue",
  tableResizableColumns: "tableResizableColumns",
  tableResizableColumnsValue: "tableResizableColumnsValue",
  tableSearch: "tableSearch",
  tableSearchValue: "tableSearchValue",
  tableSort: "tableSort",
  tableSortValue: "tableSortValue",
  tableWhitespaceNoWrap: "tableWhitespaceNoWrap",
  tableWhitespaceNoWrapValue: "tableWhitespaceNoWrapValue",
  tag: "tag",
  tagValue: "tagValue",
  target: "target",
  targetValue: "targetValue",
  technicalTerm: "technicalTerm",
  technicalTermValue: "technicalTermValue",
  term: "term",
  text: "text",
  textAlign: "textAlign",
  textAst: "textAst",
  textAstValue: "textAstValue",
  textFormat: "textFormat",
  textReference: "textReference",
  textReferenceValue: "textReferenceValue",
  texts: "texts",
  textsValue: "textsValue",
  theme: "theme",
  themeValue: "themeValue",
  thumbImage: "thumbImage",
  thumbImageValue: "thumbImageValue",
  thumbnails: "thumbnails",
  thumbnailsValue: "thumbnailsValue",
  title: "title",
  titleString: "titleString",
  titleValue: "titleValue",
  toc: "toc",
  tocContent: "tocContent",
  tocContentValue: "tocContentValue",
  tocResource: "tocResource",
  tocResourceValue: "tocResourceValue",
  tocValue: "tocValue",
  topicTag: "topicTag",
  topicTagValue: "topicTagValue",
  translationOf: "translationOf",
  translationOfValue: "translationOfValue",
  trim: "trim",
  type: "type",
  unit: "unit",
  unitAbbr: "unitAbbr",
  unitAbbrPlural: "unitAbbrPlural",
  unitPlural: "unitPlural",
  url: "url",
  validationDate: "validationDate",
  validationDateValue: "validationDateValue",
  value: "value",
  values: "values",
  valuesValue: "valuesValue",
  vendorDashboardId: "vendorDashboardId",
  vendorDashboardIdValue: "vendorDashboardIdValue",
  vendorSurveyId: "vendorSurveyId",
  vendorSurveyIdValue: "vendorSurveyIdValue",
  vendorUrl: "vendorUrl",
  vendorUrlValue: "vendorUrlValue",
  version: "version",
  video: "video",
  videoCallLink: "videoCallLink",
  videoCallLinkValue: "videoCallLinkValue",
  videoEmbed: "videoEmbed",
  videoLink: "videoLink",
  warnings: "warnings",
  warningsValue: "warningsValue",
  websiteLink: "websiteLink",
  width: "width",
  widthValue: "widthValue",
  x: "x",
  xValue: "xValue",
  y: "y",
  yValue: "yValue",
  zoomDisabled: "zoomDisabled"
};

// src/ast/Ast.ts
var Ast = class {
  constructor() {
    init();
  }
  /**
   * Walk bitmark AST, decending each branch and calling callbacks when entering, leaving, and when in between child
   * nodes.
   *
   * Walking the tree can be used to convert it to another format (e.g. bitmark markup or JSON or text) or for analysis.
   *
   * The tree is navigated from root to leaf, decending each branch greedily.
   *
   * e.g. for the tree:
   * ```
   * A
   * |__B1
   * |  |__C1
   * |  |__C2
   * |__B2
   *    |__C3
   *
   * Enter A1
   * Enter B1
   * Leaf  C1
   * Betwe B1 (C1, C2)
   * Leaf  C2
   * Exit  B1
   * Betwe A1 (B1, B2)
   * Enter B2
   * Leaf  C3
   * Exit  B2
   * Exit  A1
   * ```
   *
   * @param context - context object to pass to callbacks
   * @param ast - bitmark / text AST
   * @param type - type of AST to walk (i.e. NodeType.bitmarkAst, NodeType.textAst)
   * @param callbacks - set of callbacks to call while walking the tree
   */
  walk(ast, type, callbacks, context) {
    this.walkRecursive(ast, [{ index: 0, key: type, value: ast }], callbacks, context);
  }
  /**
   * Convert a route to a unique key that describes that route.
   *
   * For the route A1 -> B4 -> C2 the route key would be A1_B4_C2
   *
   * @param route - the tree path from the root to the curent node
   * @returns
   */
  getRouteKey(route) {
    return route.reduce((acc, val, idx) => {
      if (+val.key !== val.index) {
        acc += `${val.key}`;
        if (idx < route.length - 1) {
          acc += "_";
        }
      }
      return acc;
    }, "");
  }
  /**
   * Print an AST to the console.
   * Useful for debug / development purposes
   *
   * @param ast - AST
   * @param rootKey - root node key
   */
  printTree(ast, rootKey = NodeType.bitmarkAst) {
    this.walkRecursive(
      ast,
      [{ index: 0, key: rootKey, value: ast }],
      {
        enter: (_node, route) => {
          console.log("Enter:   " + this.getRouteKey(route));
          if (route[route.length - 1].key === NodeType.bodyJson) return false;
          return true;
        },
        between: (_node, _left, _right, route) => {
          console.log("Between: " + this.getRouteKey(route));
        },
        exit: (_node, route) => {
          console.log("Exit:    " + this.getRouteKey(route));
        },
        leaf: (_node, route) => {
          console.log("Leaf:    " + this.getRouteKey(route));
        }
      },
      void 0
    );
  }
  /**
   * Preprocess bitmark AST into a standard format (BitmarkAst object) from bitmark AST either as a string
   * or a plain JS object
   *
   * @param ast - bitmark AST as a string or a plain JS object
   * @returns bitmark AST in a standard format (BitmarkAst object)
   */
  preprocessAst(ast) {
    if (stringUtils.isString(ast)) {
      const str = ast;
      try {
        ast = JSON.parse(str);
      } catch (_e) {
        return void 0;
      }
    }
    if (this.isAst(ast)) {
      return ast;
    }
    return void 0;
  }
  /**
   * Check if a plain JS object is valid AST
   *
   * @param ast - a plain JS object that might be AST
   * @returns true if AST, otherwise false
   */
  isAst(ast) {
    if (Object.prototype.hasOwnProperty.call(ast, "bits")) {
      if (Array.isArray(ast.bits)) return true;
    }
    return false;
  }
  walkRecursive(node, route, callbacks, context) {
    const { enter, between, exit, leaf } = callbacks;
    const parentKey = route[route.length - 1].key;
    const isValue = this.isValue(node);
    const isBranch = !isValue;
    const nodeInfo = route[route.length - 1];
    if (isBranch) {
      if (enter) {
        const res = enter(nodeInfo, route, context);
        if (res === false) return;
      }
    } else {
      if (leaf) leaf(nodeInfo, route, context);
    }
    if (isBranch) {
      const isArray = this.isArray(node);
      const keys = Object.keys(node);
      for (let i = 0, len = keys.length; i < len; i++) {
        const key = keys[i];
        const lastChild = i === len - 1;
        const nodeAsAny = node;
        const child = nodeAsAny[key];
        if (child != null) {
          const childNodeInfo = {
            key: this.getAstKey(key, parentKey, isArray),
            index: i,
            value: child
          };
          const r = route.slice();
          r.push(childNodeInfo);
          this.walkRecursive(child, r, callbacks, context);
          if (!lastChild) {
            const nextKey = keys[i + 1];
            const nextChild = nodeAsAny[nextKey];
            const nextChildNodeInfo = {
              key: this.getAstKey(nextKey, parentKey, isArray),
              index: i + 1,
              value: nextChild
            };
            if (between) {
              const res = between(nodeInfo, childNodeInfo, nextChildNodeInfo, route, context);
              if (res === false) break;
            }
          }
        }
      }
    }
    if (isBranch) {
      if (exit) exit(nodeInfo, route, context);
    }
  }
  getAstKey(key, parentKey, isParentArray) {
    let astKey = key;
    if (isParentArray && parentKey) {
      astKey = `${parentKey}Value`;
    }
    return Enum4(NodeType).fromKey(astKey) || `unknown(${astKey})`;
  }
  isArray(x) {
    return Array.isArray(x);
  }
  isObject(x) {
    return Object.prototype.toString.call(x) === "[object Object]";
  }
  isValue(x) {
    return !this.isObject(x) && !this.isArray(x);
  }
};

// src/ast/Builder.ts
import { Enum as Enum7 } from "@ncoderz/superenum";

// src/model/enum/TextLocation.ts
import "@ncoderz/superenum";
var TextLocation2 = {
  tag: "tag",
  body: "body"
};

// src/model/enum/BodyTextFormat.ts
import "@ncoderz/superenum";
var BodyTextFormat = {
  plainText: "text",
  // plain text
  latex: "latex",
  // LaTeX code
  json: "json",
  // json
  xml: "xml",
  // xml
  // bitmark++ text
  bitmarkPlusPlus: "bitmark++"
};

// src/breakscaping/RegexConfigs.ts
var REGEX_MARKS = /([*`_!=])(?=\1)/;
var REGEX_BLOCKS = /^(\|)(code[\s]*|code:|image:|[\s]*$)/;
var REGEX_TITLE_BLOCKS = /^([#]{1,3})([^\S\r\n]+)/;
var REGEX_LIST_BLOCKS = /^(•)([0-9]+[iI]*|[a-zA-Z]{1}|_|\+|-|)([^\S\r\n]+)/;
var REGEX_START_OF_TAG = /(\[)([.@#▼►%!?+\-$_=&])/;
var REGEX_FOOTER_DIVIDER = /^(~)(~~~[ \t]*)$/;
var REGEX_PLAIN_TEXT_DIVIDER = /^(\$)(\$\$\$[ \t]*)$/;
var REGEX_END_OF_TAG = /(\^*])/;
var REGEX_BIT_START = /^(\[)(\^*)(\.)/;
var REGEX_HATS = /(\^+)/;
var BREAKSCAPE_BITMARK_TAG_REGEX_SOURCE = `${REGEX_MARKS.source}|${REGEX_END_OF_TAG.source}|${REGEX_HATS.source}`;
var BREAKSCAPE_PLAIN_TAG_REGEX_SOURCE = `${REGEX_END_OF_TAG.source}|${REGEX_HATS.source}`;
var BREAKSCAPE_BITMARK_BODY_REGEX_SOURCE = `${REGEX_MARKS.source}|${REGEX_BLOCKS.source}|${REGEX_TITLE_BLOCKS.source}|${REGEX_LIST_BLOCKS.source}|${REGEX_START_OF_TAG.source}|${REGEX_FOOTER_DIVIDER.source}|${REGEX_PLAIN_TEXT_DIVIDER.source}|${REGEX_HATS.source}`;
var BREAKSCAPE_PLAIN_BODY_REGEX_SOURCE = `${REGEX_BIT_START.source}`;
var BREAKSCAPE_BITMARK_TAG_REGEX = new RegExp(BREAKSCAPE_BITMARK_TAG_REGEX_SOURCE, "gm");
var BREAKSCAPE_BITMARK_TAG_REGEX_REPLACER = "$1$3^$2";
var BREAKSCAPE_PLAIN_TAG_REGEX = new RegExp(BREAKSCAPE_PLAIN_TAG_REGEX_SOURCE, "gm");
var BREAKSCAPE_PLAIN_TAG_REGEX_REPLACER = "$2^$1";
var BREAKSCAPE_BITMARK_BODY_REGEX = new RegExp(BREAKSCAPE_BITMARK_BODY_REGEX_SOURCE, "gm");
var BREAKSCAPE_BITMARK_BODY_REGEX_REPLACER = "$1$2$4$6$9$11$13$15^$3$5$7$8$10$12$14";
var BREAKSCAPE_PLAIN_BODY_REGEX = new RegExp(BREAKSCAPE_PLAIN_BODY_REGEX_SOURCE, "gm");
var BREAKSCAPE_PLAIN_BODY_REGEX_REPLACER = "$1^$2$3";
var BREAKSCAPE_V2_BODY_REGEX = new RegExp("^(?:(\\[)(\\^*)(\\.))|(\\^+)", "gm");
var BREAKSCAPE_V2_BODY_REGEX_REPLACER = "$1$4^$2$3";
var UNBREAKSCAPE_REGEX = new RegExp("\\^([\\^]*)", "gm");
var UNBREAKSCAPE_REGEX_REPLACER = "$1";
var UNBREAKSCAPE_PLAIN_IN_BODY_REGEX = new RegExp("^(\\[)\\^(\\^*)(\\.)", "gm");
var UNBREAKSCAPE_PLAIN_IN_BODY_REGEX_REPLACER = "$1$2$3";
var BREAKSCAPE_CODE_REGEX = new RegExp("^(\\||\u2022|#)", "gm");
var BREAKSCAPE_CODE_REGEX_REPLACER = "$1^";

// src/breakscaping/BreakscapeRegex.ts
var DEF = {
  format: BodyTextFormat.bitmarkPlusPlus,
  location: TextLocation2.body,
  v2: false
};
function isString(obj) {
  return typeof obj === "string" || obj instanceof String;
}
function selectBreakscapeRegexAndReplacer(textFormat, textLocation, v2 = false) {
  if (textLocation === TextLocation2.tag) {
    if (!v2 && textFormat === BodyTextFormat.bitmarkPlusPlus) {
      return {
        regex: BREAKSCAPE_BITMARK_TAG_REGEX,
        replacer: BREAKSCAPE_BITMARK_TAG_REGEX_REPLACER
      };
    }
    return {
      regex: BREAKSCAPE_PLAIN_TAG_REGEX,
      replacer: BREAKSCAPE_PLAIN_TAG_REGEX_REPLACER
    };
  } else {
    if (textFormat === BodyTextFormat.bitmarkPlusPlus) {
      if (v2) {
        return {
          regex: BREAKSCAPE_V2_BODY_REGEX,
          replacer: BREAKSCAPE_V2_BODY_REGEX_REPLACER
        };
      }
      return {
        regex: BREAKSCAPE_BITMARK_BODY_REGEX,
        replacer: BREAKSCAPE_BITMARK_BODY_REGEX_REPLACER
      };
    }
    return {
      regex: BREAKSCAPE_PLAIN_BODY_REGEX,
      replacer: BREAKSCAPE_PLAIN_BODY_REGEX_REPLACER
    };
  }
}
function selectUnbreakscapeRegexAndReplacer(textFormat, textLocation) {
  const isPlain = textFormat !== BodyTextFormat.bitmarkPlusPlus;
  if (textLocation === TextLocation2.body && isPlain) {
    return {
      regex: UNBREAKSCAPE_PLAIN_IN_BODY_REGEX,
      replacer: UNBREAKSCAPE_PLAIN_IN_BODY_REGEX_REPLACER
    };
  }
  return {
    regex: UNBREAKSCAPE_REGEX,
    replacer: UNBREAKSCAPE_REGEX_REPLACER
  };
}
var Breakscape2 = class {
  constructor() {
    __publicField(this, "EMPTY_STRING", "");
  }
  breakscape(val, opts = {}) {
    if (val == null) return void 0;
    const options = Object.assign({}, DEF, opts);
    if (Array.isArray(val)) {
      return val.map((v) => this.breakscape(v, options));
    }
    if (!isString(val)) return val;
    const { regex, replacer } = selectBreakscapeRegexAndReplacer(
      options.format,
      options.location,
      options.v2
    );
    let str = val;
    str = str.replace(regex, replacer);
    return str;
  }
  unbreakscape(val, opts = {}) {
    if (val == null) return void 0;
    const options = Object.assign({}, DEF, opts);
    if (Array.isArray(val)) {
      return val.map((v) => this.unbreakscape(v, options));
    }
    if (!isString(val)) return val;
    const { regex, replacer } = selectUnbreakscapeRegexAndReplacer(
      options.format,
      options.location
    );
    let str = val;
    str = str.replace(regex, replacer);
    return str;
  }
  /**
   * Breakscape a code string or an array of code strings.
   * If the input is an array, a new array will be returned.
   *
   * @param val input value
   * @param modifyArray if true, the original array will be modified rather than a copy being made
   * @returns the input value with any strings breakscaped
   */
  breakscapeCode(val, options) {
    if (val == null) return val;
    const opts = Object.assign({}, DEF, options);
    const breakscapeStr = (str) => {
      if (!str) return str;
      return str.replace(BREAKSCAPE_CODE_REGEX, BREAKSCAPE_CODE_REGEX_REPLACER);
    };
    if (Array.isArray(val)) {
      const newVal = opts.inPlaceArray ? val : [val.length];
      for (let i = 0, len = val.length; i < len; i++) {
        const v = val[i];
        if (isString(v)) {
          val[i] = breakscapeStr(v);
        }
      }
      val = newVal;
    } else if (isString(val)) {
      val = breakscapeStr(val);
    }
    return val;
  }
  /**
   * Gets the version of the breakscape library.
   *
   * @returns The current version string of the library.
   *
   * @example
   * ```typescript
   * const breakscape = new Breakscape();
   * console.log(breakscape.version()); // e.g., "1.0.0"
   * ```
   */
  version() {
    return PACKAGE_INFO.version;
  }
  /**
   * Gets the license information for the breakscape library.
   *
   * @returns The license string for the library.
   *
   * @example
   * ```typescript
   * const breakscape = new Breakscape();
   * console.log(breakscape.license()); // e.g., "MIT"
   * ```
   */
  license() {
    return PACKAGE_INFO.license;
  }
};

// src/breakscaping/Breakscape.ts
var DEFAULT_BREAKSCAPE_OPTIONS = {
  format: TextFormat2.bitmarkText,
  location: TextLocation2.body,
  inPlaceArray: false,
  v2: false
};
var externalBreakscape = new Breakscape2();
var Breakscape3 = class {
  constructor() {
    __publicField(this, "EMPTY_STRING", "");
  }
  /**
   * Breakscape a string or an array of strings.
   * If the input is an array, a new array will be returned.
   *
   * @param val input value
   * @param options options for breakscaping
   * @param modifyArray
   * @returns the input value with any strings breakscaped.
   */
  breakscape(val, options) {
    const opts = Object.assign({}, DEFAULT_BREAKSCAPE_OPTIONS, options);
    return externalBreakscape.breakscape(val, {
      format: opts.format,
      location: opts.location,
      inPlaceArray: opts.inPlaceArray,
      v2: opts.v2
    });
  }
  /**
   * Unbreakscape a string or an array of strings.
   * If the input is an array, a new array will be returned.
   *
   * @param val input value
   * @param modifyArray if true, the original array will be modified rather than a copy being made
   * @returns the input value with any strings unbreakscaped.
   */
  unbreakscape(val, options) {
    const opts = Object.assign({}, DEFAULT_BREAKSCAPE_OPTIONS, options);
    return externalBreakscape.unbreakscape(val, opts);
  }
  /**
   * Breakscape a code string or an array of code strings.
   * If the input is an array, a new array will be returned.
   *
   * @param val input value
   * @param modifyArray if true, the original array will be modified rather than a copy being made
   * @returns the input value with any strings breakscaped
   */
  breakscapeCode(val, options) {
    const opts = Object.assign({}, DEFAULT_BREAKSCAPE_OPTIONS, options);
    return externalBreakscape.breakscapeCode(val, opts);
  }
  /**
   * Concatenate two breakscaped strings.
   *
   * @param s1 first string
   * @param s2 second string
   * @returns the concatenated string
   */
  concatenate(s1, s2) {
    return s1 + s2;
  }
};
var instance3 = new Breakscape3();

// src/model/enum/DeprecatedTextFormat.ts
import "@ncoderz/superenum";
var DeprecatedTextFormat = {
  bitmarkMinusMinus: "bitmark--"
  // bitmark-- text format, deprecated
};

// src/parser/json/TableUtils.ts
function isTableBasicFormat(table) {
  const t = table;
  return !!(t.columns || t.data) && !(t.header || t.body || t.footer);
}
function isTableExtendedFormat(table) {
  const t = table;
  return !!(t.header || t.body || t.footer);
}
function isMixedTableFormat(table) {
  const t = table;
  const hasOld = !!(t.columns || t.data);
  const hasNew = !!(t.header || t.body || t.footer);
  return hasOld && hasNew;
}
function convertBasicToExtendedTableFormat(table) {
  const tableExtended = {};
  if (table.columns && table.columns.length > 0) {
    tableExtended.header = {
      rows: [
        {
          cells: table.columns.map((col) => ({
            content: col,
            title: true
          }))
        }
      ]
    };
  }
  if (table.data && table.data.length > 0) {
    tableExtended.body = {
      rows: table.data.map((row) => ({
        cells: row.map((cell) => ({
          content: cell
        }))
      }))
    };
  }
  if (Array.isArray(table.columnWidths) && table.columnWidths.length > 0) {
    const targetCells = tableExtended.header?.rows[0]?.cells ?? tableExtended.body?.rows[0]?.cells;
    if (targetCells) {
      for (let i = 0; i < table.columnWidths.length; i++) {
        const width = table.columnWidths[i];
        if (typeof width === "number" && width > 0 && targetCells[i]) {
          targetCells[i].colwidth = width;
        }
      }
    }
  }
  return tableExtended;
}
function convertExtendedToBasicTableFormat(tableExtended) {
  const table = {};
  const extractRowCells = (row) => {
    if (!row || !Array.isArray(row.cells)) {
      return [];
    }
    return row.cells.map((cell) => cell?.content).filter((content) => content !== void 0);
  };
  const dataRows = [];
  const widths = [];
  let hasAnyWidth = false;
  let maxCellCount = 0;
  const collectWidths = (cells) => {
    if (!cells) return;
    if (cells.length > maxCellCount) maxCellCount = cells.length;
    cells.forEach((cell, idx) => {
      if (cell && typeof cell.colwidth === "number" && cell.colwidth > 0) {
        widths[idx] = cell.colwidth;
        hasAnyWidth = true;
      }
    });
  };
  const headRows = tableExtended.header?.rows ?? [];
  headRows.forEach((row) => collectWidths(row?.cells));
  (tableExtended.body?.rows ?? []).forEach((row) => collectWidths(row?.cells));
  (tableExtended.footer?.rows ?? []).forEach((row) => collectWidths(row?.cells));
  if (headRows.length > 0) {
    const primaryHeader = extractRowCells(headRows[0]);
    if (primaryHeader.length > 0) {
      table.columns = primaryHeader;
    }
    const remainingHeadRows = headRows.slice(1);
    remainingHeadRows.forEach((row) => {
      const cells = extractRowCells(row);
      if (cells.length > 0) {
        dataRows.push(cells);
      }
    });
  }
  const appendSectionRows = (section) => {
    if (!section || !Array.isArray(section.rows)) {
      return;
    }
    section.rows.forEach((row) => {
      const cells = extractRowCells(row);
      if (cells.length > 0) {
        dataRows.push(cells);
      }
    });
  };
  appendSectionRows(tableExtended.body);
  appendSectionRows(tableExtended.footer);
  if (dataRows.length > 0) {
    table.data = dataRows;
  }
  if (hasAnyWidth) {
    const columnCount = table.columns?.length ?? maxCellCount;
    const columnWidths = new Array(columnCount).fill(null);
    for (let i = 0; i < columnCount; i++) {
      const w = widths[i];
      if (typeof w === "number") columnWidths[i] = w;
    }
    table.columnWidths = columnWidths;
  }
  return table;
}
function normalizeTableFormat(bitType, table) {
  if (isMixedTableFormat(table)) {
    const t = table;
    delete t.columns;
    delete t.data;
  }
  const isExtended = instance2.isOfBitType(bitType, BitType2.tableExtended) || instance2.isOfBitType(bitType, BitType2.tableImage);
  if (isExtended && isTableBasicFormat(table)) {
    return convertBasicToExtendedTableFormat(table);
  }
  if (!isExtended && isTableExtendedFormat(table)) {
    return convertExtendedToBasicTableFormat(table);
  }
  return table;
}

// src/utils/ArrayUtils.ts
var ArrayUtils = class {
  /**
   * Convert a value to an array, unless it is already an array.
   *
   * This function is useful for handling parameters that can be a single value or an array of values.
   *
   * @param val input value
   * @returns the input value if it is an array, otherwise a new array containing the input value
   */
  asArray(val) {
    if (val == null) return void 0;
    if (Array.isArray(val)) return val;
    return [val];
  }
  /**
   * Convert an array or value to a value, unless it is already a value.
   * The first item in the array will be selected
   *
   * This function is useful for handling parameters that can be a single value or an array of values.
   *
   * @param val input value
   * @param last if true, the last item in the array will be selected, rather than the first
   * @returns the input value if it is not an array, otherwise the first item in the array
   */
  asSingle(val, last) {
    if (val == null) return void 0;
    if (Array.isArray(val)) {
      if (val.length > 0) return last ? val[val.length - 1] : val[0];
      return void 0;
    }
    return val;
  }
  /**
   * Removes all duplicate items, keeping the first duplicate only.
   * The input array is modified in place.
   *
   * @param arr array in which to make items unique
   * @param keyFunc function(item) to return the key to check for equality. - optional
   * @returns the input array with all duplicate items removed
   */
  removeDuplicates(arr, keyFunc) {
    const seen = /* @__PURE__ */ new Set();
    let write = 0;
    for (let read = 0; read < arr.length; read++) {
      const item = arr[read];
      const k = keyFunc ? keyFunc(item) : item;
      if (!seen.has(k)) {
        seen.add(k);
        if (write !== read) arr[write] = item;
        write++;
      }
    }
    if (write < arr.length) arr.length = write;
    return arr;
  }
};
var instance4 = new ArrayUtils();

// src/generator/text/TextGenerator.ts
import { Enum as Enum5 } from "@ncoderz/superenum";

// src/model/enum/BitmarkVersion.ts
import "@ncoderz/superenum";
var BitmarkVersion = {
  v2: 2,
  v3: 3
};
var DEFAULT_BITMARK_VERSION = BitmarkVersion.v3;

// src/generator/AstWalkerGenerator.ts
var AstWalkerGenerator = class {
  constructor() {
    __publicField(this, "ast", new Ast());
    // Debugging
    __publicField(this, "debugGenerationInline", false);
    __publicField(this, "printed", false);
  }
  enter(node, route, context) {
    let res = void 0;
    const gen = this;
    const funcName = `enter_${node.key}`;
    if (!this.printed) {
      this.printed = true;
    }
    if (this.debugGenerationInline) this.writeInlineDebug(node.key, { open: true });
    if (typeof gen[funcName] === "function") {
      res = gen[funcName](node, route, context);
    }
    return res;
  }
  between(node, left, right, route, context) {
    let res = void 0;
    const gen = this;
    const funcName = `between_${node.key}`;
    if (this.debugGenerationInline) this.writeInlineDebug(node.key, { single: true });
    if (typeof gen[funcName] === "function") {
      res = gen[funcName](node, left, right, route, context);
    }
    return res;
  }
  exit(node, route, context) {
    const gen = this;
    const funcName = `exit_${node.key}`;
    if (this.debugGenerationInline) this.writeInlineDebug(node.key, { close: true });
    if (typeof gen[funcName] === "function") {
      gen[funcName](node, route, context);
    }
  }
  leaf(node, route, context) {
    const gen = this;
    const funcName = `leaf_${node.key}`;
    if (this.debugGenerationInline) this.writeInlineDebug(node.key, { open: true });
    if (typeof gen[funcName] === "function") {
      gen[funcName](node, route, context);
    }
    if (this.debugGenerationInline) this.writeInlineDebug(node.key, { close: true });
  }
  //
  // HELPER FUNCTIONS
  //
  getParentNode(route, nodesBack = 1) {
    if (route.length > nodesBack) {
      return route[route.length - nodesBack - 1];
    }
    return void 0;
  }
  getSiblingNodes(route) {
    const parentNode = this.getParentNode(route);
    if (!parentNode || !parentNode.value || !Array.isArray(parentNode.value)) {
      return {};
    }
    const siblings = parentNode.value;
    const index = siblings.findIndex((s) => s === route[route.length - 1]?.value);
    if (index === -1) {
      return {};
    }
    return {
      left: index > 0 ? siblings[index - 1] : void 0,
      right: index < siblings.length - 1 ? siblings[index + 1] : void 0
    };
  }
  isEmptyParagraph(node) {
    if (!node) return false;
    if (node.type !== TextNodeType.paragraph) return false;
    if (!node.content || !Array.isArray(node.content)) return true;
    for (const contentNode of node.content) {
      if (contentNode.type !== TextNodeType.text) return false;
      if (contentNode.text && contentNode.text.trim() !== "") return false;
    }
    return true;
  }
};

// src/generator/text/TextGenerator.ts
var DEFAULT_OPTIONS = {
  bodyBitCallback: void 0,
  debugGenerationInline: false
};
var FIRST_PARAGRAPH_CONTENT_DEPTH = 3;
var BOLD_HALF_MARK = "*";
var LIGHT_HALF_MARK = "`";
var ITALIC_HALF_MARK = "_";
var HIGHLIGHT_HALF_MARK = "!";
var INLINE_HALF_MARK = "=";
var BOLD_MARK = BOLD_HALF_MARK + BOLD_HALF_MARK;
var LIGHT_MARK = LIGHT_HALF_MARK + LIGHT_HALF_MARK;
var ITALIC_MARK = ITALIC_HALF_MARK + ITALIC_HALF_MARK;
var HIGHLIGHT_MARK = HIGHLIGHT_HALF_MARK + HIGHLIGHT_HALF_MARK;
var INLINE_MARK = INLINE_HALF_MARK + INLINE_HALF_MARK;
var ALL_HALF_MARKS = [
  BOLD_HALF_MARK,
  LIGHT_HALF_MARK,
  ITALIC_HALF_MARK,
  HIGHLIGHT_HALF_MARK,
  INLINE_HALF_MARK
];
var ALL_TAG_OPENING_ENDS = "@#\u25BC\u25BA%!?+-$_=&";
var HEADING_TAG = "#";
var STANDARD_MARKS = {
  [TextMarkType.bold]: BOLD_MARK,
  [TextMarkType.light]: LIGHT_MARK,
  [TextMarkType.italic]: ITALIC_MARK,
  [TextMarkType.highlight]: HIGHLIGHT_MARK
};
var STANDARD_MARK_TYPES = [
  TextMarkType.bold,
  TextMarkType.light,
  TextMarkType.italic,
  TextMarkType.highlight
];
var INLINE_MARK_TYPES = [
  TextMarkType.highlightOrange,
  TextMarkType.highlightYellow,
  TextMarkType.highlightGreen,
  TextMarkType.highlightBlue,
  TextMarkType.highlightPurple,
  TextMarkType.highlightPink,
  TextMarkType.highlightBrown,
  TextMarkType.highlightBlack,
  TextMarkType.highlightWhite,
  TextMarkType.highlightGray,
  TextMarkType.strike,
  TextMarkType.subscript,
  TextMarkType.superscript,
  TextMarkType.ins,
  TextMarkType.del,
  TextMarkType.underline,
  TextMarkType.doubleUnderline,
  TextMarkType.smallcaps,
  TextMarkType.circle,
  TextMarkType.languageEmRed,
  TextMarkType.languageEmOrange,
  TextMarkType.languageEmYellow,
  TextMarkType.languageEmGreen,
  TextMarkType.languageEmBlue,
  TextMarkType.languageEmPurple,
  TextMarkType.languageEmPink,
  TextMarkType.languageEmBrown,
  TextMarkType.languageEmBlack,
  TextMarkType.languageEmWhite,
  TextMarkType.languageEmGray,
  TextMarkType.languageEm,
  TextMarkType.userUnderline,
  TextMarkType.userDoubleUnderline,
  TextMarkType.userStrike,
  TextMarkType.userCircle,
  TextMarkType.userHighlight,
  TextMarkType.notranslate,
  //
  TextMarkType.link,
  TextMarkType.ref,
  TextMarkType.xref,
  TextMarkType.extref,
  TextMarkType.footnote,
  TextMarkType.footnoteStar,
  TextMarkType.symbol,
  TextMarkType.var,
  TextMarkType.code,
  TextMarkType.timer,
  TextMarkType.color,
  // legacy input, written as color: (same as textStyle)
  TextMarkType.textStyle,
  TextMarkType.colorPicker,
  TextMarkType.comment
];
var HIGHLIGHT_COLORS = [
  "orange",
  "yellow",
  "green",
  "blue",
  "purple",
  "pink",
  "brown",
  "white",
  "black",
  "gray"
];
var INDENTATION_REGEX = new RegExp(/(\n|\r\n)/, "g");
var LINK_REGEX = new RegExp(/https?:\/\/|mailto:(.*)/, "g");
var LINK_BREAKSCAPE_REGEX = new RegExp(/\]/, "g");
var Stage = {
  enter: "enter",
  between: "between",
  exit: "exit"
};
var TextGenerator = class _TextGenerator extends AstWalkerGenerator {
  /**
   * Generate text from a bitmark text AST
   *
   * @param bitmarkVersion - bitmark version, use null to use latest version
   * @param options - text generation options
   */
  constructor(bitmarkVersion, options) {
    super();
    __publicField(this, "ast", new Ast());
    __publicField(this, "bitmarkVersion");
    __publicField(this, "options");
    __publicField(this, "internalTextGenerator", null);
    // State
    __publicField(this, "generateOptions", {});
    __publicField(this, "textFormat", TextFormat2.bitmarkText);
    __publicField(this, "textLocation", TextLocation2.body);
    __publicField(this, "writerText", "");
    __publicField(this, "nodeIndex", 0);
    __publicField(this, "currentIndent", 0);
    __publicField(this, "prevIndent", 0);
    __publicField(this, "indentationStringCache", "");
    __publicField(this, "inParagraph", false);
    __publicField(this, "inHeading", false);
    __publicField(this, "inCodeBlock", false);
    __publicField(this, "inBulletList", false);
    __publicField(this, "inInline", false);
    __publicField(this, "markDepth", 0);
    __publicField(this, "textDepth", 0);
    __publicField(this, "placeholderIndex", 0);
    __publicField(this, "placeholders", {});
    // For pre-text
    __publicField(this, "rootParagraphNodeContentIndex", 0);
    __publicField(this, "previousRootParagraphContextType", TextNodeType.text);
    __publicField(this, "inPreText", false);
    __publicField(this, "thisNodeIsPreText", false);
    __publicField(this, "preTextIndexTemp", -1);
    __publicField(this, "havePreText", false);
    __publicField(this, "preTextIndex", -1);
    this.bitmarkVersion = Enum5(BitmarkVersion).fromValue(bitmarkVersion) ?? DEFAULT_BITMARK_VERSION;
    this.options = {
      ...DEFAULT_OPTIONS,
      ...options
    };
    this.debugGenerationInline = this.options.debugGenerationInline ?? false;
    if (this.bitmarkVersion === BitmarkVersion.v2) {
    } else {
    }
    this.enter = this.enter.bind(this);
    this.between = this.between.bind(this);
    this.exit = this.exit.bind(this);
    this.leaf = this.leaf.bind(this);
    if (!this.options.isInternal) {
      this.internalTextGenerator = new _TextGenerator(bitmarkVersion, {
        ...this.options,
        writeCallback: void 0,
        isInternal: true
      });
    }
  }
  /**
   * Generate text from a bitmark text AST
   *
   * @param ast bitmark text AST
   */
  async generate(ast, textFormat, textLocation, options) {
    return this.generateSync(ast, textFormat, textLocation, options);
  }
  /**
   * Generate text from a bitmark text AST synchronously
   *
   * @param ast bitmark text AST
   */
  generateSync(ast, textFormat, textLocation, options) {
    if (!ast || !Array.isArray(ast)) return "";
    this.generateOptions = Object.assign({}, options);
    this.validateGenerateOptions(ast);
    if (!this.generateOptions.plainTextDividerAllowed) {
      this.resetState(textFormat, textLocation);
      this.walkAndWrite(ast);
    } else {
      const writeCallback = this.options.writeCallback;
      const bodyBitCallback = this.options.bodyBitCallback;
      this.options.writeCallback = void 0;
      this.options.bodyBitCallback = void 0;
      this.resetState(textFormat, textLocation);
      this.walkAndWrite(ast);
      this.options.writeCallback = writeCallback;
      this.options.bodyBitCallback = bodyBitCallback;
      const havePreText = this.havePreText;
      const pti = this.preTextIndex;
      this.resetState(textFormat, textLocation);
      this.havePreText = havePreText;
      this.preTextIndex = pti;
      this.walkAndWrite(ast);
    }
    return this.writerText;
  }
  getPlaceholders() {
    return this.placeholders;
  }
  resetState(textFormat, textLocation) {
    this.printed = false;
    this.textFormat = textFormat ?? TextFormat2.bitmarkText;
    this.textLocation = textLocation;
    this.writerText = "";
    this.nodeIndex = 0;
    this.currentIndent = 0;
    this.prevIndent = 0;
    this.indentationStringCache = "";
    this.inParagraph = false;
    this.inHeading = false;
    this.inCodeBlock = false;
    this.inBulletList = false;
    this.inInline = false;
    this.markDepth = 0;
    this.textDepth = 0;
    this.placeholderIndex = 0;
    this.placeholders = {};
    this.rootParagraphNodeContentIndex = 0;
    this.previousRootParagraphContextType = TextNodeType.hardBreak;
    this.inPreText = false;
    this.thisNodeIsPreText = false;
    this.preTextIndexTemp = -1;
    this.havePreText = false;
    this.preTextIndex = -1;
  }
  walkAndWrite(ast) {
    this.ast.walk(ast, NodeType.textAst, this, void 0);
  }
  //
  // NODE HANDLERS
  //
  // * -> textAstValue
  enter_textAstValue(node, route) {
    return this.handleEnterNode(node.value, route);
  }
  between_textAstValue(node, left, right, route) {
    return this.handleBetweenNode(node.value, left, right, route);
  }
  exit_textAstValue(node, route) {
    return this.handleExitNode(node.value, route);
  }
  // * -> contentValue
  enter_contentValue(node, route) {
    return this.handleEnterContentValueNode(node, route);
  }
  between_contentValue(node, left, right, route) {
    this.handleBetweenContentValueNode(node, left, right, route);
  }
  exit_contentValue(node, route) {
    this.handleExitContentValueNode(node, route);
  }
  // * -> contentValueValue
  enter_contentValueValue(node, route) {
    return this.handleEnterNode(node.value, route);
  }
  between_contentValueValue(node, left, right, route) {
    this.handleBetweenNode(node.value, left, right, route);
  }
  exit_contentValueValue(node, route) {
    return this.handleExitNode(node.value, route);
  }
  // * -> marks
  enter_marks(_node, _route) {
    this.markDepth++;
  }
  exit_marks(_node, _route) {
    this.markDepth--;
  }
  // END NODE HANDLERS
  handleEnterNode(node, route) {
    if (this.markDepth > 0) return;
    this.handleEnterNodePreTextCheck(node, route);
    this.handleIndent(node);
    switch (node.type) {
      case TextNodeType.paragraph:
        this.inParagraph = true;
        this.writeParagraph(node, route);
        break;
      case TextNodeType.hardBreak:
        this.writeHardBreak(node);
        break;
      case TextNodeType.text: {
        this.writeMarks(node, Stage.enter);
        this.writeText(node);
        this.writeMarks(node, Stage.between);
        if (this.textDepth === 0) {
          this.inInline = true;
        }
        this.textDepth++;
        break;
      }
      case TextNodeType.heading:
        this.writeHeading(node);
        this.inHeading = true;
        break;
      case TextNodeType.section:
        this.writeSection(node);
        break;
      case TextNodeType.listItem:
      case TextNodeType.taskItem:
        this.writeBullet(node, route);
        break;
      case TextNodeType.image:
        this.writeImage(node);
        break;
      case TextNodeType.imageInline:
        this.writeImage(node);
        break;
      case TextNodeType.codeBlock:
        this.inCodeBlock = true;
        this.writeCodeBlock(node);
        break;
      case TextNodeType.latex:
        this.writeLatex(node);
        break;
      case TextNodeType.noBulletList:
      case TextNodeType.bulletList:
      case TextNodeType.orderedList:
      case TextNodeType.orderedListRoman:
      case TextNodeType.orderedListRomanLower:
      case TextNodeType.letteredList:
      case TextNodeType.letteredListLower:
      case TextNodeType.taskList:
        this.inBulletList = true;
        break;
      case TextNodeType.gap:
      case TextNodeType.select:
      case TextNodeType.highlight:
      case TextNodeType.mark:
        this.writeBodyBit(node, route);
        return false;
      default:
    }
  }
  handleBetweenNode(node, _left, _right, _route) {
    switch (node.type) {
      default:
    }
  }
  handleExitNode(node, _route) {
    if (this.markDepth > 0) return;
    switch (node.type) {
      case TextNodeType.text:
        this.textDepth--;
        if (this.textDepth === 0) {
          this.inInline = false;
        }
        this.writeMarks(node, Stage.exit);
        break;
      case TextNodeType.paragraph:
        this.writeNL();
        this.inParagraph = false;
        break;
      case TextNodeType.heading:
        this.inHeading = false;
      case TextNodeType.section:
      case TextNodeType.image:
        if (!this.inParagraph) {
          this.writeNL();
          this.writeNL();
        }
        break;
      case TextNodeType.codeBlock:
        this.writeNL();
        this.writeNL();
        this.inCodeBlock = false;
        break;
      case TextNodeType.noBulletList:
      case TextNodeType.bulletList:
      case TextNodeType.orderedList:
      case TextNodeType.orderedListRoman:
      case TextNodeType.orderedListRomanLower:
      case TextNodeType.letteredList:
      case TextNodeType.letteredListLower:
      case TextNodeType.taskList:
        if (this.currentIndent <= 1) {
          this.writeNL();
          this.inBulletList = false;
        }
        break;
      default:
    }
    this.handleDedent(node);
    this.nodeIndex++;
  }
  handleIndent(node) {
    switch (node.type) {
      case TextNodeType.noBulletList:
      case TextNodeType.bulletList:
      case TextNodeType.orderedList:
      case TextNodeType.orderedListRoman:
      case TextNodeType.orderedListRomanLower:
      case TextNodeType.letteredList:
      case TextNodeType.letteredListLower:
      case TextNodeType.taskList:
        this.currentIndent++;
        break;
      default:
    }
  }
  handleDedent(node) {
    switch (node.type) {
      case TextNodeType.noBulletList:
      case TextNodeType.bulletList:
      case TextNodeType.orderedList:
      case TextNodeType.orderedListRoman:
      case TextNodeType.orderedListRomanLower:
      case TextNodeType.letteredList:
      case TextNodeType.letteredListLower:
      case TextNodeType.taskList:
        this.currentIndent--;
        break;
      default:
    }
  }
  handleEnterContentValueNode(_node, route) {
    if (route.length === FIRST_PARAGRAPH_CONTENT_DEPTH) {
      this.thisNodeIsPreText = true;
      this.inPreText = false;
      this.previousRootParagraphContextType = TextNodeType.hardBreak;
    }
  }
  handleBetweenContentValueNode(_node, left, _right, route) {
    if (route.length === FIRST_PARAGRAPH_CONTENT_DEPTH) {
      const leftNode = left.value;
      this.updatePreTextState();
      this.thisNodeIsPreText = true;
      this.previousRootParagraphContextType = leftNode.type;
      this.rootParagraphNodeContentIndex++;
    }
  }
  handleExitContentValueNode(_node, route) {
    if (route.length === FIRST_PARAGRAPH_CONTENT_DEPTH) {
      this.updatePreTextState();
      this.thisNodeIsPreText = false;
      if (this.generateOptions.plainTextDividerAllowed && this.inPreText) {
        this.havePreText = true;
        this.preTextIndex = this.preTextIndexTemp;
      }
      this.inPreText = false;
    }
  }
  updatePreTextState() {
    if (!this.inPreText && this.thisNodeIsPreText && this.previousRootParagraphContextType === TextNodeType.hardBreak) {
      this.inPreText = true;
      this.preTextIndexTemp = this.rootParagraphNodeContentIndex;
    } else if (this.inPreText && !this.thisNodeIsPreText) {
      this.inPreText = false;
      this.preTextIndexTemp = -1;
    }
  }
  handleEnterNodePreTextCheck(node, route) {
    const firstParagraphContent = route.length === 4;
    if (firstParagraphContent) {
      if (this.inPreText && node.type === TextNodeType.hardBreak) return;
      if (node.type === TextNodeType.text) return;
    }
    this.thisNodeIsPreText = false;
  }
  /**
   * Check if a text node has a link mark, and if so, return the href
   *
   * @param node the node to check for a link mark
   * @returns the href, or false if no link mark
   */
  getLinkHref(node) {
    if (node.type === TextNodeType.text && node.marks) {
      const href = node.marks.reduce((acc, mark) => {
        if (mark.type === TextMarkType.link) {
          const linkMark = mark;
          const href2 = linkMark.attrs?.href;
          if (href2) return href2;
        }
        return acc;
      }, "");
      if (href) return href;
    }
    return false;
  }
  /**
   * Get the current indentation string
   * @returns
   */
  getIndentationString() {
    if (this.currentIndent === this.prevIndent) return this.indentationStringCache;
    let s = "";
    for (let i = 1; i < this.currentIndent; i++) s += "	";
    this.indentationStringCache = s;
    this.prevIndent = this.currentIndent;
    return s;
  }
  //
  // WRITE FUNCTIONS
  //
  writeBodyBit(node, route) {
    const placeholder = `[!${this.placeholderIndex}]`;
    this.placeholders[placeholder] = node;
    if (this.options.bodyBitCallback) {
      const bodyBit = this.options.bodyBitCallback(
        node,
        this.placeholderIndex,
        route
      );
      this.write(bodyBit);
    } else {
      this.write(placeholder);
    }
    this.placeholderIndex++;
  }
  writeText(node) {
    if (node.text == null) return;
    const codeBreakscaping = this.inCodeBlock;
    let s = node.text;
    const linkText = this.getLinkText(node);
    if (linkText) s = linkText;
    if (linkText) {
      s = linkText;
    } else {
      if (!codeBreakscaping) {
        s = this.breakscape(s, {
          format: this.textFormat,
          location: this.textLocation
        });
      } else {
        s = this.breakscape(s, {
          format: this.textFormat,
          location: this.textLocation
        });
      }
    }
    if (this.currentIndent > 1) {
      const indentationString = this.getIndentationString();
      s = s.replace(INDENTATION_REGEX, `$1${indentationString}`);
    }
    if (this.havePreText && this.rootParagraphNodeContentIndex === this.preTextIndex) {
      this.writePlainTextDivider();
      this.writeNL();
    }
    if (this.havePreText && this.rootParagraphNodeContentIndex >= this.preTextIndex) {
      const s2 = this.breakscape(node.text, {
        format: TextFormat2.plainText,
        location: this.textLocation
        // Must be body for pre-text
      });
      this.write(s2);
    } else {
      this.write(s);
    }
    if (this.currentIndent > 1 || codeBreakscaping) {
      this.thisNodeIsPreText = false;
    }
    if (!this.inPreText && node.text === s) {
      this.thisNodeIsPreText = false;
    }
  }
  /**
   * Check if a node is a link node and if so and the node has only the link mark, and the link without the
   * protocol is the same as the text, return true. Otherwise return false.
   *
   * @param node
   * @returns true if a simple link, otherwise false
   */
  isSimpleLink(node) {
    if (node.text == null) return false;
    if (node.marks?.length !== 1) return false;
    const href = this.getLinkHref(node);
    if (href) {
      const hrefText = href.replace(LINK_REGEX, "$1");
      return hrefText === node.text;
    }
    return false;
  }
  /**
   * Get the link text if the node is a link, otherwise return false
   *
   * @param node
   * @returns
   */
  getLinkText(node) {
    if (node.text == null) return false;
    const href = this.getLinkHref(node);
    if (href) {
      let res;
      const hrefText = href.replace(LINK_REGEX, "$1");
      if (hrefText === node.text) {
        res = href;
      } else {
        res = node.text;
      }
      res = (res ?? "").replace(LINK_BREAKSCAPE_REGEX, "^]");
      return res;
    }
    return false;
  }
  /**
   * If the existing written text ends with a half-mark, and the new text starts with the same half-mark, insert a
   * breakscape ^ between them to break the sequence.
   *
   * Only applies to bitmark version > 2
   *
   * @param s
   */
  getInterTextBreakscape(s) {
    if (this.bitmarkVersion === BitmarkVersion.v2) return "";
    if (this.textFormat === TextFormat2.plainText) return "";
    const lastChar = this.writerText.slice(-1);
    const firstChar = s.slice(0, 1);
    if (lastChar === firstChar && ALL_HALF_MARKS.indexOf(lastChar) !== -1) {
      return "^";
    }
    if (this.textLocation === TextLocation2.body) {
      if (lastChar === "[" && ALL_TAG_OPENING_ENDS.indexOf(firstChar) !== -1) {
        return "^";
      }
    }
    return "";
  }
  // protected getHrefTextFromHref
  validateGenerateOptions(ast) {
    if (this.generateOptions.plainTextDividerAllowed) {
      if (ast.length !== 1) {
        this.generateOptions.plainTextDividerAllowed = false;
      } else if (ast.length === 1 && ast[0].type !== TextNodeType.paragraph) {
        this.generateOptions.plainTextDividerAllowed = false;
      }
    }
  }
  /**
   * Write the marks for the node.
   *
   * All marks need to be considered at once, because if there are multiple marks, they need to be written in
   * a combined manner.
   *
   * e.g. if there are bold and italic marks, they need to be written as
   *
   * ==the text==|bold|italic|
   * rather than
   * **__the text__**
   * or
   * **__the text**__
   *
   * @param node the text node
   * @param start true if starting (before) the text, false if ending (after) the text
   */
  writeMarks(node, stage) {
    if (node.marks) {
      const forceSingleMark = this.generateOptions.forceInline || !!(this.inInline || this.inHeading);
      this.thisNodeIsPreText = false;
      const emptyMarks = node.marks.length === 0;
      if (emptyMarks) {
        this.writeMarkTextWrapper(INLINE_MARK);
        return;
      }
      const marks = this.getWritableMarks(node.marks);
      if (marks.length === 0) return;
      const singleMark = marks.length === 1 && forceSingleMark;
      const markStartEnd = marks.reduce(
        (acc, mark) => {
          if (acc) return acc;
          if (this.isStandardMark(mark)) {
            if (singleMark) return STANDARD_MARKS[mark.type];
            return INLINE_MARK;
          } else if (this.isInlineMark(mark)) {
            return INLINE_MARK;
          } else {
          }
          return acc;
        },
        void 0
      );
      const isSimpleLinkMark = this.isSimpleLink(node);
      const haveMark = markStartEnd != null && !isSimpleLinkMark;
      if (haveMark) {
        if (stage == Stage.enter || stage == Stage.between) {
          this.writeMarkTextWrapper(markStartEnd);
        }
        if (stage == Stage.between) {
          for (const mark of marks) {
            if (this.isStandardMark(mark)) {
              if (!singleMark) {
                this.writeInlineMarkStartEnd();
                this.writeInlineMark(mark);
              }
            } else if (TextMarkType.comment === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeCommentMark(mark);
            } else if (TextMarkType.link === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeLinkMark(mark);
            } else if (TextMarkType.ref === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeRefMark(mark);
            } else if (TextMarkType.xref === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeXRefMark(mark);
            } else if (TextMarkType.extref === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeExtRefMark(mark);
            } else if (TextMarkType.footnote === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeFootnoteMark(mark);
            } else if (TextMarkType.footnoteStar === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeFootnoteStarMark(mark);
            } else if (TextMarkType.symbol === mark.type) {
              this.writeInlineMarkStartEnd();
              this.writeSymbolMark(mark);
            } else if (this.isInlineMark(mark)) {
              this.writeInlineMarkStartEnd();
              this.writeInlineMark(mark);
            } else {
            }
          }
        }
        if (stage == Stage.exit) {
          let inlineMarkWritten = false;
          for (const mark of marks) {
            if (this.isStandardMark(mark)) {
              if (!singleMark) {
                inlineMarkWritten = true;
              }
            } else if (TextMarkType.comment === mark.type || TextMarkType.link === mark.type || TextMarkType.ref === mark.type || TextMarkType.xref === mark.type || TextMarkType.footnote === mark.type || TextMarkType.footnoteStar === mark.type || this.isInlineMark(mark)) {
              inlineMarkWritten = true;
            } else {
            }
          }
          if (inlineMarkWritten) this.writeInlineMarkStartEnd();
        }
      }
    }
  }
  writeParagraph(node, route) {
    if (this.textFormat === TextFormat2.bitmarkText) {
      if (this.inBulletList) return;
      const siblingNodes = this.getSiblingNodes(route);
      const prevSiblingNodeType = siblingNodes.left?.type;
      const inBody = this.textLocation === TextLocation2.body;
      const isEmptyParagraph = this.isEmptyParagraph(node);
      if (!inBody || !isEmptyParagraph && !(prevSiblingNodeType === TextNodeType.codeBlock || prevSiblingNodeType === TextNodeType.paragraph)) {
        return;
      }
      this.write("|");
      this.writeNL();
      if (prevSiblingNodeType === TextNodeType.codeBlock) {
        this.writeNL();
      }
    }
  }
  writeHardBreak(_node) {
    this.writeNL();
    if (this.currentIndent > 1) {
      const indentationString = this.getIndentationString();
      this.write(indentationString);
    }
  }
  writeHeading(node) {
    let s = "";
    const level = node.attrs?.level ?? 1;
    for (let i = 0; i < +level; i++) s += HEADING_TAG;
    s += " ";
    this.write(s);
  }
  writeSection(node) {
    let s = "";
    if (node.section) {
      s = `|${node.section}: `;
    } else {
      s = "|";
    }
    this.write(s);
  }
  writeBullet(node, route) {
    let bullet = this.getIndentationString();
    const listParent = this.getParentNode(route, 2);
    const listParentNode = listParent?.value;
    const listType = listParentNode?.type;
    const start = listParentNode?.attrs?.start ?? 1;
    if (listType === TextNodeType.bulletList) {
      bullet += "\u2022 ";
    } else if (listType === TextNodeType.noBulletList) {
      bullet += "\u2022_ ";
    } else if (listType === TextNodeType.orderedList) {
      bullet += `\u2022${start} `;
    } else if (listType === TextNodeType.orderedListRoman) {
      bullet += `\u2022${start}I `;
    } else if (listType === TextNodeType.orderedListRomanLower) {
      bullet += `\u2022${start}i `;
    } else if (listType === TextNodeType.letteredList) {
      bullet += "\u2022A ";
    } else if (listType === TextNodeType.letteredListLower) {
      bullet += "\u2022a ";
    } else if (listType === TextNodeType.taskList) {
      const taskList = node;
      const checked = taskList.attrs?.checked ?? false;
      bullet += checked ? "\u2022+ " : "\u2022- ";
    }
    if (bullet) this.write(bullet);
  }
  writeImage(node) {
    if (node.attrs == null || !node.attrs.src) return;
    const attrs = node.attrs;
    const inlineImage = node.type === TextNodeType.imageInline;
    let ignoreAttributes;
    if (inlineImage) {
      ignoreAttributes = /* @__PURE__ */ new Set(["alt", "zoomDisabled", "title"]);
      const a = attrs;
      if (a.alignmentVertical === "top") ignoreAttributes.add("alignmentVertical");
      if (a.size === "line-height") ignoreAttributes.add("size");
    }
    const mediaAttrs = this.getMediaAttrs(inlineImage ? "imageInline" : "image", attrs, {
      ignoreAttributes
    });
    let s = "";
    if (inlineImage) {
      s = `==${attrs.alt ?? ""}==`;
    }
    s += mediaAttrs ? `|${mediaAttrs}|` : "";
    this.write(s);
  }
  writeCodeBlock(node) {
    if (node.attrs == null || !node.attrs.language) return;
    const attrs = node.attrs;
    const s = `|code:${attrs.language}
`;
    this.write(s);
  }
  writeLatex(node) {
    if (node.attrs == null || !node.attrs.formula) return;
    const attrs = node.attrs;
    const formula = this.breakscape(attrs.formula, {
      format: this.textFormat,
      location: this.textLocation
    });
    const s = `==${formula}==|latex|`;
    this.write(s);
  }
  writeMarkTextWrapper(s) {
    if (s) this.write(s);
  }
  writeInlineMark(mark) {
    const attrs = mark.attrs ?? {};
    let s;
    switch (mark.type) {
      case TextMarkType.textStyle:
      case TextMarkType.color:
        s = `color:${attrs.color ?? ""}`;
        break;
      case TextMarkType.timer:
        s = attrs.name ? `timer:${attrs.name}` : "timer";
        s += `|duration:${attrs.duration ?? ""}`;
        break;
      case TextMarkType.highlight:
      case TextMarkType.userHighlight:
        s = attrs.color ? `${mark.type}|color:${attrs.color}` : `${mark.type}`;
        break;
      default: {
        s = `${mark.type}`;
        for (const [k, v] of Object.entries(attrs)) {
          if (k === "language" && v !== "plain text" || k === "propertyRef" || k === "name") {
            s = `${s}:${v}`;
          }
        }
      }
    }
    this.write(s);
  }
  /**
   * Resolve the marks of a text node into the marks that will actually be written.
   *
   * - Merges a legacy standalone 'duration' mark into its sibling 'timer' mark.
   * - Drops marks the current grammar cannot express (standalone 'duration', 'timer'
   *   without a duration).
   * - Moves a 'textStyle'/'color' mark before a preceding bare 'highlight'/'userHighlight'
   *   mark when its color is a valid highlight color; written in the original order the
   *   output would re-parse as a highlight-with-color compound mark and change meaning.
   */
  getWritableMarks(nodeMarks) {
    const attrsOf = (m) => m.attrs ?? {};
    let marks = [...nodeMarks];
    const timer = marks.find((m) => m.type === TextMarkType.timer);
    const duration = marks.find((m) => m.type === TextMarkType.duration);
    if (timer && duration && !attrsOf(timer).duration) {
      marks = marks.map(
        (m) => m === timer ? {
          ...timer,
          attrs: { ...attrsOf(timer), duration: attrsOf(duration).duration }
        } : m
      );
    }
    marks = marks.filter((m) => {
      if (m.type === TextMarkType.duration) return false;
      if (m.type === TextMarkType.timer) return !!attrsOf(m).duration;
      return true;
    });
    const isBareHighlight = (m) => (m.type === TextMarkType.highlight || m.type === TextMarkType.userHighlight) && !attrsOf(m).color;
    const isHighlightColorStyle = (m) => (m.type === TextMarkType.textStyle || m.type === TextMarkType.color) && HIGHLIGHT_COLORS.indexOf(attrsOf(m).color) !== -1;
    for (let i = 0; i < marks.length - 1; i++) {
      if (isBareHighlight(marks[i]) && isHighlightColorStyle(marks[i + 1])) {
        const tmp = marks[i];
        marks[i] = marks[i + 1];
        marks[i + 1] = tmp;
      }
    }
    return marks;
  }
  /**
   * True if the mark is written using its standard form (e.g. **bold**, !!highlight!!)
   * when it is the only mark. A highlight mark with a color attribute is excluded - it
   * can only be expressed using the inline chain form (==text==|highlight|color:x|).
   */
  isStandardMark(mark) {
    if (STANDARD_MARK_TYPES.indexOf(mark.type) === -1) return false;
    return !(mark.attrs && mark.attrs.color);
  }
  /**
   * True if the mark is written using the inline chain form (==text==|mark|).
   */
  isInlineMark(mark) {
    if (INLINE_MARK_TYPES.indexOf(mark.type) !== -1) return true;
    return mark.type === TextMarkType.highlight && !!(mark.attrs && mark.attrs.color);
  }
  writeCommentMark(mark) {
    const comment = mark.comment || "";
    const s = `#${comment}`;
    this.write(s);
  }
  writeLinkMark(mark) {
    const href = mark.attrs?.href || "";
    const linkText = (href ?? "").replace(LINK_BREAKSCAPE_REGEX, "^]");
    const s = `link:${linkText}`;
    this.write(s);
  }
  writeRefMark(mark) {
    const ref = mark.attrs?.reference ?? "";
    const s = `\u25BA${ref}`;
    this.write(s);
  }
  writeXRefMark(mark) {
    const xref = mark.attrs?.xref ?? "";
    const ref = mark.attrs?.reference ?? "";
    let s = `xref:${xref}`;
    if (ref) s += `|\u25BA${ref}`;
    this.write(s);
  }
  writeExtRefMark(mark) {
    const extref = mark.attrs?.extref ?? "";
    const refs = mark.attrs?.references ?? [];
    const provider = mark.attrs?.provider ?? "";
    let s = `extref:${extref}`;
    for (const ref of refs) {
      s += `|\u25BA${ref ?? ""}`;
    }
    s += `|provider:${provider}`;
    this.write(s);
  }
  writeFootnoteMark(mark) {
    const s = `footnote:`;
    this.write(s);
    const text = this.internalTextGenerator?.generateSync(
      mark.attrs?.content,
      this.textFormat,
      this.textLocation,
      {
        ...this.generateOptions,
        forceInline: true
      }
    ) ?? "";
    this.write(text);
  }
  writeFootnoteStarMark(mark) {
    const s = `footnote*:`;
    this.write(s);
    const text = this.internalTextGenerator?.generateSync(
      mark.attrs?.content,
      this.textFormat,
      this.textLocation,
      {
        ...this.generateOptions,
        forceInline: true
      }
    ) ?? "";
    this.write(text);
  }
  writeSymbolMark(mark) {
    if (mark.attrs == null) return;
    const attrs = mark.attrs;
    const mediaAttrs = this.getMediaAttrs("symbol", attrs);
    const s = mediaAttrs ?? "";
    this.write(s);
  }
  writeInlineMarkStartEnd() {
    this.write("|");
  }
  writePlainTextDivider() {
    this.write("==== text ====");
  }
  writeString(s) {
    if (s != null) this.write(`${s}`);
  }
  writeNL() {
    if (this.options.debugGenerationInline) {
      this.write("\\n");
      return;
    }
    this.write("\n");
  }
  writeInlineDebug(key, state) {
    let tag = key;
    if (state.open) {
      tag = `<${key}>`;
    } else if (state.close) {
      tag = `</${key}>`;
    } else if (state.single) {
      tag = `<${key} />`;
    }
    this.writeString(tag);
  }
  getMediaAttrs(mediaType, attrs, options) {
    if (!mediaType) return void 0;
    const opts = Object.assign({}, options);
    const ignoreAttributes = opts.ignoreAttributes ?? /* @__PURE__ */ new Set();
    let s = `${mediaType}:${attrs?.src ?? ""}`;
    const entries = Object.entries(attrs).filter(([k, _v]) => k !== "src");
    for (let i = 0; i < entries.length; i++) {
      const [k, v] = entries[i];
      if (ignoreAttributes.has(k)) continue;
      switch (k) {
        case "textAlign":
          if (v !== "left") s += `|captionAlign:${v}`;
          break;
        case "alignment":
          if (v !== "center") {
            if (v) s += `|alignment:${v}`;
          }
          break;
        case "title":
          if (v) s += `|caption:${v}`;
          break;
        case "class":
          if (v !== "center") {
            if (v) s += `|align:${v}`;
          }
          break;
        case "comment":
          if (v) s += `|#${v}`;
          break;
        case "":
          if (stringUtils.isString(v)) s += `|:${v}`;
          else s += `|`;
          break;
        case "zoomDisabled":
          if (!v) s += "|zoomDisabled:false";
          break;
        case "alt":
        case "width":
        case "height":
        default:
          if (k && v) s += `|${k}:${v}`;
          break;
      }
    }
    return s;
  }
  //
  // Helper functions
  //
  breakscape(s, options) {
    if (this.generateOptions.noBreakscaping) return s;
    return instance3.breakscape(s, options);
  }
  getBitType(route) {
    for (const node of route) {
      if (node.key === NodeType.bitsValue) {
        const n = node.value;
        return n?.bitType;
      }
    }
    return void 0;
  }
  //
  // Writer interface
  //
  /**
   * Writes a string value to the output.
   * @param value - The string value to be written.
   */
  write(value) {
    value = this.getInterTextBreakscape(value) + value;
    if (this.options.writeCallback) {
      this.options.writeCallback(value);
    }
    this.writerText += value;
    return this;
  }
  /**
   * Writes a new line to the output. The line is indented automatically. The line is ended with the endOfLineString.
   * @param value - The line to write. When omitted, only the endOfLineString is written.
   */
  writeLine(value) {
    this.write(`${value}
`);
    return this;
  }
  /**
   * Writes a collection of lines to the output. Each line is indented automatically and ended with the endOfLineString.
   * @param values - The lines to write.
   * @param delimiter - An optional delimiter to be written at the end of each line, except for the last one.
   */
  writeLines(values, delimiter) {
    for (const value of values) {
      this.write(`${value}${delimiter ?? ""}
`);
    }
    return this;
  }
  /**
   * Writes a single whitespace character to the output.
   */
  writeWhiteSpace() {
    this.write(" ");
    return this;
  }
};

// src/generated/parser/text/text-peggy-parser.js
var VERSION = "8.41.1";
function unbreakscape(str) {
  if (typeof str !== "string") return null;
  return instance3.unbreakscape(str);
}
function removeTempParsingParent(obj) {
  if (obj && typeof obj === "object") {
    delete obj._tempParsingParent;
    delete obj._tempListStart;
    Object.keys(obj).forEach((key) => {
      removeTempParsingParent(obj[key]);
    });
  }
}
function cleanEmptyTextNodes(root) {
  const isEmptyTextNode = (n) => n && n.type === "text" && !n.text;
  function cleanContentArray(arr) {
    for (let i = arr.length - 1; i >= 0; i--) {
      const item = arr[i];
      if (isEmptyTextNode(item)) {
        arr.splice(i, 1);
      } else if (item && Array.isArray(item.content)) {
        cleanContentArray(item.content);
      }
    }
  }
  if (Array.isArray(root)) {
    cleanContentArray(root);
  } else if (root && Array.isArray(root.content)) {
    cleanContentArray(root.content);
  }
  return root;
}
function bitmarkPlusString(_str) {
  if (typeof parser !== "undefined") {
    return parser.parse(_str, { startRule: "bitmarkPlusString" });
  }
  return peg$parse(_str, { startRule: "bitmarkPlusString" });
}
var peg$SyntaxError = class extends SyntaxError {
  constructor(message, expected, found, location) {
    super(message);
    this.expected = expected;
    this.found = found;
    this.location = location;
    this.name = "SyntaxError";
  }
  format(sources) {
    let str = "Error: " + this.message;
    if (this.location) {
      let src = null;
      const st = sources.find((s2) => s2.source === this.location.source);
      if (st) {
        src = st.text.split(/\r\n|\n|\r/g);
      }
      const s = this.location.start;
      const offset_s = this.location.source && typeof this.location.source.offset === "function" ? this.location.source.offset(s) : s;
      const loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
      if (src) {
        const e = this.location.end;
        const filler = "".padEnd(offset_s.line.toString().length, " ");
        const line = src[s.line - 1];
        const last = s.line === e.line ? e.column : line.length + 1;
        const hatLen = last - s.column || 1;
        str += "\n --> " + loc + "\n" + filler + " |\n" + offset_s.line + " | " + line + "\n" + filler + " | " + "".padEnd(s.column - 1, " ") + "".padEnd(hatLen, "^");
      } else {
        str += "\n at " + loc;
      }
    }
    return str;
  }
  static buildMessage(expected, found) {
    function hex(ch) {
      return ch.codePointAt(0).toString(16).toUpperCase();
    }
    const nonPrintable = Object.prototype.hasOwnProperty.call(RegExp.prototype, "unicode") ? new RegExp("[\\p{C}\\p{Mn}\\p{Mc}]", "gu") : null;
    function unicodeEscape(s) {
      if (nonPrintable) {
        return s.replace(nonPrintable, (ch) => "\\u{" + hex(ch) + "}");
      }
      return s;
    }
    function literalEscape(s) {
      return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
    }
    function classEscape(s) {
      return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
    }
    const DESCRIBE_EXPECTATION_FNS = {
      literal(expectation) {
        return '"' + literalEscape(expectation.text) + '"';
      },
      class(expectation) {
        const escapedParts = expectation.parts.map(
          (part) => Array.isArray(part) ? classEscape(part[0]) + "-" + classEscape(part[1]) : classEscape(part)
        );
        return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]" + (expectation.unicode ? "u" : "");
      },
      any() {
        return "any character";
      },
      end() {
        return "end of input";
      },
      other(expectation) {
        return expectation.description;
      }
    };
    function describeExpectation(expectation) {
      return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
    }
    function describeExpected(expected2) {
      const descriptions = expected2.map(describeExpectation);
      descriptions.sort();
      if (descriptions.length > 0) {
        let j = 1;
        for (let i = 1; i < descriptions.length; i++) {
          if (descriptions[i - 1] !== descriptions[i]) {
            descriptions[j] = descriptions[i];
            j++;
          }
        }
        descriptions.length = j;
      }
      switch (descriptions.length) {
        case 1:
          return descriptions[0];
        case 2:
          return descriptions[0] + " or " + descriptions[1];
        default:
          return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1];
      }
    }
    function describeFound(found2) {
      return found2 ? '"' + literalEscape(found2) + '"' : "end of input";
    }
    return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
  }
};
function peg$parse(input, options) {
  options = options !== void 0 ? options : {};
  const peg$FAILED = {};
  const peg$source = options.grammarSource;
  const peg$startRuleFunctions = {
    version: peg$parseversion,
    bitmarkPlusPlus: peg$parsebitmarkPlusPlus,
    bitmarkPlus: peg$parsebitmarkPlus,
    bitmarkMinusMinus: peg$parsebitmarkMinusMinus,
    bitmarkPlusString: peg$parsebitmarkPlusString,
    bitmarkMinusMinusString: peg$parsebitmarkMinusMinusString
  };
  let peg$startRuleFunction = peg$parseversion;
  const peg$c0 = "|";
  const peg$c1 = "### ";
  const peg$c2 = "## ";
  const peg$c3 = "# ";
  const peg$c4 = "code";
  const peg$c5 = ":";
  const peg$c6 = "bitmark++";
  const peg$c7 = "bitmark--";
  const peg$c8 = "JavaScript";
  const peg$c9 = "\u2022 ";
  const peg$c10 = "\u2022_ ";
  const peg$c11 = "\u2022";
  const peg$c12 = " ";
  const peg$c13 = "I ";
  const peg$c14 = "i ";
  const peg$c15 = "\u2022A ";
  const peg$c16 = "\u2022a ";
  const peg$c17 = "\u2022+ ";
  const peg$c18 = "\u2022- ";
  const peg$c19 = "	";
  const peg$c20 = "image";
  const peg$c21 = "#";
  const peg$c22 = "@";
  const peg$c23 = "width";
  const peg$c24 = "height";
  const peg$c25 = "alignment";
  const peg$c26 = "captionAlign";
  const peg$c27 = "left";
  const peg$c28 = "center";
  const peg$c29 = "right";
  const peg$c30 = "alt";
  const peg$c31 = "caption";
  const peg$c32 = "zoomDisabled";
  const peg$c33 = "true";
  const peg$c34 = "false";
  const peg$c35 = "=";
  const peg$c36 = "|latex|";
  const peg$c37 = "|imageInline:";
  const peg$c38 = "srcAlt";
  const peg$c39 = "alignmentVertical";
  const peg$c40 = "size";
  const peg$c41 = "top";
  const peg$c42 = "middle";
  const peg$c43 = "bottom";
  const peg$c44 = "baseline";
  const peg$c45 = "sub";
  const peg$c46 = "super";
  const peg$c47 = "text-bottom";
  const peg$c48 = "text-top";
  const peg$c49 = "line-height";
  const peg$c50 = "font-height";
  const peg$c51 = "explicit";
  const peg$c52 = "link:";
  const peg$c53 = "extref:";
  const peg$c54 = "provider:";
  const peg$c55 = "xref:";
  const peg$c56 = "\u25BA";
  const peg$c57 = "symbol:";
  const peg$c58 = "footnote:";
  const peg$c59 = "footnote*:";
  const peg$c60 = "var:";
  const peg$c61 = "code:";
  const peg$c62 = "timer";
  const peg$c63 = "duration:";
  const peg$c64 = "P";
  const peg$c65 = "timer:";
  const peg$c66 = "highlight";
  const peg$c67 = "color:";
  const peg$c68 = "userHighlight";
  const peg$c69 = "colorPicker:";
  const peg$c70 = "|\u25BA";
  const peg$c71 = "bold";
  const peg$c72 = "italic";
  const peg$c73 = "light";
  const peg$c74 = "highlightOrange";
  const peg$c75 = "highlightYellow";
  const peg$c76 = "highlightGreen";
  const peg$c77 = "highlightBlue";
  const peg$c78 = "highlightPurple";
  const peg$c79 = "highlightPink";
  const peg$c80 = "highlightBrown";
  const peg$c81 = "highlightBlack";
  const peg$c82 = "highlightWhite";
  const peg$c83 = "highlightGray";
  const peg$c84 = "strike";
  const peg$c85 = "subscript";
  const peg$c86 = "superscript";
  const peg$c87 = "ins";
  const peg$c88 = "del";
  const peg$c89 = "underline";
  const peg$c90 = "doubleUnderline";
  const peg$c91 = "smallcaps";
  const peg$c92 = "circle";
  const peg$c93 = "languageEmRed";
  const peg$c94 = "languageEmOrange";
  const peg$c95 = "languageEmYellow";
  const peg$c96 = "languageEmGreen";
  const peg$c97 = "languageEmBlue";
  const peg$c98 = "languageEmPurple";
  const peg$c99 = "languageEmPink";
  const peg$c100 = "languageEmBrown";
  const peg$c101 = "languageEmBlack";
  const peg$c102 = "languageEmWhite";
  const peg$c103 = "languageEmGray";
  const peg$c104 = "languageEm";
  const peg$c105 = "userUnderline";
  const peg$c106 = "userDoubleUnderline";
  const peg$c107 = "userStrike";
  const peg$c108 = "userCircle";
  const peg$c109 = "notranslate";
  const peg$c110 = "aqua";
  const peg$c111 = "black";
  const peg$c112 = "blue";
  const peg$c113 = "brown";
  const peg$c114 = "fuchsia";
  const peg$c115 = "lightgrey";
  const peg$c116 = "gray";
  const peg$c117 = "darkgray";
  const peg$c118 = "green";
  const peg$c119 = "lime";
  const peg$c120 = "magenta";
  const peg$c121 = "maroon";
  const peg$c122 = "navy";
  const peg$c123 = "olive";
  const peg$c124 = "orange";
  const peg$c125 = "pink";
  const peg$c126 = "purple";
  const peg$c127 = "red";
  const peg$c128 = "silver";
  const peg$c129 = "teal";
  const peg$c130 = "violet";
  const peg$c131 = "white";
  const peg$c132 = "yellow";
  const peg$c133 = "*";
  const peg$c134 = "_";
  const peg$c135 = "`";
  const peg$c136 = "!";
  const peg$c137 = "[!";
  const peg$c138 = "]";
  const peg$c139 = "\n";
  const peg$c140 = "\r\n";
  const peg$c141 = "http";
  const peg$c142 = "s";
  const peg$c143 = "://";
  const peg$c144 = "mailto:";
  const peg$r0 = /^[ \t]/;
  const peg$r1 = /^[0-9]/;
  const peg$r2 = /^[\r\u2028-\u2029]/;
  const peg$r3 = /^[^\n\r\u2028\u2029]/;
  const peg$r4 = /^[!#-;=?-[\]-_a-{}-~]/;
  const peg$e0 = peg$anyExpectation();
  const peg$e1 = peg$otherExpectation("StyledText");
  const peg$e2 = peg$literalExpectation("|", false);
  const peg$e3 = peg$literalExpectation("### ", false);
  const peg$e4 = peg$literalExpectation("## ", false);
  const peg$e5 = peg$literalExpectation("# ", false);
  const peg$e6 = peg$literalExpectation("code", false);
  const peg$e7 = peg$classExpectation([" ", "	"], false, false, false);
  const peg$e8 = peg$literalExpectation(":", false);
  const peg$e9 = peg$literalExpectation("bitmark++", false);
  const peg$e10 = peg$literalExpectation("bitmark--", false);
  const peg$e11 = peg$literalExpectation("JavaScript", false);
  const peg$e12 = peg$literalExpectation("\u2022 ", false);
  const peg$e13 = peg$literalExpectation("\u2022_ ", false);
  const peg$e14 = peg$literalExpectation("\u2022", false);
  const peg$e15 = peg$classExpectation([["0", "9"]], false, false, false);
  const peg$e16 = peg$literalExpectation(" ", false);
  const peg$e17 = peg$literalExpectation("I ", false);
  const peg$e18 = peg$literalExpectation("i ", false);
  const peg$e19 = peg$literalExpectation("\u2022A ", false);
  const peg$e20 = peg$literalExpectation("\u2022a ", false);
  const peg$e21 = peg$literalExpectation("\u2022+ ", false);
  const peg$e22 = peg$literalExpectation("\u2022- ", false);
  const peg$e23 = peg$literalExpectation("	", false);
  const peg$e24 = peg$literalExpectation("image", false);
  const peg$e25 = peg$literalExpectation("#", false);
  const peg$e26 = peg$literalExpectation("@", false);
  const peg$e27 = peg$literalExpectation("width", false);
  const peg$e28 = peg$literalExpectation("height", false);
  const peg$e29 = peg$literalExpectation("alignment", false);
  const peg$e30 = peg$literalExpectation("captionAlign", false);
  const peg$e31 = peg$literalExpectation("left", false);
  const peg$e32 = peg$literalExpectation("center", false);
  const peg$e33 = peg$literalExpectation("right", false);
  const peg$e34 = peg$literalExpectation("alt", false);
  const peg$e35 = peg$literalExpectation("caption", false);
  const peg$e36 = peg$literalExpectation("zoomDisabled", false);
  const peg$e37 = peg$literalExpectation("true", false);
  const peg$e38 = peg$literalExpectation("false", false);
  const peg$e39 = peg$literalExpectation("=", false);
  const peg$e40 = peg$literalExpectation("|latex|", false);
  const peg$e41 = peg$literalExpectation("|imageInline:", false);
  const peg$e42 = peg$literalExpectation("srcAlt", false);
  const peg$e43 = peg$literalExpectation("alignmentVertical", false);
  const peg$e44 = peg$literalExpectation("size", false);
  const peg$e45 = peg$literalExpectation("top", false);
  const peg$e46 = peg$literalExpectation("middle", false);
  const peg$e47 = peg$literalExpectation("bottom", false);
  const peg$e48 = peg$literalExpectation("baseline", false);
  const peg$e49 = peg$literalExpectation("sub", false);
  const peg$e50 = peg$literalExpectation("super", false);
  const peg$e51 = peg$literalExpectation("text-bottom", false);
  const peg$e52 = peg$literalExpectation("text-top", false);
  const peg$e53 = peg$literalExpectation("line-height", false);
  const peg$e54 = peg$literalExpectation("font-height", false);
  const peg$e55 = peg$literalExpectation("explicit", false);
  const peg$e56 = peg$literalExpectation("link:", false);
  const peg$e57 = peg$literalExpectation("extref:", false);
  const peg$e58 = peg$literalExpectation("provider:", false);
  const peg$e59 = peg$literalExpectation("xref:", false);
  const peg$e60 = peg$literalExpectation("\u25BA", false);
  const peg$e61 = peg$literalExpectation("symbol:", false);
  const peg$e62 = peg$literalExpectation("footnote:", false);
  const peg$e63 = peg$literalExpectation("footnote*:", false);
  const peg$e64 = peg$literalExpectation("var:", false);
  const peg$e65 = peg$literalExpectation("code:", false);
  const peg$e66 = peg$literalExpectation("timer", false);
  const peg$e67 = peg$literalExpectation("duration:", false);
  const peg$e68 = peg$literalExpectation("P", false);
  const peg$e69 = peg$literalExpectation("timer:", false);
  const peg$e70 = peg$literalExpectation("highlight", false);
  const peg$e71 = peg$literalExpectation("color:", false);
  const peg$e72 = peg$literalExpectation("userHighlight", false);
  const peg$e73 = peg$literalExpectation("colorPicker:", false);
  const peg$e74 = peg$literalExpectation("|\u25BA", false);
  const peg$e75 = peg$literalExpectation("bold", false);
  const peg$e76 = peg$literalExpectation("italic", false);
  const peg$e77 = peg$literalExpectation("light", false);
  const peg$e78 = peg$literalExpectation("highlightOrange", false);
  const peg$e79 = peg$literalExpectation("highlightYellow", false);
  const peg$e80 = peg$literalExpectation("highlightGreen", false);
  const peg$e81 = peg$literalExpectation("highlightBlue", false);
  const peg$e82 = peg$literalExpectation("highlightPurple", false);
  const peg$e83 = peg$literalExpectation("highlightPink", false);
  const peg$e84 = peg$literalExpectation("highlightBrown", false);
  const peg$e85 = peg$literalExpectation("highlightBlack", false);
  const peg$e86 = peg$literalExpectation("highlightWhite", false);
  const peg$e87 = peg$literalExpectation("highlightGray", false);
  const peg$e88 = peg$literalExpectation("strike", false);
  const peg$e89 = peg$literalExpectation("subscript", false);
  const peg$e90 = peg$literalExpectation("superscript", false);
  const peg$e91 = peg$literalExpectation("ins", false);
  const peg$e92 = peg$literalExpectation("del", false);
  const peg$e93 = peg$literalExpectation("underline", false);
  const peg$e94 = peg$literalExpectation("doubleUnderline", false);
  const peg$e95 = peg$literalExpectation("smallcaps", false);
  const peg$e96 = peg$literalExpectation("circle", false);
  const peg$e97 = peg$literalExpectation("languageEmRed", false);
  const peg$e98 = peg$literalExpectation("languageEmOrange", false);
  const peg$e99 = peg$literalExpectation("languageEmYellow", false);
  const peg$e100 = peg$literalExpectation("languageEmGreen", false);
  const peg$e101 = peg$literalExpectation("languageEmBlue", false);
  const peg$e102 = peg$literalExpectation("languageEmPurple", false);
  const peg$e103 = peg$literalExpectation("languageEmPink", false);
  const peg$e104 = peg$literalExpectation("languageEmBrown", false);
  const peg$e105 = peg$literalExpectation("languageEmBlack", false);
  const peg$e106 = peg$literalExpectation("languageEmWhite", false);
  const peg$e107 = peg$literalExpectation("languageEmGray", false);
  const peg$e108 = peg$literalExpectation("languageEm", false);
  const peg$e109 = peg$literalExpectation("userUnderline", false);
  const peg$e110 = peg$literalExpectation("userDoubleUnderline", false);
  const peg$e111 = peg$literalExpectation("userStrike", false);
  const peg$e112 = peg$literalExpectation("userCircle", false);
  const peg$e113 = peg$literalExpectation("notranslate", false);
  const peg$e114 = peg$literalExpectation("aqua", false);
  const peg$e115 = peg$literalExpectation("black", false);
  const peg$e116 = peg$literalExpectation("blue", false);
  const peg$e117 = peg$literalExpectation("brown", false);
  const peg$e118 = peg$literalExpectation("fuchsia", false);
  const peg$e119 = peg$literalExpectation("lightgrey", false);
  const peg$e120 = peg$literalExpectation("gray", false);
  const peg$e121 = peg$literalExpectation("darkgray", false);
  const peg$e122 = peg$literalExpectation("green", false);
  const peg$e123 = peg$literalExpectation("lime", false);
  const peg$e124 = peg$literalExpectation("magenta", false);
  const peg$e125 = peg$literalExpectation("maroon", false);
  const peg$e126 = peg$literalExpectation("navy", false);
  const peg$e127 = peg$literalExpectation("olive", false);
  const peg$e128 = peg$literalExpectation("orange", false);
  const peg$e129 = peg$literalExpectation("pink", false);
  const peg$e130 = peg$literalExpectation("purple", false);
  const peg$e131 = peg$literalExpectation("red", false);
  const peg$e132 = peg$literalExpectation("silver", false);
  const peg$e133 = peg$literalExpectation("teal", false);
  const peg$e134 = peg$literalExpectation("violet", false);
  const peg$e135 = peg$literalExpectation("white", false);
  const peg$e136 = peg$literalExpectation("yellow", false);
  const peg$e137 = peg$literalExpectation("*", false);
  const peg$e138 = peg$literalExpectation("_", false);
  const peg$e139 = peg$literalExpectation("`", false);
  const peg$e140 = peg$literalExpectation("!", false);
  const peg$e141 = peg$literalExpectation("[!", false);
  const peg$e142 = peg$literalExpectation("]", false);
  const peg$e143 = peg$otherExpectation("Line Terminator");
  const peg$e144 = peg$literalExpectation("\n", false);
  const peg$e145 = peg$literalExpectation("\r\n", false);
  const peg$e146 = peg$classExpectation(["\r", ["\u2028", "\u2029"]], false, false, false);
  const peg$e147 = peg$classExpectation(["\n", "\r", "\u2028", "\u2029"], true, false, false);
  const peg$e148 = peg$literalExpectation("http", false);
  const peg$e149 = peg$literalExpectation("s", false);
  const peg$e150 = peg$literalExpectation("://", false);
  const peg$e151 = peg$literalExpectation("mailto:", false);
  const peg$e152 = peg$classExpectation(["!", ["#", ";"], "=", ["?", "["], ["]", "_"], ["a", "{"], ["}", "~"]], false, false, false);
  function peg$f0() {
    return VERSION;
  }
  function peg$f1(b) {
    const cleaned_ = cleanEmptyTextNodes({ ...b });
    return cleaned_;
  }
  function peg$f2(b) {
    let lb_ = { ...b };
    removeTempParsingParent(lb_);
    const cleaned_ = cleanEmptyTextNodes(lb_);
    return cleaned_;
  }
  function peg$f3(b) {
    return { ...b };
  }
  function peg$f4(b) {
    return { ...b };
  }
  function peg$f5(b) {
    const cleaned_ = cleanEmptyTextNodes({ ...b });
    return cleaned_;
  }
  function peg$f6() {
    return [];
  }
  function peg$f7(h, t) {
    return { type: "heading", content: bitmarkPlusString(t), attrs: { level: h.length - 1 } };
  }
  function peg$f8(t) {
    return t;
  }
  function peg$f9(h, b) {
    return { ...h, content: b };
  }
  function peg$f10() {
    return { type: "codeBlock", language: "" };
  }
  function peg$f11(l) {
    return { type: "codeBlock", attrs: { language: l.trim().toLowerCase() } };
  }
  function peg$f12(c) {
    return [{ type: "text", text: unbreakscape(c.trim()) }];
  }
  function peg$f13(t) {
    return t;
  }
  function peg$f14(c, bl) {
    return { ...c, content: bl, attrs: {} };
  }
  function peg$f15(c, bl) {
    return { ...c, content: bl, attrs: {} };
  }
  function peg$f16(c, bl) {
    let start = bl[0]._tempListStart;
    return { ...c, attrs: { start }, content: bl };
  }
  function peg$f17(c, bl) {
    let start = bl[0]._tempListStart;
    return { ...c, attrs: { start }, content: bl };
  }
  function peg$f18(c, bl) {
    let start = bl[0]._tempListStart;
    return { ...c, attrs: { start }, content: bl };
  }
  function peg$f19(c, bl) {
    return { ...c, content: bl, attrs: {} };
  }
  function peg$f20(c, bl) {
    return { ...c, content: bl, attrs: {} };
  }
  function peg$f21(c, bl) {
    return { ...c, content: bl, attrs: {} };
  }
  function peg$f22() {
    return { type: "bulletList" };
  }
  function peg$f23() {
    return { type: "noBulletList" };
  }
  function peg$f24() {
    return { type: "orderedList" };
  }
  function peg$f25() {
    return { type: "orderedListRoman" };
  }
  function peg$f26() {
    return { type: "orderedListRomanLower" };
  }
  function peg$f27() {
    return { type: "letteredList" };
  }
  function peg$f28() {
    return { type: "letteredListLower" };
  }
  function peg$f29() {
    return { type: "taskList" };
  }
  function peg$f30(lt, listItem, lines, c) {
    return c;
  }
  function peg$f31(lt, listItem, lines, children) {
    let _tempParsingParent = "bulletList";
    const matchOrdered = lt.match(/•([0-9]+)([Ii]?) /);
    let start = 1;
    if (matchOrdered) {
      _tempParsingParent = "orderedList";
      if (matchOrdered.length > 1) {
        start = Number(matchOrdered[1]);
      }
      if (matchOrdered.length > 2) {
        let roman = matchOrdered[2];
        if ("I" == roman) {
          _tempParsingParent = "orderedListRoman";
        }
        if ("i" == roman) {
          _tempParsingParent = "orderedListRomanLower";
        }
      }
    }
    if ("\u2022_ " == lt) {
      _tempParsingParent = "noBulletList";
    }
    if ("\u2022A " == lt) {
      _tempParsingParent = "letteredList";
    }
    if ("\u2022a " == lt) {
      _tempParsingParent = "letteredListLower";
    }
    if ("\u2022+ " == lt || "\u2022- " == lt) {
      _tempParsingParent = "taskList";
    }
    let li = (listItem + lines.join("")).trim();
    let item = {
      type: "paragraph",
      attrs: {},
      content: bitmarkPlusString(li)
    };
    let content = [item];
    if (children && children[0] && children[0]._tempParsingParent) {
      let sublist = {
        type: children[0]._tempParsingParent,
        attrs: { start },
        content: children
      };
      if (sublist.type.startsWith("orderedList") || sublist.type.startsWith("letteredList")) {
        if (children[0]._tempListStart) {
          const start2 = children[0]._tempListStart;
          if (start2 > 1) sublist.attrs.start = start2;
        }
      }
      content.push(sublist);
    }
    let t = "listItem";
    let attrs = null;
    if ("taskList" == _tempParsingParent) {
      t = "taskItem";
      let checked = false;
      if ("\u2022+ " == lt) {
        checked = true;
      }
      attrs = { checked };
    }
    const _tempListStart = start;
    if (attrs) {
      return { type: t, attrs, content, _tempParsingParent, _tempListStart };
    } else {
      return { type: t, content, _tempParsingParent, _tempListStart };
    }
  }
  function peg$f32(ll) {
    return ll;
  }
  function peg$f33(i) {
    return i.join("") === indent;
  }
  function peg$f34(i) {
    return i.length > indent.length;
  }
  function peg$f35(i) {
    indentStack.push(indent);
    indent = i.join("");
  }
  function peg$f36() {
    indent = indentStack.pop();
    return true;
  }
  function peg$f37(body) {
    return { type: "paragraph", content: bitmarkPlusString(body.trim()), attrs: {} };
  }
  function peg$f38(body) {
    return { type: "paragraph", content: bitmarkPlusString(body.trim()), attrs: {} };
  }
  function peg$f39(body) {
    return { type: "paragraph", content: bitmarkPlusString(body.trim()), attrs: {} };
  }
  function peg$f40(t) {
    return t;
  }
  function peg$f41(t, u, ch) {
    const chain = Object.assign({}, ...ch);
    let imageAlignment_ = chain.alignment || "center";
    delete chain.alignment;
    let textAlign_ = chain.captionAlign || "left";
    delete chain.captionAlign;
    let alt_ = chain.alt || null;
    delete chain.alt;
    let title_ = chain.caption || null;
    delete chain.caption;
    let class_ = chain.align || "center";
    delete chain.align;
    let zoomDisabled_ = chain.zoomDisabled === void 0 ? true : typeof chain.zoomDisabled === "boolean" ? chain.zoomDisabled : String(chain.zoomDisabled).toLowerCase() === "true";
    delete chain.zoomDisabled;
    let image = {
      type: t,
      attrs: {
        alignment: imageAlignment_,
        textAlign: textAlign_,
        src: u,
        alt: unbreakscape(alt_),
        title: unbreakscape(title_),
        class: class_,
        zoomDisabled: zoomDisabled_,
        ...chain
      }
    };
    return image;
  }
  function peg$f42(ch) {
    return ch;
  }
  function peg$f43(str) {
    return { comment: str.trim() };
  }
  function peg$f44(p, v) {
    return { [p]: parseInt(v) };
  }
  function peg$f45(p, v) {
    return { [p]: v.trim() };
  }
  function peg$f46(p, v) {
    return { [p]: v };
  }
  function peg$f47(p, v) {
    return { [p]: v };
  }
  function peg$f48(p) {
    return { [p]: true };
  }
  function peg$f49() {
    return;
  }
  function peg$f50(p) {
    return { error: "Found unknown property or invalid value: " + p };
  }
  function peg$f51(bs) {
    return [{ type: "paragraph", content: bs, attrs: {} }];
  }
  function peg$f52(first, more) {
    const cleaned_ = cleanEmptyTextNodes(first ? [first, ...more.flat()] : more.flat());
    return cleaned_;
  }
  function peg$f53() {
    return { "type": "hardBreak" };
  }
  function peg$f54(t) {
    return { text: unbreakscape(t), type: "text" };
  }
  function peg$f55(t) {
    return { index: +t, type: "bit" };
  }
  function peg$f56(t) {
    return { attrs: { formula: unbreakscape(t) }, type: "latex" };
  }
  function peg$f57(alt, u, ch) {
    return { attrs: { alt, src: (u.pr + u.t).trim(), "alignmentVertical": "top", size: "line-height", ...Object.assign({}, ...ch), zoomDisabled: true }, type: "imageInline" };
  }
  function peg$f58(t, marks) {
    if (!marks) marks = [];
    return { marks, text: unbreakscape(t), type: "text" };
  }
  function peg$f59(t) {
    return { marks: [{ type: "bold" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f60(t) {
    return { marks: [{ type: "italic" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f61(t) {
    return { marks: [{ type: "light" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f62(t) {
    return { marks: [{ type: "highlight" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f63(u) {
    return { marks: [{ type: "link", attrs: { href: (u.pr + u.t).trim(), target: "_blank" } }], text: u.t, type: "text" };
  }
  function peg$f64(ch) {
    return ch;
  }
  function peg$f65(ch) {
    return ch;
  }
  function peg$f66(str) {
    return { comment: str };
  }
  function peg$f67(p, v) {
    return { [p]: v };
  }
  function peg$f68(p, v) {
    return { [p]: parseInt(v) };
  }
  function peg$f69(p, v) {
    return { type: "error", msg: p + " must be a positive integer.", found: v };
  }
  function peg$f70(p, v) {
    return { [p]: v };
  }
  function peg$f71(p, v) {
    return { [p]: v };
  }
  function peg$f72(str) {
    return { type: "link", attrs: { href: str.trim(), target: "_blank" } };
  }
  function peg$f73(str, rc, p) {
    return { type: "extref", attrs: { extref: str.trim(), references: rc, provider: p.trim() } };
  }
  function peg$f74(str, str2) {
    return { type: "xref", attrs: { xref: str.trim(), reference: str2.trim() } };
  }
  function peg$f75(str) {
    return { type: "xref", attrs: { xref: str.trim(), reference: "" } };
  }
  function peg$f76(str) {
    return { type: "ref", attrs: { reference: str.trim() } };
  }
  function peg$f77(str, ch) {
    const chain = Object.assign({}, ...ch);
    return { type: "symbol", attrs: { src: str.trim(), ...chain } };
  }
  function peg$f78(str) {
    return { type: "footnote", attrs: { content: bitmarkPlusString(str.trim()) } };
  }
  function peg$f79(str) {
    return { type: "footnote*", attrs: { content: bitmarkPlusString(str.trim()) } };
  }
  function peg$f80(str) {
    return { type: "var", attrs: { name: str.trim() } };
  }
  function peg$f81() {
    return { type: "code", attrs: { language: "plain text" } };
  }
  function peg$f82(lang) {
    return { type: "code", attrs: { language: lang.trim().toLowerCase() } };
  }
  function peg$f83(d) {
    return { type: "timer", attrs: { name: "", duration: d.trim() } };
  }
  function peg$f84(str, d) {
    return { type: "timer", attrs: { name: str.trim(), duration: d.trim() } };
  }
  function peg$f85(color) {
    return { type: "highlight", attrs: { color } };
  }
  function peg$f86(color) {
    return { type: "userHighlight", attrs: { color } };
  }
  function peg$f87(color) {
    return { type: "textStyle", attrs: { color } };
  }
  function peg$f88(str) {
    return { type: "colorPicker", attrs: { propertyRef: str.trim() } };
  }
  function peg$f89(style) {
    return { type: style };
  }
  function peg$f90(str) {
    return { type: "comment", comment: str };
  }
  function peg$f91(r) {
    return r.trim();
  }
  function peg$f92(bs) {
    return [{ type: "paragraph", content: bs, attrs: {} }];
  }
  function peg$f93(first, more) {
    const cleaned_ = cleanEmptyTextNodes(first ? [first, ...more.flat()] : more.flat());
    return cleaned_;
  }
  function peg$f94() {
    return { "type": "hardBreak" };
  }
  function peg$f95(t) {
    return { text: unbreakscape(t), type: "text" };
  }
  function peg$f96(t) {
    return { index: +t, type: "bit" };
  }
  function peg$f97(t) {
    return { marks: [{ type: "bold" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f98(t) {
    return { marks: [{ type: "italic" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f99(t) {
    return { marks: [{ type: "light" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f100(t) {
    return { marks: [{ type: "highlight" }], text: unbreakscape(t), type: "text" };
  }
  function peg$f101(pr, t) {
    return { pr, t };
  }
  let peg$currPos = options.peg$currPos | 0;
  let peg$savedPos = peg$currPos;
  const peg$posDetailsCache = [{ line: 1, column: 1 }];
  let peg$maxFailPos = peg$currPos;
  let peg$maxFailExpected = options.peg$maxFailExpected || [];
  let peg$silentFails = options.peg$silentFails | 0;
  let peg$result;
  if (options.startRule) {
    if (!(options.startRule in peg$startRuleFunctions)) {
      throw new Error(`Can't start parsing from rule "` + options.startRule + '".');
    }
    peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
  }
  function text() {
    return input.substring(peg$savedPos, peg$currPos);
  }
  function offset() {
    return peg$savedPos;
  }
  function range() {
    return {
      source: peg$source,
      start: peg$savedPos,
      end: peg$currPos
    };
  }
  function location() {
    return peg$computeLocation(peg$savedPos, peg$currPos);
  }
  function expected(description, location2) {
    location2 = location2 !== void 0 ? location2 : peg$computeLocation(peg$savedPos, peg$currPos);
    throw peg$buildStructuredError(
      [peg$otherExpectation(description)],
      input.substring(peg$savedPos, peg$currPos),
      location2
    );
  }
  function error(message, location2) {
    location2 = location2 !== void 0 ? location2 : peg$computeLocation(peg$savedPos, peg$currPos);
    throw peg$buildSimpleError(message, location2);
  }
  function peg$getUnicode(pos = peg$currPos) {
    const cp = input.codePointAt(pos);
    if (cp === void 0) {
      return "";
    }
    return String.fromCodePoint(cp);
  }
  function peg$literalExpectation(text2, ignoreCase) {
    return { type: "literal", text: text2, ignoreCase };
  }
  function peg$classExpectation(parts, inverted, ignoreCase, unicode) {
    return { type: "class", parts, inverted, ignoreCase, unicode };
  }
  function peg$anyExpectation() {
    return { type: "any" };
  }
  function peg$endExpectation() {
    return { type: "end" };
  }
  function peg$otherExpectation(description) {
    return { type: "other", description };
  }
  function peg$computePosDetails(pos) {
    let details = peg$posDetailsCache[pos];
    let p;
    if (details) {
      return details;
    } else {
      if (pos >= peg$posDetailsCache.length) {
        p = peg$posDetailsCache.length - 1;
      } else {
        p = pos;
        while (!peg$posDetailsCache[--p]) {
        }
      }
      details = peg$posDetailsCache[p];
      details = {
        line: details.line,
        column: details.column
      };
      while (p < pos) {
        if (input.charCodeAt(p) === 10) {
          details.line++;
          details.column = 1;
        } else {
          details.column++;
        }
        p++;
      }
      peg$posDetailsCache[pos] = details;
      return details;
    }
  }
  function peg$computeLocation(startPos, endPos, offset2) {
    const startPosDetails = peg$computePosDetails(startPos);
    const endPosDetails = peg$computePosDetails(endPos);
    const res = {
      source: peg$source,
      start: {
        offset: startPos,
        line: startPosDetails.line,
        column: startPosDetails.column
      },
      end: {
        offset: endPos,
        line: endPosDetails.line,
        column: endPosDetails.column
      }
    };
    if (offset2 && peg$source && typeof peg$source.offset === "function") {
      res.start = peg$source.offset(res.start);
      res.end = peg$source.offset(res.end);
    }
    return res;
  }
  function peg$fail(expected2) {
    if (peg$currPos < peg$maxFailPos) {
      return;
    }
    if (peg$currPos > peg$maxFailPos) {
      peg$maxFailPos = peg$currPos;
      peg$maxFailExpected = [];
    }
    peg$maxFailExpected.push(expected2);
  }
  function peg$buildSimpleError(message, location2) {
    return new peg$SyntaxError(message, null, null, location2);
  }
  function peg$buildStructuredError(expected2, found, location2) {
    return new peg$SyntaxError(
      peg$SyntaxError.buildMessage(expected2, found),
      expected2,
      found,
      location2
    );
  }
  function peg$parseversion() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    if (input.length > peg$currPos) {
      s2 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e0);
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      if (input.length > peg$currPos) {
        s2 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e0);
        }
      }
    }
    peg$savedPos = s0;
    s1 = peg$f0();
    s0 = s1;
    return s0;
  }
  function peg$parsebitmarkPlusPlus() {
    let s0, s1;
    peg$silentFails++;
    s0 = [];
    s1 = peg$parseBlock();
    if (s1 !== peg$FAILED) {
      while (s1 !== peg$FAILED) {
        s0.push(s1);
        s1 = peg$parseBlock();
      }
    } else {
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$parseNoContent();
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e1);
      }
    }
    return s0;
  }
  function peg$parseBlock() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseTitleBlock();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f1(s1);
    }
    s0 = s1;
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseListBlock();
      if (s1 !== peg$FAILED) {
        peg$savedPos = s0;
        s1 = peg$f2(s1);
      }
      s0 = s1;
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$parseImageBlock();
        if (s1 !== peg$FAILED) {
          peg$savedPos = s0;
          s1 = peg$f3(s1);
        }
        s0 = s1;
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          s1 = peg$parseCodeBlock();
          if (s1 !== peg$FAILED) {
            peg$savedPos = s0;
            s1 = peg$f4(s1);
          }
          s0 = s1;
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parseParagraph();
            if (s1 !== peg$FAILED) {
              peg$savedPos = s0;
              s1 = peg$f5(s1);
            }
            s0 = s1;
          }
        }
      }
    }
    return s0;
  }
  function peg$parseBlockStart() {
    let s0;
    s0 = peg$parseTitleTags();
    if (s0 === peg$FAILED) {
      s0 = peg$parseListTags();
      if (s0 === peg$FAILED) {
        s0 = peg$parseImageBlock();
        if (s0 === peg$FAILED) {
          s0 = peg$parseCodeHeader();
          if (s0 === peg$FAILED) {
            s0 = peg$parseExplicitParagraphHeader();
          }
        }
      }
    }
    return s0;
  }
  function peg$parseBlockTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 124) {
      s0 = peg$c0;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e2);
      }
    }
    return s0;
  }
  function peg$parseNoContent() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = "";
    peg$savedPos = s0;
    s1 = peg$f6();
    s0 = s1;
    return s0;
  }
  function peg$parseTitleTags() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c1) {
      s0 = peg$c1;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e3);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 3) === peg$c2) {
        s0 = peg$c2;
        peg$currPos += 3;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e4);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 2) === peg$c3) {
          s0 = peg$c3;
          peg$currPos += 2;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e5);
          }
        }
      }
    }
    return s0;
  }
  function peg$parseTitleBlock() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$parseTitleTags();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$parsechar();
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$parsechar();
      }
      s2 = input.substring(s2, peg$currPos);
      s3 = peg$parseEOL();
      if (s3 !== peg$FAILED) {
        s4 = peg$parseNL();
        if (s4 === peg$FAILED) {
          s4 = null;
        }
        peg$savedPos = s0;
        s0 = peg$f7(s1, s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCodeType() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c4) {
      s0 = peg$c4;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e6);
      }
    }
    return s0;
  }
  function peg$parseCodeTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseBlockTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseCodeType();
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f8(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCodeBlock() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseCodeHeader();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseCodeBody();
      peg$savedPos = s0;
      s0 = peg$f9(s1, s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCodeHeader() {
    let s0, s1, s2, s3, s4, s5, s6, s7;
    s0 = peg$currPos;
    s1 = peg$parseCodeTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = [];
      s5 = input.charAt(peg$currPos);
      if (peg$r0.test(s5)) {
        peg$currPos++;
      } else {
        s5 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e7);
        }
      }
      while (s5 !== peg$FAILED) {
        s4.push(s5);
        s5 = input.charAt(peg$currPos);
        if (peg$r0.test(s5)) {
          peg$currPos++;
        } else {
          s5 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e7);
          }
        }
      }
      s5 = peg$parseEOL();
      if (s5 !== peg$FAILED) {
        s4 = [s4, s5];
        s3 = s4;
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f10();
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseCodeTag();
      if (s1 !== peg$FAILED) {
        if (input.charCodeAt(peg$currPos) === 58) {
          s2 = peg$c5;
          peg$currPos++;
        } else {
          s2 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e8);
          }
        }
        if (s2 !== peg$FAILED) {
          s3 = peg$parseCodeLanguage();
          if (s3 !== peg$FAILED) {
            s4 = peg$currPos;
            s5 = peg$currPos;
            s6 = [];
            s7 = input.charAt(peg$currPos);
            if (peg$r0.test(s7)) {
              peg$currPos++;
            } else {
              s7 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e7);
              }
            }
            while (s7 !== peg$FAILED) {
              s6.push(s7);
              s7 = input.charAt(peg$currPos);
              if (peg$r0.test(s7)) {
                peg$currPos++;
              } else {
                s7 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e7);
                }
              }
            }
            s7 = peg$parseEOL();
            if (s7 !== peg$FAILED) {
              s6 = [s6, s7];
              s5 = s6;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              s4 = input.substring(s4, peg$currPos);
            } else {
              s4 = s5;
            }
            if (s4 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f11(s3);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseCodeLanguage() {
    let s0, s1, s2;
    if (input.substr(peg$currPos, 9) === peg$c6) {
      s0 = peg$c6;
      peg$currPos += 9;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e9);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 9) === peg$c7) {
        s0 = peg$c7;
        peg$currPos += 9;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e10);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 10) === peg$c8) {
          s0 = peg$c8;
          peg$currPos += 10;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e11);
          }
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          s1 = [];
          s2 = peg$parsechar();
          if (s2 !== peg$FAILED) {
            while (s2 !== peg$FAILED) {
              s1.push(s2);
              s2 = peg$parsechar();
            }
          } else {
            s1 = peg$FAILED;
          }
          if (s1 !== peg$FAILED) {
            s0 = input.substring(s0, peg$currPos);
          } else {
            s0 = s1;
          }
          if (s0 === peg$FAILED) {
            s0 = "";
          }
        }
      }
    }
    return s0;
  }
  function peg$parseCodeBody() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = [];
    s3 = peg$parseCodeLine();
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      s3 = peg$parseCodeLine();
    }
    s1 = input.substring(s1, peg$currPos);
    peg$savedPos = s0;
    s1 = peg$f12(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseCodeLine() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseBlockStart();
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = [];
      s5 = peg$parsechar();
      if (s5 !== peg$FAILED) {
        while (s5 !== peg$FAILED) {
          s4.push(s5);
          s5 = peg$parsechar();
        }
      } else {
        s4 = peg$FAILED;
      }
      if (s4 !== peg$FAILED) {
        s5 = peg$parseEOL();
        if (s5 !== peg$FAILED) {
          s4 = [s4, s5];
          s3 = s4;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f13(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$parseNL();
    }
    return s0;
  }
  function peg$parseBulletListTag() {
    let s0;
    if (input.substr(peg$currPos, 2) === peg$c9) {
      s0 = peg$c9;
      peg$currPos += 2;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e12);
      }
    }
    return s0;
  }
  function peg$parseNoBulletListTag() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c10) {
      s0 = peg$c10;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e13);
      }
    }
    return s0;
  }
  function peg$parseOrderedListTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 8226) {
      s2 = peg$c11;
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e14);
      }
    }
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r1.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = input.charAt(peg$currPos);
          if (peg$r1.test(s4)) {
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e15);
            }
          }
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        if (input.charCodeAt(peg$currPos) === 32) {
          s4 = peg$c12;
          peg$currPos++;
        } else {
          s4 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e16);
          }
        }
        if (s4 !== peg$FAILED) {
          s2 = [s2, s3, s4];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parseOrderedListRomanTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 8226) {
      s2 = peg$c11;
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e14);
      }
    }
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r1.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = input.charAt(peg$currPos);
          if (peg$r1.test(s4)) {
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e15);
            }
          }
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        if (input.substr(peg$currPos, 2) === peg$c13) {
          s4 = peg$c13;
          peg$currPos += 2;
        } else {
          s4 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e17);
          }
        }
        if (s4 !== peg$FAILED) {
          s2 = [s2, s3, s4];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parseOrderedListRomanLowerTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 8226) {
      s2 = peg$c11;
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e14);
      }
    }
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r1.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = input.charAt(peg$currPos);
          if (peg$r1.test(s4)) {
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e15);
            }
          }
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        if (input.substr(peg$currPos, 2) === peg$c14) {
          s4 = peg$c14;
          peg$currPos += 2;
        } else {
          s4 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e18);
          }
        }
        if (s4 !== peg$FAILED) {
          s2 = [s2, s3, s4];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parseLetteredListTag() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c15) {
      s0 = peg$c15;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e19);
      }
    }
    return s0;
  }
  function peg$parseLetteredListLowerTag() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c16) {
      s0 = peg$c16;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e20);
      }
    }
    return s0;
  }
  function peg$parseTaskListTag() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c17) {
      s0 = peg$c17;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e21);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 3) === peg$c18) {
        s0 = peg$c18;
        peg$currPos += 3;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e22);
        }
      }
    }
    return s0;
  }
  function peg$parseListTags() {
    let s0;
    s0 = peg$parseBulletListTag();
    if (s0 === peg$FAILED) {
      s0 = peg$parseNoBulletListTag();
      if (s0 === peg$FAILED) {
        s0 = peg$parseOrderedListTag();
        if (s0 === peg$FAILED) {
          s0 = peg$parseOrderedListRomanTag();
          if (s0 === peg$FAILED) {
            s0 = peg$parseOrderedListRomanLowerTag();
            if (s0 === peg$FAILED) {
              s0 = peg$parseLetteredListTag();
              if (s0 === peg$FAILED) {
                s0 = peg$parseLetteredListLowerTag();
                if (s0 === peg$FAILED) {
                  s0 = peg$parseTaskListTag();
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseListBlock() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$parseBulletListContainer();
    if (s1 !== peg$FAILED) {
      s2 = [];
      s3 = peg$parseBulletListLine();
      if (s3 !== peg$FAILED) {
        while (s3 !== peg$FAILED) {
          s2.push(s3);
          s3 = peg$parseBulletListLine();
        }
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseNL();
        if (s3 === peg$FAILED) {
          s3 = null;
        }
        peg$savedPos = s0;
        s0 = peg$f14(s1, s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseNoBulletListContainer();
      if (s1 !== peg$FAILED) {
        s2 = [];
        s3 = peg$parseBulletListLine();
        if (s3 !== peg$FAILED) {
          while (s3 !== peg$FAILED) {
            s2.push(s3);
            s3 = peg$parseBulletListLine();
          }
        } else {
          s2 = peg$FAILED;
        }
        if (s2 !== peg$FAILED) {
          s3 = peg$parseNL();
          if (s3 === peg$FAILED) {
            s3 = null;
          }
          peg$savedPos = s0;
          s0 = peg$f15(s1, s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$parseOrderedListContainer();
        if (s1 !== peg$FAILED) {
          s2 = [];
          s3 = peg$parseBulletListLine();
          if (s3 !== peg$FAILED) {
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parseBulletListLine();
            }
          } else {
            s2 = peg$FAILED;
          }
          if (s2 !== peg$FAILED) {
            s3 = peg$parseNL();
            if (s3 === peg$FAILED) {
              s3 = null;
            }
            peg$savedPos = s0;
            s0 = peg$f16(s1, s2);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          s1 = peg$parseOrderedListRomanContainer();
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parseBulletListLine();
            if (s3 !== peg$FAILED) {
              while (s3 !== peg$FAILED) {
                s2.push(s3);
                s3 = peg$parseBulletListLine();
              }
            } else {
              s2 = peg$FAILED;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parseNL();
              if (s3 === peg$FAILED) {
                s3 = null;
              }
              peg$savedPos = s0;
              s0 = peg$f17(s1, s2);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parseOrderedListRomanLowerContainer();
            if (s1 !== peg$FAILED) {
              s2 = [];
              s3 = peg$parseBulletListLine();
              if (s3 !== peg$FAILED) {
                while (s3 !== peg$FAILED) {
                  s2.push(s3);
                  s3 = peg$parseBulletListLine();
                }
              } else {
                s2 = peg$FAILED;
              }
              if (s2 !== peg$FAILED) {
                s3 = peg$parseNL();
                if (s3 === peg$FAILED) {
                  s3 = null;
                }
                peg$savedPos = s0;
                s0 = peg$f18(s1, s2);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
            if (s0 === peg$FAILED) {
              s0 = peg$currPos;
              s1 = peg$parseLetteredListContainer();
              if (s1 !== peg$FAILED) {
                s2 = [];
                s3 = peg$parseBulletListLine();
                if (s3 !== peg$FAILED) {
                  while (s3 !== peg$FAILED) {
                    s2.push(s3);
                    s3 = peg$parseBulletListLine();
                  }
                } else {
                  s2 = peg$FAILED;
                }
                if (s2 !== peg$FAILED) {
                  s3 = peg$parseNL();
                  if (s3 === peg$FAILED) {
                    s3 = null;
                  }
                  peg$savedPos = s0;
                  s0 = peg$f19(s1, s2);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
              if (s0 === peg$FAILED) {
                s0 = peg$currPos;
                s1 = peg$parseLetteredListLowerContainer();
                if (s1 !== peg$FAILED) {
                  s2 = [];
                  s3 = peg$parseBulletListLine();
                  if (s3 !== peg$FAILED) {
                    while (s3 !== peg$FAILED) {
                      s2.push(s3);
                      s3 = peg$parseBulletListLine();
                    }
                  } else {
                    s2 = peg$FAILED;
                  }
                  if (s2 !== peg$FAILED) {
                    s3 = peg$parseNL();
                    if (s3 === peg$FAILED) {
                      s3 = null;
                    }
                    peg$savedPos = s0;
                    s0 = peg$f20(s1, s2);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
                if (s0 === peg$FAILED) {
                  s0 = peg$currPos;
                  s1 = peg$parseTaskListContainer();
                  if (s1 !== peg$FAILED) {
                    s2 = [];
                    s3 = peg$parseBulletListLine();
                    if (s3 !== peg$FAILED) {
                      while (s3 !== peg$FAILED) {
                        s2.push(s3);
                        s3 = peg$parseBulletListLine();
                      }
                    } else {
                      s2 = peg$FAILED;
                    }
                    if (s2 !== peg$FAILED) {
                      s3 = peg$parseNL();
                      if (s3 === peg$FAILED) {
                        s3 = null;
                      }
                      peg$savedPos = s0;
                      s0 = peg$f21(s1, s2);
                    } else {
                      peg$currPos = s0;
                      s0 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseBulletListContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseBulletListTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f22();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseNoBulletListContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseNoBulletListTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f23();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseOrderedListContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseOrderedListTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f24();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseOrderedListRomanContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseOrderedListRomanTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f25();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseOrderedListRomanLowerContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseOrderedListRomanLowerTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f26();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseLetteredListContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseLetteredListTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f27();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseLetteredListLowerContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseLetteredListLowerTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f28();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseTaskListContainer() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseTaskListTag();
    peg$silentFails--;
    if (s2 !== peg$FAILED) {
      peg$currPos = s1;
      s1 = void 0;
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f29();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseBulletListLine() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8;
    s0 = peg$currPos;
    s1 = peg$parseSAMEDENT();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseListTags();
      if (s2 !== peg$FAILED) {
        s3 = peg$currPos;
        s4 = peg$currPos;
        s5 = [];
        s6 = peg$currPos;
        s7 = peg$currPos;
        peg$silentFails++;
        s8 = peg$parseNL();
        peg$silentFails--;
        if (s8 === peg$FAILED) {
          s7 = void 0;
        } else {
          peg$currPos = s7;
          s7 = peg$FAILED;
        }
        if (s7 !== peg$FAILED) {
          if (input.length > peg$currPos) {
            s8 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s8 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e0);
            }
          }
          if (s8 !== peg$FAILED) {
            s7 = [s7, s8];
            s6 = s7;
          } else {
            peg$currPos = s6;
            s6 = peg$FAILED;
          }
        } else {
          peg$currPos = s6;
          s6 = peg$FAILED;
        }
        while (s6 !== peg$FAILED) {
          s5.push(s6);
          s6 = peg$currPos;
          s7 = peg$currPos;
          peg$silentFails++;
          s8 = peg$parseNL();
          peg$silentFails--;
          if (s8 === peg$FAILED) {
            s7 = void 0;
          } else {
            peg$currPos = s7;
            s7 = peg$FAILED;
          }
          if (s7 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s8 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s8 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s8 !== peg$FAILED) {
              s7 = [s7, s8];
              s6 = s7;
            } else {
              peg$currPos = s6;
              s6 = peg$FAILED;
            }
          } else {
            peg$currPos = s6;
            s6 = peg$FAILED;
          }
        }
        s6 = peg$parseNL();
        if (s6 === peg$FAILED) {
          s6 = null;
        }
        s5 = [s5, s6];
        s4 = s5;
        s3 = input.substring(s3, peg$currPos);
        s4 = [];
        s5 = peg$parseListLine();
        while (s5 !== peg$FAILED) {
          s4.push(s5);
          s5 = peg$parseListLine();
        }
        s5 = peg$currPos;
        s6 = peg$parseINDENT();
        if (s6 !== peg$FAILED) {
          s7 = [];
          s8 = peg$parseBulletListLine();
          while (s8 !== peg$FAILED) {
            s7.push(s8);
            s8 = peg$parseBulletListLine();
          }
          s8 = peg$parseDEDENT();
          if (s8 !== peg$FAILED) {
            peg$savedPos = s5;
            s5 = peg$f30(s2, s3, s4, s7);
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 === peg$FAILED) {
          s5 = null;
        }
        peg$savedPos = s0;
        s0 = peg$f31(s2, s3, s4, s5);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseListLine() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseBlankLine();
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseSAMEDENT();
      if (s2 !== peg$FAILED) {
        s3 = peg$currPos;
        peg$silentFails++;
        s4 = peg$parseListTags();
        peg$silentFails--;
        if (s4 === peg$FAILED) {
          s3 = void 0;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
        if (s3 !== peg$FAILED) {
          s4 = peg$currPos;
          s5 = peg$currPos;
          s6 = [];
          s7 = peg$currPos;
          s8 = peg$currPos;
          peg$silentFails++;
          s9 = peg$parseNL();
          peg$silentFails--;
          if (s9 === peg$FAILED) {
            s8 = void 0;
          } else {
            peg$currPos = s8;
            s8 = peg$FAILED;
          }
          if (s8 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s9 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s9 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s9 !== peg$FAILED) {
              s8 = [s8, s9];
              s7 = s8;
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
          } else {
            peg$currPos = s7;
            s7 = peg$FAILED;
          }
          if (s7 !== peg$FAILED) {
            while (s7 !== peg$FAILED) {
              s6.push(s7);
              s7 = peg$currPos;
              s8 = peg$currPos;
              peg$silentFails++;
              s9 = peg$parseNL();
              peg$silentFails--;
              if (s9 === peg$FAILED) {
                s8 = void 0;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
              if (s8 !== peg$FAILED) {
                if (input.length > peg$currPos) {
                  s9 = input.charAt(peg$currPos);
                  peg$currPos++;
                } else {
                  s9 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e0);
                  }
                }
                if (s9 !== peg$FAILED) {
                  s8 = [s8, s9];
                  s7 = s8;
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            }
          } else {
            s6 = peg$FAILED;
          }
          if (s6 !== peg$FAILED) {
            s7 = peg$parseEOL();
            if (s7 !== peg$FAILED) {
              s6 = [s6, s7];
              s5 = s6;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s4 = input.substring(s4, peg$currPos);
          } else {
            s4 = s5;
          }
          if (s4 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f32(s4);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBlankLine() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = input.charAt(peg$currPos);
    if (peg$r0.test(s2)) {
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e7);
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = input.charAt(peg$currPos);
      if (peg$r0.test(s2)) {
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e7);
        }
      }
    }
    s2 = peg$parseNL();
    if (s2 !== peg$FAILED) {
      s1 = [s1, s2];
      s0 = s1;
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseSAMEDENT() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    if (input.charCodeAt(peg$currPos) === 9) {
      s2 = peg$c19;
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e23);
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      if (input.charCodeAt(peg$currPos) === 9) {
        s2 = peg$c19;
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e23);
        }
      }
    }
    peg$savedPos = peg$currPos;
    s2 = peg$f33(s1);
    if (s2) {
      s2 = void 0;
    } else {
      s2 = peg$FAILED;
    }
    if (s2 !== peg$FAILED) {
      s1 = [s1, s2];
      s0 = s1;
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseINDENT() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    peg$silentFails++;
    s1 = peg$currPos;
    s2 = [];
    if (input.charCodeAt(peg$currPos) === 9) {
      s3 = peg$c19;
      peg$currPos++;
    } else {
      s3 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e23);
      }
    }
    if (s3 !== peg$FAILED) {
      while (s3 !== peg$FAILED) {
        s2.push(s3);
        if (input.charCodeAt(peg$currPos) === 9) {
          s3 = peg$c19;
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e23);
          }
        }
      }
    } else {
      s2 = peg$FAILED;
    }
    if (s2 !== peg$FAILED) {
      peg$savedPos = peg$currPos;
      s3 = peg$f34(s2);
      if (s3) {
        s3 = void 0;
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        peg$savedPos = s1;
        s1 = peg$f35(s2);
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    peg$silentFails--;
    if (s1 !== peg$FAILED) {
      peg$currPos = s0;
      s0 = void 0;
    } else {
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseDEDENT() {
    let s0;
    peg$savedPos = peg$currPos;
    s0 = peg$f36();
    if (s0) {
      s0 = void 0;
    } else {
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseParagraph() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseBlockStart();
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseParagraphBody();
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f37(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseExplicitParagraphHeader();
      if (s1 !== peg$FAILED) {
        s2 = peg$parseParagraphBody();
        if (s2 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f38(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$parseExplicitParagraphHeader();
        if (s1 !== peg$FAILED) {
          s2 = "";
          peg$savedPos = s0;
          s0 = peg$f39(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      }
    }
    return s0;
  }
  function peg$parseParagraphBody() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseParagraphLine();
    if (s2 !== peg$FAILED) {
      while (s2 !== peg$FAILED) {
        s1.push(s2);
        s2 = peg$parseParagraphLine();
      }
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parseParagraphLine() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$parseBlockStart();
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = [];
      s5 = peg$parsechar();
      if (s5 !== peg$FAILED) {
        while (s5 !== peg$FAILED) {
          s4.push(s5);
          s5 = peg$parsechar();
        }
      } else {
        s4 = peg$FAILED;
      }
      if (s4 !== peg$FAILED) {
        s5 = peg$parseEOL();
        if (s5 !== peg$FAILED) {
          s4 = [s4, s5];
          s3 = s4;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$parseNL();
    }
    return s0;
  }
  function peg$parseExplicitParagraphHeader() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$parseBlockTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = [];
      s5 = input.charAt(peg$currPos);
      if (peg$r0.test(s5)) {
        peg$currPos++;
      } else {
        s5 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e7);
        }
      }
      while (s5 !== peg$FAILED) {
        s4.push(s5);
        s5 = input.charAt(peg$currPos);
        if (peg$r0.test(s5)) {
          peg$currPos++;
        } else {
          s5 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e7);
          }
        }
      }
      s5 = peg$parseEOL();
      if (s5 !== peg$FAILED) {
        s4 = [s4, s5];
        s3 = s4;
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseNL();
        if (s3 === peg$FAILED) {
          s3 = null;
        }
        s1 = [s1, s2, s3];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseImageType() {
    let s0;
    if (input.substr(peg$currPos, 5) === peg$c20) {
      s0 = peg$c20;
      peg$currPos += 5;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e24);
      }
    }
    return s0;
  }
  function peg$parseImageTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseBlockTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseImageType();
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f40(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseImageBlock() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
    s0 = peg$currPos;
    s1 = peg$parseImageTag();
    if (s1 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 58) {
        s2 = peg$c5;
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e8);
        }
      }
      if (s2 !== peg$FAILED) {
        if (input.charCodeAt(peg$currPos) === 32) {
          s3 = peg$c12;
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e16);
          }
        }
        if (s3 === peg$FAILED) {
          s3 = null;
        }
        s4 = peg$parseUrlHttp();
        if (s4 !== peg$FAILED) {
          s5 = [];
          if (input.charCodeAt(peg$currPos) === 32) {
            s6 = peg$c12;
            peg$currPos++;
          } else {
            s6 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e16);
            }
          }
          while (s6 !== peg$FAILED) {
            s5.push(s6);
            if (input.charCodeAt(peg$currPos) === 32) {
              s6 = peg$c12;
              peg$currPos++;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
          }
          s6 = peg$parseBlockTag();
          if (s6 !== peg$FAILED) {
            s7 = peg$parseMediaChain();
            s8 = peg$currPos;
            s9 = peg$currPos;
            s10 = [];
            s11 = input.charAt(peg$currPos);
            if (peg$r0.test(s11)) {
              peg$currPos++;
            } else {
              s11 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e7);
              }
            }
            while (s11 !== peg$FAILED) {
              s10.push(s11);
              s11 = input.charAt(peg$currPos);
              if (peg$r0.test(s11)) {
                peg$currPos++;
              } else {
                s11 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e7);
                }
              }
            }
            s11 = peg$parseEOL();
            if (s11 !== peg$FAILED) {
              s10 = [s10, s11];
              s9 = s10;
            } else {
              peg$currPos = s9;
              s9 = peg$FAILED;
            }
            if (s9 !== peg$FAILED) {
              s8 = input.substring(s8, peg$currPos);
            } else {
              s8 = s9;
            }
            if (s8 !== peg$FAILED) {
              s9 = peg$parseNL();
              if (s9 === peg$FAILED) {
                s9 = null;
              }
              peg$savedPos = s0;
              s0 = peg$f41(s1, s4, s7);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseMediaChain() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseMediaChainItem();
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseMediaChainItem();
    }
    peg$savedPos = s0;
    s1 = peg$f42(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseMediaChainItem() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 35) {
      s1 = peg$c21;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e25);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$currPos;
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseBlockTag();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s5 = [s5, s6];
          s4 = s5;
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      } else {
        peg$currPos = s4;
        s4 = peg$FAILED;
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parsechar();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      }
      s2 = input.substring(s2, peg$currPos);
      s3 = peg$parseBlockTag();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f43(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.charCodeAt(peg$currPos) === 64) {
        s1 = peg$c22;
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e26);
        }
      }
      if (s1 === peg$FAILED) {
        s1 = null;
      }
      s2 = peg$parseMediaSizeTags();
      if (s2 !== peg$FAILED) {
        if (input.charCodeAt(peg$currPos) === 58) {
          s3 = peg$c5;
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e8);
          }
        }
        if (s3 !== peg$FAILED) {
          s4 = [];
          if (input.charCodeAt(peg$currPos) === 32) {
            s5 = peg$c12;
            peg$currPos++;
          } else {
            s5 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e16);
            }
          }
          while (s5 !== peg$FAILED) {
            s4.push(s5);
            if (input.charCodeAt(peg$currPos) === 32) {
              s5 = peg$c12;
              peg$currPos++;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
          }
          s5 = peg$currPos;
          s6 = [];
          s7 = peg$currPos;
          s8 = peg$currPos;
          peg$silentFails++;
          s9 = peg$parseBlockTag();
          peg$silentFails--;
          if (s9 === peg$FAILED) {
            s8 = void 0;
          } else {
            peg$currPos = s8;
            s8 = peg$FAILED;
          }
          if (s8 !== peg$FAILED) {
            s9 = input.charAt(peg$currPos);
            if (peg$r1.test(s9)) {
              peg$currPos++;
            } else {
              s9 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e15);
              }
            }
            if (s9 !== peg$FAILED) {
              s8 = [s8, s9];
              s7 = s8;
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
          } else {
            peg$currPos = s7;
            s7 = peg$FAILED;
          }
          if (s7 !== peg$FAILED) {
            while (s7 !== peg$FAILED) {
              s6.push(s7);
              s7 = peg$currPos;
              s8 = peg$currPos;
              peg$silentFails++;
              s9 = peg$parseBlockTag();
              peg$silentFails--;
              if (s9 === peg$FAILED) {
                s8 = void 0;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
              if (s8 !== peg$FAILED) {
                s9 = input.charAt(peg$currPos);
                if (peg$r1.test(s9)) {
                  peg$currPos++;
                } else {
                  s9 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e15);
                  }
                }
                if (s9 !== peg$FAILED) {
                  s8 = [s8, s9];
                  s7 = s8;
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            }
          } else {
            s6 = peg$FAILED;
          }
          if (s6 !== peg$FAILED) {
            s5 = input.substring(s5, peg$currPos);
          } else {
            s5 = s6;
          }
          if (s5 !== peg$FAILED) {
            s6 = [];
            if (input.charCodeAt(peg$currPos) === 32) {
              s7 = peg$c12;
              peg$currPos++;
            } else {
              s7 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
            while (s7 !== peg$FAILED) {
              s6.push(s7);
              if (input.charCodeAt(peg$currPos) === 32) {
                s7 = peg$c12;
                peg$currPos++;
              } else {
                s7 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e16);
                }
              }
            }
            s7 = peg$parseBlockTag();
            if (s7 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f44(s2, s5);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        if (input.charCodeAt(peg$currPos) === 64) {
          s1 = peg$c22;
          peg$currPos++;
        } else {
          s1 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e26);
          }
        }
        if (s1 === peg$FAILED) {
          s1 = null;
        }
        s2 = peg$parseMediaTextTags();
        if (s2 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 58) {
            s3 = peg$c5;
            peg$currPos++;
          } else {
            s3 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e8);
            }
          }
          if (s3 !== peg$FAILED) {
            s4 = [];
            if (input.charCodeAt(peg$currPos) === 32) {
              s5 = peg$c12;
              peg$currPos++;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
            while (s5 !== peg$FAILED) {
              s4.push(s5);
              if (input.charCodeAt(peg$currPos) === 32) {
                s5 = peg$c12;
                peg$currPos++;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e16);
                }
              }
            }
            s5 = peg$currPos;
            s6 = [];
            s7 = peg$currPos;
            s8 = peg$currPos;
            peg$silentFails++;
            s9 = peg$parseBlockTag();
            peg$silentFails--;
            if (s9 === peg$FAILED) {
              s8 = void 0;
            } else {
              peg$currPos = s8;
              s8 = peg$FAILED;
            }
            if (s8 !== peg$FAILED) {
              s9 = peg$parsechar();
              if (s9 !== peg$FAILED) {
                s8 = [s8, s9];
                s7 = s8;
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
            while (s7 !== peg$FAILED) {
              s6.push(s7);
              s7 = peg$currPos;
              s8 = peg$currPos;
              peg$silentFails++;
              s9 = peg$parseBlockTag();
              peg$silentFails--;
              if (s9 === peg$FAILED) {
                s8 = void 0;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
              if (s8 !== peg$FAILED) {
                s9 = peg$parsechar();
                if (s9 !== peg$FAILED) {
                  s8 = [s8, s9];
                  s7 = s8;
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            }
            s5 = input.substring(s5, peg$currPos);
            s6 = peg$parseBlockTag();
            if (s6 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f45(s2, s5);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          if (input.charCodeAt(peg$currPos) === 64) {
            s1 = peg$c22;
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e26);
            }
          }
          if (s1 === peg$FAILED) {
            s1 = null;
          }
          s2 = peg$parseMediaAlignmentTags();
          if (s2 !== peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 58) {
              s3 = peg$c5;
              peg$currPos++;
            } else {
              s3 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e8);
              }
            }
            if (s3 !== peg$FAILED) {
              s4 = [];
              if (input.charCodeAt(peg$currPos) === 32) {
                s5 = peg$c12;
                peg$currPos++;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e16);
                }
              }
              while (s5 !== peg$FAILED) {
                s4.push(s5);
                if (input.charCodeAt(peg$currPos) === 32) {
                  s5 = peg$c12;
                  peg$currPos++;
                } else {
                  s5 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
              }
              s5 = peg$parseMediaAlignment();
              if (s5 !== peg$FAILED) {
                s6 = [];
                if (input.charCodeAt(peg$currPos) === 32) {
                  s7 = peg$c12;
                  peg$currPos++;
                } else {
                  s7 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
                while (s7 !== peg$FAILED) {
                  s6.push(s7);
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s7 = peg$c12;
                    peg$currPos++;
                  } else {
                    s7 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                }
                s7 = peg$parseBlockTag();
                if (s7 !== peg$FAILED) {
                  peg$savedPos = s0;
                  s0 = peg$f46(s2, s5);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            if (input.charCodeAt(peg$currPos) === 64) {
              s1 = peg$c22;
              peg$currPos++;
            } else {
              s1 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e26);
              }
            }
            if (s1 === peg$FAILED) {
              s1 = null;
            }
            s2 = peg$parseMediaBooleanTags();
            if (s2 !== peg$FAILED) {
              if (input.charCodeAt(peg$currPos) === 58) {
                s3 = peg$c5;
                peg$currPos++;
              } else {
                s3 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e8);
                }
              }
              if (s3 !== peg$FAILED) {
                s4 = [];
                if (input.charCodeAt(peg$currPos) === 32) {
                  s5 = peg$c12;
                  peg$currPos++;
                } else {
                  s5 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
                while (s5 !== peg$FAILED) {
                  s4.push(s5);
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s5 = peg$c12;
                    peg$currPos++;
                  } else {
                    s5 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                }
                s5 = peg$parseBoolean();
                if (s5 !== peg$FAILED) {
                  s6 = [];
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s7 = peg$c12;
                    peg$currPos++;
                  } else {
                    s7 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                  while (s7 !== peg$FAILED) {
                    s6.push(s7);
                    if (input.charCodeAt(peg$currPos) === 32) {
                      s7 = peg$c12;
                      peg$currPos++;
                    } else {
                      s7 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e16);
                      }
                    }
                  }
                  s7 = peg$parseBlockTag();
                  if (s7 !== peg$FAILED) {
                    peg$savedPos = s0;
                    s0 = peg$f47(s2, s5);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
            if (s0 === peg$FAILED) {
              s0 = peg$currPos;
              if (input.charCodeAt(peg$currPos) === 64) {
                s1 = peg$c22;
                peg$currPos++;
              } else {
                s1 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e26);
                }
              }
              if (s1 === peg$FAILED) {
                s1 = null;
              }
              s2 = peg$parseMediaBooleanTags();
              if (s2 !== peg$FAILED) {
                s3 = [];
                if (input.charCodeAt(peg$currPos) === 32) {
                  s4 = peg$c12;
                  peg$currPos++;
                } else {
                  s4 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
                while (s4 !== peg$FAILED) {
                  s3.push(s4);
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s4 = peg$c12;
                    peg$currPos++;
                  } else {
                    s4 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                }
                s4 = peg$parseBlockTag();
                if (s4 !== peg$FAILED) {
                  peg$savedPos = s0;
                  s0 = peg$f48(s2);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
              if (s0 === peg$FAILED) {
                s0 = peg$currPos;
                if (input.charCodeAt(peg$currPos) === 64) {
                  s1 = peg$c22;
                  peg$currPos++;
                } else {
                  s1 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e26);
                  }
                }
                if (s1 === peg$FAILED) {
                  s1 = null;
                }
                s2 = peg$parseBlockTag();
                if (s2 !== peg$FAILED) {
                  peg$savedPos = s0;
                  s0 = peg$f49();
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
                if (s0 === peg$FAILED) {
                  s0 = peg$currPos;
                  if (input.charCodeAt(peg$currPos) === 64) {
                    s1 = peg$c22;
                    peg$currPos++;
                  } else {
                    s1 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e26);
                    }
                  }
                  if (s1 === peg$FAILED) {
                    s1 = null;
                  }
                  s2 = peg$currPos;
                  s3 = [];
                  s4 = peg$currPos;
                  s5 = peg$currPos;
                  peg$silentFails++;
                  s6 = peg$parseBlockTag();
                  peg$silentFails--;
                  if (s6 === peg$FAILED) {
                    s5 = void 0;
                  } else {
                    peg$currPos = s5;
                    s5 = peg$FAILED;
                  }
                  if (s5 !== peg$FAILED) {
                    s6 = peg$parsechar();
                    if (s6 !== peg$FAILED) {
                      s5 = [s5, s6];
                      s4 = s5;
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                  while (s4 !== peg$FAILED) {
                    s3.push(s4);
                    s4 = peg$currPos;
                    s5 = peg$currPos;
                    peg$silentFails++;
                    s6 = peg$parseBlockTag();
                    peg$silentFails--;
                    if (s6 === peg$FAILED) {
                      s5 = void 0;
                    } else {
                      peg$currPos = s5;
                      s5 = peg$FAILED;
                    }
                    if (s5 !== peg$FAILED) {
                      s6 = peg$parsechar();
                      if (s6 !== peg$FAILED) {
                        s5 = [s5, s6];
                        s4 = s5;
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  }
                  s2 = input.substring(s2, peg$currPos);
                  s3 = peg$parseBlockTag();
                  if (s3 !== peg$FAILED) {
                    peg$savedPos = s0;
                    s0 = peg$f50(s2);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseMediaSizeTags() {
    let s0;
    if (input.substr(peg$currPos, 5) === peg$c23) {
      s0 = peg$c23;
      peg$currPos += 5;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e27);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 6) === peg$c24) {
        s0 = peg$c24;
        peg$currPos += 6;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e28);
        }
      }
    }
    return s0;
  }
  function peg$parseMediaAlignmentTags() {
    let s0;
    if (input.substr(peg$currPos, 9) === peg$c25) {
      s0 = peg$c25;
      peg$currPos += 9;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e29);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 12) === peg$c26) {
        s0 = peg$c26;
        peg$currPos += 12;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e30);
        }
      }
    }
    return s0;
  }
  function peg$parseMediaAlignment() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c27) {
      s0 = peg$c27;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e31);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 6) === peg$c28) {
        s0 = peg$c28;
        peg$currPos += 6;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e32);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 5) === peg$c29) {
          s0 = peg$c29;
          peg$currPos += 5;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e33);
          }
        }
      }
    }
    return s0;
  }
  function peg$parseMediaTextTags() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c30) {
      s0 = peg$c30;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e34);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 7) === peg$c31) {
        s0 = peg$c31;
        peg$currPos += 7;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e35);
        }
      }
    }
    return s0;
  }
  function peg$parseMediaBooleanTags() {
    let s0;
    if (input.substr(peg$currPos, 12) === peg$c32) {
      s0 = peg$c32;
      peg$currPos += 12;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e36);
      }
    }
    return s0;
  }
  function peg$parseBoolean() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c33) {
      s0 = peg$c33;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e37);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 5) === peg$c34) {
        s0 = peg$c34;
        peg$currPos += 5;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e38);
        }
      }
    }
    return s0;
  }
  function peg$parsebitmarkPlus() {
    let s0, s1;
    peg$silentFails++;
    s0 = peg$currPos;
    s1 = peg$parseInlineTags();
    peg$savedPos = s0;
    s1 = peg$f51(s1);
    s0 = s1;
    peg$silentFails--;
    return s0;
  }
  function peg$parsebitmarkPlusString() {
    let s0;
    peg$silentFails++;
    s0 = peg$parseInlineTags();
    peg$silentFails--;
    return s0;
  }
  function peg$parseInlineTags() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$parseInlinePlainText();
    if (s1 === peg$FAILED) {
      s1 = null;
    }
    s2 = [];
    s3 = peg$parseInlineStyledText();
    if (s3 === peg$FAILED) {
      s3 = peg$parseInlinePlainText();
    }
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      s3 = peg$parseInlineStyledText();
      if (s3 === peg$FAILED) {
        s3 = peg$parseInlinePlainText();
      }
    }
    peg$savedPos = s0;
    s0 = peg$f52(s1, s2);
    return s0;
  }
  function peg$parseInlinePlainText() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    s1 = peg$parseNL();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f53();
    }
    s0 = s1;
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$currPos;
      s2 = [];
      s3 = peg$currPos;
      s4 = peg$parseInlineTagTags();
      if (s4 === peg$FAILED) {
        s4 = null;
      }
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseInlineStyledText();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s4 = [s4, s5, s6];
          s3 = s4;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 === peg$FAILED) {
        s3 = peg$currPos;
        s4 = peg$parseInlineTagTags();
        if (s4 !== peg$FAILED) {
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseInlineStyledText();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s4 = [s4, s5];
            s3 = s4;
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      }
      if (s3 !== peg$FAILED) {
        while (s3 !== peg$FAILED) {
          s2.push(s3);
          s3 = peg$currPos;
          s4 = peg$parseInlineTagTags();
          if (s4 === peg$FAILED) {
            s4 = null;
          }
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseInlineStyledText();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s6 = peg$parsechar();
            if (s6 !== peg$FAILED) {
              s4 = [s4, s5, s6];
              s3 = s4;
            } else {
              peg$currPos = s3;
              s3 = peg$FAILED;
            }
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
          if (s3 === peg$FAILED) {
            s3 = peg$currPos;
            s4 = peg$parseInlineTagTags();
            if (s4 !== peg$FAILED) {
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseInlineStyledText();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                s4 = [s4, s5];
                s3 = s4;
              } else {
                peg$currPos = s3;
                s3 = peg$FAILED;
              }
            } else {
              peg$currPos = s3;
              s3 = peg$FAILED;
            }
          }
        }
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        s1 = input.substring(s1, peg$currPos);
      } else {
        s1 = s2;
      }
      if (s1 !== peg$FAILED) {
        peg$savedPos = s0;
        s1 = peg$f54(s1);
      }
      s0 = s1;
    }
    return s0;
  }
  function peg$parseInlineHalfTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 61) {
      s0 = peg$c35;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e39);
      }
    }
    return s0;
  }
  function peg$parseInlineTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseInlineHalfTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseInlineHalfTag();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseInlineStyledText() {
    let s0, s1, s2, s3, s4, s5, s6, s7;
    s0 = peg$currPos;
    s1 = peg$parseBodyBitOpenTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r1.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = input.charAt(peg$currPos);
          if (peg$r1.test(s4)) {
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e15);
            }
          }
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseBodyBitCloseTag();
        if (s3 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f55(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseInlineTag();
      if (s1 !== peg$FAILED) {
        s2 = peg$currPos;
        s3 = [];
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseInlineTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          if (input.length > peg$currPos) {
            s6 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s6 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e0);
            }
          }
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseInlineTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s6 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        }
        s2 = input.substring(s2, peg$currPos);
        s3 = peg$parseInlineTag();
        if (s3 !== peg$FAILED) {
          if (input.substr(peg$currPos, 7) === peg$c36) {
            s4 = peg$c36;
            peg$currPos += 7;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e40);
            }
          }
          if (s4 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f56(s2);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$parseInlineTag();
        if (s1 !== peg$FAILED) {
          s2 = peg$currPos;
          s3 = [];
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseInlineTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s6 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
          while (s4 !== peg$FAILED) {
            s3.push(s4);
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseInlineTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              if (input.length > peg$currPos) {
                s6 = input.charAt(peg$currPos);
                peg$currPos++;
              } else {
                s6 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e0);
                }
              }
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          }
          s2 = input.substring(s2, peg$currPos);
          s3 = peg$parseInlineTag();
          if (s3 !== peg$FAILED) {
            if (input.substr(peg$currPos, 13) === peg$c37) {
              s4 = peg$c37;
              peg$currPos += 13;
            } else {
              s4 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e41);
              }
            }
            if (s4 !== peg$FAILED) {
              s5 = peg$parseUrl();
              if (s5 !== peg$FAILED) {
                if (input.charCodeAt(peg$currPos) === 124) {
                  s6 = peg$c0;
                  peg$currPos++;
                } else {
                  s6 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e2);
                  }
                }
                if (s6 !== peg$FAILED) {
                  s7 = peg$parseInlineMediaChain();
                  peg$savedPos = s0;
                  s0 = peg$f57(s2, s5, s7);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          s1 = peg$parseInlineTag();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseInlineTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              if (input.length > peg$currPos) {
                s6 = input.charAt(peg$currPos);
                peg$currPos++;
              } else {
                s6 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e0);
                }
              }
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseInlineTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                if (input.length > peg$currPos) {
                  s6 = input.charAt(peg$currPos);
                  peg$currPos++;
                } else {
                  s6 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e0);
                  }
                }
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            }
            s2 = input.substring(s2, peg$currPos);
            s3 = peg$parseInlineTag();
            if (s3 !== peg$FAILED) {
              s4 = peg$parseAttrChain();
              if (s4 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f58(s2, s4);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parseBoldTag();
            if (s1 !== peg$FAILED) {
              s2 = peg$currPos;
              s3 = [];
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseBoldTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                if (input.length > peg$currPos) {
                  s6 = input.charAt(peg$currPos);
                  peg$currPos++;
                } else {
                  s6 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e0);
                  }
                }
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
              while (s4 !== peg$FAILED) {
                s3.push(s4);
                s4 = peg$currPos;
                s5 = peg$currPos;
                peg$silentFails++;
                s6 = peg$parseBoldTag();
                peg$silentFails--;
                if (s6 === peg$FAILED) {
                  s5 = void 0;
                } else {
                  peg$currPos = s5;
                  s5 = peg$FAILED;
                }
                if (s5 !== peg$FAILED) {
                  if (input.length > peg$currPos) {
                    s6 = input.charAt(peg$currPos);
                    peg$currPos++;
                  } else {
                    s6 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e0);
                    }
                  }
                  if (s6 !== peg$FAILED) {
                    s5 = [s5, s6];
                    s4 = s5;
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              }
              s2 = input.substring(s2, peg$currPos);
              s3 = peg$parseBoldTag();
              if (s3 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f59(s2);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
            if (s0 === peg$FAILED) {
              s0 = peg$currPos;
              s1 = peg$parseItalicTag();
              if (s1 !== peg$FAILED) {
                s2 = peg$currPos;
                s3 = [];
                s4 = peg$currPos;
                s5 = peg$currPos;
                peg$silentFails++;
                s6 = peg$parseItalicTag();
                peg$silentFails--;
                if (s6 === peg$FAILED) {
                  s5 = void 0;
                } else {
                  peg$currPos = s5;
                  s5 = peg$FAILED;
                }
                if (s5 !== peg$FAILED) {
                  if (input.length > peg$currPos) {
                    s6 = input.charAt(peg$currPos);
                    peg$currPos++;
                  } else {
                    s6 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e0);
                    }
                  }
                  if (s6 !== peg$FAILED) {
                    s5 = [s5, s6];
                    s4 = s5;
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
                while (s4 !== peg$FAILED) {
                  s3.push(s4);
                  s4 = peg$currPos;
                  s5 = peg$currPos;
                  peg$silentFails++;
                  s6 = peg$parseItalicTag();
                  peg$silentFails--;
                  if (s6 === peg$FAILED) {
                    s5 = void 0;
                  } else {
                    peg$currPos = s5;
                    s5 = peg$FAILED;
                  }
                  if (s5 !== peg$FAILED) {
                    if (input.length > peg$currPos) {
                      s6 = input.charAt(peg$currPos);
                      peg$currPos++;
                    } else {
                      s6 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e0);
                      }
                    }
                    if (s6 !== peg$FAILED) {
                      s5 = [s5, s6];
                      s4 = s5;
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                }
                s2 = input.substring(s2, peg$currPos);
                s3 = peg$parseItalicTag();
                if (s3 !== peg$FAILED) {
                  peg$savedPos = s0;
                  s0 = peg$f60(s2);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
              if (s0 === peg$FAILED) {
                s0 = peg$currPos;
                s1 = peg$parseLightTag();
                if (s1 !== peg$FAILED) {
                  s2 = peg$currPos;
                  s3 = [];
                  s4 = peg$currPos;
                  s5 = peg$currPos;
                  peg$silentFails++;
                  s6 = peg$parseLightTag();
                  peg$silentFails--;
                  if (s6 === peg$FAILED) {
                    s5 = void 0;
                  } else {
                    peg$currPos = s5;
                    s5 = peg$FAILED;
                  }
                  if (s5 !== peg$FAILED) {
                    if (input.length > peg$currPos) {
                      s6 = input.charAt(peg$currPos);
                      peg$currPos++;
                    } else {
                      s6 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e0);
                      }
                    }
                    if (s6 !== peg$FAILED) {
                      s5 = [s5, s6];
                      s4 = s5;
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                  while (s4 !== peg$FAILED) {
                    s3.push(s4);
                    s4 = peg$currPos;
                    s5 = peg$currPos;
                    peg$silentFails++;
                    s6 = peg$parseLightTag();
                    peg$silentFails--;
                    if (s6 === peg$FAILED) {
                      s5 = void 0;
                    } else {
                      peg$currPos = s5;
                      s5 = peg$FAILED;
                    }
                    if (s5 !== peg$FAILED) {
                      if (input.length > peg$currPos) {
                        s6 = input.charAt(peg$currPos);
                        peg$currPos++;
                      } else {
                        s6 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e0);
                        }
                      }
                      if (s6 !== peg$FAILED) {
                        s5 = [s5, s6];
                        s4 = s5;
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  }
                  s2 = input.substring(s2, peg$currPos);
                  s3 = peg$parseLightTag();
                  if (s3 !== peg$FAILED) {
                    peg$savedPos = s0;
                    s0 = peg$f61(s2);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
                if (s0 === peg$FAILED) {
                  s0 = peg$currPos;
                  s1 = peg$parseHighlightTag();
                  if (s1 !== peg$FAILED) {
                    s2 = peg$currPos;
                    s3 = [];
                    s4 = peg$currPos;
                    s5 = peg$currPos;
                    peg$silentFails++;
                    s6 = peg$parseHighlightTag();
                    peg$silentFails--;
                    if (s6 === peg$FAILED) {
                      s5 = void 0;
                    } else {
                      peg$currPos = s5;
                      s5 = peg$FAILED;
                    }
                    if (s5 !== peg$FAILED) {
                      if (input.length > peg$currPos) {
                        s6 = input.charAt(peg$currPos);
                        peg$currPos++;
                      } else {
                        s6 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e0);
                        }
                      }
                      if (s6 !== peg$FAILED) {
                        s5 = [s5, s6];
                        s4 = s5;
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                    while (s4 !== peg$FAILED) {
                      s3.push(s4);
                      s4 = peg$currPos;
                      s5 = peg$currPos;
                      peg$silentFails++;
                      s6 = peg$parseHighlightTag();
                      peg$silentFails--;
                      if (s6 === peg$FAILED) {
                        s5 = void 0;
                      } else {
                        peg$currPos = s5;
                        s5 = peg$FAILED;
                      }
                      if (s5 !== peg$FAILED) {
                        if (input.length > peg$currPos) {
                          s6 = input.charAt(peg$currPos);
                          peg$currPos++;
                        } else {
                          s6 = peg$FAILED;
                          if (peg$silentFails === 0) {
                            peg$fail(peg$e0);
                          }
                        }
                        if (s6 !== peg$FAILED) {
                          s5 = [s5, s6];
                          s4 = s5;
                        } else {
                          peg$currPos = s4;
                          s4 = peg$FAILED;
                        }
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    }
                    s2 = input.substring(s2, peg$currPos);
                    s3 = peg$parseHighlightTag();
                    if (s3 !== peg$FAILED) {
                      peg$savedPos = s0;
                      s0 = peg$f62(s2);
                    } else {
                      peg$currPos = s0;
                      s0 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                  if (s0 === peg$FAILED) {
                    s0 = peg$currPos;
                    s1 = peg$parseUrl();
                    if (s1 !== peg$FAILED) {
                      peg$savedPos = s0;
                      s1 = peg$f63(s1);
                    }
                    s0 = s1;
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseInlineTagTags() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseInlineTag();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseInlineHalfTag();
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = peg$parseInlineHalfTag();
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = [s2, s3];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$currPos;
      s2 = peg$parseLightTag();
      if (s2 !== peg$FAILED) {
        s3 = [];
        s4 = peg$parseLightHalfTag();
        if (s4 !== peg$FAILED) {
          while (s4 !== peg$FAILED) {
            s3.push(s4);
            s4 = peg$parseLightHalfTag();
          }
        } else {
          s3 = peg$FAILED;
        }
        if (s3 !== peg$FAILED) {
          s2 = [s2, s3];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
      if (s1 !== peg$FAILED) {
        s0 = input.substring(s0, peg$currPos);
      } else {
        s0 = s1;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$currPos;
        s2 = peg$parseHighlightTag();
        if (s2 !== peg$FAILED) {
          s3 = [];
          s4 = peg$parseHighlightHalfTag();
          if (s4 !== peg$FAILED) {
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$parseHighlightHalfTag();
            }
          } else {
            s3 = peg$FAILED;
          }
          if (s3 !== peg$FAILED) {
            s2 = [s2, s3];
            s1 = s2;
          } else {
            peg$currPos = s1;
            s1 = peg$FAILED;
          }
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
        if (s1 !== peg$FAILED) {
          s0 = input.substring(s0, peg$currPos);
        } else {
          s0 = s1;
        }
      }
    }
    return s0;
  }
  function peg$parseAttrChain() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 124) {
      s1 = peg$c0;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e2);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = [];
      s3 = peg$parseAttrChainItem();
      if (s3 !== peg$FAILED) {
        while (s3 !== peg$FAILED) {
          s2.push(s3);
          s3 = peg$parseAttrChainItem();
        }
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f64(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseInlineMediaChain() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseInlineMediaChainItem();
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseInlineMediaChainItem();
    }
    peg$savedPos = s0;
    s1 = peg$f65(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseInlineMediaChainItem() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 35) {
      s1 = peg$c21;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e25);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$currPos;
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseBlockTag();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s5 = [s5, s6];
          s4 = s5;
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      } else {
        peg$currPos = s4;
        s4 = peg$FAILED;
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parsechar();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      }
      s2 = input.substring(s2, peg$currPos);
      s3 = peg$parseBlockTag();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f66(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.charCodeAt(peg$currPos) === 64) {
        s1 = peg$c22;
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e26);
        }
      }
      if (s1 === peg$FAILED) {
        s1 = null;
      }
      if (input.substr(peg$currPos, 6) === peg$c38) {
        s2 = peg$c38;
        peg$currPos += 6;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e42);
        }
      }
      if (s2 !== peg$FAILED) {
        if (input.charCodeAt(peg$currPos) === 58) {
          s3 = peg$c5;
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e8);
          }
        }
        if (s3 !== peg$FAILED) {
          s4 = [];
          if (input.charCodeAt(peg$currPos) === 32) {
            s5 = peg$c12;
            peg$currPos++;
          } else {
            s5 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e16);
            }
          }
          while (s5 !== peg$FAILED) {
            s4.push(s5);
            if (input.charCodeAt(peg$currPos) === 32) {
              s5 = peg$c12;
              peg$currPos++;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
          }
          s5 = peg$currPos;
          s6 = [];
          s7 = peg$currPos;
          s8 = peg$currPos;
          peg$silentFails++;
          s9 = peg$parseBlockTag();
          peg$silentFails--;
          if (s9 === peg$FAILED) {
            s8 = void 0;
          } else {
            peg$currPos = s8;
            s8 = peg$FAILED;
          }
          if (s8 !== peg$FAILED) {
            s9 = peg$parsechar();
            if (s9 !== peg$FAILED) {
              s8 = [s8, s9];
              s7 = s8;
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
          } else {
            peg$currPos = s7;
            s7 = peg$FAILED;
          }
          while (s7 !== peg$FAILED) {
            s6.push(s7);
            s7 = peg$currPos;
            s8 = peg$currPos;
            peg$silentFails++;
            s9 = peg$parseBlockTag();
            peg$silentFails--;
            if (s9 === peg$FAILED) {
              s8 = void 0;
            } else {
              peg$currPos = s8;
              s8 = peg$FAILED;
            }
            if (s8 !== peg$FAILED) {
              s9 = peg$parsechar();
              if (s9 !== peg$FAILED) {
                s8 = [s8, s9];
                s7 = s8;
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
          }
          s5 = input.substring(s5, peg$currPos);
          s6 = peg$parseBlockTag();
          if (s6 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f67(s2, s5);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        if (input.charCodeAt(peg$currPos) === 64) {
          s1 = peg$c22;
          peg$currPos++;
        } else {
          s1 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e26);
          }
        }
        if (s1 === peg$FAILED) {
          s1 = null;
        }
        s2 = peg$parseMediaSizeTags();
        if (s2 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 58) {
            s3 = peg$c5;
            peg$currPos++;
          } else {
            s3 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e8);
            }
          }
          if (s3 !== peg$FAILED) {
            s4 = [];
            if (input.charCodeAt(peg$currPos) === 32) {
              s5 = peg$c12;
              peg$currPos++;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e16);
              }
            }
            while (s5 !== peg$FAILED) {
              s4.push(s5);
              if (input.charCodeAt(peg$currPos) === 32) {
                s5 = peg$c12;
                peg$currPos++;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e16);
                }
              }
            }
            s5 = peg$currPos;
            s6 = [];
            s7 = peg$currPos;
            s8 = peg$currPos;
            peg$silentFails++;
            s9 = peg$parseBlockTag();
            peg$silentFails--;
            if (s9 === peg$FAILED) {
              s8 = void 0;
            } else {
              peg$currPos = s8;
              s8 = peg$FAILED;
            }
            if (s8 !== peg$FAILED) {
              s9 = input.charAt(peg$currPos);
              if (peg$r1.test(s9)) {
                peg$currPos++;
              } else {
                s9 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e15);
                }
              }
              if (s9 !== peg$FAILED) {
                s8 = [s8, s9];
                s7 = s8;
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
            } else {
              peg$currPos = s7;
              s7 = peg$FAILED;
            }
            if (s7 !== peg$FAILED) {
              while (s7 !== peg$FAILED) {
                s6.push(s7);
                s7 = peg$currPos;
                s8 = peg$currPos;
                peg$silentFails++;
                s9 = peg$parseBlockTag();
                peg$silentFails--;
                if (s9 === peg$FAILED) {
                  s8 = void 0;
                } else {
                  peg$currPos = s8;
                  s8 = peg$FAILED;
                }
                if (s8 !== peg$FAILED) {
                  s9 = input.charAt(peg$currPos);
                  if (peg$r1.test(s9)) {
                    peg$currPos++;
                  } else {
                    s9 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e15);
                    }
                  }
                  if (s9 !== peg$FAILED) {
                    s8 = [s8, s9];
                    s7 = s8;
                  } else {
                    peg$currPos = s7;
                    s7 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              }
            } else {
              s6 = peg$FAILED;
            }
            if (s6 !== peg$FAILED) {
              s5 = input.substring(s5, peg$currPos);
            } else {
              s5 = s6;
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parseBlockTag();
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f68(s2, s5);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          if (input.charCodeAt(peg$currPos) === 64) {
            s1 = peg$c22;
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e26);
            }
          }
          if (s1 === peg$FAILED) {
            s1 = null;
          }
          s2 = peg$parseMediaSizeTags();
          if (s2 !== peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 58) {
              s3 = peg$c5;
              peg$currPos++;
            } else {
              s3 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e8);
              }
            }
            if (s3 !== peg$FAILED) {
              s4 = [];
              if (input.charCodeAt(peg$currPos) === 32) {
                s5 = peg$c12;
                peg$currPos++;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e16);
                }
              }
              while (s5 !== peg$FAILED) {
                s4.push(s5);
                if (input.charCodeAt(peg$currPos) === 32) {
                  s5 = peg$c12;
                  peg$currPos++;
                } else {
                  s5 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
              }
              s5 = peg$currPos;
              s6 = [];
              s7 = peg$currPos;
              s8 = peg$currPos;
              peg$silentFails++;
              s9 = peg$parseBlockTag();
              peg$silentFails--;
              if (s9 === peg$FAILED) {
                s8 = void 0;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
              if (s8 !== peg$FAILED) {
                s9 = peg$parsechar();
                if (s9 !== peg$FAILED) {
                  s8 = [s8, s9];
                  s7 = s8;
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
              while (s7 !== peg$FAILED) {
                s6.push(s7);
                s7 = peg$currPos;
                s8 = peg$currPos;
                peg$silentFails++;
                s9 = peg$parseBlockTag();
                peg$silentFails--;
                if (s9 === peg$FAILED) {
                  s8 = void 0;
                } else {
                  peg$currPos = s8;
                  s8 = peg$FAILED;
                }
                if (s8 !== peg$FAILED) {
                  s9 = peg$parsechar();
                  if (s9 !== peg$FAILED) {
                    s8 = [s8, s9];
                    s7 = s8;
                  } else {
                    peg$currPos = s7;
                    s7 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              }
              s5 = input.substring(s5, peg$currPos);
              s6 = peg$parseBlockTag();
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f69(s2, s5);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            if (input.charCodeAt(peg$currPos) === 64) {
              s1 = peg$c22;
              peg$currPos++;
            } else {
              s1 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e26);
              }
            }
            if (s1 === peg$FAILED) {
              s1 = null;
            }
            if (input.substr(peg$currPos, 17) === peg$c39) {
              s2 = peg$c39;
              peg$currPos += 17;
            } else {
              s2 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e43);
              }
            }
            if (s2 !== peg$FAILED) {
              if (input.charCodeAt(peg$currPos) === 58) {
                s3 = peg$c5;
                peg$currPos++;
              } else {
                s3 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e8);
                }
              }
              if (s3 !== peg$FAILED) {
                s4 = [];
                if (input.charCodeAt(peg$currPos) === 32) {
                  s5 = peg$c12;
                  peg$currPos++;
                } else {
                  s5 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e16);
                  }
                }
                while (s5 !== peg$FAILED) {
                  s4.push(s5);
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s5 = peg$c12;
                    peg$currPos++;
                  } else {
                    s5 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                }
                s5 = peg$parseInlineMediaAlignment();
                if (s5 !== peg$FAILED) {
                  s6 = peg$parseBlockTag();
                  if (s6 !== peg$FAILED) {
                    peg$savedPos = s0;
                    s0 = peg$f70(s2, s5);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
            if (s0 === peg$FAILED) {
              s0 = peg$currPos;
              if (input.charCodeAt(peg$currPos) === 64) {
                s1 = peg$c22;
                peg$currPos++;
              } else {
                s1 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e26);
                }
              }
              if (s1 === peg$FAILED) {
                s1 = null;
              }
              if (input.substr(peg$currPos, 4) === peg$c40) {
                s2 = peg$c40;
                peg$currPos += 4;
              } else {
                s2 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e44);
                }
              }
              if (s2 !== peg$FAILED) {
                if (input.charCodeAt(peg$currPos) === 58) {
                  s3 = peg$c5;
                  peg$currPos++;
                } else {
                  s3 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e8);
                  }
                }
                if (s3 !== peg$FAILED) {
                  s4 = [];
                  if (input.charCodeAt(peg$currPos) === 32) {
                    s5 = peg$c12;
                    peg$currPos++;
                  } else {
                    s5 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e16);
                    }
                  }
                  while (s5 !== peg$FAILED) {
                    s4.push(s5);
                    if (input.charCodeAt(peg$currPos) === 32) {
                      s5 = peg$c12;
                      peg$currPos++;
                    } else {
                      s5 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e16);
                      }
                    }
                  }
                  s5 = peg$parseInlineMediaSize();
                  if (s5 !== peg$FAILED) {
                    s6 = peg$parseBlockTag();
                    if (s6 !== peg$FAILED) {
                      peg$savedPos = s0;
                      s0 = peg$f71(s2, s5);
                    } else {
                      peg$currPos = s0;
                      s0 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseInlineMediaAlignment() {
    let s0;
    if (input.substr(peg$currPos, 3) === peg$c41) {
      s0 = peg$c41;
      peg$currPos += 3;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e45);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 6) === peg$c42) {
        s0 = peg$c42;
        peg$currPos += 6;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e46);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 6) === peg$c43) {
          s0 = peg$c43;
          peg$currPos += 6;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e47);
          }
        }
        if (s0 === peg$FAILED) {
          if (input.substr(peg$currPos, 8) === peg$c44) {
            s0 = peg$c44;
            peg$currPos += 8;
          } else {
            s0 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e48);
            }
          }
          if (s0 === peg$FAILED) {
            if (input.substr(peg$currPos, 3) === peg$c45) {
              s0 = peg$c45;
              peg$currPos += 3;
            } else {
              s0 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e49);
              }
            }
            if (s0 === peg$FAILED) {
              if (input.substr(peg$currPos, 5) === peg$c46) {
                s0 = peg$c46;
                peg$currPos += 5;
              } else {
                s0 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e50);
                }
              }
              if (s0 === peg$FAILED) {
                if (input.substr(peg$currPos, 11) === peg$c47) {
                  s0 = peg$c47;
                  peg$currPos += 11;
                } else {
                  s0 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e51);
                  }
                }
                if (s0 === peg$FAILED) {
                  if (input.substr(peg$currPos, 8) === peg$c48) {
                    s0 = peg$c48;
                    peg$currPos += 8;
                  } else {
                    s0 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e52);
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseInlineMediaSize() {
    let s0;
    if (input.substr(peg$currPos, 11) === peg$c49) {
      s0 = peg$c49;
      peg$currPos += 11;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e53);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 11) === peg$c50) {
        s0 = peg$c50;
        peg$currPos += 11;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e54);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 5) === peg$c46) {
          s0 = peg$c46;
          peg$currPos += 5;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e50);
          }
        }
        if (s0 === peg$FAILED) {
          if (input.substr(peg$currPos, 3) === peg$c45) {
            s0 = peg$c45;
            peg$currPos += 3;
          } else {
            s0 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e49);
            }
          }
          if (s0 === peg$FAILED) {
            if (input.substr(peg$currPos, 8) === peg$c51) {
              s0 = peg$c51;
              peg$currPos += 8;
            } else {
              s0 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e55);
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseAttrChainItem() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 5) === peg$c52) {
      s1 = peg$c52;
      peg$currPos += 5;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e56);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$currPos;
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseBlockTag();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s5 = [s5, s6];
          s4 = s5;
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      } else {
        peg$currPos = s4;
        s4 = peg$FAILED;
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parsechar();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      }
      s2 = input.substring(s2, peg$currPos);
      s3 = peg$parseBlockTag();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f72(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 7) === peg$c53) {
        s1 = peg$c53;
        peg$currPos += 7;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e57);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = peg$currPos;
        s3 = [];
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parsechar();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseBlockTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s6 = peg$parsechar();
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        }
        s2 = input.substring(s2, peg$currPos);
        s3 = peg$parseRefsChain();
        s4 = peg$parseBlockTag();
        if (s4 !== peg$FAILED) {
          if (input.substr(peg$currPos, 9) === peg$c54) {
            s5 = peg$c54;
            peg$currPos += 9;
          } else {
            s5 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e58);
            }
          }
          if (s5 !== peg$FAILED) {
            s6 = peg$currPos;
            s7 = [];
            s8 = peg$currPos;
            s9 = peg$currPos;
            peg$silentFails++;
            s10 = peg$parseBlockTag();
            peg$silentFails--;
            if (s10 === peg$FAILED) {
              s9 = void 0;
            } else {
              peg$currPos = s9;
              s9 = peg$FAILED;
            }
            if (s9 !== peg$FAILED) {
              s10 = peg$parsechar();
              if (s10 !== peg$FAILED) {
                s9 = [s9, s10];
                s8 = s9;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
            } else {
              peg$currPos = s8;
              s8 = peg$FAILED;
            }
            while (s8 !== peg$FAILED) {
              s7.push(s8);
              s8 = peg$currPos;
              s9 = peg$currPos;
              peg$silentFails++;
              s10 = peg$parseBlockTag();
              peg$silentFails--;
              if (s10 === peg$FAILED) {
                s9 = void 0;
              } else {
                peg$currPos = s9;
                s9 = peg$FAILED;
              }
              if (s9 !== peg$FAILED) {
                s10 = peg$parsechar();
                if (s10 !== peg$FAILED) {
                  s9 = [s9, s10];
                  s8 = s9;
                } else {
                  peg$currPos = s8;
                  s8 = peg$FAILED;
                }
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
            }
            s6 = input.substring(s6, peg$currPos);
            s7 = peg$parseBlockTag();
            if (s7 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f73(s2, s3, s6);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        if (input.substr(peg$currPos, 5) === peg$c55) {
          s1 = peg$c55;
          peg$currPos += 5;
        } else {
          s1 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e59);
          }
        }
        if (s1 !== peg$FAILED) {
          s2 = peg$currPos;
          s3 = [];
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseBlockTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s6 = peg$parsechar();
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
          while (s4 !== peg$FAILED) {
            s3.push(s4);
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseBlockTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parsechar();
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          }
          s2 = input.substring(s2, peg$currPos);
          s3 = peg$parseBlockTag();
          if (s3 !== peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 9658) {
              s4 = peg$c56;
              peg$currPos++;
            } else {
              s4 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e60);
              }
            }
            if (s4 !== peg$FAILED) {
              s5 = peg$currPos;
              s6 = [];
              s7 = peg$currPos;
              s8 = peg$currPos;
              peg$silentFails++;
              s9 = peg$parseBlockTag();
              peg$silentFails--;
              if (s9 === peg$FAILED) {
                s8 = void 0;
              } else {
                peg$currPos = s8;
                s8 = peg$FAILED;
              }
              if (s8 !== peg$FAILED) {
                s9 = peg$parsechar();
                if (s9 !== peg$FAILED) {
                  s8 = [s8, s9];
                  s7 = s8;
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              } else {
                peg$currPos = s7;
                s7 = peg$FAILED;
              }
              while (s7 !== peg$FAILED) {
                s6.push(s7);
                s7 = peg$currPos;
                s8 = peg$currPos;
                peg$silentFails++;
                s9 = peg$parseBlockTag();
                peg$silentFails--;
                if (s9 === peg$FAILED) {
                  s8 = void 0;
                } else {
                  peg$currPos = s8;
                  s8 = peg$FAILED;
                }
                if (s8 !== peg$FAILED) {
                  s9 = peg$parsechar();
                  if (s9 !== peg$FAILED) {
                    s8 = [s8, s9];
                    s7 = s8;
                  } else {
                    peg$currPos = s7;
                    s7 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s7;
                  s7 = peg$FAILED;
                }
              }
              s5 = input.substring(s5, peg$currPos);
              s6 = peg$parseBlockTag();
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f74(s2, s5);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          if (input.substr(peg$currPos, 5) === peg$c55) {
            s1 = peg$c55;
            peg$currPos += 5;
          } else {
            s1 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e59);
            }
          }
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseBlockTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parsechar();
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseBlockTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                s6 = peg$parsechar();
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            }
            s2 = input.substring(s2, peg$currPos);
            s3 = peg$parseBlockTag();
            if (s3 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f75(s2);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            if (input.charCodeAt(peg$currPos) === 9658) {
              s1 = peg$c56;
              peg$currPos++;
            } else {
              s1 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e60);
              }
            }
            if (s1 !== peg$FAILED) {
              s2 = peg$currPos;
              s3 = [];
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseBlockTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                s6 = peg$parsechar();
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
              while (s4 !== peg$FAILED) {
                s3.push(s4);
                s4 = peg$currPos;
                s5 = peg$currPos;
                peg$silentFails++;
                s6 = peg$parseBlockTag();
                peg$silentFails--;
                if (s6 === peg$FAILED) {
                  s5 = void 0;
                } else {
                  peg$currPos = s5;
                  s5 = peg$FAILED;
                }
                if (s5 !== peg$FAILED) {
                  s6 = peg$parsechar();
                  if (s6 !== peg$FAILED) {
                    s5 = [s5, s6];
                    s4 = s5;
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              }
              s2 = input.substring(s2, peg$currPos);
              s3 = peg$parseBlockTag();
              if (s3 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f76(s2);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
            if (s0 === peg$FAILED) {
              s0 = peg$currPos;
              if (input.substr(peg$currPos, 7) === peg$c57) {
                s1 = peg$c57;
                peg$currPos += 7;
              } else {
                s1 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e61);
                }
              }
              if (s1 !== peg$FAILED) {
                s2 = peg$currPos;
                s3 = [];
                s4 = peg$currPos;
                s5 = peg$currPos;
                peg$silentFails++;
                s6 = peg$parseBlockTag();
                peg$silentFails--;
                if (s6 === peg$FAILED) {
                  s5 = void 0;
                } else {
                  peg$currPos = s5;
                  s5 = peg$FAILED;
                }
                if (s5 !== peg$FAILED) {
                  s6 = peg$parsechar();
                  if (s6 !== peg$FAILED) {
                    s5 = [s5, s6];
                    s4 = s5;
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
                while (s4 !== peg$FAILED) {
                  s3.push(s4);
                  s4 = peg$currPos;
                  s5 = peg$currPos;
                  peg$silentFails++;
                  s6 = peg$parseBlockTag();
                  peg$silentFails--;
                  if (s6 === peg$FAILED) {
                    s5 = void 0;
                  } else {
                    peg$currPos = s5;
                    s5 = peg$FAILED;
                  }
                  if (s5 !== peg$FAILED) {
                    s6 = peg$parsechar();
                    if (s6 !== peg$FAILED) {
                      s5 = [s5, s6];
                      s4 = s5;
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                }
                s2 = input.substring(s2, peg$currPos);
                s3 = peg$parseBlockTag();
                if (s3 !== peg$FAILED) {
                  s4 = peg$parseMediaChain();
                  peg$savedPos = s0;
                  s0 = peg$f77(s2, s4);
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
              if (s0 === peg$FAILED) {
                s0 = peg$currPos;
                if (input.substr(peg$currPos, 9) === peg$c58) {
                  s1 = peg$c58;
                  peg$currPos += 9;
                } else {
                  s1 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e62);
                  }
                }
                if (s1 !== peg$FAILED) {
                  s2 = peg$currPos;
                  s3 = [];
                  s4 = peg$currPos;
                  s5 = peg$currPos;
                  peg$silentFails++;
                  s6 = peg$parseBlockTag();
                  peg$silentFails--;
                  if (s6 === peg$FAILED) {
                    s5 = void 0;
                  } else {
                    peg$currPos = s5;
                    s5 = peg$FAILED;
                  }
                  if (s5 !== peg$FAILED) {
                    s6 = peg$parsechar();
                    if (s6 !== peg$FAILED) {
                      s5 = [s5, s6];
                      s4 = s5;
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                  while (s4 !== peg$FAILED) {
                    s3.push(s4);
                    s4 = peg$currPos;
                    s5 = peg$currPos;
                    peg$silentFails++;
                    s6 = peg$parseBlockTag();
                    peg$silentFails--;
                    if (s6 === peg$FAILED) {
                      s5 = void 0;
                    } else {
                      peg$currPos = s5;
                      s5 = peg$FAILED;
                    }
                    if (s5 !== peg$FAILED) {
                      s6 = peg$parsechar();
                      if (s6 !== peg$FAILED) {
                        s5 = [s5, s6];
                        s4 = s5;
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                  }
                  s2 = input.substring(s2, peg$currPos);
                  s3 = peg$parseBlockTag();
                  if (s3 !== peg$FAILED) {
                    peg$savedPos = s0;
                    s0 = peg$f78(s2);
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$FAILED;
                }
                if (s0 === peg$FAILED) {
                  s0 = peg$currPos;
                  if (input.substr(peg$currPos, 10) === peg$c59) {
                    s1 = peg$c59;
                    peg$currPos += 10;
                  } else {
                    s1 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e63);
                    }
                  }
                  if (s1 !== peg$FAILED) {
                    s2 = peg$currPos;
                    s3 = [];
                    s4 = peg$currPos;
                    s5 = peg$currPos;
                    peg$silentFails++;
                    s6 = peg$parseBlockTag();
                    peg$silentFails--;
                    if (s6 === peg$FAILED) {
                      s5 = void 0;
                    } else {
                      peg$currPos = s5;
                      s5 = peg$FAILED;
                    }
                    if (s5 !== peg$FAILED) {
                      s6 = peg$parsechar();
                      if (s6 !== peg$FAILED) {
                        s5 = [s5, s6];
                        s4 = s5;
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s4;
                      s4 = peg$FAILED;
                    }
                    while (s4 !== peg$FAILED) {
                      s3.push(s4);
                      s4 = peg$currPos;
                      s5 = peg$currPos;
                      peg$silentFails++;
                      s6 = peg$parseBlockTag();
                      peg$silentFails--;
                      if (s6 === peg$FAILED) {
                        s5 = void 0;
                      } else {
                        peg$currPos = s5;
                        s5 = peg$FAILED;
                      }
                      if (s5 !== peg$FAILED) {
                        s6 = peg$parsechar();
                        if (s6 !== peg$FAILED) {
                          s5 = [s5, s6];
                          s4 = s5;
                        } else {
                          peg$currPos = s4;
                          s4 = peg$FAILED;
                        }
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                    }
                    s2 = input.substring(s2, peg$currPos);
                    s3 = peg$parseBlockTag();
                    if (s3 !== peg$FAILED) {
                      peg$savedPos = s0;
                      s0 = peg$f79(s2);
                    } else {
                      peg$currPos = s0;
                      s0 = peg$FAILED;
                    }
                  } else {
                    peg$currPos = s0;
                    s0 = peg$FAILED;
                  }
                  if (s0 === peg$FAILED) {
                    s0 = peg$currPos;
                    if (input.substr(peg$currPos, 4) === peg$c60) {
                      s1 = peg$c60;
                      peg$currPos += 4;
                    } else {
                      s1 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e64);
                      }
                    }
                    if (s1 !== peg$FAILED) {
                      s2 = peg$currPos;
                      s3 = [];
                      s4 = peg$currPos;
                      s5 = peg$currPos;
                      peg$silentFails++;
                      s6 = peg$parseBlockTag();
                      peg$silentFails--;
                      if (s6 === peg$FAILED) {
                        s5 = void 0;
                      } else {
                        peg$currPos = s5;
                        s5 = peg$FAILED;
                      }
                      if (s5 !== peg$FAILED) {
                        s6 = peg$parsechar();
                        if (s6 !== peg$FAILED) {
                          s5 = [s5, s6];
                          s4 = s5;
                        } else {
                          peg$currPos = s4;
                          s4 = peg$FAILED;
                        }
                      } else {
                        peg$currPos = s4;
                        s4 = peg$FAILED;
                      }
                      while (s4 !== peg$FAILED) {
                        s3.push(s4);
                        s4 = peg$currPos;
                        s5 = peg$currPos;
                        peg$silentFails++;
                        s6 = peg$parseBlockTag();
                        peg$silentFails--;
                        if (s6 === peg$FAILED) {
                          s5 = void 0;
                        } else {
                          peg$currPos = s5;
                          s5 = peg$FAILED;
                        }
                        if (s5 !== peg$FAILED) {
                          s6 = peg$parsechar();
                          if (s6 !== peg$FAILED) {
                            s5 = [s5, s6];
                            s4 = s5;
                          } else {
                            peg$currPos = s4;
                            s4 = peg$FAILED;
                          }
                        } else {
                          peg$currPos = s4;
                          s4 = peg$FAILED;
                        }
                      }
                      s2 = input.substring(s2, peg$currPos);
                      s3 = peg$parseBlockTag();
                      if (s3 !== peg$FAILED) {
                        peg$savedPos = s0;
                        s0 = peg$f80(s2);
                      } else {
                        peg$currPos = s0;
                        s0 = peg$FAILED;
                      }
                    } else {
                      peg$currPos = s0;
                      s0 = peg$FAILED;
                    }
                    if (s0 === peg$FAILED) {
                      s0 = peg$currPos;
                      if (input.substr(peg$currPos, 4) === peg$c4) {
                        s1 = peg$c4;
                        peg$currPos += 4;
                      } else {
                        s1 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e6);
                        }
                      }
                      if (s1 !== peg$FAILED) {
                        s2 = peg$parseBlockTag();
                        if (s2 !== peg$FAILED) {
                          peg$savedPos = s0;
                          s0 = peg$f81();
                        } else {
                          peg$currPos = s0;
                          s0 = peg$FAILED;
                        }
                      } else {
                        peg$currPos = s0;
                        s0 = peg$FAILED;
                      }
                      if (s0 === peg$FAILED) {
                        s0 = peg$currPos;
                        if (input.substr(peg$currPos, 5) === peg$c61) {
                          s1 = peg$c61;
                          peg$currPos += 5;
                        } else {
                          s1 = peg$FAILED;
                          if (peg$silentFails === 0) {
                            peg$fail(peg$e65);
                          }
                        }
                        if (s1 !== peg$FAILED) {
                          s2 = peg$currPos;
                          s3 = [];
                          s4 = peg$currPos;
                          s5 = peg$currPos;
                          peg$silentFails++;
                          s6 = peg$parseBlockTag();
                          peg$silentFails--;
                          if (s6 === peg$FAILED) {
                            s5 = void 0;
                          } else {
                            peg$currPos = s5;
                            s5 = peg$FAILED;
                          }
                          if (s5 !== peg$FAILED) {
                            s6 = peg$parsechar();
                            if (s6 !== peg$FAILED) {
                              s5 = [s5, s6];
                              s4 = s5;
                            } else {
                              peg$currPos = s4;
                              s4 = peg$FAILED;
                            }
                          } else {
                            peg$currPos = s4;
                            s4 = peg$FAILED;
                          }
                          while (s4 !== peg$FAILED) {
                            s3.push(s4);
                            s4 = peg$currPos;
                            s5 = peg$currPos;
                            peg$silentFails++;
                            s6 = peg$parseBlockTag();
                            peg$silentFails--;
                            if (s6 === peg$FAILED) {
                              s5 = void 0;
                            } else {
                              peg$currPos = s5;
                              s5 = peg$FAILED;
                            }
                            if (s5 !== peg$FAILED) {
                              s6 = peg$parsechar();
                              if (s6 !== peg$FAILED) {
                                s5 = [s5, s6];
                                s4 = s5;
                              } else {
                                peg$currPos = s4;
                                s4 = peg$FAILED;
                              }
                            } else {
                              peg$currPos = s4;
                              s4 = peg$FAILED;
                            }
                          }
                          s2 = input.substring(s2, peg$currPos);
                          s3 = peg$parseBlockTag();
                          if (s3 !== peg$FAILED) {
                            peg$savedPos = s0;
                            s0 = peg$f82(s2);
                          } else {
                            peg$currPos = s0;
                            s0 = peg$FAILED;
                          }
                        } else {
                          peg$currPos = s0;
                          s0 = peg$FAILED;
                        }
                        if (s0 === peg$FAILED) {
                          s0 = peg$currPos;
                          if (input.substr(peg$currPos, 5) === peg$c62) {
                            s1 = peg$c62;
                            peg$currPos += 5;
                          } else {
                            s1 = peg$FAILED;
                            if (peg$silentFails === 0) {
                              peg$fail(peg$e66);
                            }
                          }
                          if (s1 !== peg$FAILED) {
                            s2 = peg$parseBlockTag();
                            if (s2 !== peg$FAILED) {
                              if (input.substr(peg$currPos, 9) === peg$c63) {
                                s3 = peg$c63;
                                peg$currPos += 9;
                              } else {
                                s3 = peg$FAILED;
                                if (peg$silentFails === 0) {
                                  peg$fail(peg$e67);
                                }
                              }
                              if (s3 !== peg$FAILED) {
                                s4 = peg$currPos;
                                s5 = peg$currPos;
                                if (input.charCodeAt(peg$currPos) === 80) {
                                  s6 = peg$c64;
                                  peg$currPos++;
                                } else {
                                  s6 = peg$FAILED;
                                  if (peg$silentFails === 0) {
                                    peg$fail(peg$e68);
                                  }
                                }
                                if (s6 !== peg$FAILED) {
                                  s7 = peg$currPos;
                                  s8 = [];
                                  s9 = peg$currPos;
                                  s10 = peg$currPos;
                                  peg$silentFails++;
                                  s11 = peg$parseBlockTag();
                                  peg$silentFails--;
                                  if (s11 === peg$FAILED) {
                                    s10 = void 0;
                                  } else {
                                    peg$currPos = s10;
                                    s10 = peg$FAILED;
                                  }
                                  if (s10 !== peg$FAILED) {
                                    s11 = peg$parsechar();
                                    if (s11 !== peg$FAILED) {
                                      s10 = [s10, s11];
                                      s9 = s10;
                                    } else {
                                      peg$currPos = s9;
                                      s9 = peg$FAILED;
                                    }
                                  } else {
                                    peg$currPos = s9;
                                    s9 = peg$FAILED;
                                  }
                                  while (s9 !== peg$FAILED) {
                                    s8.push(s9);
                                    s9 = peg$currPos;
                                    s10 = peg$currPos;
                                    peg$silentFails++;
                                    s11 = peg$parseBlockTag();
                                    peg$silentFails--;
                                    if (s11 === peg$FAILED) {
                                      s10 = void 0;
                                    } else {
                                      peg$currPos = s10;
                                      s10 = peg$FAILED;
                                    }
                                    if (s10 !== peg$FAILED) {
                                      s11 = peg$parsechar();
                                      if (s11 !== peg$FAILED) {
                                        s10 = [s10, s11];
                                        s9 = s10;
                                      } else {
                                        peg$currPos = s9;
                                        s9 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s9;
                                      s9 = peg$FAILED;
                                    }
                                  }
                                  s7 = input.substring(s7, peg$currPos);
                                  s6 = [s6, s7];
                                  s5 = s6;
                                } else {
                                  peg$currPos = s5;
                                  s5 = peg$FAILED;
                                }
                                if (s5 !== peg$FAILED) {
                                  s4 = input.substring(s4, peg$currPos);
                                } else {
                                  s4 = s5;
                                }
                                if (s4 !== peg$FAILED) {
                                  s5 = peg$parseBlockTag();
                                  if (s5 !== peg$FAILED) {
                                    peg$savedPos = s0;
                                    s0 = peg$f83(s4);
                                  } else {
                                    peg$currPos = s0;
                                    s0 = peg$FAILED;
                                  }
                                } else {
                                  peg$currPos = s0;
                                  s0 = peg$FAILED;
                                }
                              } else {
                                peg$currPos = s0;
                                s0 = peg$FAILED;
                              }
                            } else {
                              peg$currPos = s0;
                              s0 = peg$FAILED;
                            }
                          } else {
                            peg$currPos = s0;
                            s0 = peg$FAILED;
                          }
                          if (s0 === peg$FAILED) {
                            s0 = peg$currPos;
                            if (input.substr(peg$currPos, 6) === peg$c65) {
                              s1 = peg$c65;
                              peg$currPos += 6;
                            } else {
                              s1 = peg$FAILED;
                              if (peg$silentFails === 0) {
                                peg$fail(peg$e69);
                              }
                            }
                            if (s1 !== peg$FAILED) {
                              s2 = peg$currPos;
                              s3 = [];
                              s4 = peg$currPos;
                              s5 = peg$currPos;
                              peg$silentFails++;
                              s6 = peg$parseBlockTag();
                              peg$silentFails--;
                              if (s6 === peg$FAILED) {
                                s5 = void 0;
                              } else {
                                peg$currPos = s5;
                                s5 = peg$FAILED;
                              }
                              if (s5 !== peg$FAILED) {
                                s6 = peg$parsechar();
                                if (s6 !== peg$FAILED) {
                                  s5 = [s5, s6];
                                  s4 = s5;
                                } else {
                                  peg$currPos = s4;
                                  s4 = peg$FAILED;
                                }
                              } else {
                                peg$currPos = s4;
                                s4 = peg$FAILED;
                              }
                              while (s4 !== peg$FAILED) {
                                s3.push(s4);
                                s4 = peg$currPos;
                                s5 = peg$currPos;
                                peg$silentFails++;
                                s6 = peg$parseBlockTag();
                                peg$silentFails--;
                                if (s6 === peg$FAILED) {
                                  s5 = void 0;
                                } else {
                                  peg$currPos = s5;
                                  s5 = peg$FAILED;
                                }
                                if (s5 !== peg$FAILED) {
                                  s6 = peg$parsechar();
                                  if (s6 !== peg$FAILED) {
                                    s5 = [s5, s6];
                                    s4 = s5;
                                  } else {
                                    peg$currPos = s4;
                                    s4 = peg$FAILED;
                                  }
                                } else {
                                  peg$currPos = s4;
                                  s4 = peg$FAILED;
                                }
                              }
                              s2 = input.substring(s2, peg$currPos);
                              s3 = peg$parseBlockTag();
                              if (s3 !== peg$FAILED) {
                                if (input.substr(peg$currPos, 9) === peg$c63) {
                                  s4 = peg$c63;
                                  peg$currPos += 9;
                                } else {
                                  s4 = peg$FAILED;
                                  if (peg$silentFails === 0) {
                                    peg$fail(peg$e67);
                                  }
                                }
                                if (s4 !== peg$FAILED) {
                                  s5 = peg$currPos;
                                  s6 = peg$currPos;
                                  if (input.charCodeAt(peg$currPos) === 80) {
                                    s7 = peg$c64;
                                    peg$currPos++;
                                  } else {
                                    s7 = peg$FAILED;
                                    if (peg$silentFails === 0) {
                                      peg$fail(peg$e68);
                                    }
                                  }
                                  if (s7 !== peg$FAILED) {
                                    s8 = peg$currPos;
                                    s9 = [];
                                    s10 = peg$currPos;
                                    s11 = peg$currPos;
                                    peg$silentFails++;
                                    s12 = peg$parseBlockTag();
                                    peg$silentFails--;
                                    if (s12 === peg$FAILED) {
                                      s11 = void 0;
                                    } else {
                                      peg$currPos = s11;
                                      s11 = peg$FAILED;
                                    }
                                    if (s11 !== peg$FAILED) {
                                      s12 = peg$parsechar();
                                      if (s12 !== peg$FAILED) {
                                        s11 = [s11, s12];
                                        s10 = s11;
                                      } else {
                                        peg$currPos = s10;
                                        s10 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s10;
                                      s10 = peg$FAILED;
                                    }
                                    while (s10 !== peg$FAILED) {
                                      s9.push(s10);
                                      s10 = peg$currPos;
                                      s11 = peg$currPos;
                                      peg$silentFails++;
                                      s12 = peg$parseBlockTag();
                                      peg$silentFails--;
                                      if (s12 === peg$FAILED) {
                                        s11 = void 0;
                                      } else {
                                        peg$currPos = s11;
                                        s11 = peg$FAILED;
                                      }
                                      if (s11 !== peg$FAILED) {
                                        s12 = peg$parsechar();
                                        if (s12 !== peg$FAILED) {
                                          s11 = [s11, s12];
                                          s10 = s11;
                                        } else {
                                          peg$currPos = s10;
                                          s10 = peg$FAILED;
                                        }
                                      } else {
                                        peg$currPos = s10;
                                        s10 = peg$FAILED;
                                      }
                                    }
                                    s8 = input.substring(s8, peg$currPos);
                                    s7 = [s7, s8];
                                    s6 = s7;
                                  } else {
                                    peg$currPos = s6;
                                    s6 = peg$FAILED;
                                  }
                                  if (s6 !== peg$FAILED) {
                                    s5 = input.substring(s5, peg$currPos);
                                  } else {
                                    s5 = s6;
                                  }
                                  if (s5 !== peg$FAILED) {
                                    s6 = peg$parseBlockTag();
                                    if (s6 !== peg$FAILED) {
                                      peg$savedPos = s0;
                                      s0 = peg$f84(s2, s5);
                                    } else {
                                      peg$currPos = s0;
                                      s0 = peg$FAILED;
                                    }
                                  } else {
                                    peg$currPos = s0;
                                    s0 = peg$FAILED;
                                  }
                                } else {
                                  peg$currPos = s0;
                                  s0 = peg$FAILED;
                                }
                              } else {
                                peg$currPos = s0;
                                s0 = peg$FAILED;
                              }
                            } else {
                              peg$currPos = s0;
                              s0 = peg$FAILED;
                            }
                            if (s0 === peg$FAILED) {
                              s0 = peg$currPos;
                              if (input.substr(peg$currPos, 9) === peg$c66) {
                                s1 = peg$c66;
                                peg$currPos += 9;
                              } else {
                                s1 = peg$FAILED;
                                if (peg$silentFails === 0) {
                                  peg$fail(peg$e70);
                                }
                              }
                              if (s1 !== peg$FAILED) {
                                s2 = peg$parseBlockTag();
                                if (s2 !== peg$FAILED) {
                                  if (input.substr(peg$currPos, 6) === peg$c67) {
                                    s3 = peg$c67;
                                    peg$currPos += 6;
                                  } else {
                                    s3 = peg$FAILED;
                                    if (peg$silentFails === 0) {
                                      peg$fail(peg$e71);
                                    }
                                  }
                                  if (s3 !== peg$FAILED) {
                                    s4 = peg$parseHighlightColor();
                                    if (s4 !== peg$FAILED) {
                                      s5 = peg$parseBlockTag();
                                      if (s5 !== peg$FAILED) {
                                        peg$savedPos = s0;
                                        s0 = peg$f85(s4);
                                      } else {
                                        peg$currPos = s0;
                                        s0 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s0;
                                      s0 = peg$FAILED;
                                    }
                                  } else {
                                    peg$currPos = s0;
                                    s0 = peg$FAILED;
                                  }
                                } else {
                                  peg$currPos = s0;
                                  s0 = peg$FAILED;
                                }
                              } else {
                                peg$currPos = s0;
                                s0 = peg$FAILED;
                              }
                              if (s0 === peg$FAILED) {
                                s0 = peg$currPos;
                                if (input.substr(peg$currPos, 13) === peg$c68) {
                                  s1 = peg$c68;
                                  peg$currPos += 13;
                                } else {
                                  s1 = peg$FAILED;
                                  if (peg$silentFails === 0) {
                                    peg$fail(peg$e72);
                                  }
                                }
                                if (s1 !== peg$FAILED) {
                                  s2 = peg$parseBlockTag();
                                  if (s2 !== peg$FAILED) {
                                    if (input.substr(peg$currPos, 6) === peg$c67) {
                                      s3 = peg$c67;
                                      peg$currPos += 6;
                                    } else {
                                      s3 = peg$FAILED;
                                      if (peg$silentFails === 0) {
                                        peg$fail(peg$e71);
                                      }
                                    }
                                    if (s3 !== peg$FAILED) {
                                      s4 = peg$parseHighlightColor();
                                      if (s4 !== peg$FAILED) {
                                        s5 = peg$parseBlockTag();
                                        if (s5 !== peg$FAILED) {
                                          peg$savedPos = s0;
                                          s0 = peg$f86(s4);
                                        } else {
                                          peg$currPos = s0;
                                          s0 = peg$FAILED;
                                        }
                                      } else {
                                        peg$currPos = s0;
                                        s0 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s0;
                                      s0 = peg$FAILED;
                                    }
                                  } else {
                                    peg$currPos = s0;
                                    s0 = peg$FAILED;
                                  }
                                } else {
                                  peg$currPos = s0;
                                  s0 = peg$FAILED;
                                }
                                if (s0 === peg$FAILED) {
                                  s0 = peg$currPos;
                                  if (input.substr(peg$currPos, 6) === peg$c67) {
                                    s1 = peg$c67;
                                    peg$currPos += 6;
                                  } else {
                                    s1 = peg$FAILED;
                                    if (peg$silentFails === 0) {
                                      peg$fail(peg$e71);
                                    }
                                  }
                                  if (s1 !== peg$FAILED) {
                                    s2 = peg$parseColor();
                                    if (s2 !== peg$FAILED) {
                                      s3 = peg$parseBlockTag();
                                      if (s3 !== peg$FAILED) {
                                        peg$savedPos = s0;
                                        s0 = peg$f87(s2);
                                      } else {
                                        peg$currPos = s0;
                                        s0 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s0;
                                      s0 = peg$FAILED;
                                    }
                                  } else {
                                    peg$currPos = s0;
                                    s0 = peg$FAILED;
                                  }
                                  if (s0 === peg$FAILED) {
                                    s0 = peg$currPos;
                                    if (input.substr(peg$currPos, 12) === peg$c69) {
                                      s1 = peg$c69;
                                      peg$currPos += 12;
                                    } else {
                                      s1 = peg$FAILED;
                                      if (peg$silentFails === 0) {
                                        peg$fail(peg$e73);
                                      }
                                    }
                                    if (s1 !== peg$FAILED) {
                                      s2 = peg$currPos;
                                      s3 = [];
                                      s4 = peg$currPos;
                                      s5 = peg$currPos;
                                      peg$silentFails++;
                                      s6 = peg$parseBlockTag();
                                      peg$silentFails--;
                                      if (s6 === peg$FAILED) {
                                        s5 = void 0;
                                      } else {
                                        peg$currPos = s5;
                                        s5 = peg$FAILED;
                                      }
                                      if (s5 !== peg$FAILED) {
                                        s6 = peg$parsechar();
                                        if (s6 !== peg$FAILED) {
                                          s5 = [s5, s6];
                                          s4 = s5;
                                        } else {
                                          peg$currPos = s4;
                                          s4 = peg$FAILED;
                                        }
                                      } else {
                                        peg$currPos = s4;
                                        s4 = peg$FAILED;
                                      }
                                      while (s4 !== peg$FAILED) {
                                        s3.push(s4);
                                        s4 = peg$currPos;
                                        s5 = peg$currPos;
                                        peg$silentFails++;
                                        s6 = peg$parseBlockTag();
                                        peg$silentFails--;
                                        if (s6 === peg$FAILED) {
                                          s5 = void 0;
                                        } else {
                                          peg$currPos = s5;
                                          s5 = peg$FAILED;
                                        }
                                        if (s5 !== peg$FAILED) {
                                          s6 = peg$parsechar();
                                          if (s6 !== peg$FAILED) {
                                            s5 = [s5, s6];
                                            s4 = s5;
                                          } else {
                                            peg$currPos = s4;
                                            s4 = peg$FAILED;
                                          }
                                        } else {
                                          peg$currPos = s4;
                                          s4 = peg$FAILED;
                                        }
                                      }
                                      s2 = input.substring(s2, peg$currPos);
                                      s3 = peg$parseBlockTag();
                                      if (s3 !== peg$FAILED) {
                                        peg$savedPos = s0;
                                        s0 = peg$f88(s2);
                                      } else {
                                        peg$currPos = s0;
                                        s0 = peg$FAILED;
                                      }
                                    } else {
                                      peg$currPos = s0;
                                      s0 = peg$FAILED;
                                    }
                                    if (s0 === peg$FAILED) {
                                      s0 = peg$currPos;
                                      s1 = peg$parseAlternativeStyleTags();
                                      if (s1 !== peg$FAILED) {
                                        s2 = peg$parseBlockTag();
                                        if (s2 !== peg$FAILED) {
                                          peg$savedPos = s0;
                                          s0 = peg$f89(s1);
                                        } else {
                                          peg$currPos = s0;
                                          s0 = peg$FAILED;
                                        }
                                      } else {
                                        peg$currPos = s0;
                                        s0 = peg$FAILED;
                                      }
                                      if (s0 === peg$FAILED) {
                                        s0 = peg$currPos;
                                        if (input.charCodeAt(peg$currPos) === 35) {
                                          s1 = peg$c21;
                                          peg$currPos++;
                                        } else {
                                          s1 = peg$FAILED;
                                          if (peg$silentFails === 0) {
                                            peg$fail(peg$e25);
                                          }
                                        }
                                        if (s1 !== peg$FAILED) {
                                          s2 = peg$currPos;
                                          s3 = [];
                                          s4 = peg$currPos;
                                          s5 = peg$currPos;
                                          peg$silentFails++;
                                          s6 = peg$parseBlockTag();
                                          peg$silentFails--;
                                          if (s6 === peg$FAILED) {
                                            s5 = void 0;
                                          } else {
                                            peg$currPos = s5;
                                            s5 = peg$FAILED;
                                          }
                                          if (s5 !== peg$FAILED) {
                                            s6 = peg$parsechar();
                                            if (s6 !== peg$FAILED) {
                                              s5 = [s5, s6];
                                              s4 = s5;
                                            } else {
                                              peg$currPos = s4;
                                              s4 = peg$FAILED;
                                            }
                                          } else {
                                            peg$currPos = s4;
                                            s4 = peg$FAILED;
                                          }
                                          while (s4 !== peg$FAILED) {
                                            s3.push(s4);
                                            s4 = peg$currPos;
                                            s5 = peg$currPos;
                                            peg$silentFails++;
                                            s6 = peg$parseBlockTag();
                                            peg$silentFails--;
                                            if (s6 === peg$FAILED) {
                                              s5 = void 0;
                                            } else {
                                              peg$currPos = s5;
                                              s5 = peg$FAILED;
                                            }
                                            if (s5 !== peg$FAILED) {
                                              s6 = peg$parsechar();
                                              if (s6 !== peg$FAILED) {
                                                s5 = [s5, s6];
                                                s4 = s5;
                                              } else {
                                                peg$currPos = s4;
                                                s4 = peg$FAILED;
                                              }
                                            } else {
                                              peg$currPos = s4;
                                              s4 = peg$FAILED;
                                            }
                                          }
                                          s2 = input.substring(s2, peg$currPos);
                                          s3 = peg$parseBlockTag();
                                          if (s3 !== peg$FAILED) {
                                            peg$savedPos = s0;
                                            s0 = peg$f90(s2);
                                          } else {
                                            peg$currPos = s0;
                                            s0 = peg$FAILED;
                                          }
                                        } else {
                                          peg$currPos = s0;
                                          s0 = peg$FAILED;
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseRefsChain() {
    let s0, s1;
    s0 = [];
    s1 = peg$parseRef();
    while (s1 !== peg$FAILED) {
      s0.push(s1);
      s1 = peg$parseRef();
    }
    return s0;
  }
  function peg$parseRef() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c70) {
      s1 = peg$c70;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e74);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$currPos;
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseBlockTag();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s5 = [s5, s6];
          s4 = s5;
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      } else {
        peg$currPos = s4;
        s4 = peg$FAILED;
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parsechar();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      }
      s2 = input.substring(s2, peg$currPos);
      peg$savedPos = s0;
      s0 = peg$f91(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseAlternativeStyleTags() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c71) {
      s0 = peg$c71;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e75);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 6) === peg$c72) {
        s0 = peg$c72;
        peg$currPos += 6;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e76);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 5) === peg$c73) {
          s0 = peg$c73;
          peg$currPos += 5;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e77);
          }
        }
        if (s0 === peg$FAILED) {
          if (input.substr(peg$currPos, 15) === peg$c74) {
            s0 = peg$c74;
            peg$currPos += 15;
          } else {
            s0 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e78);
            }
          }
          if (s0 === peg$FAILED) {
            if (input.substr(peg$currPos, 15) === peg$c75) {
              s0 = peg$c75;
              peg$currPos += 15;
            } else {
              s0 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e79);
              }
            }
            if (s0 === peg$FAILED) {
              if (input.substr(peg$currPos, 14) === peg$c76) {
                s0 = peg$c76;
                peg$currPos += 14;
              } else {
                s0 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e80);
                }
              }
              if (s0 === peg$FAILED) {
                if (input.substr(peg$currPos, 13) === peg$c77) {
                  s0 = peg$c77;
                  peg$currPos += 13;
                } else {
                  s0 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e81);
                  }
                }
                if (s0 === peg$FAILED) {
                  if (input.substr(peg$currPos, 15) === peg$c78) {
                    s0 = peg$c78;
                    peg$currPos += 15;
                  } else {
                    s0 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e82);
                    }
                  }
                  if (s0 === peg$FAILED) {
                    if (input.substr(peg$currPos, 13) === peg$c79) {
                      s0 = peg$c79;
                      peg$currPos += 13;
                    } else {
                      s0 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e83);
                      }
                    }
                    if (s0 === peg$FAILED) {
                      if (input.substr(peg$currPos, 14) === peg$c80) {
                        s0 = peg$c80;
                        peg$currPos += 14;
                      } else {
                        s0 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e84);
                        }
                      }
                      if (s0 === peg$FAILED) {
                        if (input.substr(peg$currPos, 14) === peg$c81) {
                          s0 = peg$c81;
                          peg$currPos += 14;
                        } else {
                          s0 = peg$FAILED;
                          if (peg$silentFails === 0) {
                            peg$fail(peg$e85);
                          }
                        }
                        if (s0 === peg$FAILED) {
                          if (input.substr(peg$currPos, 14) === peg$c82) {
                            s0 = peg$c82;
                            peg$currPos += 14;
                          } else {
                            s0 = peg$FAILED;
                            if (peg$silentFails === 0) {
                              peg$fail(peg$e86);
                            }
                          }
                          if (s0 === peg$FAILED) {
                            if (input.substr(peg$currPos, 13) === peg$c83) {
                              s0 = peg$c83;
                              peg$currPos += 13;
                            } else {
                              s0 = peg$FAILED;
                              if (peg$silentFails === 0) {
                                peg$fail(peg$e87);
                              }
                            }
                            if (s0 === peg$FAILED) {
                              if (input.substr(peg$currPos, 9) === peg$c66) {
                                s0 = peg$c66;
                                peg$currPos += 9;
                              } else {
                                s0 = peg$FAILED;
                                if (peg$silentFails === 0) {
                                  peg$fail(peg$e70);
                                }
                              }
                              if (s0 === peg$FAILED) {
                                if (input.substr(peg$currPos, 6) === peg$c84) {
                                  s0 = peg$c84;
                                  peg$currPos += 6;
                                } else {
                                  s0 = peg$FAILED;
                                  if (peg$silentFails === 0) {
                                    peg$fail(peg$e88);
                                  }
                                }
                                if (s0 === peg$FAILED) {
                                  if (input.substr(peg$currPos, 9) === peg$c85) {
                                    s0 = peg$c85;
                                    peg$currPos += 9;
                                  } else {
                                    s0 = peg$FAILED;
                                    if (peg$silentFails === 0) {
                                      peg$fail(peg$e89);
                                    }
                                  }
                                  if (s0 === peg$FAILED) {
                                    if (input.substr(peg$currPos, 11) === peg$c86) {
                                      s0 = peg$c86;
                                      peg$currPos += 11;
                                    } else {
                                      s0 = peg$FAILED;
                                      if (peg$silentFails === 0) {
                                        peg$fail(peg$e90);
                                      }
                                    }
                                    if (s0 === peg$FAILED) {
                                      if (input.substr(peg$currPos, 3) === peg$c87) {
                                        s0 = peg$c87;
                                        peg$currPos += 3;
                                      } else {
                                        s0 = peg$FAILED;
                                        if (peg$silentFails === 0) {
                                          peg$fail(peg$e91);
                                        }
                                      }
                                      if (s0 === peg$FAILED) {
                                        if (input.substr(peg$currPos, 3) === peg$c88) {
                                          s0 = peg$c88;
                                          peg$currPos += 3;
                                        } else {
                                          s0 = peg$FAILED;
                                          if (peg$silentFails === 0) {
                                            peg$fail(peg$e92);
                                          }
                                        }
                                        if (s0 === peg$FAILED) {
                                          if (input.substr(peg$currPos, 9) === peg$c89) {
                                            s0 = peg$c89;
                                            peg$currPos += 9;
                                          } else {
                                            s0 = peg$FAILED;
                                            if (peg$silentFails === 0) {
                                              peg$fail(peg$e93);
                                            }
                                          }
                                          if (s0 === peg$FAILED) {
                                            if (input.substr(peg$currPos, 15) === peg$c90) {
                                              s0 = peg$c90;
                                              peg$currPos += 15;
                                            } else {
                                              s0 = peg$FAILED;
                                              if (peg$silentFails === 0) {
                                                peg$fail(peg$e94);
                                              }
                                            }
                                            if (s0 === peg$FAILED) {
                                              if (input.substr(peg$currPos, 9) === peg$c91) {
                                                s0 = peg$c91;
                                                peg$currPos += 9;
                                              } else {
                                                s0 = peg$FAILED;
                                                if (peg$silentFails === 0) {
                                                  peg$fail(peg$e95);
                                                }
                                              }
                                              if (s0 === peg$FAILED) {
                                                if (input.substr(peg$currPos, 6) === peg$c92) {
                                                  s0 = peg$c92;
                                                  peg$currPos += 6;
                                                } else {
                                                  s0 = peg$FAILED;
                                                  if (peg$silentFails === 0) {
                                                    peg$fail(peg$e96);
                                                  }
                                                }
                                                if (s0 === peg$FAILED) {
                                                  if (input.substr(peg$currPos, 13) === peg$c93) {
                                                    s0 = peg$c93;
                                                    peg$currPos += 13;
                                                  } else {
                                                    s0 = peg$FAILED;
                                                    if (peg$silentFails === 0) {
                                                      peg$fail(peg$e97);
                                                    }
                                                  }
                                                  if (s0 === peg$FAILED) {
                                                    if (input.substr(peg$currPos, 16) === peg$c94) {
                                                      s0 = peg$c94;
                                                      peg$currPos += 16;
                                                    } else {
                                                      s0 = peg$FAILED;
                                                      if (peg$silentFails === 0) {
                                                        peg$fail(peg$e98);
                                                      }
                                                    }
                                                    if (s0 === peg$FAILED) {
                                                      if (input.substr(peg$currPos, 16) === peg$c95) {
                                                        s0 = peg$c95;
                                                        peg$currPos += 16;
                                                      } else {
                                                        s0 = peg$FAILED;
                                                        if (peg$silentFails === 0) {
                                                          peg$fail(peg$e99);
                                                        }
                                                      }
                                                      if (s0 === peg$FAILED) {
                                                        if (input.substr(peg$currPos, 15) === peg$c96) {
                                                          s0 = peg$c96;
                                                          peg$currPos += 15;
                                                        } else {
                                                          s0 = peg$FAILED;
                                                          if (peg$silentFails === 0) {
                                                            peg$fail(peg$e100);
                                                          }
                                                        }
                                                        if (s0 === peg$FAILED) {
                                                          if (input.substr(peg$currPos, 14) === peg$c97) {
                                                            s0 = peg$c97;
                                                            peg$currPos += 14;
                                                          } else {
                                                            s0 = peg$FAILED;
                                                            if (peg$silentFails === 0) {
                                                              peg$fail(peg$e101);
                                                            }
                                                          }
                                                          if (s0 === peg$FAILED) {
                                                            if (input.substr(peg$currPos, 16) === peg$c98) {
                                                              s0 = peg$c98;
                                                              peg$currPos += 16;
                                                            } else {
                                                              s0 = peg$FAILED;
                                                              if (peg$silentFails === 0) {
                                                                peg$fail(peg$e102);
                                                              }
                                                            }
                                                            if (s0 === peg$FAILED) {
                                                              if (input.substr(peg$currPos, 14) === peg$c99) {
                                                                s0 = peg$c99;
                                                                peg$currPos += 14;
                                                              } else {
                                                                s0 = peg$FAILED;
                                                                if (peg$silentFails === 0) {
                                                                  peg$fail(peg$e103);
                                                                }
                                                              }
                                                              if (s0 === peg$FAILED) {
                                                                if (input.substr(peg$currPos, 15) === peg$c100) {
                                                                  s0 = peg$c100;
                                                                  peg$currPos += 15;
                                                                } else {
                                                                  s0 = peg$FAILED;
                                                                  if (peg$silentFails === 0) {
                                                                    peg$fail(peg$e104);
                                                                  }
                                                                }
                                                                if (s0 === peg$FAILED) {
                                                                  if (input.substr(peg$currPos, 15) === peg$c101) {
                                                                    s0 = peg$c101;
                                                                    peg$currPos += 15;
                                                                  } else {
                                                                    s0 = peg$FAILED;
                                                                    if (peg$silentFails === 0) {
                                                                      peg$fail(peg$e105);
                                                                    }
                                                                  }
                                                                  if (s0 === peg$FAILED) {
                                                                    if (input.substr(peg$currPos, 15) === peg$c102) {
                                                                      s0 = peg$c102;
                                                                      peg$currPos += 15;
                                                                    } else {
                                                                      s0 = peg$FAILED;
                                                                      if (peg$silentFails === 0) {
                                                                        peg$fail(peg$e106);
                                                                      }
                                                                    }
                                                                    if (s0 === peg$FAILED) {
                                                                      if (input.substr(peg$currPos, 14) === peg$c103) {
                                                                        s0 = peg$c103;
                                                                        peg$currPos += 14;
                                                                      } else {
                                                                        s0 = peg$FAILED;
                                                                        if (peg$silentFails === 0) {
                                                                          peg$fail(peg$e107);
                                                                        }
                                                                      }
                                                                      if (s0 === peg$FAILED) {
                                                                        if (input.substr(peg$currPos, 10) === peg$c104) {
                                                                          s0 = peg$c104;
                                                                          peg$currPos += 10;
                                                                        } else {
                                                                          s0 = peg$FAILED;
                                                                          if (peg$silentFails === 0) {
                                                                            peg$fail(peg$e108);
                                                                          }
                                                                        }
                                                                        if (s0 === peg$FAILED) {
                                                                          if (input.substr(peg$currPos, 13) === peg$c105) {
                                                                            s0 = peg$c105;
                                                                            peg$currPos += 13;
                                                                          } else {
                                                                            s0 = peg$FAILED;
                                                                            if (peg$silentFails === 0) {
                                                                              peg$fail(peg$e109);
                                                                            }
                                                                          }
                                                                          if (s0 === peg$FAILED) {
                                                                            if (input.substr(peg$currPos, 19) === peg$c106) {
                                                                              s0 = peg$c106;
                                                                              peg$currPos += 19;
                                                                            } else {
                                                                              s0 = peg$FAILED;
                                                                              if (peg$silentFails === 0) {
                                                                                peg$fail(peg$e110);
                                                                              }
                                                                            }
                                                                            if (s0 === peg$FAILED) {
                                                                              if (input.substr(peg$currPos, 10) === peg$c107) {
                                                                                s0 = peg$c107;
                                                                                peg$currPos += 10;
                                                                              } else {
                                                                                s0 = peg$FAILED;
                                                                                if (peg$silentFails === 0) {
                                                                                  peg$fail(peg$e111);
                                                                                }
                                                                              }
                                                                              if (s0 === peg$FAILED) {
                                                                                if (input.substr(peg$currPos, 10) === peg$c108) {
                                                                                  s0 = peg$c108;
                                                                                  peg$currPos += 10;
                                                                                } else {
                                                                                  s0 = peg$FAILED;
                                                                                  if (peg$silentFails === 0) {
                                                                                    peg$fail(peg$e112);
                                                                                  }
                                                                                }
                                                                                if (s0 === peg$FAILED) {
                                                                                  if (input.substr(peg$currPos, 13) === peg$c68) {
                                                                                    s0 = peg$c68;
                                                                                    peg$currPos += 13;
                                                                                  } else {
                                                                                    s0 = peg$FAILED;
                                                                                    if (peg$silentFails === 0) {
                                                                                      peg$fail(peg$e72);
                                                                                    }
                                                                                  }
                                                                                  if (s0 === peg$FAILED) {
                                                                                    if (input.substr(peg$currPos, 11) === peg$c109) {
                                                                                      s0 = peg$c109;
                                                                                      peg$currPos += 11;
                                                                                    } else {
                                                                                      s0 = peg$FAILED;
                                                                                      if (peg$silentFails === 0) {
                                                                                        peg$fail(peg$e113);
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                }
                                                                              }
                                                                            }
                                                                          }
                                                                        }
                                                                      }
                                                                    }
                                                                  }
                                                                }
                                                              }
                                                            }
                                                          }
                                                        }
                                                      }
                                                    }
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseColor() {
    let s0;
    if (input.substr(peg$currPos, 4) === peg$c110) {
      s0 = peg$c110;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e114);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 5) === peg$c111) {
        s0 = peg$c111;
        peg$currPos += 5;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e115);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 4) === peg$c112) {
          s0 = peg$c112;
          peg$currPos += 4;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e116);
          }
        }
        if (s0 === peg$FAILED) {
          if (input.substr(peg$currPos, 5) === peg$c113) {
            s0 = peg$c113;
            peg$currPos += 5;
          } else {
            s0 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e117);
            }
          }
          if (s0 === peg$FAILED) {
            if (input.substr(peg$currPos, 7) === peg$c114) {
              s0 = peg$c114;
              peg$currPos += 7;
            } else {
              s0 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e118);
              }
            }
            if (s0 === peg$FAILED) {
              if (input.substr(peg$currPos, 9) === peg$c115) {
                s0 = peg$c115;
                peg$currPos += 9;
              } else {
                s0 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e119);
                }
              }
              if (s0 === peg$FAILED) {
                if (input.substr(peg$currPos, 4) === peg$c116) {
                  s0 = peg$c116;
                  peg$currPos += 4;
                } else {
                  s0 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e120);
                  }
                }
                if (s0 === peg$FAILED) {
                  if (input.substr(peg$currPos, 8) === peg$c117) {
                    s0 = peg$c117;
                    peg$currPos += 8;
                  } else {
                    s0 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e121);
                    }
                  }
                  if (s0 === peg$FAILED) {
                    if (input.substr(peg$currPos, 5) === peg$c118) {
                      s0 = peg$c118;
                      peg$currPos += 5;
                    } else {
                      s0 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e122);
                      }
                    }
                    if (s0 === peg$FAILED) {
                      if (input.substr(peg$currPos, 4) === peg$c119) {
                        s0 = peg$c119;
                        peg$currPos += 4;
                      } else {
                        s0 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e123);
                        }
                      }
                      if (s0 === peg$FAILED) {
                        if (input.substr(peg$currPos, 7) === peg$c120) {
                          s0 = peg$c120;
                          peg$currPos += 7;
                        } else {
                          s0 = peg$FAILED;
                          if (peg$silentFails === 0) {
                            peg$fail(peg$e124);
                          }
                        }
                        if (s0 === peg$FAILED) {
                          if (input.substr(peg$currPos, 6) === peg$c121) {
                            s0 = peg$c121;
                            peg$currPos += 6;
                          } else {
                            s0 = peg$FAILED;
                            if (peg$silentFails === 0) {
                              peg$fail(peg$e125);
                            }
                          }
                          if (s0 === peg$FAILED) {
                            if (input.substr(peg$currPos, 4) === peg$c122) {
                              s0 = peg$c122;
                              peg$currPos += 4;
                            } else {
                              s0 = peg$FAILED;
                              if (peg$silentFails === 0) {
                                peg$fail(peg$e126);
                              }
                            }
                            if (s0 === peg$FAILED) {
                              if (input.substr(peg$currPos, 5) === peg$c123) {
                                s0 = peg$c123;
                                peg$currPos += 5;
                              } else {
                                s0 = peg$FAILED;
                                if (peg$silentFails === 0) {
                                  peg$fail(peg$e127);
                                }
                              }
                              if (s0 === peg$FAILED) {
                                if (input.substr(peg$currPos, 6) === peg$c124) {
                                  s0 = peg$c124;
                                  peg$currPos += 6;
                                } else {
                                  s0 = peg$FAILED;
                                  if (peg$silentFails === 0) {
                                    peg$fail(peg$e128);
                                  }
                                }
                                if (s0 === peg$FAILED) {
                                  if (input.substr(peg$currPos, 4) === peg$c125) {
                                    s0 = peg$c125;
                                    peg$currPos += 4;
                                  } else {
                                    s0 = peg$FAILED;
                                    if (peg$silentFails === 0) {
                                      peg$fail(peg$e129);
                                    }
                                  }
                                  if (s0 === peg$FAILED) {
                                    if (input.substr(peg$currPos, 6) === peg$c126) {
                                      s0 = peg$c126;
                                      peg$currPos += 6;
                                    } else {
                                      s0 = peg$FAILED;
                                      if (peg$silentFails === 0) {
                                        peg$fail(peg$e130);
                                      }
                                    }
                                    if (s0 === peg$FAILED) {
                                      if (input.substr(peg$currPos, 3) === peg$c127) {
                                        s0 = peg$c127;
                                        peg$currPos += 3;
                                      } else {
                                        s0 = peg$FAILED;
                                        if (peg$silentFails === 0) {
                                          peg$fail(peg$e131);
                                        }
                                      }
                                      if (s0 === peg$FAILED) {
                                        if (input.substr(peg$currPos, 6) === peg$c128) {
                                          s0 = peg$c128;
                                          peg$currPos += 6;
                                        } else {
                                          s0 = peg$FAILED;
                                          if (peg$silentFails === 0) {
                                            peg$fail(peg$e132);
                                          }
                                        }
                                        if (s0 === peg$FAILED) {
                                          if (input.substr(peg$currPos, 4) === peg$c129) {
                                            s0 = peg$c129;
                                            peg$currPos += 4;
                                          } else {
                                            s0 = peg$FAILED;
                                            if (peg$silentFails === 0) {
                                              peg$fail(peg$e133);
                                            }
                                          }
                                          if (s0 === peg$FAILED) {
                                            if (input.substr(peg$currPos, 6) === peg$c130) {
                                              s0 = peg$c130;
                                              peg$currPos += 6;
                                            } else {
                                              s0 = peg$FAILED;
                                              if (peg$silentFails === 0) {
                                                peg$fail(peg$e134);
                                              }
                                            }
                                            if (s0 === peg$FAILED) {
                                              if (input.substr(peg$currPos, 5) === peg$c131) {
                                                s0 = peg$c131;
                                                peg$currPos += 5;
                                              } else {
                                                s0 = peg$FAILED;
                                                if (peg$silentFails === 0) {
                                                  peg$fail(peg$e135);
                                                }
                                              }
                                              if (s0 === peg$FAILED) {
                                                if (input.substr(peg$currPos, 6) === peg$c132) {
                                                  s0 = peg$c132;
                                                  peg$currPos += 6;
                                                } else {
                                                  s0 = peg$FAILED;
                                                  if (peg$silentFails === 0) {
                                                    peg$fail(peg$e136);
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseHighlightColor() {
    let s0;
    if (input.substr(peg$currPos, 6) === peg$c124) {
      s0 = peg$c124;
      peg$currPos += 6;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e128);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 6) === peg$c132) {
        s0 = peg$c132;
        peg$currPos += 6;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e136);
        }
      }
      if (s0 === peg$FAILED) {
        if (input.substr(peg$currPos, 5) === peg$c118) {
          s0 = peg$c118;
          peg$currPos += 5;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e122);
          }
        }
        if (s0 === peg$FAILED) {
          if (input.substr(peg$currPos, 4) === peg$c112) {
            s0 = peg$c112;
            peg$currPos += 4;
          } else {
            s0 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e116);
            }
          }
          if (s0 === peg$FAILED) {
            if (input.substr(peg$currPos, 6) === peg$c126) {
              s0 = peg$c126;
              peg$currPos += 6;
            } else {
              s0 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e130);
              }
            }
            if (s0 === peg$FAILED) {
              if (input.substr(peg$currPos, 4) === peg$c125) {
                s0 = peg$c125;
                peg$currPos += 4;
              } else {
                s0 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e129);
                }
              }
              if (s0 === peg$FAILED) {
                if (input.substr(peg$currPos, 5) === peg$c113) {
                  s0 = peg$c113;
                  peg$currPos += 5;
                } else {
                  s0 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e117);
                  }
                }
                if (s0 === peg$FAILED) {
                  if (input.substr(peg$currPos, 5) === peg$c131) {
                    s0 = peg$c131;
                    peg$currPos += 5;
                  } else {
                    s0 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e135);
                    }
                  }
                  if (s0 === peg$FAILED) {
                    if (input.substr(peg$currPos, 5) === peg$c111) {
                      s0 = peg$c111;
                      peg$currPos += 5;
                    } else {
                      s0 = peg$FAILED;
                      if (peg$silentFails === 0) {
                        peg$fail(peg$e115);
                      }
                    }
                    if (s0 === peg$FAILED) {
                      if (input.substr(peg$currPos, 4) === peg$c116) {
                        s0 = peg$c116;
                        peg$currPos += 4;
                      } else {
                        s0 = peg$FAILED;
                        if (peg$silentFails === 0) {
                          peg$fail(peg$e120);
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parsebitmarkMinusMinus() {
    let s0, s1;
    peg$silentFails++;
    s0 = peg$currPos;
    s1 = peg$parsebitmarkMinusMinusString();
    peg$savedPos = s0;
    s1 = peg$f92(s1);
    s0 = s1;
    peg$silentFails--;
    return s0;
  }
  function peg$parsebitmarkMinusMinusString() {
    let s0, s1, s2, s3;
    peg$silentFails++;
    s0 = peg$currPos;
    s1 = peg$parsePlainText();
    if (s1 === peg$FAILED) {
      s1 = null;
    }
    s2 = [];
    s3 = peg$parseStyledText();
    if (s3 === peg$FAILED) {
      s3 = peg$parsePlainText();
    }
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      s3 = peg$parseStyledText();
      if (s3 === peg$FAILED) {
        s3 = peg$parsePlainText();
      }
    }
    peg$savedPos = s0;
    s0 = peg$f93(s1, s2);
    peg$silentFails--;
    return s0;
  }
  function peg$parsePlainText() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    s1 = peg$parseNL();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f94();
    }
    s0 = s1;
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$currPos;
      s2 = [];
      s3 = peg$currPos;
      s4 = peg$parseTagTags();
      if (s4 === peg$FAILED) {
        s4 = null;
      }
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseStyledText();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parsechar();
        if (s6 !== peg$FAILED) {
          s4 = [s4, s5, s6];
          s3 = s4;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 === peg$FAILED) {
        s3 = peg$currPos;
        s4 = peg$parseTagTags();
        if (s4 !== peg$FAILED) {
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseStyledText();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s4 = [s4, s5];
            s3 = s4;
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      }
      if (s3 !== peg$FAILED) {
        while (s3 !== peg$FAILED) {
          s2.push(s3);
          s3 = peg$currPos;
          s4 = peg$parseTagTags();
          if (s4 === peg$FAILED) {
            s4 = null;
          }
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseStyledText();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            s6 = peg$parsechar();
            if (s6 !== peg$FAILED) {
              s4 = [s4, s5, s6];
              s3 = s4;
            } else {
              peg$currPos = s3;
              s3 = peg$FAILED;
            }
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
          if (s3 === peg$FAILED) {
            s3 = peg$currPos;
            s4 = peg$parseTagTags();
            if (s4 !== peg$FAILED) {
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseStyledText();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                s4 = [s4, s5];
                s3 = s4;
              } else {
                peg$currPos = s3;
                s3 = peg$FAILED;
              }
            } else {
              peg$currPos = s3;
              s3 = peg$FAILED;
            }
          }
        }
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        s1 = input.substring(s1, peg$currPos);
      } else {
        s1 = s2;
      }
      if (s1 !== peg$FAILED) {
        peg$savedPos = s0;
        s1 = peg$f95(s1);
      }
      s0 = s1;
    }
    return s0;
  }
  function peg$parseBoldHalfTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 42) {
      s0 = peg$c133;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e137);
      }
    }
    return s0;
  }
  function peg$parseItalicHalfTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 95) {
      s0 = peg$c134;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e138);
      }
    }
    return s0;
  }
  function peg$parseLightHalfTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 96) {
      s0 = peg$c135;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e139);
      }
    }
    return s0;
  }
  function peg$parseHighlightHalfTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 33) {
      s0 = peg$c136;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e140);
      }
    }
    return s0;
  }
  function peg$parseBoldTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseBoldHalfTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseBoldHalfTag();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseItalicTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseItalicHalfTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseItalicHalfTag();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseLightTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseLightHalfTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseLightHalfTag();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseHighlightTag() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseHighlightHalfTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseHighlightHalfTag();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBodyBitOpenTag() {
    let s0;
    if (input.substr(peg$currPos, 2) === peg$c137) {
      s0 = peg$c137;
      peg$currPos += 2;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e141);
      }
    }
    return s0;
  }
  function peg$parseBodyBitCloseTag() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 93) {
      s0 = peg$c138;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e142);
      }
    }
    return s0;
  }
  function peg$parseStyledText() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    s1 = peg$parseBodyBitOpenTag();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r1.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = input.charAt(peg$currPos);
          if (peg$r1.test(s4)) {
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e15);
            }
          }
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseBodyBitCloseTag();
        if (s3 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f96(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$parseBoldTag();
      if (s1 !== peg$FAILED) {
        s2 = peg$currPos;
        s3 = [];
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBoldTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          if (input.length > peg$currPos) {
            s6 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s6 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e0);
            }
          }
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseBoldTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s6 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        }
        s2 = input.substring(s2, peg$currPos);
        s3 = peg$parseBoldTag();
        if (s3 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f97(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
      if (s0 === peg$FAILED) {
        s0 = peg$currPos;
        s1 = peg$parseItalicTag();
        if (s1 !== peg$FAILED) {
          s2 = peg$currPos;
          s3 = [];
          s4 = peg$currPos;
          s5 = peg$currPos;
          peg$silentFails++;
          s6 = peg$parseItalicTag();
          peg$silentFails--;
          if (s6 === peg$FAILED) {
            s5 = void 0;
          } else {
            peg$currPos = s5;
            s5 = peg$FAILED;
          }
          if (s5 !== peg$FAILED) {
            if (input.length > peg$currPos) {
              s6 = input.charAt(peg$currPos);
              peg$currPos++;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e0);
              }
            }
            if (s6 !== peg$FAILED) {
              s5 = [s5, s6];
              s4 = s5;
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
          while (s4 !== peg$FAILED) {
            s3.push(s4);
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseItalicTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              if (input.length > peg$currPos) {
                s6 = input.charAt(peg$currPos);
                peg$currPos++;
              } else {
                s6 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e0);
                }
              }
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
          }
          s2 = input.substring(s2, peg$currPos);
          s3 = peg$parseItalicTag();
          if (s3 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f98(s2);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
        if (s0 === peg$FAILED) {
          s0 = peg$currPos;
          s1 = peg$parseLightTag();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$currPos;
            s5 = peg$currPos;
            peg$silentFails++;
            s6 = peg$parseLightTag();
            peg$silentFails--;
            if (s6 === peg$FAILED) {
              s5 = void 0;
            } else {
              peg$currPos = s5;
              s5 = peg$FAILED;
            }
            if (s5 !== peg$FAILED) {
              if (input.length > peg$currPos) {
                s6 = input.charAt(peg$currPos);
                peg$currPos++;
              } else {
                s6 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e0);
                }
              }
              if (s6 !== peg$FAILED) {
                s5 = [s5, s6];
                s4 = s5;
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            } else {
              peg$currPos = s4;
              s4 = peg$FAILED;
            }
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseLightTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                if (input.length > peg$currPos) {
                  s6 = input.charAt(peg$currPos);
                  peg$currPos++;
                } else {
                  s6 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e0);
                  }
                }
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
            }
            s2 = input.substring(s2, peg$currPos);
            s3 = peg$parseLightTag();
            if (s3 !== peg$FAILED) {
              peg$savedPos = s0;
              s0 = peg$f99(s2);
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parseHighlightTag();
            if (s1 !== peg$FAILED) {
              s2 = peg$currPos;
              s3 = [];
              s4 = peg$currPos;
              s5 = peg$currPos;
              peg$silentFails++;
              s6 = peg$parseHighlightTag();
              peg$silentFails--;
              if (s6 === peg$FAILED) {
                s5 = void 0;
              } else {
                peg$currPos = s5;
                s5 = peg$FAILED;
              }
              if (s5 !== peg$FAILED) {
                if (input.length > peg$currPos) {
                  s6 = input.charAt(peg$currPos);
                  peg$currPos++;
                } else {
                  s6 = peg$FAILED;
                  if (peg$silentFails === 0) {
                    peg$fail(peg$e0);
                  }
                }
                if (s6 !== peg$FAILED) {
                  s5 = [s5, s6];
                  s4 = s5;
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              } else {
                peg$currPos = s4;
                s4 = peg$FAILED;
              }
              while (s4 !== peg$FAILED) {
                s3.push(s4);
                s4 = peg$currPos;
                s5 = peg$currPos;
                peg$silentFails++;
                s6 = peg$parseHighlightTag();
                peg$silentFails--;
                if (s6 === peg$FAILED) {
                  s5 = void 0;
                } else {
                  peg$currPos = s5;
                  s5 = peg$FAILED;
                }
                if (s5 !== peg$FAILED) {
                  if (input.length > peg$currPos) {
                    s6 = input.charAt(peg$currPos);
                    peg$currPos++;
                  } else {
                    s6 = peg$FAILED;
                    if (peg$silentFails === 0) {
                      peg$fail(peg$e0);
                    }
                  }
                  if (s6 !== peg$FAILED) {
                    s5 = [s5, s6];
                    s4 = s5;
                  } else {
                    peg$currPos = s4;
                    s4 = peg$FAILED;
                  }
                } else {
                  peg$currPos = s4;
                  s4 = peg$FAILED;
                }
              }
              s2 = input.substring(s2, peg$currPos);
              s3 = peg$parseHighlightTag();
              if (s3 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f100(s2);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          }
        }
      }
    }
    return s0;
  }
  function peg$parseTagTags() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseLightTag();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseLightHalfTag();
      if (s4 !== peg$FAILED) {
        while (s4 !== peg$FAILED) {
          s3.push(s4);
          s4 = peg$parseLightHalfTag();
        }
      } else {
        s3 = peg$FAILED;
      }
      if (s3 !== peg$FAILED) {
        s2 = [s2, s3];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$currPos;
      s2 = peg$parseHighlightTag();
      if (s2 !== peg$FAILED) {
        s3 = [];
        s4 = peg$parseHighlightHalfTag();
        if (s4 !== peg$FAILED) {
          while (s4 !== peg$FAILED) {
            s3.push(s4);
            s4 = peg$parseHighlightHalfTag();
          }
        } else {
          s3 = peg$FAILED;
        }
        if (s3 !== peg$FAILED) {
          s2 = [s2, s3];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
      if (s1 !== peg$FAILED) {
        s0 = input.substring(s0, peg$currPos);
      } else {
        s0 = s1;
      }
    }
    return s0;
  }
  function peg$parseNL() {
    let s0, s1;
    peg$silentFails++;
    if (input.charCodeAt(peg$currPos) === 10) {
      s0 = peg$c139;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e144);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 2) === peg$c140) {
        s0 = peg$c140;
        peg$currPos += 2;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e145);
        }
      }
      if (s0 === peg$FAILED) {
        s0 = input.charAt(peg$currPos);
        if (peg$r2.test(s0)) {
          peg$currPos++;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e146);
          }
        }
      }
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e143);
      }
    }
    return s0;
  }
  function peg$parsechar() {
    let s0;
    s0 = input.charAt(peg$currPos);
    if (peg$r3.test(s0)) {
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e147);
      }
    }
    return s0;
  }
  function peg$parseEOL() {
    let s0, s1;
    s0 = peg$parseNL();
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      peg$silentFails++;
      if (input.length > peg$currPos) {
        s1 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e0);
        }
      }
      peg$silentFails--;
      if (s1 === peg$FAILED) {
        s0 = void 0;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseUrlHttp() {
    let s0, s1, s2, s3, s4, s5, s6, s7, s8;
    s0 = peg$currPos;
    s1 = peg$currPos;
    if (input.substr(peg$currPos, 4) === peg$c141) {
      s2 = peg$c141;
      peg$currPos += 4;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e148);
      }
    }
    if (s2 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 115) {
        s3 = peg$c142;
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e149);
        }
      }
      if (s3 === peg$FAILED) {
        s3 = null;
      }
      if (input.substr(peg$currPos, 3) === peg$c143) {
        s4 = peg$c143;
        peg$currPos += 3;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e150);
        }
      }
      if (s4 !== peg$FAILED) {
        s5 = [];
        s6 = peg$currPos;
        s7 = peg$currPos;
        peg$silentFails++;
        s8 = peg$parseBlockTag();
        peg$silentFails--;
        if (s8 === peg$FAILED) {
          s7 = void 0;
        } else {
          peg$currPos = s7;
          s7 = peg$FAILED;
        }
        if (s7 !== peg$FAILED) {
          s8 = peg$parseUrlChars();
          if (s8 !== peg$FAILED) {
            s7 = [s7, s8];
            s6 = s7;
          } else {
            peg$currPos = s6;
            s6 = peg$FAILED;
          }
        } else {
          peg$currPos = s6;
          s6 = peg$FAILED;
        }
        while (s6 !== peg$FAILED) {
          s5.push(s6);
          s6 = peg$currPos;
          s7 = peg$currPos;
          peg$silentFails++;
          s8 = peg$parseBlockTag();
          peg$silentFails--;
          if (s8 === peg$FAILED) {
            s7 = void 0;
          } else {
            peg$currPos = s7;
            s7 = peg$FAILED;
          }
          if (s7 !== peg$FAILED) {
            s8 = peg$parseUrlChars();
            if (s8 !== peg$FAILED) {
              s7 = [s7, s8];
              s6 = s7;
            } else {
              peg$currPos = s6;
              s6 = peg$FAILED;
            }
          } else {
            peg$currPos = s6;
            s6 = peg$FAILED;
          }
        }
        s2 = [s2, s3, s4, s5];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parseUrl() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$currPos;
    if (input.substr(peg$currPos, 4) === peg$c141) {
      s3 = peg$c141;
      peg$currPos += 4;
    } else {
      s3 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e148);
      }
    }
    if (s3 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 115) {
        s4 = peg$c142;
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e149);
        }
      }
      if (s4 === peg$FAILED) {
        s4 = null;
      }
      if (input.substr(peg$currPos, 3) === peg$c143) {
        s5 = peg$c143;
        peg$currPos += 3;
      } else {
        s5 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e150);
        }
      }
      if (s5 !== peg$FAILED) {
        s3 = [s3, s4, s5];
        s2 = s3;
      } else {
        peg$currPos = s2;
        s2 = peg$FAILED;
      }
    } else {
      peg$currPos = s2;
      s2 = peg$FAILED;
    }
    if (s2 === peg$FAILED) {
      if (input.substr(peg$currPos, 7) === peg$c144) {
        s2 = peg$c144;
        peg$currPos += 7;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e151);
        }
      }
    }
    if (s2 !== peg$FAILED) {
      s1 = input.substring(s1, peg$currPos);
    } else {
      s1 = s2;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = peg$currPos;
      s5 = peg$currPos;
      peg$silentFails++;
      s6 = peg$parseBlockTag();
      peg$silentFails--;
      if (s6 === peg$FAILED) {
        s5 = void 0;
      } else {
        peg$currPos = s5;
        s5 = peg$FAILED;
      }
      if (s5 !== peg$FAILED) {
        s6 = peg$parseUrlChars();
        if (s6 !== peg$FAILED) {
          s5 = [s5, s6];
          s4 = s5;
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      } else {
        peg$currPos = s4;
        s4 = peg$FAILED;
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$currPos;
        s5 = peg$currPos;
        peg$silentFails++;
        s6 = peg$parseBlockTag();
        peg$silentFails--;
        if (s6 === peg$FAILED) {
          s5 = void 0;
        } else {
          peg$currPos = s5;
          s5 = peg$FAILED;
        }
        if (s5 !== peg$FAILED) {
          s6 = peg$parseUrlChars();
          if (s6 !== peg$FAILED) {
            s5 = [s5, s6];
            s4 = s5;
          } else {
            peg$currPos = s4;
            s4 = peg$FAILED;
          }
        } else {
          peg$currPos = s4;
          s4 = peg$FAILED;
        }
      }
      s2 = input.substring(s2, peg$currPos);
      peg$savedPos = s0;
      s0 = peg$f101(s1, s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseUrlChars() {
    let s0;
    s0 = input.charAt(peg$currPos);
    if (peg$r4.test(s0)) {
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e152);
      }
    }
    return s0;
  }
  var indentStack = [], indent = "";
  input = input.replace(/\s+$/, "");
  input = input.trimStart();
  peg$result = peg$startRuleFunction();
  const peg$success = peg$result !== peg$FAILED && peg$currPos === input.length;
  function peg$throw() {
    if (peg$result !== peg$FAILED && peg$currPos < input.length) {
      peg$fail(peg$endExpectation());
    }
    throw peg$buildStructuredError(
      peg$maxFailExpected,
      peg$maxFailPos < input.length ? peg$getUnicode(peg$maxFailPos) : null,
      peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
    );
  }
  if (options.peg$library) {
    return (
      /** @type {any} */
      {
        peg$result,
        peg$currPos,
        peg$FAILED,
        peg$maxFailExpected,
        peg$maxFailPos,
        peg$success,
        peg$throw: peg$success ? void 0 : peg$throw
      }
    );
  }
  if (peg$success) {
    return peg$result;
  } else {
    peg$throw();
  }
}

// src/parser/text/peg/TextPegParser.ts
var parse = peg$parse;

// src/parser/text/TextParser.ts
var TextParser = class {
  /**
   * Get the version of the text parser
   */
  version() {
    return parse("", {
      startRule: "version"
    });
  }
  /**
   * Preprocess bitmark text AST into a standard format (TextAst object) from bitmark text AST either as a string
   * or a plain JS object
   *
   * @param ast - bitmark text AST as a string or a plain JS object
   * @returns bitmark text AST in a standard format (BitmarkAst object)
   */
  preprocessAst(ast) {
    if (stringUtils.isString(ast)) {
      const str = ast;
      try {
        ast = JSON.parse(str);
      } catch (_e) {
        return void 0;
      }
    }
    if (this.isAst(ast)) {
      return ast;
    }
    return void 0;
  }
  /**
   * Check if a plain JS object is valid text AST
   *
   * @param ast - a plain JS object that might be text AST
   * @returns true if text AST, otherwise false
   */
  isAst(ast) {
    if (Array.isArray(ast)) {
      if (ast.length === 0) return true;
      if (Object.prototype.hasOwnProperty.call(ast[0], "type")) {
        return true;
      }
    }
    return false;
  }
  /**
   * Convert Bitmark text to text AST.
   *
   * The Bitmark text should be a string.
   * If parsed bitmark text AST is passed to this function, it will be returned as is.
   *
   * @param text - bitmark text
   * @returns bitmark text AST as plain JS object
   */
  toAst(text, options) {
    if (Array.isArray(text)) return text;
    let str = text ?? "";
    if (!str) return [];
    const opts = Object.assign({}, options);
    let startRule = "bitmarkPlusPlus";
    if (opts.location === TextLocation2.tag) {
      startRule = "bitmarkPlus";
    }
    str = str.trim();
    return parse(str, {
      startRule
    });
  }
  /**
   * Extract all the body bits from the text AST
   *
   * @param text
   * @returns
   */
  extractBodyBits(text) {
    if (!Array.isArray(text)) return [];
    const bodyBits = [];
    const textAst = text;
    for (const node of textAst) {
      switch (node.type) {
        case TextNodeType.gap:
        case TextNodeType.select:
        case TextNodeType.highlight:
        case TextNodeType.mark:
          bodyBits.push(node);
          break;
        default: {
          const bits = this.extractBodyBits(node.content);
          for (const bit of bits) {
            bodyBits.push(bit);
          }
        }
      }
    }
    return bodyBits;
  }
  /**
   * Walk all the body bits from the text AST
   *
   * @param text
   * @returns
   */
  walkBodyBits(text, callback) {
    if (!Array.isArray(text)) return;
    const textAst = text;
    for (let i = 0; i < textAst.length; i++) {
      const node = textAst[i];
      switch (node.type) {
        case TextNodeType.gap:
        case TextNodeType.select:
        case TextNodeType.highlight:
        case TextNodeType.mark:
          callback(textAst, i, node);
          break;
        default: {
          this.walkBodyBits(node.content, callback);
        }
      }
    }
  }
};

// src/utils/BooleanUtils.ts
var BooleanUtils = class {
  /**
   * Check if an object is a boolean.
   *
   * @param val input value
   * @returns the input value if it is an array, otherwise a new array containing the input value
   */
  isBoolean(val) {
    if (val === true) return true;
    if (val === false) return true;
    return false;
  }
  /**
   * Check if an object is a boolean, or a boolean string.
   *
   * @param val input value
   * @returns the input value if it is an array, otherwise a new array containing the input value
   */
  isBooleanString(val) {
    if (this.isBoolean(val)) return true;
    if (val === "true") return true;
    if (val === "false") return true;
    return false;
  }
  /**
   * Convert a value to a boolean.
   *
   * By default, the return will be false unless the value is truthy (true, "true").
   * Setting defaultVal to true will return true unless the value is falsy (false, "false").
   *
   * @param val input value
   * @param defaultVal default value to return if val does not match a boolean or 'true' / 'false'
   * @returns val, converted to a boolean
   */
  toBoolean(val, defaultVal) {
    if (defaultVal) {
      if (val === false) return false;
      if (val === "false") return false;
      return true;
    } else {
      if (val === true) return true;
      if (val === "true") return true;
      return false;
    }
  }
};
var instance5 = new BooleanUtils();

// src/utils/NumberUtils.ts
var NumberUtils = class {
  /**
   * Convert a value to a number, unless it is already a number.
   *
   * The value returned if conversion cannot be performed defaults to undefined but can be configured
   * via defaultVal.
   *
   * @param val input value
   * @param defaultVal value to return if conversion cannot be performed
   * @returns the input value if it is an array, otherwise a new array containing the input value
   */
  asNumber(val, defaultVal) {
    if (val == null) return defaultVal ?? void 0;
    const number = +val;
    if (Number.isNaN(number) || !isFinite(number)) return defaultVal ?? void 0;
    return number;
  }
  /**
   * Return true if the input value IS or CAN BE converted to a number.
   *
   * e.g.
   * 123 -> true
   * 123.45 -> true
   * '123' -> true
   * '123.45' -> true
   * '-123.45' -> true
   * '+123.45' -> true
   * '0xAc1' -> true
   * '0xAc1' -> true
   * '0b101' -> true
   * '0o701' -> true
   * '1e4' -> true
   * '1e-4' -> true
   * '++1e-4' -> false
   * '7^2' -> false
   * '0xFFG' -> false
   * '0b121' -> false
   * '0o781' -> false
   * '1x10e4' -> false
   * '123abc' -> false
   * 'abc' -> false
   * null -> false
   *
   * @param val input value
   *
   * @returns true if the input value can be converted to a number.
   */
  isNumeric(val) {
    if (val == null) return false;
    const number = +val;
    return !Number.isNaN(number) && isFinite(number);
  }
};
var instance6 = new NumberUtils();

// src/ast/BaseBuilder.ts
var BaseBuilder = class {
  constructor() {
    __publicField(this, "textParser");
    __publicField(this, "textGenerator");
    this.textParser = new TextParser();
    this.textGenerator = new TextGenerator();
  }
  /**
   * Convert example to an Example.
   * - If example is set, then the isExample will be true and example with be example as a BreakscapedText.
   * - Else if __isDefaultExample is true, then __isDefaultExample / isExample will both be true.
   * - Else __isDefaultExample / isExample will both be false.
   *
   * @param __isDefaultExample - true if the example is the default value
   * @param example - the example to convert (BreakscapedText, boolean) or undefined if none / default
   * @returns example/__isDefaultExample resolved to an Example object
   */
  toExample(__isDefaultExample, example, defaultExample) {
    if (example != void 0) {
      let exampleValue;
      if (instance5.isBoolean(defaultExample)) {
        exampleValue = instance5.toBoolean(example);
      } else {
        exampleValue = example;
      }
      return {
        isExample: true,
        example: exampleValue,
        __defaultExample: defaultExample ?? null
      };
    }
    if (__isDefaultExample) {
      return {
        isExample: true,
        example: defaultExample ?? null,
        __defaultExample: defaultExample ?? null
      };
    }
    return {
      isExample: false,
      example: null,
      __defaultExample: defaultExample ?? null
    };
  }
  /**
   * Convert a raw bitmark property to an AST property.
   *
   * @param key
   * @param value
   * @param options
   * @returns
   */
  toAstProperty(bitType, key, value, options) {
    if (value == null) return void 0;
    const bitConfig = instance2.getBitConfig(bitType);
    let tagsConfig = bitConfig.tags;
    if (options?.cardSet) {
      const { sideNo, variantNo } = options.cardSet;
      const cardTagsConfig = instance2.getTagsConfigForCardSet(bitType, sideNo, variantNo);
      tagsConfig = cardTagsConfig ?? tagsConfig;
    }
    const propertyConfig = instance2.getTagConfigForTag(tagsConfig, key);
    if (propertyConfig?.array && !Array.isArray(value)) {
      value = [value];
    } else if (!propertyConfig?.array && Array.isArray(value)) {
      value = value[value.length - 1];
    }
    const processValue = (v) => {
      if (v == null) return void 0;
      const format = propertyConfig?.format ?? TagFormat.plainText;
      switch (format) {
        case TagFormat.plainText:
          if (instance6.asNumber(v) != null) v = `${v}`;
          return stringUtils.isString(v) ? stringUtils.trimmedString(v) : void 0;
        case TagFormat.number:
          return instance6.asNumber(v);
        case TagFormat.boolean:
          return instance5.toBoolean(v, true);
        case TagFormat.invertedBoolean:
          return !instance5.toBoolean(v, true);
      }
      return v;
    };
    if (Array.isArray(value)) {
      const valueArray = value;
      for (let i = 0, len = valueArray.length; i < len; i++) {
        valueArray[i] = processValue(valueArray[i]);
      }
    } else {
      value = processValue(value);
    }
    return value;
  }
  getEmptyTextAst(context) {
    return this.handleJsonText(context, TextLocation2.tag, " ");
  }
  /**
   * Convert the JsonText from the JSON to the AST format:
   * Input:
   *  - Bitmark v2: breakscaped string
   *  - Bitmark v3: bitmark text JSON (TextAst)
   * Output:
   *  - bitmark text JSON (TextAst) with __tag property to indicate item is text
   *
   * In the case of Bitmark v2 type texts, there is nothing to do but cast the type.
   *
   * @param context BuildContext
   * @param textFormat format of TextAst
   * @param textLocation location of the text (body, tag, etc.)
   * @param text JsonText or JsonText[] to convert
   * @returns Breakscaped string or breakscaped string[]
   */
  handleJsonText(context, textLocation, text) {
    let res;
    const { textFormat } = context;
    if (text == null) {
      res = [];
    } else {
      if (this.textParser.isAst(text)) {
        res = text;
      } else if (Array.isArray(text)) {
        const strArray = [];
        for (let i = 0, len = text.length; i < len; i++) {
          const t = text[i];
          if (this.textParser.isAst(t)) {
            strArray[i] = t;
          } else if (stringUtils.isString(t)) {
            strArray[i] = this.textParser.toAst(
              instance3.breakscape(t, {
                format: textFormat,
                location: textLocation
              }),
              {
                format: textFormat,
                location: textLocation
              }
            );
            strArray[i].__tag = "text";
          } else {
            strArray[i] = [];
          }
        }
        return strArray;
      } else if (stringUtils.isString(text)) {
        res = this.textParser.toAst(
          instance3.breakscape(text, {
            format: textFormat,
            location: textLocation,
            v2: true
          }),
          {
            format: textFormat,
            location: textLocation
          }
        );
      } else {
        res = [];
      }
    }
    res.__tag = "text";
    return res;
  }
  // /**
  //  * Remove any property with a key starting with an underscore.
  //  *
  //  * @param json
  //  */
  // protected removeTemporaryProperties(json: Record<string, unknown> | unknown[]): void {
  //   const obj = json as Record<string, unknown>;
  //   for (const key in obj) {
  //     if (key.startsWith('_')) {
  //       delete obj[key];
  //     } else if (typeof obj[key] === 'object') {
  //       this.removeTemporaryProperties(obj[key] as Record<string, unknown>);
  //     }
  //   }
  // }
};

// src/utils/BitUtils.ts
var textParser = new TextParser();
var BitUtils = class {
  /**
   * Helper function to fill in the example / isExample of a node based on the values of __isDefaultExample and example.
   * This function if for 'string' examples.
   *
   * @param nodes
   * @param __isDefaultExample
   * @param example
   * @param firstOnly
   * @returns
   */
  fillStringExample(textFormat, nodes, __isDefaultExample, example, firstOnly) {
    if (!nodes) return;
    if (!Array.isArray(nodes)) nodes = [nodes];
    for (const node of nodes) {
      if (!node.isExample) {
        if (__isDefaultExample) {
          node.example = node.__defaultExample;
          node.isExample = true;
        } else {
          node.example = example ? textParser.toAst(example, {
            format: textFormat,
            location: TextLocation2.tag
          }) : void 0;
        }
        if (firstOnly) break;
      }
    }
  }
  /**
   * Helper function to fill in the example / isExample of a node based on the values of __isDefaultExample and example.
   * This function if for 'boolean' examples.
   *
   * @param nodes
   * @param __isDefaultExample
   * @param example
   * @param firstCorrectOnly
   * @returns
   */
  fillBooleanExample(nodes, __isDefaultExample, example, firstCorrectOnly) {
    if (!nodes) return;
    if (!Array.isArray(nodes)) nodes = [nodes];
    for (const node of nodes) {
      if (!node.isExample && (!firstCorrectOnly || node.isCorrect)) {
        if (__isDefaultExample) {
          node.example = node.__defaultExample;
          node.isExample = true;
        } else {
          node.example = instance5.toBoolean(example);
        }
        if (firstCorrectOnly) break;
      }
    }
  }
};
var instance7 = new BitUtils();

// src/ast/ResourceBuilder.ts
import { Enum as Enum6 } from "@ncoderz/superenum";

// src/utils/UrlUtils.ts
var UrlUtils = class {
  fileExtensionFromUrl(url) {
    let format;
    if (url) {
      try {
        const parsedUrl = new URL(url);
        const pathParts = parsedUrl.pathname.split(".");
        if (pathParts.length > 1) {
          format = pathParts[pathParts.length - 1];
        }
      } catch (_e) {
      }
    }
    return format;
  }
  domainFromUrl(url) {
    let domain;
    if (url) {
      try {
        const parsedUrl = new URL(url);
        domain = parsedUrl.hostname;
      } catch (_e) {
      }
    }
    return domain;
  }
};
var instance8 = new UrlUtils();

// src/ast/rules/NodeValidator.ts
var NodeValidator = class {
  validateBit(bit) {
    if (!bit) return bit;
    const ret = bit;
    return ret;
  }
  validateCardBit(bit) {
    if (!bit) return bit;
    const ret = bit;
    return ret;
  }
  validateResource(resource) {
    if (!resource) return void 0;
    const ret = resource;
    let valid = true;
    switch (resource.type) {
      case ResourceType.image: {
        const imageResource = resource.image;
        if (!stringUtils.isString(imageResource.src)) imageResource.src = "";
        break;
      }
      case ResourceType.imageLink: {
        const imageLink = resource.imageLink;
        if (!stringUtils.isString(imageLink.url)) imageLink.url = "";
        break;
      }
      case ResourceType.audio: {
        const audio = resource.audio;
        if (!stringUtils.isString(audio.src)) audio.src = "";
        break;
      }
      case ResourceType.audioEmbed: {
        const audioEmbed = resource.audioEmbed;
        if (!stringUtils.isString(audioEmbed.src)) audioEmbed.src = "";
        break;
      }
      case ResourceType.audioLink: {
        const audioLink = resource.audioLink;
        if (!stringUtils.isString(audioLink.url)) audioLink.url = "";
        break;
      }
      case ResourceType.video: {
        const video = resource.video;
        if (!stringUtils.isString(video.src)) video.src = "";
        break;
      }
      case ResourceType.videoEmbed: {
        const videoEmbed = resource.videoEmbed;
        if (!stringUtils.isString(videoEmbed.url)) videoEmbed.url = "";
        break;
      }
      case ResourceType.videoLink: {
        const videoLink = resource.videoLink;
        if (!stringUtils.isString(videoLink.url)) videoLink.url = "";
        break;
      }
      case ResourceType.stillImageFilm: {
        const stillImageFilm = resource;
        if (!stringUtils.isString(stillImageFilm.image.src)) stillImageFilm.image.src = "";
        if (!stringUtils.isString(stillImageFilm.audio.src)) stillImageFilm.audio.src = "";
        break;
      }
      case ResourceType.stillImageFilmEmbed: {
        const stillImageFilmEmbed = resource.stillImageFilmEmbed;
        if (!stringUtils.isString(stillImageFilmEmbed.url)) stillImageFilmEmbed.url = "";
        break;
      }
      case ResourceType.stillImageFilmLink: {
        const stillImageFilmLink = resource.stillImageFilmLink;
        if (!stringUtils.isString(stillImageFilmLink.url)) stillImageFilmLink.url = "";
        break;
      }
      case ResourceType.article: {
        const article = resource.article;
        if (!stringUtils.isString(article.body)) article.body = "";
        break;
      }
      case ResourceType.document: {
        const document2 = resource.document;
        if (!stringUtils.isString(document2.url)) document2.url = "";
        break;
      }
      case ResourceType.documentEmbed: {
        const documentEmbed = resource.documentEmbed;
        if (!stringUtils.isString(documentEmbed.url)) documentEmbed.url = "";
        break;
      }
      case ResourceType.documentLink: {
        const documentLink = resource.documentLink;
        if (!stringUtils.isString(documentLink.url)) documentLink.url = "";
        break;
      }
      case ResourceType.documentDownload: {
        const documentDownload = resource.documentDownload;
        if (!stringUtils.isString(documentDownload.url)) documentDownload.url = "";
        break;
      }
      case ResourceType.appLink: {
        const appLink = resource.appLink;
        if (!stringUtils.isString(appLink.url)) appLink.url = "";
        break;
      }
      case ResourceType.websiteLink: {
        const websiteLink = resource.websiteLink;
        if (!stringUtils.isString(websiteLink.url)) websiteLink.url = "";
        break;
      }
      default:
        valid = false;
    }
    if (!valid) {
      if (resource.type) {
        resource.__invalid = true;
      }
    }
    return ret;
  }
  isRequired(val, name) {
    if (val) return;
    throw new Error(`${name} is required but is not set`);
  }
  isOneOfRequired(vals, names) {
    if (Array.isArray(vals)) {
      for (const v of vals) {
        if (v) return;
      }
    }
    throw new Error(`One of '${names.join(", ")}' is required but none is set`);
  }
  isString(val, name) {
    if (stringUtils.isString(val)) return;
    throw new Error(`${name} is required to be a string`);
  }
  isNonEmptyString(val, name) {
    if (stringUtils.isString(val) && val !== "") return;
    throw new Error(`${name} is required to be a non-empty string`);
  }
  isStringOrBoolean(val, name) {
    if (stringUtils.isString(val)) return;
    if (val === true || val === false) return;
    throw new Error(`${name} is required to be a string or a boolean`);
  }
  isNonEmptyStringOrBoolean(val, name) {
    if (stringUtils.isString(val) && val !== "") return;
    if (val === true || val === false) return;
    throw new Error(`${name} is required to be a non-empty string or a boolean`);
  }
  isArray(val, name) {
    if (Array.isArray(val)) return;
    throw new Error(`${name} is required to be a array`);
  }
  isNonEmptyArray(val, name) {
    if (Array.isArray(val) && val.length > 0) return;
    throw new Error(`${name} is required to be a non-empty array`);
  }
  isNumber(val, name) {
    if (Number.isFinite(val)) return;
    throw new Error(`${name} is required to be a number`);
  }
  isBoolean(val, name) {
    if (val === true || val === false) return;
    throw new Error(`${name} is required to be a boolean`);
  }
  isStringOrNumberOrBooleanOrNullOrUndefined(val, name) {
    if (stringUtils.isString(val)) return;
    if (Number.isFinite(val)) return;
    if (val === true || val === false) return;
    if (val == null) return;
    throw new Error(
      `${name} is required to be a string or a number or a boolean or null or undefined`
    );
  }
  //
  // private
  //
  // private validateInterviewBit(bit: Bit): Bit | undefined {
  //   // Ensure bit has a questions array as the
  //   // ===
  //   // ===
  //   // must be included in the markup
  //   if (!bit.cardNode) {
  //     bit.cardNode = {};
  //   }
  //   if (!bit.cardNode.questions) {
  //     bit.cardNode.questions = [];
  //   }
  //   return bit;
  // }
};
var nodeValidator = new NodeValidator();

// src/ast/ResourceBuilder.ts
var ResourceBuilder = class extends BaseBuilder {
  /**
   * Build and validate resource JSON object(s) from external resource JSON object(s)
   *
   * Note: a single input resource object can be converted to multiple resource objects
   * (e.g. stillImageFilm -> image + audio)
   *
   * @param bitType
   * @param resource
   * @returns
   */
  resourceFromResourceJson(context, resource) {
    if (!resource) return void 0;
    const nodes = [];
    if (!Array.isArray(resource)) resource = [resource];
    for (const thisResource of resource) {
      let type = Enum6(ResourceType).fromValue(thisResource.type);
      if (!type) return void 0;
      const resourceKey = Enum6(ResourceType).keyFromValue(type);
      if (!resourceKey) return void 0;
      const __typeAlias = Enum6(ResourceType).fromValue(thisResource.__typeAlias);
      type = __typeAlias ?? type;
      let data;
      if (type === ResourceType.imageResponsive) {
        const r = thisResource;
        const imagePortraitNode = this.resourceFromResourceDataJson(
          context,
          ResourceType.imagePortrait,
          r.imagePortrait
        );
        const imageLandscapeNode = this.resourceFromResourceDataJson(
          context,
          ResourceType.imageLandscape,
          r.imageLandscape
        );
        if (imagePortraitNode) nodes.push(imagePortraitNode);
        if (imageLandscapeNode) nodes.push(imageLandscapeNode);
      } else if (type === ResourceType.stillImageFilm) {
        const r = thisResource;
        const imageNode = this.resourceFromResourceDataJson(context, ResourceType.image, r.image);
        const audioNode = this.resourceFromResourceDataJson(context, ResourceType.audio, r.audio);
        if (imageNode) nodes.push(imageNode);
        if (audioNode) nodes.push(audioNode);
      } else {
        data = thisResource[resourceKey];
        if (!data) return void 0;
        const node = this.resourceFromResourceDataJson(context, type, data);
        if (node) nodes.push(node);
      }
    }
    if (nodes.length === 0) return void 0;
    if (nodes.length === 1) return nodes[0];
    return nodes;
  }
  resourceFromResourceDataJson(context, type, data) {
    if (!data) return void 0;
    type = Enum6(ResourceType).fromValue(type);
    if (!type) return void 0;
    const dataAsString = stringUtils.isString(data) ? data : void 0;
    const url = data.url || data.src || data.body || dataAsString;
    const posterImage = data.posterImage ? this.resourceFromResourceDataJson(
      context,
      ResourceType.image,
      data.posterImage
    )?.image : void 0;
    const thumbnails = data.thumbnails ? data.thumbnails.map((t) => {
      return this.resourceFromResourceDataJson(
        context,
        ResourceType.image,
        t
      )?.image;
    }) : void 0;
    const node = this.resource(context, {
      type,
      // Generic (except Article / Document)
      value: url,
      // Alternative source
      srcAlt: data.srcAlt,
      // ImageLikeResource / AudioLikeResource / VideoLikeResource / Article / Document
      format: data.format,
      // ImageLikeResource
      src1x: data.src1x,
      src2x: data.src2x,
      src3x: data.src3x,
      src4x: data.src4x,
      caption: this.handleJsonText(context, TextLocation2.tag, data.caption),
      // ImageLikeResource / VideoLikeResource
      width: data.width ?? void 0,
      height: data.height ?? void 0,
      alt: data.alt,
      zoomDisabled: data.zoomDisabled,
      // VideoLikeResource
      duration: data.duration,
      mute: data.mute,
      autoplay: data.autoplay,
      allowSubtitles: data.allowSubtitles,
      showSubtitles: data.showSubtitles,
      posterImage,
      thumbnails,
      // WebsiteLinkResource
      siteName: void 0,
      //data.siteName,
      // Generic Resource
      license: data.license,
      copyright: data.copyright,
      showInIndex: data.showInIndex,
      search: data.search,
      selected: data.selected
    });
    return node;
  }
  /**
   * Build resource node
   *
   * @param data - data for the node
   * @returns
   */
  /* private */
  resource(context, data) {
    let node;
    const { type, value: valueIn, format: formatIn, ...rest } = data;
    const finalData = {
      type,
      value: valueIn ?? instance3.EMPTY_STRING,
      format: formatIn ?? instance3.EMPTY_STRING,
      ...rest
    };
    switch (type) {
      case ResourceType.video:
      case ResourceType.videoEmbed:
      case ResourceType.videoLink:
      case ResourceType.stillImageFilmEmbed:
      case ResourceType.stillImageFilmLink: {
        const thumbnailKeys = ["src1x", "src2x", "src3x", "src4x"];
        const thumbnails = [];
        for (const k of thumbnailKeys) {
          const dataAsAny = data;
          const value = dataAsAny[k];
          if (value) {
            const image = this.resource(context, {
              type: ResourceType.image,
              value
            });
            if (image) thumbnails.push(image.image);
          }
        }
        finalData.thumbnails = [...finalData.thumbnails || [], ...thumbnails];
      }
    }
    switch (type) {
      case ResourceType.image:
      case ResourceType.imagePortrait:
      case ResourceType.imageLandscape:
      case ResourceType.backgroundWallpaper:
      case ResourceType.imagePlaceholder:
      case ResourceType.icon:
      case ResourceType.platformIcon:
      case ResourceType.platformLogo:
      case ResourceType.platformBackgroundImage:
      case ResourceType.previewImage:
      case ResourceType.coverImage:
      case ResourceType.backgroundImage:
        node = this.imageResource(context, finalData, type);
        break;
      // case ResourceType.imageResponsive: {
      //   node = this.imageResponsiveResource({
      //     imagePortrait: this.imageResource(
      //       finalData.imagePortrait ?? {
      //         format: '',
      //         value: '',
      //       },
      //     ),
      //     imageLandscape: this.imageResource(
      //       finalData.imageLandscape ?? {
      //         format: '',
      //         value: '',
      //       },
      //     ),
      //   });
      //   break;
      // }
      case ResourceType.imageLink:
        node = this.imageLinkResource(context, finalData);
        break;
      case ResourceType.audio:
        node = this.audioResource(context, finalData);
        break;
      case ResourceType.audioEmbed:
        node = this.audioEmbedResource(context, finalData);
        break;
      case ResourceType.audioLink:
        node = this.audioLinkResource(context, finalData);
        break;
      case ResourceType.video:
      case ResourceType.previewVideo:
        node = this.videoResource(context, finalData);
        break;
      case ResourceType.videoEmbed:
        node = this.videoEmbedResource(context, finalData);
        break;
      case ResourceType.videoLink:
        node = this.videoLinkResource(context, finalData);
        break;
      // case ResourceType.stillImageFilm: {
      //   node = this.stillImageFilmResource({
      //     image: this.imageResource(context,
      //       finalData.image ?? {
      //         format: '',
      //         value: '',
      //       },
      //     ),
      //     audio: this.audioResource(context,
      //       finalData.audio ?? {
      //         format: '',
      //         value: '',
      //       },
      //     ),
      //   });
      //   break;
      // }
      case ResourceType.stillImageFilmEmbed:
        node = this.stillImageFilmEmbedResource(context, finalData);
        break;
      case ResourceType.stillImageFilmLink:
        node = this.stillImageFilmLinkResource(context, finalData);
        break;
      case ResourceType.article:
        node = this.articleResource(context, finalData);
        break;
      case ResourceType.document:
        node = this.documentResource(context, finalData);
        break;
      case ResourceType.documentEmbed:
        node = this.documentEmbedResource(context, finalData);
        break;
      case ResourceType.documentLink:
        node = this.documentLinkResource(context, finalData);
        break;
      case ResourceType.documentDownload:
        node = this.documentDownloadResource(context, finalData);
        break;
      case ResourceType.appLink:
        node = this.appLinkResource(context, finalData);
        break;
      case ResourceType.websiteLink:
        node = this.websiteLinkResource(context, finalData);
        break;
      default:
    }
    return node;
  }
  /**
   * Build imageResource node
   *
   * @param data - data for the node
   * @returns
   */
  imageResource(context, data, __typeAlias) {
    const {
      value,
      srcAlt,
      src1x,
      src2x,
      src3x,
      src4x,
      width,
      height,
      alt,
      zoomDisabled,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const { bitType } = context;
    let zoomDisabledDefault = false;
    if (instance2.isOfBitType(bitType, [
      BitType2.imageSeparator,
      BitType2.pageBanner,
      BitType2.imagesLogoGrave,
      BitType2.prototypeImages,
      BitType2.extractorImage
    ])) {
      zoomDisabledDefault = true;
    }
    const node = {
      type: ResourceType.image,
      __typeAlias: __typeAlias ?? ResourceType.image,
      __configKey: resourceTypeToConfigKey(__typeAlias ?? ResourceType.image),
      image: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        src: value ?? "",
        srcAlt: srcAlt ?? void 0,
        src1x: src1x ?? void 0,
        src2x: src2x ?? void 0,
        src3x: src3x ?? void 0,
        src4x: src4x ?? void 0,
        width: width ?? null,
        height: height ?? null,
        alt: alt ?? "",
        zoomDisabled: zoomDisabled ?? zoomDisabledDefault,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.image, {
      ignoreFalse: ["zoomDisabled", "showInIndex", "selected"],
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: ["src", "alt", "license", "copyright"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build imageLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  imageLinkResource(context, data) {
    const {
      value,
      srcAlt,
      src1x,
      src2x,
      src3x,
      src4x,
      width,
      height,
      alt,
      zoomDisabled,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.imageLink,
      __typeAlias: ResourceType.imageLink,
      __configKey: resourceTypeToConfigKey(ResourceType.imageLink),
      imageLink: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        src1x: src1x ?? void 0,
        src2x: src2x ?? void 0,
        src3x: src3x ?? void 0,
        src4x: src4x ?? void 0,
        width: width ?? null,
        height: height ?? null,
        alt: alt ?? "",
        zoomDisabled: zoomDisabled ?? false,
        // TODO: Default depends on the bit(!)
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.imageLink, {
      ignoreFalse: ["zoomDisabled", "showInIndex", "selected"],
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build audioResource node
   *
   * @param data - data for the node
   * @returns
   */
  audioResource(context, data) {
    const {
      value,
      srcAlt,
      duration,
      mute,
      autoplay,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.audio,
      __typeAlias: ResourceType.audio,
      __configKey: resourceTypeToConfigKey(ResourceType.audio),
      audio: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        src: value ?? "",
        srcAlt: srcAlt ?? void 0,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.audio, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["src", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build audioEmbedResource node
   *
   * @param data - data for the node
   * @returns
   */
  audioEmbedResource(context, data) {
    const {
      value,
      srcAlt,
      duration,
      mute,
      autoplay,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.audioEmbed,
      __typeAlias: ResourceType.audioEmbed,
      __configKey: resourceTypeToConfigKey(ResourceType.audioEmbed),
      audioEmbed: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        src: value ?? "",
        srcAlt: srcAlt ?? void 0,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.audioEmbed, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: [
        "src",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build audioLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  audioLinkResource(context, data) {
    const {
      value,
      srcAlt,
      duration,
      mute,
      autoplay,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.audioLink,
      __typeAlias: ResourceType.audioLink,
      __configKey: resourceTypeToConfigKey(ResourceType.audioLink),
      audioLink: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.audioLink, {
      // ignoreEmptyArrays: ['caption'],
      ignoreEmptyString: [
        "url"
        /*'alt', 'license', 'copyright'*/
      ],
      ignoreFalse: ["selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build videoResource node
   *
   * @param data - data for the node
   * @returns
   */
  videoResource(context, data) {
    const {
      value,
      srcAlt,
      width,
      height,
      duration,
      mute,
      autoplay,
      allowSubtitles,
      showSubtitles,
      alt,
      posterImage,
      thumbnails,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.video,
      __typeAlias: ResourceType.video,
      __configKey: resourceTypeToConfigKey(ResourceType.video),
      video: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        src: value ?? "",
        srcAlt: srcAlt ?? void 0,
        width: width ?? null,
        height: height ?? null,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        allowSubtitles: allowSubtitles ?? void 0,
        showSubtitles: showSubtitles ?? void 0,
        alt: alt ?? "",
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false,
        // Have sub-chains so must be at end of chain
        posterImage: posterImage ?? void 0,
        thumbnails: thumbnails ?? void 0
      }
    };
    objectUtils.removeUnwantedProperties(node.video, {
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: [
        "src",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build videoEmbedResource node
   *
   * @param data - data for the node
   * @returns
   */
  videoEmbedResource(context, data) {
    const {
      value,
      srcAlt,
      width,
      height,
      duration,
      mute,
      autoplay,
      allowSubtitles,
      showSubtitles,
      alt,
      posterImage,
      thumbnails,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.videoEmbed,
      __typeAlias: ResourceType.videoEmbed,
      __configKey: resourceTypeToConfigKey(ResourceType.videoEmbed),
      videoEmbed: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        width: width ?? null,
        height: height ?? null,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        allowSubtitles: allowSubtitles ?? void 0,
        showSubtitles: showSubtitles ?? void 0,
        alt: alt ?? "",
        posterImage: posterImage ?? void 0,
        thumbnails: thumbnails ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.videoEmbed, {
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: [
        "url",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build videoLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  videoLinkResource(context, data) {
    const {
      value,
      srcAlt,
      width,
      height,
      duration,
      mute,
      autoplay,
      allowSubtitles,
      showSubtitles,
      alt,
      posterImage,
      thumbnails,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.videoLink,
      __typeAlias: ResourceType.videoLink,
      __configKey: resourceTypeToConfigKey(ResourceType.videoLink),
      videoLink: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        width: width ?? null,
        height: height ?? null,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        allowSubtitles: allowSubtitles ?? void 0,
        showSubtitles: showSubtitles ?? void 0,
        alt: alt ?? "",
        posterImage: posterImage ?? void 0,
        thumbnails: thumbnails ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.videoLink, {
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: [
        "url",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  // /**
  //  * Build stillImageFilmResource node
  //  *
  //  * @param data - data for the node
  //  * @returns
  //  */
  // stillImageFilmResource(context: BuildContext, data: { image?: ImageResource; audio?: AudioResource }): StillImageFilmResource {
  //   const { image, audio } = data;
  //   // NOTE: Node order is important and is defined here
  //   const node: StillImageFilmResource = {
  //     type: ResourceType.stillImageFilm,
  //     __typeAlias: ResourceType.stillImageFilm,
  //     image: image ?? this.imageResource({ format: '', value: '' }),
  //     audio: audio ?? this.audioResource({ format: '', value: '' }),
  //   };
  //   // Remove Unset Optionals
  //   ObjectUtils.removeUnwantedProperties(node);
  //   // Validate and correct invalid bits as much as possible
  //   return NodeValidator.validateResource(node) ;
  // }
  /**
   * Build stillImageFilmEmbedResource node
   *
   * @param data - data for the node
   * @returns
   */
  stillImageFilmEmbedResource(context, data) {
    const {
      value,
      srcAlt,
      width,
      height,
      duration,
      mute,
      autoplay,
      allowSubtitles,
      showSubtitles,
      alt,
      posterImage,
      thumbnails,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.stillImageFilmEmbed,
      __typeAlias: ResourceType.stillImageFilmEmbed,
      __configKey: resourceTypeToConfigKey(ResourceType.stillImageFilmEmbed),
      stillImageFilmEmbed: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        width: width ?? null,
        height: height ?? null,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        allowSubtitles: allowSubtitles ?? void 0,
        showSubtitles: showSubtitles ?? void 0,
        alt: alt ?? "",
        posterImage: posterImage ?? void 0,
        thumbnails: thumbnails ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.stillImageFilmEmbed, {
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: [
        "url",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build stillImageFilmLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  stillImageFilmLinkResource(context, data) {
    const {
      value,
      srcAlt,
      width,
      height,
      duration,
      mute,
      autoplay,
      allowSubtitles,
      showSubtitles,
      alt,
      posterImage,
      thumbnails,
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.stillImageFilmLink,
      __typeAlias: ResourceType.stillImageFilmLink,
      __configKey: resourceTypeToConfigKey(ResourceType.stillImageFilmLink),
      stillImageFilmLink: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        // src: value ?? '',
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        width: width ?? null,
        height: height ?? null,
        duration: duration ?? void 0,
        mute: mute ?? void 0,
        autoplay: autoplay ?? void 0,
        allowSubtitles: allowSubtitles ?? void 0,
        showSubtitles: showSubtitles ?? void 0,
        alt: alt ?? "",
        posterImage: posterImage ?? void 0,
        thumbnails: thumbnails ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.stillImageFilmLink, {
      ignoreEmptyArrays: ["caption"],
      ignoreUndefined: ["width", "height"],
      ignoreEmptyString: [
        "url",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build articleResource node
   *
   * @param data - data for the node
   * @returns
   */
  articleResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.article,
      __typeAlias: ResourceType.article,
      __configKey: resourceTypeToConfigKey(ResourceType.article),
      article: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        body: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.article, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["body", "alt", "license", "copyright"],
      ignoreFalse: ["selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build documentResource node
   *
   * @param data - data for the node
   * @returns
   */
  documentResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.document,
      __typeAlias: ResourceType.document,
      __configKey: resourceTypeToConfigKey(ResourceType.document),
      document: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.document, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build documentEmbedResource node
   *
   * @param data - data for the node
   * @returns
   */
  documentEmbedResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.documentEmbed,
      __typeAlias: ResourceType.documentEmbed,
      __configKey: resourceTypeToConfigKey(ResourceType.documentEmbed),
      documentEmbed: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.documentEmbed, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build documentLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  documentLinkResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.documentLink,
      __typeAlias: ResourceType.documentLink,
      __configKey: resourceTypeToConfigKey(ResourceType.documentLink),
      documentLink: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.documentLink, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build documentDownloadResource node
   *
   * @param data - data for the node
   * @returns
   */
  documentDownloadResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.documentDownload,
      __typeAlias: ResourceType.documentDownload,
      __configKey: resourceTypeToConfigKey(ResourceType.documentDownload),
      documentDownload: {
        format: instance8.fileExtensionFromUrl(value) ?? void 0,
        provider: instance8.domainFromUrl(value) ?? void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.documentDownload, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build appLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  appLinkResource(context, data) {
    const { value, srcAlt, license, copyright, showInIndex, caption, search, selected } = data;
    const node = {
      type: ResourceType.appLink,
      __typeAlias: ResourceType.appLink,
      __configKey: resourceTypeToConfigKey(ResourceType.appLink),
      appLink: {
        // format: (UrlUtils.fileExtensionFromUrl(value) ?? undefined) as string,
        // provider: (UrlUtils.domainFromUrl(value) ?? undefined) as string,
        provider: void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.appLink, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: [
        "url",
        /*'alt',*/
        "license",
        "copyright"
      ],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  /**
   * Build websiteLinkResource node
   *
   * @param data - data for the node
   * @returns
   */
  websiteLinkResource(context, data) {
    const {
      value,
      srcAlt,
      /*siteName,*/
      license,
      copyright,
      showInIndex,
      caption,
      search,
      selected
    } = data;
    const node = {
      type: ResourceType.websiteLink,
      __typeAlias: ResourceType.websiteLink,
      __configKey: resourceTypeToConfigKey(ResourceType.websiteLink),
      websiteLink: {
        // provider: (UrlUtils.domainFromUrl(value) ?? undefined) as string,
        provider: void 0,
        url: value ?? "",
        srcAlt: srcAlt ?? void 0,
        // siteName,
        license: license ?? "",
        copyright: copyright ?? "",
        showInIndex: showInIndex ?? false,
        caption: this.handleJsonText(context, TextLocation2.tag, caption),
        search: search ?? void 0,
        selected: selected ?? false
      }
    };
    objectUtils.removeUnwantedProperties(node.websiteLink, {
      ignoreEmptyArrays: ["caption"],
      ignoreEmptyString: ["url", "alt", "license", "copyright"],
      ignoreFalse: ["showInIndex", "selected"]
    });
    return nodeValidator.validateResource(node);
  }
  //
  //
  //
  //
  // Private
  //
};

// src/ast/Builder.ts
var Builder = class extends BaseBuilder {
  constructor() {
    super(...arguments);
    __publicField(this, "resourceBuilder", new ResourceBuilder());
  }
  /**
   * Build bitmark node
   *
   * @param data - data for the node
   * @returns
   */
  buildBitmark(data) {
    const { bits, errors } = data;
    const node = {
      bits,
      errors
    };
    objectUtils.removeUnwantedProperties(node);
    return node;
  }
  /**
   * Build bit node
   *
   * @param data - data for the node
   * @returns
   */
  buildBit(data, options) {
    data = structuredClone(data);
    const migratedBitType = instance2.getMigratedBitType(data.bitType);
    if (migratedBitType) {
      data.bitType = migratedBitType;
      data.isCollapsible = data.isCollapsible ?? true;
    }
    const bitType = data.bitType;
    const bitConfig = instance2.getBitConfig(bitType);
    const isCollapsibleDefaultsTrue = bitConfig.tags[ConfigKey.property_isCollapsible]?.defaultValue === "true";
    const deprecatedTextFormat = Enum7(DeprecatedTextFormat).fromValue(data.textFormat);
    let textFormat = Enum7(TextFormat2).fromValue(data.textFormat) ?? bitConfig.textFormatDefault;
    if (deprecatedTextFormat === DeprecatedTextFormat.bitmarkMinusMinus) {
      textFormat = TextFormat2.bitmarkText;
    }
    const context = {
      bitConfig,
      bitType,
      textFormat
    };
    const cardNode = this.buildCardNode(context, data);
    const reasonableNumOfCharsProperty = instance2.isOfBitType(data.bitType, BitType2.essay) ? this.toAstProperty(
      bitType,
      ConfigKey.property_reasonableNumOfChars,
      data.reasonableNumOfChars,
      options
    ) : void 0;
    const convertedExample = {
      ...this.toExample(data.__isDefaultExample, data.example)
    };
    const node = {
      bitType,
      bitLevel: data.bitLevel,
      textFormat,
      resourceType: Enum7(ResourceType).fromValue(data.resourceType),
      isCommented: data.isCommented,
      // Properties
      id: this.toAstProperty(bitType, ConfigKey.property_id, data.id, options),
      gUri: this.toAstProperty(bitType, ConfigKey.property_gUri, data.gUri, options),
      internalComment: this.toAstProperty(
        bitType,
        ConfigKey.property_internalComment,
        data.internalComment,
        options
      ),
      customerId: this.toAstProperty(
        bitType,
        ConfigKey.property_customerId,
        data.customerId,
        options
      ),
      customerExternalId: this.toAstProperty(
        bitType,
        ConfigKey.property_customerExternalId,
        data.customerExternalId,
        options
      ),
      externalId: this.toAstProperty(
        bitType,
        ConfigKey.property_externalId,
        data.externalId,
        options
      ),
      spaceId: this.toAstProperty(bitType, ConfigKey.property_spaceId, data.spaceId, options),
      padletId: this.toAstProperty(bitType, ConfigKey.property_padletId, data.padletId, options),
      jupyterId: this.toAstProperty(bitType, ConfigKey.property_jupyterId, data.jupyterId, options),
      jupyterExecutionCount: this.toAstProperty(
        bitType,
        ConfigKey.property_jupyterExecutionCount,
        data.jupyterExecutionCount,
        options
      ),
      copyright: this.toAstProperty(bitType, ConfigKey.property_copyright, data.copyright, options),
      license: this.toAstProperty(bitType, ConfigKey.property_license, data.license, options),
      rightsPolicy: this.toAstProperty(
        bitType,
        ConfigKey.property_rightsPolicy,
        data.rightsPolicy,
        options
      ),
      sourceRL: this.toAstProperty(bitType, ConfigKey.property_sourceRL, data.sourceRL, options),
      isPublic: this.toAstProperty(bitType, ConfigKey.property_isPublic, data.isPublic, options),
      isTemplate: this.toAstProperty(
        bitType,
        ConfigKey.property_isTemplate,
        data.isTemplate,
        options
      ),
      isTemplateStripTheme: this.toAstProperty(
        bitType,
        ConfigKey.property_isTemplateStripTheme,
        data.isTemplateStripTheme,
        options
      ),
      isEditable: this.toAstProperty(
        bitType,
        ConfigKey.property_isEditable,
        data.isEditable,
        options
      ),
      needsValidation: this.toAstProperty(
        bitType,
        ConfigKey.property_needsValidation,
        data.needsValidation,
        options
      ),
      validationDate: this.toAstProperty(
        bitType,
        ConfigKey.property_validationDate,
        data.validationDate,
        options
      ),
      aiGenerated: this.toAstProperty(
        bitType,
        ConfigKey.property_aiGenerated,
        data.aiGenerated,
        options
      ),
      machineTranslated: this.toAstProperty(
        bitType,
        ConfigKey.property_machineTranslated,
        data.machineTranslated,
        options
      ),
      translationOf: this.toAstProperty(
        bitType,
        ConfigKey.property_translationOf,
        data.translationOf,
        options
      ),
      spansPageBreak: this.toAstProperty(
        bitType,
        ConfigKey.property_spansPageBreak,
        data.spansPageBreak,
        options
      ),
      searchIndex: this.toAstProperty(
        bitType,
        ConfigKey.property_searchIndex,
        data.searchIndex,
        options
      ),
      analyticsTag: this.toAstProperty(
        bitType,
        ConfigKey.property_analyticsTag,
        data.analyticsTag,
        options
      ),
      categoryTag: this.toAstProperty(
        bitType,
        ConfigKey.property_categoryTag,
        data.categoryTag,
        options
      ),
      topicTag: this.toAstProperty(bitType, ConfigKey.property_topicTag, data.topicTag, options),
      reportTag: this.toAstProperty(bitType, ConfigKey.property_reportTag, data.reportTag, options),
      altLangTag: this.toAstProperty(
        bitType,
        ConfigKey.property_altLangTag,
        data.altLangTag,
        options
      ),
      feedbackEngine: this.toAstProperty(
        bitType,
        ConfigKey.property_feedbackEngine,
        data.feedbackEngine,
        options
      ),
      feedbackType: this.toAstProperty(
        bitType,
        ConfigKey.property_feedbackType,
        data.feedbackType,
        options
      ),
      disableFeedback: this.toAstProperty(
        bitType,
        ConfigKey.property_disableFeedback,
        data.disableFeedback,
        options
      ),
      diffTo: this.toAstProperty(bitType, ConfigKey.property_diffTo, data.diffTo, options),
      diffOp: this.toAstProperty(bitType, ConfigKey.property_diffOp, data.diffOp, options),
      diffRef: this.toAstProperty(bitType, ConfigKey.property_diffRef, data.diffRef, options),
      diffContext: this.toAstProperty(
        bitType,
        ConfigKey.property_diffContext,
        data.diffContext,
        options
      ),
      diffTime: this.toAstProperty(bitType, ConfigKey.property_diffTime, data.diffTime, options),
      path: this.toAstProperty(bitType, ConfigKey.property_path, data.path, options),
      releaseVersion: this.toAstProperty(
        bitType,
        ConfigKey.property_releaseVersion,
        data.releaseVersion,
        options
      ),
      releaseKind: this.toAstProperty(
        bitType,
        ConfigKey.property_releaseKind,
        data.releaseKind,
        options
      ),
      releaseDate: this.toAstProperty(
        bitType,
        ConfigKey.property_releaseDate,
        data.releaseDate,
        options
      ),
      iframeSrc: this.toAstProperty(bitType, ConfigKey.property_iframeSrc, data.iframeSrc, options),
      iframeName: this.toAstProperty(
        bitType,
        ConfigKey.property_iframeName,
        data.iframeName,
        options
      ),
      book: this.buildBooks(context, data.book),
      ageRange: this.toAstProperty(bitType, ConfigKey.property_ageRange, data.ageRange, options),
      lang: this.toAstProperty(bitType, ConfigKey.property_lang, data.lang, options),
      language: this.toAstProperty(bitType, ConfigKey.property_language, data.language, options),
      publisher: this.toAstProperty(bitType, ConfigKey.property_publisher, data.publisher, options),
      publisherName: this.toAstProperty(
        bitType,
        ConfigKey.property_publisherName,
        data.publisherName,
        options
      ),
      displayName: this.toAstProperty(
        bitType,
        ConfigKey.property_displayName,
        data.displayName,
        options
      ),
      theme: this.toAstProperty(bitType, ConfigKey.property_theme, data.theme, options),
      rtl: this.toAstProperty(bitType, ConfigKey.property_rtl, data.rtl, options),
      computerLanguage: this.toAstProperty(
        bitType,
        ConfigKey.property_computerLanguage,
        data.computerLanguage,
        options
      ),
      target: this.toAstProperty(bitType, ConfigKey.property_target, data.target, options),
      slug: this.toAstProperty(bitType, ConfigKey.property_slug, data.slug, options),
      tag: this.toAstProperty(bitType, ConfigKey.property_tag, data.tag, options),
      groupTag: this.buildGroupTags(context, data.groupTag),
      reviewTag: this.toAstProperty(bitType, ConfigKey.property_reviewTag, data.reviewTag, options),
      reviewerTag: this.toAstProperty(
        bitType,
        ConfigKey.property_reviewerTag,
        data.reviewerTag,
        options
      ),
      reductionTag: this.toAstProperty(
        bitType,
        ConfigKey.property_reductionTag,
        data.reductionTag,
        options
      ),
      bubbleTag: this.toAstProperty(bitType, ConfigKey.property_bubbleTag, data.bubbleTag, options),
      extractorTag: this.toAstProperty(
        bitType,
        ConfigKey.property_extractorTag,
        data.extractorTag,
        options
      ),
      extractorInternal: this.toAstProperty(
        bitType,
        ConfigKey.property_extractorInternal,
        data.extractorInternal,
        options
      ),
      sourceBB: this.normaliseSourceBB(data.sourceBB),
      bitFingerprint: this.toAstProperty(
        bitType,
        ConfigKey.property_bitFingerprint,
        data.bitFingerprint,
        options
      ),
      levelCEFRp: this.toAstProperty(
        bitType,
        ConfigKey.property_levelCEFRp,
        data.levelCEFRp,
        options
      ),
      levelCEFR: this.toAstProperty(bitType, ConfigKey.property_levelCEFR, data.levelCEFR, options),
      levelILR: this.toAstProperty(bitType, ConfigKey.property_levelILR, data.levelILR, options),
      levelACTFL: this.toAstProperty(
        bitType,
        ConfigKey.property_levelACTFL,
        data.levelACTFL,
        options
      ),
      icon: this.toAstProperty(bitType, ConfigKey.property_icon, data.icon, options),
      iconTag: this.toAstProperty(bitType, ConfigKey.property_iconTag, data.iconTag, options),
      colorTag: this.toAstProperty(bitType, ConfigKey.property_colorTag, data.colorTag, options),
      flashcardSet: this.toAstProperty(
        bitType,
        ConfigKey.property_flashcardSet,
        data.flashcardSet,
        options
      ),
      subtype: this.toAstProperty(bitType, ConfigKey.property_subtype, data.subtype, options),
      bookAlias: this.toAstProperty(bitType, ConfigKey.property_bookAlias, data.bookAlias, options),
      bookDiff: this.toAstProperty(bitType, ConfigKey.property_bookDiff, data.bookDiff, options),
      coverImage: typeof data.coverImage === "string" || Array.isArray(data.coverImage) ? this.toAstProperty(
        bitType,
        ConfigKey.property_coverImage,
        instance4.asArray(data.coverImage),
        options
      ) : this.toImageResource(context, data.coverImage),
      coverColor: this.toAstProperty(
        bitType,
        ConfigKey.property_coverColor,
        data.coverColor,
        options
      ),
      backgroundImage: typeof data.backgroundImage === "string" || Array.isArray(data.backgroundImage) ? this.toAstProperty(
        bitType,
        ConfigKey.property_backgroundImage,
        instance4.asArray(data.backgroundImage),
        options
      ) : this.toImageResource(context, data.backgroundImage),
      publications: this.toAstProperty(
        bitType,
        ConfigKey.property_publications,
        data.publications,
        options
      ),
      relatedBook: this.toAstProperty(
        bitType,
        ConfigKey.property_relatedBook,
        data.relatedBook,
        options
      ),
      translationOfBook: this.toAstProperty(
        bitType,
        ConfigKey.property_translationOfBook,
        data.translationOfBook,
        options
      ),
      userFeedbackTranslationUrl: this.toAstProperty(
        bitType,
        ConfigKey.property_userFeedbackTranslationUrl,
        data.userFeedbackTranslationUrl,
        options
      ),
      userFeedbackContentUrl: this.toAstProperty(
        bitType,
        ConfigKey.property_userFeedbackContentUrl,
        data.userFeedbackContentUrl,
        options
      ),
      author: this.toAstProperty(bitType, ConfigKey.property_author, data.author, options),
      subject: this.toAstProperty(bitType, ConfigKey.property_subject, data.subject, options),
      date: this.toAstProperty(bitType, ConfigKey.property_date, data.date, options),
      dateEnd: this.toAstProperty(bitType, ConfigKey.property_dateEnd, data.dateEnd, options),
      location: this.toAstProperty(bitType, ConfigKey.property_location, data.location, options),
      kind: this.toAstProperty(bitType, ConfigKey.property_kind, data.kind, options),
      hasMarkAsDone: this.toAstProperty(
        bitType,
        ConfigKey.property_hasMarkAsDone,
        data.hasMarkAsDone,
        options
      ),
      processHandIn: this.toAstProperty(
        bitType,
        ConfigKey.property_processHandIn,
        data.processHandIn,
        options
      ),
      processHandInLocation: this.toAstProperty(
        bitType,
        ConfigKey.property_processHandInLocation,
        data.processHandInLocation,
        options
      ),
      chatWithBook: this.toAstProperty(
        bitType,
        ConfigKey.property_chatWithBook,
        data.chatWithBook,
        options
      ),
      chatWithBookBrainKey: this.toAstProperty(
        bitType,
        ConfigKey.property_chatWithBookBrainKey,
        data.chatWithBookBrainKey,
        options
      ),
      currency: this.toAstProperty(bitType, ConfigKey.property_currency, data.currency, options),
      bookIsbn: this.toAstProperty(bitType, ConfigKey.property_bookIsbn, data.bookIsbn, options),
      bookAuthor: this.toAstProperty(
        bitType,
        ConfigKey.property_bookAuthor,
        data.bookAuthor,
        options
      ),
      bookType: this.toAstProperty(bitType, ConfigKey.property_bookType, data.bookType, options),
      bookBindingType: this.toAstProperty(
        bitType,
        ConfigKey.property_bookBindingType,
        data.bookBindingType,
        options
      ),
      bookNumberOfPages: this.toAstProperty(
        bitType,
        ConfigKey.property_bookNumberOfPages,
        data.bookNumberOfPages,
        options
      ),
      bookRating: this.toAstProperty(
        bitType,
        ConfigKey.property_bookRating,
        data.bookRating,
        options
      ),
      bookSeriesTitle: this.toAstProperty(
        bitType,
        ConfigKey.property_bookSeriesTitle,
        data.bookSeriesTitle,
        options
      ),
      bookSeriesId: this.toAstProperty(
        bitType,
        ConfigKey.property_bookSeriesId,
        data.bookSeriesId,
        options
      ),
      bookSeriesNumber: this.toAstProperty(
        bitType,
        ConfigKey.property_bookSeriesNumber,
        data.bookSeriesNumber,
        options
      ),
      bookLanguage: this.toAstProperty(
        bitType,
        ConfigKey.property_bookLanguage,
        data.bookLanguage,
        options
      ),
      bookPublishingDate: this.toAstProperty(
        bitType,
        ConfigKey.property_bookPublishingDate,
        data.bookPublishingDate,
        options
      ),
      bookPublisher: this.toAstProperty(
        bitType,
        ConfigKey.property_bookPublisher,
        data.bookPublisher,
        options
      ),
      bookTranslator: this.toAstProperty(
        bitType,
        ConfigKey.property_bookTranslator,
        data.bookTranslator,
        options
      ),
      bookOriginalTitle: this.toAstProperty(
        bitType,
        ConfigKey.property_bookOriginalTitle,
        data.bookOriginalTitle,
        options
      ),
      gmbExternalShop: this.toAstProperty(
        bitType,
        ConfigKey.property_gmbExternalShop,
        data.gmbExternalShop,
        options
      ),
      gmbExternalShopItems: this.toAstProperty(
        bitType,
        ConfigKey.property_gmbExternalShopItems,
        data.gmbExternalShopItems,
        options
      ),
      action: this.toAstProperty(bitType, ConfigKey.property_action, data.action, options),
      showInIndex: this.toAstProperty(
        bitType,
        ConfigKey.property_showInIndex,
        data.showInIndex,
        options
      ),
      refAuthor: this.toAstProperty(bitType, ConfigKey.property_refAuthor, data.refAuthor, options),
      refBookTitle: this.toAstProperty(
        bitType,
        ConfigKey.property_refBookTitle,
        data.refBookTitle,
        options
      ),
      refPublisher: this.toAstProperty(
        bitType,
        ConfigKey.property_refPublisher,
        data.refPublisher,
        options
      ),
      refPublicationYear: this.toAstProperty(
        bitType,
        ConfigKey.property_refPublicationYear,
        data.refPublicationYear,
        options
      ),
      citationStyle: this.toAstProperty(
        bitType,
        ConfigKey.property_citationStyle,
        data.citationStyle,
        options
      ),
      blockId: this.toAstProperty(bitType, ConfigKey.property_blockId, data.blockId, options),
      pageNo: this.toAstProperty(bitType, ConfigKey.property_pageNo, data.pageNo, options),
      x: this.toAstProperty(bitType, ConfigKey.property_x, data.x, options),
      y: this.toAstProperty(bitType, ConfigKey.property_y, data.y, options),
      width: this.toAstProperty(bitType, ConfigKey.property_width, data.width, options),
      height: this.toAstProperty(bitType, ConfigKey.property_height, data.height, options),
      index: this.toAstProperty(bitType, ConfigKey.property_index, data.index, options),
      classification: this.toAstProperty(
        bitType,
        ConfigKey.property_classification,
        data.classification,
        options
      ),
      availableClassifications: this.toAstProperty(
        bitType,
        ConfigKey.property_availableClassifications,
        data.availableClassifications,
        options
      ),
      allowedBit: this.toAstProperty(
        bitType,
        ConfigKey.property_allowedBit,
        data.allowedBit,
        options
      ),
      authorFullName: this.toAstProperty(
        bitType,
        ConfigKey.property_authorFullName,
        data.authorFullName,
        options
      ),
      authorPseudonym: this.toAstProperty(
        bitType,
        ConfigKey.property_authorPseudonym,
        data.authorPseudonym,
        options
      ),
      authorTitle: this.toAstProperty(
        bitType,
        ConfigKey.property_authorTitle,
        data.authorTitle,
        options
      ),
      authorJobTitle: this.toAstProperty(
        bitType,
        ConfigKey.property_authorJobTitle,
        data.authorJobTitle,
        options
      ),
      tableFixedHeader: this.toAstProperty(
        bitType,
        ConfigKey.property_tableFixedHeader,
        data.tableFixedHeader,
        options
      ),
      tableHeaderWhitespaceNoWrap: this.toAstProperty(
        bitType,
        ConfigKey.property_tableHeaderWhitespaceNoWrap,
        data.tableHeaderWhitespaceNoWrap,
        options
      ),
      tableSearch: this.toAstProperty(
        bitType,
        ConfigKey.property_tableSearch,
        data.tableSearch,
        options
      ),
      tableSort: this.toAstProperty(bitType, ConfigKey.property_tableSort, data.tableSort, options),
      tablePagination: this.toAstProperty(
        bitType,
        ConfigKey.property_tablePagination,
        data.tablePagination,
        options
      ),
      tablePaginationLimit: this.toAstProperty(
        bitType,
        ConfigKey.property_tablePaginationLimit,
        data.tablePaginationLimit,
        options
      ),
      tableHeight: this.toAstProperty(
        bitType,
        ConfigKey.property_tableHeight,
        data.tableHeight,
        options
      ),
      tableWhitespaceNoWrap: this.toAstProperty(
        bitType,
        ConfigKey.property_tableWhitespaceNoWrap,
        data.tableWhitespaceNoWrap,
        options
      ),
      tableAutoWidth: this.toAstProperty(
        bitType,
        ConfigKey.property_tableAutoWidth,
        data.tableAutoWidth,
        options
      ),
      tableResizableColumns: this.toAstProperty(
        bitType,
        ConfigKey.property_tableResizableColumns,
        data.tableResizableColumns,
        options
      ),
      tableColumnMinWidth: this.toAstProperty(
        bitType,
        ConfigKey.property_tableColumnMinWidth,
        data.tableColumnMinWidth,
        options
      ),
      quizCountItems: this.toAstProperty(
        bitType,
        ConfigKey.property_quizCountItems,
        data.quizCountItems,
        options
      ),
      quizStrikethroughSolutions: this.toAstProperty(
        bitType,
        ConfigKey.property_quizStrikethroughSolutions,
        data.quizStrikethroughSolutions,
        options
      ),
      codeLineNumbers: this.toAstProperty(
        bitType,
        ConfigKey.property_codeLineNumbers,
        data.codeLineNumbers,
        options
      ),
      codeMinimap: this.toAstProperty(
        bitType,
        ConfigKey.property_codeMinimap,
        data.codeMinimap,
        options
      ),
      stripePricingTableId: this.toAstProperty(
        bitType,
        ConfigKey.property_stripePricingTableId,
        data.stripePricingTableId,
        options
      ),
      stripePricingTableDescriptiveName: this.toAstProperty(
        bitType,
        ConfigKey.property_stripePricingTableDescriptiveName,
        data.stripePricingTableDescriptiveName,
        options
      ),
      stripePublishableKey: this.toAstProperty(
        bitType,
        ConfigKey.property_stripePublishableKey,
        data.stripePublishableKey,
        options
      ),
      thumbImage: this.toAstProperty(
        bitType,
        ConfigKey.property_thumbImage,
        data.thumbImage,
        options
      ),
      scormSource: this.toAstProperty(
        bitType,
        ConfigKey.property_scormSource,
        data.scormSource,
        options
      ),
      posterImage: this.toAstProperty(
        bitType,
        ConfigKey.property_posterImage,
        data.posterImage,
        options
      ),
      focusX: this.toAstProperty(bitType, ConfigKey.property_focusX, data.focusX, options),
      focusY: this.toAstProperty(bitType, ConfigKey.property_focusY, data.focusY, options),
      pointerLeft: this.toAstProperty(
        bitType,
        ConfigKey.property_pointerLeft,
        data.pointerLeft,
        options
      ),
      pointerTop: this.toAstProperty(
        bitType,
        ConfigKey.property_pointerTop,
        data.pointerTop,
        options
      ),
      listItemIndent: this.toAstProperty(
        bitType,
        ConfigKey.property_listItemIndent,
        data.listItemIndent,
        options
      ),
      backgroundWallpaper: this.toImageResource(context, data.backgroundWallpaper),
      hasBookNavigation: this.toAstProperty(
        bitType,
        ConfigKey.property_hasBookNavigation,
        data.hasBookNavigation,
        options
      ),
      duration: this.toAstProperty(bitType, ConfigKey.property_duration, data.duration, options),
      deeplink: this.toAstProperty(bitType, ConfigKey.property_deeplink, data.deeplink, options),
      externalLink: this.toAstProperty(
        bitType,
        ConfigKey.property_externalLink,
        data.externalLink,
        options
      ),
      externalLinkText: this.toAstProperty(
        bitType,
        ConfigKey.property_externalLinkText,
        data.externalLinkText,
        options
      ),
      videoCallLink: this.toAstProperty(
        bitType,
        ConfigKey.property_videoCallLink,
        data.videoCallLink,
        options
      ),
      vendorDashboardId: this.toAstProperty(
        bitType,
        ConfigKey.property_vendorDashboardId,
        data.vendorDashboardId,
        options
      ),
      vendorSurveyId: this.toAstProperty(
        bitType,
        ConfigKey.property_vendorSurveyId,
        data.vendorSurveyId,
        options
      ),
      vendorUrl: this.toAstProperty(bitType, ConfigKey.property_vendorUrl, data.vendorUrl, options),
      search: this.toAstProperty(bitType, ConfigKey.property_search, data.search, options),
      list: this.toAstProperty(bitType, ConfigKey.property_list, data.list, options),
      layer: this.toAstProperty(bitType, ConfigKey.property_layer, data.layer, options),
      layerRole: this.toAstProperty(bitType, ConfigKey.property_layerRole, data.layerRole, options),
      textReference: this.toAstProperty(
        bitType,
        ConfigKey.property_textReference,
        data.textReference,
        options
      ),
      isTracked: this.toAstProperty(bitType, ConfigKey.property_isTracked, data.isTracked, options),
      isInfoOnly: this.toAstProperty(
        bitType,
        ConfigKey.property_isInfoOnly,
        data.isInfoOnly,
        options
      ),
      imageFirst: this.toAstProperty(
        bitType,
        ConfigKey.property_imageFirst,
        data.imageFirst,
        options
      ),
      activityType: this.toAstProperty(
        bitType,
        ConfigKey.property_activityType,
        data.activityType,
        options
      ),
      labelTrue: this.toAstProperty(bitType, ConfigKey.property_labelTrue, data.labelTrue, options),
      labelFalse: this.toAstProperty(
        bitType,
        ConfigKey.property_labelFalse,
        data.labelFalse,
        options
      ),
      content2Buy: this.toAstProperty(
        bitType,
        ConfigKey.property_content2Buy,
        data.content2Buy,
        options
      ),
      listPrice: this.toAstProperty(bitType, ConfigKey.property_listPrice, data.listPrice, options),
      customerProductId: this.toAstProperty(
        bitType,
        ConfigKey.property_customerProductId,
        data.customerProductId,
        options
      ),
      customerProductShopLink: this.toAstProperty(
        bitType,
        ConfigKey.property_customerProductShopLink,
        data.customerProductShopLink,
        options
      ),
      mailingList: this.toAstProperty(
        bitType,
        ConfigKey.property_mailingList,
        data.mailingList,
        options
      ),
      buttonCaption: this.toAstProperty(
        bitType,
        ConfigKey.property_buttonCaption,
        data.buttonCaption,
        options
      ),
      callToActionUrl: this.toAstProperty(
        bitType,
        ConfigKey.property_callToActionUrl,
        data.callToActionUrl,
        options
      ),
      advertisingClickUrl: this.toAstProperty(
        bitType,
        ConfigKey.property_advertisingClickUrl,
        data.advertisingClickUrl,
        options
      ),
      brandColor: this.toAstProperty(
        bitType,
        ConfigKey.property_brandColor,
        data.brandColor,
        options
      ),
      brandColorName: this.toAstProperty(
        bitType,
        ConfigKey.property_brandColorName,
        data.brandColorName,
        options
      ),
      caption: this.handleJsonText(context, TextLocation2.tag, data.caption),
      quotedPerson: this.toAstProperty(
        bitType,
        ConfigKey.property_quotedPerson,
        data.quotedPerson,
        options
      ),
      reasonableNumOfChars: reasonableNumOfCharsProperty,
      resolved: this.toAstProperty(bitType, ConfigKey.property_resolved, data.resolved, options),
      resolvedDate: this.toAstProperty(
        bitType,
        ConfigKey.property_resolvedDate,
        data.resolvedDate,
        options
      ),
      resolvedBy: this.toAstProperty(
        bitType,
        ConfigKey.property_resolvedBy,
        data.resolvedBy,
        options
      ),
      handInAcceptFileType: this.toAstProperty(
        bitType,
        ConfigKey.property_handInAcceptFileType,
        data.handInAcceptFileType,
        options
      ),
      handInRequirement: this.toAstProperty(
        bitType,
        ConfigKey.property_handInRequirement,
        data.handInRequirement,
        options
      ),
      handInInstruction: this.toAstProperty(
        bitType,
        ConfigKey.property_handInInstruction,
        data.handInInstruction,
        options
      ),
      maxCreatedBits: this.toAstProperty(
        bitType,
        ConfigKey.property_maxCreatedBits,
        data.maxCreatedBits,
        options
      ),
      maxDisplayLevel: this.toAstProperty(
        bitType,
        ConfigKey.property_maxDisplayLevel,
        data.maxDisplayLevel,
        options
      ),
      maxTocChapterLevel: this.toAstProperty(
        bitType,
        ConfigKey.property_maxTocChapterLevel,
        data.maxTocChapterLevel,
        options
      ),
      sourceDocument: this.toAstProperty(
        bitType,
        ConfigKey.property_sourceDocument,
        data.sourceDocument,
        options
      ),
      internalPrintPdf: this.toAstProperty(
        bitType,
        ConfigKey.property_internalPrintPdf,
        data.internalPrintPdf,
        options
      ),
      hasPrintRestriction: this.toAstProperty(
        bitType,
        ConfigKey.property_hasPrintRestriction,
        data.hasPrintRestriction,
        options
      ),
      enforceUpdateOverUserInput: this.toAstProperty(
        bitType,
        ConfigKey.property_enforceUpdateOverUserInput,
        data.enforceUpdateOverUserInput,
        options
      ),
      extractorExtractionTimestamp: this.toAstProperty(
        bitType,
        ConfigKey.property_extractorExtractionTimestamp,
        data.extractorExtractionTimestamp,
        options
      ),
      tocResource: this.toAstProperty(
        bitType,
        ConfigKey.property_tocResource,
        data.tocResource,
        options
      ),
      tocContent: this.toAstProperty(
        bitType,
        ConfigKey.property_tocContent,
        data.tocContent,
        options
      ),
      page: this.toAstProperty(bitType, ConfigKey.property_page, data.page, options),
      allowPrint: this.toAstProperty(
        bitType,
        ConfigKey.property_allowPrint,
        data.allowPrint,
        options
      ),
      printParentChapterLevel: this.toAstProperty(
        bitType,
        ConfigKey.property_printParentChapterLevel,
        data.printParentChapterLevel,
        options
      ),
      printOnRequestCustomerId: this.toAstProperty(
        bitType,
        ConfigKey.property_printOnRequestCustomerId,
        data.printOnRequestCustomerId,
        options
      ),
      printOnRequestProductId: this.toAstProperty(
        bitType,
        ConfigKey.property_printOnRequestProductId,
        data.printOnRequestProductId,
        options
      ),
      platformBrandTarget: this.toAstProperty(
        bitType,
        ConfigKey.property_platformBrandTarget,
        data.platformBrandTarget,
        options
      ),
      platformName: this.toAstProperty(
        bitType,
        ConfigKey.property_platformName,
        data.platformName,
        options
      ),
      platformIcon: this.toImageResource(context, data.platformIcon),
      platformLogo: this.toImageResource(context, data.platformLogo),
      platformLogoMaxHeight: this.toAstProperty(
        bitType,
        ConfigKey.property_platformLogoMaxHeight,
        data.platformLogoMaxHeight,
        options
      ),
      platformPrimaryColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformPrimaryColor,
        data.platformPrimaryColor,
        options
      ),
      platformSecondaryColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformSecondaryColor,
        data.platformSecondaryColor,
        options
      ),
      platformBackgroundColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformBackgroundColor,
        data.platformBackgroundColor,
        options
      ),
      platformScrollbarColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformScrollbarColor,
        data.platformScrollbarColor,
        options
      ),
      platformSelectionColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformSelectionColor,
        data.platformSelectionColor,
        options
      ),
      platformSeparatorColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformSeparatorColor,
        data.platformSeparatorColor,
        options
      ),
      platformPlaceholderColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformPlaceholderColor,
        data.platformPlaceholderColor,
        options
      ),
      platformTextSelectionColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformTextSelectionColor,
        data.platformTextSelectionColor,
        options
      ),
      platformButtonPrimaryColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformButtonPrimaryColor,
        data.platformButtonPrimaryColor,
        options
      ),
      platformButtonBackgroundColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformButtonBackgroundColor,
        data.platformButtonBackgroundColor,
        options
      ),
      platformMessageBackgroundColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformMessageBackgroundColor,
        data.platformMessageBackgroundColor,
        options
      ),
      platformBackgroundImage: this.toImageResource(context, data.platformBackgroundImage),
      platformBorderColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformBorderColor,
        data.platformBorderColor,
        options
      ),
      platformSelectionTextColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformSelectionTextColor,
        data.platformSelectionTextColor,
        options
      ),
      platformBaseLayerColor: this.toAstProperty(
        bitType,
        ConfigKey.property_platformBaseLayerColor,
        data.platformBaseLayerColor,
        options
      ),
      platformMargin: this.toAstProperty(
        bitType,
        ConfigKey.property_platformMargin,
        data.platformMargin,
        options
      ),
      platformBorderRadius: this.toAstProperty(
        bitType,
        ConfigKey.property_platformBorderRadius,
        data.platformBorderRadius,
        options
      ),
      platformSelectionBorderRadius: this.toAstProperty(
        bitType,
        ConfigKey.property_platformSelectionBorderRadius,
        data.platformSelectionBorderRadius,
        options
      ),
      platformNeedsShadow: this.toAstProperty(
        bitType,
        ConfigKey.property_platformNeedsShadow,
        data.platformNeedsShadow,
        options
      ),
      productId: this.toAstProperty(bitType, ConfigKey.property_productId, data.productId, options),
      product: this.toAstProperty(bitType, ConfigKey.property_product, data.product, options),
      // productList: this.toAstProperty(
      //   bitType,
      //   ConfigKey.property_productList,
      //   data.productList,
      //   options,
      // ),
      productVideo: this.toAstProperty(
        bitType,
        ConfigKey.property_productVideo,
        data.productVideo,
        options
      ),
      // productVideoList: this.toAstProperty(
      //   bitType,
      //   ConfigKey.property_productVideoList,
      //   data.productVideoList,
      //   options,
      // ),
      productFolder: this.toAstProperty(
        bitType,
        ConfigKey.property_productFolder,
        data.productFolder,
        options
      ),
      technicalTerm: this.buildTechnicalTerm(context, data.technicalTerm),
      servings: this.buildServings(context, data.servings),
      ratingLevelStart: this.buildRatingLevelStartEnd(context, data.ratingLevelStart),
      ratingLevelEnd: this.buildRatingLevelStartEnd(context, data.ratingLevelEnd),
      ratingLevelSelected: this.toAstProperty(
        bitType,
        ConfigKey.property_ratingLevelSelected,
        data.ratingLevelSelected,
        options
      ),
      formula: this.toAstProperty(bitType, ConfigKey.property_formula, data.formula, options),
      markConfig: this.buildMarkConfigs(context, data.markConfig),
      imageSource: this.buildImageSource(context, data.imageSource),
      person: this.buildPerson(context, data.person),
      bot: this.toAstProperty(bitType, ConfigKey.property_bot, data.bot, options),
      referenceProperty: this.toAstProperty(
        bitType,
        ConfigKey.property_reference,
        data.referenceProperty,
        options
      ),
      // Author data
      fullName: this.toAstProperty(bitType, ConfigKey.property_fullName, data.fullName, options),
      pseudonym: this.toAstProperty(bitType, ConfigKey.property_pseudonym, data.pseudonym, options),
      titleString: this.toAstProperty(bitType, ConfigKey.property_title, data.titleString, options),
      jobTitle: this.toAstProperty(bitType, ConfigKey.property_jobTitle, data.jobTitle, options),
      // Book data
      title: this.handleJsonText(context, TextLocation2.tag, data.title),
      subtitle: this.handleJsonText(context, TextLocation2.tag, data.subtitle),
      level: instance6.asNumber(data.level),
      toc: this.toAstProperty(bitType, ConfigKey.property_toc, data.toc, options),
      progress: this.toAstProperty(bitType, ConfigKey.property_progress, data.progress, options),
      isCollapsible: this.toAstProperty(
        bitType,
        ConfigKey.property_isCollapsible,
        data.isCollapsible ?? (isCollapsibleDefaultsTrue ? true : void 0),
        options
      ),
      anchor: data.anchor,
      reference: data.reference,
      referenceEnd: data.referenceEnd,
      revealSolutions: this.toAstProperty(
        bitType,
        ConfigKey.property_revealSolutions,
        data.revealSolutions,
        options
      ),
      // Item, Lead, Hint, Instruction
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      pageNumber: this.handleJsonText(context, TextLocation2.tag, data.pageNumber),
      marginNumber: this.handleJsonText(context, TextLocation2.tag, data.marginNumber),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      partialAnswer: this.toAstProperty(
        bitType,
        ConfigKey.property_partialAnswer,
        data.partialAnswer,
        options
      ),
      sampleSolution: this.toAstProperty(
        bitType,
        ConfigKey.property_sampleSolution,
        data.sampleSolution,
        options
      ),
      additionalSolutions: this.toAstProperty(
        bitType,
        ConfigKey.property_additionalSolutions,
        data.additionalSolutions,
        options
      ),
      // Example
      ...this.toExample(data.__isDefaultExample, data.example),
      // Person
      previewImage: this.toImageResources(context, data.previewImage),
      previewVideo: this.toVideoResources(context, data.previewVideo),
      imagePlaceholder: this.toImageResource(context, data.imagePlaceholder),
      resources: instance4.asArray(
        this.resourceBuilder.resourceFromResourceJson(context, data.resources)
      ),
      // Body, Card, Footer, must be after all other properties except the extraProperties and markup / parser
      body: this.buildBody(context, data.body),
      cardNode,
      footer: this.buildFooter(context, data.footer),
      // Must be after other properties/tags in the AST so key clashes are avoided correctly
      extraProperties: this.parseExtraProperties(data.extraProperties),
      markup: data.markup,
      parser: data.parser,
      // Private properties
      __isDefaultExample: data.__isDefaultExample ?? false
    };
    if (instance2.isOfBitType(node.bitType, BitType2.interview)) {
      this.pushDownTree(
        context,
        void 0,
        void 0,
        cardNode,
        "questions",
        "reasonableNumOfChars",
        data.reasonableNumOfChars
      );
    }
    if (instance2.isOfBitType(node.bitType, BitType2.feedback)) {
      this.pushDownTree(
        context,
        void 0,
        void 0,
        cardNode,
        ["feedbacks", "reason"],
        "reasonableNumOfChars",
        data.reasonableNumOfChars
      );
    }
    this.pushDownTree(
      context,
      [node.body, ...cardNode?.cardBits?.map((cardBit) => cardBit.body) || []],
      [BodyBitType.gap],
      void 0,
      void 0,
      //'isCaseSensitive',
      "isCaseSensitive",
      data.isCaseSensitive ?? true
    );
    this.pushDownTree(
      context,
      void 0,
      void 0,
      cardNode,
      "pairs",
      "isCaseSensitive",
      data.isCaseSensitive ?? true
    );
    this.pushDownTree(
      context,
      void 0,
      void 0,
      cardNode,
      ["matrix", "cells"],
      "isCaseSensitive",
      data.isCaseSensitive ?? true
    );
    this.pushExampleDownTree(
      context,
      node.body,
      cardNode,
      node.__isDefaultExample,
      convertedExample.example
    );
    this.setDefaultBitValues(node);
    this.setIsExampleFlags(node);
    this.addVersionToParserInfo(node);
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreUndefined: ["example"],
      ignoreEmptyArrays: ["item", "lead", "pageNumber", "marginNumber", "hint", "instruction"]
    });
    return nodeValidator.validateBit(node);
  }
  /**
   * Build books[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildBooks(context, data) {
    if (stringUtils.isString(data)) return data;
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildBook(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build choice node
   *
   * @param data - data for the node
   * @returns
   */
  buildBook(_context, data) {
    if (!data) return void 0;
    const node = {
      book: data.book ?? "",
      reference: data.reference ?? "",
      referenceEnd: data.referenceEnd ?? void 0
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyString: ["book", "reference"]
    });
    return node;
  }
  /**
   * Build groupTag[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildGroupTags(context, data) {
    if (!data) return void 0;
    if (!Array.isArray(data)) data = [data];
    let nodes = data.map((d) => this.buildGroupTag(context, d)).filter((d) => d != null);
    const combinedNodes = {};
    for (const node of nodes) {
      if (!node) continue;
      if (!combinedNodes[node.name]) {
        combinedNodes[node.name] = node;
      } else {
        combinedNodes[node.name].tags = instance4.removeDuplicates([
          ...combinedNodes[node.name].tags,
          ...node.tags
        ]);
      }
    }
    nodes = Object.values(combinedNodes);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Normalise a sourceBB input into the AST shape (always number[][]).
   * Accepts a single 4-number tuple or an array of tuples; returns undefined when empty.
   */
  normaliseSourceBB(data) {
    if (data == null) return void 0;
    if (!Array.isArray(data) || data.length === 0) return void 0;
    const isNested = data.some(Array.isArray);
    const tuples = (isNested ? data : [data]).filter(
      (t) => Array.isArray(t) && t.length === 4 && t.every((n) => typeof n === "number")
    );
    return tuples.length > 0 ? tuples : void 0;
  }
  /**
   * Build groupTag node
   *
   * @param data - data for the node
   * @returns
   */
  buildGroupTag(_context, data) {
    if (!data) return void 0;
    const tags2 = Array.isArray(data.tags) ? data.tags : [];
    const node = {
      name: data.name ?? "",
      tags: tags2.map((tag) => tag?.trim() ?? "") ?? []
    };
    return node;
  }
  /**
   * Build feedbackChoices[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildFeedbackChoices(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildFeedbackChoice(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build feedbackChoice node
   *
   * @param data - data for the node
   * @returns
   */
  buildFeedbackChoice(context, data) {
    if (!data) return void 0;
    const node = {
      choice: data.choice ?? "",
      requireReason: !!data.requireReason,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, true)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build feedbackReason node
   *
   * @param data - data for the node
   * @returns
   */
  buildFeedbackReason(context, data) {
    if (!data) return void 0;
    const defaultExample = this.handleJsonText(context, TextLocation2.tag, "true");
    const node = {
      text: data.text ?? "",
      reasonableNumOfChars: data.reasonableNumOfChars ?? void 0,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, defaultExample),
      __textAst: data.__textAst
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"],
      ignoreUndefined: ["example", "reasonableNumOfChars"]
    });
    return node;
  }
  /**
   * Build choice[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildChoices(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildChoice(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build choice node
   *
   * @param data - data for the node
   * @returns
   */
  buildChoice(context, data) {
    if (!data) return void 0;
    const node = {
      choice: data.choice ?? "",
      isCorrect: !!data.isCorrect,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, !!data.isCorrect)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build response[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildResponses(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildResponse(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build response node
   *
   * @param data - data for the node
   * @returns
   */
  buildResponse(context, data) {
    if (!data) return void 0;
    const node = {
      response: data.response ?? "",
      isCorrect: !!data.isCorrect,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, !!data.isCorrect)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build bot response[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildBotResponses(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.botResponse(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build bot response node
   *
   * @param data - data for the node
   * @returns
   */
  botResponse(context, data) {
    if (!data) return void 0;
    const node = {
      response: data.response ?? "",
      reaction: data.reaction ?? "",
      feedback: data.feedback ?? "",
      item: data.item ?? "",
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyString: ["response", "reaction", "feedback", "item"],
      ignoreAllFalse: true,
      ignoreEmptyArrays: [],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build feedbacks[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildFeedbacks(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildFeedback(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build feedback node
   *
   * @param data - data for the node
   * @returns
   */
  buildFeedback(context, data) {
    if (!data) return void 0;
    let choices;
    let reason;
    if (data.choices) {
      choices = this.buildFeedbackChoices(context, data.choices);
    } else {
      return void 0;
    }
    if (data.reason) {
      reason = this.buildFeedbackReason(context, data.reason);
    }
    const node = {
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      // isExample: !!data.__defaultExample,
      choices,
      reason
      // Might be undefined
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"]
    });
    return node;
  }
  /**
   * Build quizzes[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildQuizzes(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildQuiz(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build quiz node
   *
   * @param data - data for the node
   * @returns
   */
  buildQuiz(context, data) {
    if (!data) return void 0;
    const convertedExample = {
      ...this.toExample(data.__isDefaultExample, data.__defaultExample)
    };
    let choices;
    let responses;
    if (data.choices) {
      choices = this.buildChoices(context, data.choices);
      this.pushExampleDownTreeBoolean(
        context,
        data.__isDefaultExample,
        convertedExample.example,
        true,
        choices
      );
    } else if (data.responses) {
      responses = this.buildResponses(context, data.responses);
      this.pushExampleDownTreeBoolean(
        context,
        data.__isDefaultExample,
        convertedExample.example,
        false,
        responses
      );
    } else {
      return void 0;
    }
    const node = {
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      isExample: !!data.__defaultExample,
      choices,
      responses
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction"]
    });
    return node;
  }
  /**
   * Build heading node
   *
   * @param data - data for the node
   * @returns
   */
  buildHeading(_context, data) {
    if (!data) return void 0;
    if (data.forKeys == null) return void 0;
    const node = {
      forKeys: data.forKeys ?? "",
      forValues: data.forValues ?? data.__forValuesDefault ?? ""
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyString: ["forKeys", "forValues"],
      ignoreEmptyArrays: ["forValues"]
    });
    return node;
  }
  /**
   * Build pair[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildPairs(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildPair(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build pair node
   *
   * @param data - data for the node
   * @returns
   */
  buildPair(context, data) {
    if (!data) return void 0;
    const defaultExample = Array.isArray(data.__valuesAst) && data.__valuesAst.length > 0 ? data.__valuesAst[0] : null;
    const keyAudio = this.resourceBuilder.resourceFromResourceJson(
      context,
      data.keyAudio
    );
    const keyImage = this.resourceBuilder.resourceFromResourceJson(
      context,
      data.keyImage
    );
    const node = {
      key: data.key ?? "",
      keyAudio,
      keyImage,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      isCaseSensitive: data.isCaseSensitive,
      ...this.toExample(data.__isDefaultExample, data.example, defaultExample),
      values: data.values || [],
      __valuesAst: data.__valuesAst
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["item", "hint", "instruction", "values"],
      ignoreUndefined: ["example", "isCaseSensitive"]
    });
    return node;
  }
  /**
   * Build matrix[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildMatricies(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildMatrix(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build matrix node
   *
   * @param data - data for the node
   * @returns
   */
  buildMatrix(context, data) {
    if (!data) return void 0;
    let isExample = false;
    for (const c of data.cells || []) {
      if (!c) continue;
      if (data.__isDefaultExample && !c.isExample) {
        c.isExample = true;
      }
      isExample = c.isExample ? true : isExample;
    }
    const node = {
      key: data.key ?? "",
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      isExample,
      cells: (data.cells || []).map((c) => this.buildMatrixCell(context, c)).filter((c) => c != null)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["hint", "item", "cells"],
      ignoreUndefined: ["isCaseSensitive"]
    });
    return node;
  }
  /**
   * Build matrixCell node
   *
   * @param data - data for the node
   * @returns
   */
  buildMatrixCell(context, data) {
    if (!data) return void 0;
    const defaultExample = Array.isArray(data.__valuesAst) && data.__valuesAst.length > 0 ? data.__valuesAst[0] : null;
    const node = {
      values: data.values || [],
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      isCaseSensitive: data.isCaseSensitive,
      ...this.toExample(data.__isDefaultExample, data.example, defaultExample),
      __valuesAst: data.__valuesAst
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["instruction", "item", "values"],
      ignoreUndefined: ["example", "isCaseSensitive"]
    });
    return node;
  }
  /**
   * Build pronunciation table node
   *
   * @param data - data for the node
   * @returns
   */
  buildPronunciationTable(context, dataIn) {
    if (!dataIn) return void 0;
    const node = {
      data: (dataIn.data || []).map(
        (row) => (row || []).map((cell) => {
          const audio = this.resourceBuilder.resourceFromResourceJson(
            context,
            cell.audio
          );
          return {
            title: this.handleJsonText(context, TextLocation2.tag, cell.title),
            body: this.handleJsonText(context, TextLocation2.tag, cell.body),
            audio
          };
        })
      )
    };
    return node;
  }
  /**
   * Build table / table extended node
   *
   * @param data - data for the node
   * @returns
   */
  buildTable(context, dataIn) {
    if (!dataIn) return void 0;
    const node = normalizeTableFormat(context.bitType, dataIn);
    const nodeTable = node;
    const nodeTableExtended = node;
    if (Array.isArray(nodeTable.columns) && nodeTable.columns.length > 0) {
      nodeTable.columns = nodeTable.columns.map(
        (col) => this.handleJsonText(context, TextLocation2.tag, col)
      );
    }
    if (Array.isArray(nodeTable.data) && nodeTable.data.length > 0) {
      nodeTable.data = nodeTable.data.map(
        (row) => (row || []).map((cell) => this.handleJsonText(context, TextLocation2.tag, cell))
      );
    }
    if (Array.isArray(nodeTable.columnWidths)) {
      const columnCount = Array.isArray(nodeTable.columns) ? nodeTable.columns.length : void 0;
      const source = columnCount != null ? nodeTable.columnWidths.slice(0, columnCount) : nodeTable.columnWidths;
      const normalized = source.map((w) => typeof w === "number" && w > 0 ? w : null);
      const hasAny = normalized.some((w) => w != null);
      if (hasAny) {
        nodeTable.columnWidths = normalized;
      } else {
        delete nodeTable.columnWidths;
      }
    }
    const buildSection = (section) => {
      if (!section || !Array.isArray(section.rows)) return void 0;
      const rows = section.rows.map((row) => {
        const cells = (row.cells || []).map((cell) => {
          const tableCell = {
            content: this.handleJsonText(context, TextLocation2.tag, cell.content)
          };
          if (cell.title != null) tableCell.title = cell.title;
          if (cell.rowspan != null) tableCell.rowspan = cell.rowspan;
          if (cell.colspan != null) tableCell.colspan = cell.colspan;
          if (cell.scope) tableCell.scope = cell.scope;
          if (cell.colwidth != null) tableCell.colwidth = cell.colwidth;
          return tableCell;
        });
        return {
          cells
        };
      });
      return {
        rows
      };
    };
    const header = buildSection(nodeTableExtended.header);
    if (header && header.rows?.length > 0) nodeTableExtended.header = header;
    const body = buildSection(nodeTableExtended.body);
    if (body && body.rows?.length > 0) nodeTableExtended.body = body;
    const footer = buildSection(nodeTableExtended.footer);
    if (footer && footer.rows?.length > 0) nodeTableExtended.footer = footer;
    return node;
  }
  /**
   * Build question[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildQuestions(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildQuestion(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build question node
   *
   * @param data - data for the node
   * @returns
   */
  buildQuestion(context, data) {
    if (!data) return void 0;
    const defaultExample = data.__sampleSolutionAst;
    const node = {
      question: this.handleJsonText(context, TextLocation2.body, data.question),
      partialAnswer: data.partialAnswer ?? "",
      sampleSolution: data.sampleSolution ?? "",
      additionalSolutions: data.additionalSolutions ?? void 0,
      reasonableNumOfChars: data.reasonableNumOfChars ?? void 0,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, defaultExample),
      __sampleSolutionAst: data.__sampleSolutionAst
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["question", "item", "hint", "instruction"],
      ignoreUndefined: ["example"],
      ignoreEmptyString: ["partialAnswer", "sampleSolution"]
    });
    return node;
  }
  /**
   * Build ingredient[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildIngredients(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildIngredient(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build ingredient node
   *
   * @param data - data for the node
   * @returns
   */
  buildIngredient(context, data) {
    if (!data) return void 0;
    const node = {
      title: this.handleJsonText(context, TextLocation2.tag, data.title),
      checked: data.checked ?? false,
      ingredient: this.handleJsonText(context, TextLocation2.tag, data.ingredient),
      quantity: data.quantity ?? 0,
      unit: data.unit ?? "",
      unitPlural: data.unitPlural ?? "",
      unitAbbr: data.unitAbbr ?? "",
      unitAbbrPlural: data.unitAbbrPlural ?? "",
      decimalPlaces: data.decimalPlaces ?? 1,
      disableCalculation: data.disableCalculation ?? false,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      // ignoreAllEmptyArrays: true,
      // ignoreUndefined: ['example'],
      ignoreEmptyString: ["item", "unit"]
    });
    return node;
  }
  /**
   * Build body node
   * - Handles JSON, Bitmark text, and plain text
   *   - body: set to either string, TextAst, or JSON
   *   - bodyBits: set to array of BodyBitJson (will be validated and merged into body if it is a string)
   *   - placeholders: set to placeholders from v2 JSON when body is a v2 JSON string
   *   - bodyString: set to the original string if body is a string (only used for card body)
   *
   * @param data - data for the node
   * @returns
   */
  buildBody(context, data) {
    if (!data) return void 0;
    let body;
    const bodyBits = [];
    const placeholders = data.placeholders;
    const bodyString = data.bodyString;
    const { textFormat } = context;
    const handleJsonBody = () => {
      if (typeof data.body === "string") {
        try {
          body = JSON.parse(data.body);
        } catch (_e) {
          body = null;
        }
      } else {
        body = data.body;
      }
    };
    const handleBitmarkTextBody = () => {
      let rawBody = data.body;
      let bodyStr;
      const placeholderNodes = {};
      if (stringUtils.isString(data.body)) {
        bodyStr = (data.body ?? "").trim();
        rawBody = [];
      } else if (Array.isArray(data.body)) {
      } else {
        rawBody = [];
      }
      if (placeholders) {
        for (const [key, val] of Object.entries(placeholders)) {
          placeholderNodes[key] = val;
        }
      }
      if (bodyStr) {
        bodyStr = instance3.breakscape(bodyStr, {
          format: textFormat,
          location: TextLocation2.body,
          v2: true
        });
        let index = 0;
        const newPlaceholderNodes = [];
        for (const [key, val] of Object.entries(placeholderNodes)) {
          if (bodyStr) {
            const newKey = `[!${index}]`;
            bodyStr = bodyStr.replace(`${key}`, newKey);
            newPlaceholderNodes.push(val);
            index++;
          }
        }
        rawBody = this.textParser.toAst(bodyStr, {
          format: textFormat,
          location: TextLocation2.body
        });
        const replaceBitsRecursive = (bodyText) => {
          for (let i = 0, len = bodyText.length; i < len; i++) {
            const bodyPart = bodyText[i];
            if (bodyPart.type === "bit") {
              const index2 = bodyPart.index;
              if (newPlaceholderNodes[index2] != null) {
                bodyText[i] = newPlaceholderNodes[index2];
              }
            } else {
              if (bodyPart.content) replaceBitsRecursive(bodyPart.content);
            }
          }
        };
        replaceBitsRecursive(rawBody);
      }
      rawBody = structuredClone(rawBody);
      this.textParser.walkBodyBits(rawBody, (parent, index, bodyBit) => {
        let parsedBit;
        switch (bodyBit.type) {
          case BodyBitType.gap:
            parsedBit = this.buildGap(context, bodyBit);
            break;
          case BodyBitType.mark:
            parsedBit = this.buildMark(context, bodyBit);
            break;
          case BodyBitType.select:
            parsedBit = this.buildSelect(context, bodyBit);
            break;
          case BodyBitType.highlight:
            parsedBit = this.buildHighlight(context, bodyBit);
            break;
          default:
        }
        if (parsedBit != void 0) {
          parent[index] = parsedBit;
          bodyBits.push(parsedBit);
        }
      });
      body = rawBody;
      body.__tag = "text";
    };
    const handlePlainTextBody = () => {
      body = data.body;
    };
    const isBitmarkText = textFormat === TextFormat2.bitmarkText;
    if (textFormat === TextFormat2.json) {
      handleJsonBody();
    } else if (isBitmarkText) {
      handleBitmarkTextBody();
    } else {
      handlePlainTextBody();
    }
    const node = {
      body,
      bodyBits,
      bodyString
      // Placeholders are only ever parsed into AST, never stored directly
    };
    return node;
  }
  /**
   * Build footer node
   *
   * @param data - data for the node
   * @returns
   */
  buildFooter(context, data) {
    if (!data) return void 0;
    const node = {
      footer: this.handleJsonText(context, TextLocation2.body, data.footer)
    };
    return node;
  }
  /**
   * Build gap node
   *
   * @param data - data for the node
   * @returns
   */
  buildGap(context, data) {
    if (!data) return void 0;
    const defaultExample = Array.isArray(data.__solutionsAst) && data.__solutionsAst.length > 0 ? data.__solutionsAst[0] : null;
    data = this.bodyBitCopyFromAttrs(data);
    const node = {
      type: BodyBitType.gap,
      solutions: data.solutions || [],
      // Must be before other properties except type
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      isCaseSensitive: data.isCaseSensitive,
      ...this.toExample(data.__isDefaultExample, data.example, defaultExample),
      __solutionsAst: data.__solutionsAst,
      attrs: {}
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreUndefined: ["example", "isCaseSensitive"]
    });
    return node;
  }
  /**
   * Build mark configs node
   *
   * @param data - data for the node
   * @returns
   */
  buildMarkConfigs(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildMarkConfig(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build mark config node
   *
   * @param data - data for the node
   * @returns
   */
  buildMarkConfig(_context, data) {
    if (!data) return void 0;
    const node = {
      mark: data.mark ?? "unknown",
      color: data.color ?? "",
      emphasis: data.emphasis ?? ""
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllEmptyString: true
    });
    return node;
  }
  /**
   * Build mark node
   *
   * @param data - data for the node
   * @returns
   */
  buildMark(context, data) {
    if (!data) return void 0;
    data = this.bodyBitCopyFromAttrs(data);
    const node = {
      type: BodyBitType.mark,
      solution: data.solution ?? "",
      // Must be before other properties except type
      mark: data.mark ?? "",
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, true),
      attrs: {}
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreUndefined: ["example"],
      ignoreEmptyString: ["solution", "mark"]
    });
    return node;
  }
  /**
   * Build select node
   *
   * @param data - data for the node
   * @returns
   */
  buildSelect(context, data) {
    if (!data) return void 0;
    data = this.bodyBitCopyFromAttrs(data);
    const node = {
      type: BodyBitType.select,
      options: this.buildSelectOptions(context, data.options) || [],
      // Must be before other properties except type
      prefix: data.prefix ?? "",
      postfix: data.postfix ?? "",
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(false, void 0, void 0),
      // Will be set in later
      __hintString: data.__hintString,
      __instructionString: data.__instructionString,
      attrs: {}
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreEmptyString: ["prefix", "postfix"]
    });
    return node;
  }
  /**
   * Build selectOption[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildSelectOptions(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildSelectOption(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build selectOption node
   *
   * @param data - data for the node
   * @returns
   */
  buildSelectOption(context, data) {
    if (!data) return void 0;
    const node = {
      text: data.text ?? "",
      // Must be before other properties except type
      isCorrect: !!data.isCorrect,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, !!data.isCorrect)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build highlight node
   *
   * @param data - data for the node
   * @returns
   */
  buildHighlight(context, data) {
    if (!data) return void 0;
    data = this.bodyBitCopyFromAttrs(data);
    const node = {
      type: BodyBitType.highlight,
      texts: this.buildHighlightTexts(context, data.texts) || [],
      // Must be before other properties except type
      prefix: data.prefix ?? "",
      postfix: data.postfix ?? "",
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(false, void 0, void 0),
      // Will be set in later
      attrs: {}
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreEmptyString: ["prefix", "postfix"]
    });
    return node;
  }
  /**
   * Build highlightText[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildHighlightTexts(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildHighlightText(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build highlightText node
   *
   * @param data - data for the node
   * @returns
   */
  buildHighlightText(context, data) {
    if (!data) return void 0;
    const node = {
      text: data.text ?? "",
      // Must be before other properties except type
      isCorrect: !!data.isCorrect,
      isHighlighted: !!data.isHighlighted,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, !!data.isCorrect)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreAllEmptyArrays: true,
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build flashcards node
   *
   * @param data - data for the node
   * @returns
   */
  buildFlashcards(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildFlashcard(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build flashcard node
   *
   * @param data - data for the node
   * @returns
   */
  buildFlashcard(context, data) {
    if (!data) return void 0;
    const node = {
      question: this.buildTextAndIcon(context, data.question),
      answer: this.buildTextAndIcon(context, data.answer),
      alternativeAnswers: (data.alternativeAnswers || []).map((d) => this.buildTextAndIcon(context, d)).filter((d) => d != null),
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, true)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: [
        "question",
        "answer",
        "alternativeAnswers",
        "item",
        "hint",
        "instruction"
      ],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build definitionListItem[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildDefinitionList(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildDefinitionListItem(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build definitionListItem node
   *
   * @param data - data for the node
   * @returns
   */
  buildDefinitionListItem(context, data) {
    if (!data) return void 0;
    const { bitType } = context;
    const textAsStrings = instance2.isOfBitType(bitType, [BitType2.metaSearchDefaultTerms]);
    const node = {
      term: this.buildTextAndIcon(context, data.term, textAsStrings),
      definition: this.buildTextAndIcon(context, data.definition, textAsStrings),
      alternativeDefinitions: (data.alternativeDefinitions || []).map((d) => this.buildTextAndIcon(context, d, textAsStrings)).filter((d) => d != null),
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, true)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: [
        "question",
        "answer",
        "alternativeDefinitions",
        "item",
        "hint",
        "instruction"
      ],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  buildTextAndIcon(context, data, textAsStrings = false) {
    const icon = this.resourceBuilder.resourceFromResourceJson(
      context,
      data?.icon
    );
    let text = this.handleJsonText(
      context,
      TextLocation2.tag,
      data?.text || data
    );
    if (textAsStrings) {
      text = this.textGenerator.generateSync(text, TextFormat2.plainText, TextLocation2.body, {
        noBreakscaping: true
      }).trim();
    }
    const node = {
      text,
      icon
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyArrays: ["text"],
      ignoreUndefined: ["icon"]
    });
    return node;
  }
  /**
   * Build statement[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildStatements(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildStatement(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build statement node
   *
   * @param data - data for the node
   * @returns
   */
  buildStatement(context, data) {
    if (!data) return void 0;
    const node = {
      statement: this.handleJsonText(context, TextLocation2.tag, data.statement),
      isCorrect: !!data.isCorrect,
      item: this.handleJsonText(context, TextLocation2.tag, data.item),
      lead: this.handleJsonText(context, TextLocation2.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation2.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation2.tag, data.instruction),
      ...this.toExample(data.__isDefaultExample, data.example, !!data.isCorrect)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      ignoreEmptyArrays: ["statement"],
      ignoreUndefined: ["example"]
    });
    return node;
  }
  /**
   * Build (image-on-device) imageSource node
   *
   * @param data - data for the node
   * @returns
   */
  buildImageSource(_context, data) {
    if (!data) return void 0;
    const { url, mockupId, size, format, trim } = data;
    const node = {
      url: url ?? "",
      mockupId: mockupId ?? "",
      size: size ?? null,
      format: format ?? null,
      trim: instance5.isBoolean(trim) ? trim : null
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreFalse: ["trim"],
      ignoreEmptyString: ["url", "mockupId"],
      ignoreAllUndefined: true
    });
    return node;
  }
  /**
   * Build (chat) person node
   *
   * @param data - data for the node
   * @returns
   */
  buildPerson(context, data) {
    if (!data) return void 0;
    const { name, title, avatarImage } = data;
    const node = {
      name: name ?? "",
      title: title ?? void 0,
      avatarImage: this.resourceBuilder.resourceFromResourceJson(
        context,
        avatarImage
      )
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyString: ["name"],
      ignoreAllUndefined: true
    });
    return node;
  }
  /**
   * Build (cook-ingredients) technicalTerm node
   *
   * @param data - data for the node
   * @returns
   */
  buildTechnicalTerm(_context, data) {
    if (!data) return void 0;
    const { technicalTerm, lang } = data;
    const node = {
      technicalTerm: technicalTerm ?? "",
      lang: lang ?? ""
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyString: ["technicalTerm"]
      // ignoreAllUndefined: true,
    });
    return node;
  }
  /**
   * Build (cook-ingredients) servings node
   *
   * @param data - data for the node
   * @returns
   */
  buildServings(_context, data) {
    if (!data) return void 0;
    const {
      servings,
      unit,
      unitPlural,
      unitAbbr,
      unitAbbrPlural,
      decimalPlaces,
      disableCalculation,
      hint
    } = data;
    const node = {
      servings: servings ?? 0,
      unit: unit ?? "",
      unitPlural: unitPlural ?? "",
      unitAbbr: unitAbbr ?? "",
      unitAbbrPlural: unitAbbrPlural ?? "",
      decimalPlaces: decimalPlaces ?? 1,
      disableCalculation: disableCalculation ?? false,
      hint: hint ?? ""
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyString: ["servings", "unit"]
      // ignoreAllUndefined: true,
    });
    return node;
  }
  /**
   * Build (survey-rating) ratingLevelStart / ratingLevelEnd node
   *
   * @param data - data for the node
   * @returns
   */
  buildRatingLevelStartEnd(context, data) {
    if (!data) return void 0;
    const { level, label } = data;
    const node = {
      level: level ?? 0,
      label: this.handleJsonText(context, TextLocation2.tag, label)
    };
    objectUtils.removeUnwantedProperties(node, {
      // ignoreEmptyArrays: ['servings', 'unit'],
      // ignoreAllUndefined: true,
    });
    return node;
  }
  // DEPRECATED - TO BE REMOVED IN FUTURE
  // /**
  //  * Build captionDefinition node
  //  *
  //  * @param data - data for the node
  //  * @returns
  //  */
  // protected buildCaptionDefinition(
  //   _context: BuildContext,
  //   data: Partial<CaptionDefinitionJson> | undefined,
  // ): CaptionDefinitionJson | undefined {
  //   if (!data) return undefined;
  //   // NOTE: Node order is important and is defined here
  //   const node: CaptionDefinitionJson = {
  //     // term: this.convertJsonTextToAstText(data.term),
  //     // description: this.convertJsonTextToAstText(data.description),
  //     term: data.term ?? '',
  //     definition: data.definition ?? '',
  //   };
  //   // Remove Unset Optionals
  //   ObjectUtils.removeUnwantedProperties(node, {
  //     ignoreEmptyString: ['term', 'description'],
  //     // ignoreAllUndefined: true,
  //   });
  //   return node;
  // }
  // /**
  //  * Build captionDefinitionList node
  //  *
  //  * @param data - data for the node
  //  * @returns
  //  */
  // protected buildCaptionDefinitionList(
  //   context: BuildContext,
  //   data: Partial<CaptionDefinitionListJson> | undefined,
  // ): CaptionDefinitionListJson | undefined {
  //   if (!data) return undefined;
  //   // NOTE: Node order is important and is defined here
  //   const node: CaptionDefinitionListJson = {
  //     columns: data.columns || [],
  //     definitions: (data.definitions || [])
  //       .map((d) => {
  //         return this.buildCaptionDefinition(context, {
  //           term: d.term,
  //           definition: d.definition,
  //         });
  //       })
  //       .filter((d) => d != null),
  //   };
  //   // Remove Unset Optionals
  //   ObjectUtils.removeUnwantedProperties(node, {
  //     ignoreAllEmptyArrays: true,
  //   });
  //   return node;
  // }
  /**
   * Build card bit[] node
   *
   * @param data - data for the node
   * @returns
   */
  buildCardBits(context, data) {
    if (!Array.isArray(data)) return void 0;
    const nodes = data.map((d) => this.buildCardBit(context, d)).filter((d) => d != null);
    return nodes.length > 0 ? nodes : void 0;
  }
  /**
   * Build card bit node
   *
   * @param data - data for the node
   * @returns
   */
  buildCardBit(context, data) {
    if (!data) return void 0;
    const { bitType, textFormat } = context;
    const node = this.buildBit({ ...data, bitType, textFormat }, {
      cardSet: {
        sideNo: 0,
        variantNo: 0
      }
    });
    objectUtils.removeUnwantedProperties(node, {
      ignoreAllFalse: true,
      // ignoreEmptyArrays: ['example'],
      ignoreUndefined: ["example"]
    });
    return nodeValidator.validateCardBit(node);
  }
  toImageResource(context, data) {
    return instance4.asSingle(
      this.resourceBuilder.resourceFromResourceDataJson(context, ResourceType.image, data?.image)
    );
  }
  toImageResources(context, data) {
    const arr = instance4.asArray(data);
    const results = [];
    if (arr && arr.length > 0) {
      for (const item of arr) {
        const res = this.resourceBuilder.resourceFromResourceDataJson(
          context,
          ResourceType.image,
          item.image
        );
        results.push(res);
      }
    }
    return results;
  }
  toVideoResources(context, data) {
    const arr = instance4.asArray(data);
    const results = [];
    if (arr && arr.length > 0) {
      for (const item of arr) {
        const res = this.resourceBuilder.resourceFromResourceDataJson(
          context,
          ResourceType.video,
          item.video
        );
        results.push(res);
      }
    }
    return results;
  }
  //
  // Private
  //
  buildCardNode(context, data) {
    const node = {
      heading: this.buildHeading(context, data.heading),
      questions: this.buildQuestions(context, data.questions),
      elements: this.handleJsonText(context, TextLocation2.body, data.elements),
      flashcards: this.buildFlashcards(context, data.flashcards),
      definitions: this.buildDefinitionList(context, data.definitions ?? data.legend),
      statement: this.buildStatement(context, data.statement),
      statements: this.buildStatements(context, data.statements),
      choices: this.buildChoices(context, data.choices),
      responses: this.buildResponses(context, data.responses),
      feedbacks: this.buildFeedbacks(context, data.feedbacks),
      quizzes: this.buildQuizzes(context, data.quizzes),
      pairs: this.buildPairs(context, data.pairs),
      matrix: this.buildMatricies(context, data.matrix),
      pronunciationTable: this.buildPronunciationTable(context, data.pronunciationTable),
      table: this.buildTable(context, data.table),
      botResponses: this.buildBotResponses(context, data.botResponses),
      ingredients: this.buildIngredients(context, data.ingredients),
      // DEPRECATED - TO BE REMOVED IN FUTURE
      // captionDefinitionList: this.buildCaptionDefinitionList(context, data.captionDefinitionList),
      cardBits: this.buildCardBits(context, data.cardBits)
    };
    objectUtils.removeUnwantedProperties(node, {
      ignoreEmptyObjects: ["table"]
    });
    return Object.keys(node).length > 0 ? node : void 0;
  }
  /**
   * Copy any attributes from 'attrs' property to the root node (necessary when the input if from JSON)
   * The values in 'attrs' will overwrite any existing values in the node
   *
   * The original object is not modified, a new object is returned
   *
   * @param data
   */
  bodyBitCopyFromAttrs(data) {
    const copy = { ...data };
    if (data.attrs) {
      for (const [key, value] of Object.entries(data.attrs)) {
        copy[key] = value;
      }
    }
    return copy;
  }
  /**
   * Set examples down the tree
   *
   * @param body
   * @param cardNode
   * @param __isDefaultExample
   * @param example
   * @returns true if any of the answers has an example, otherwise undefined
   */
  pushExampleDownTree(context, body, cardNode, __isDefaultExample, example) {
    if (__isDefaultExample || example != null) {
      if (cardNode) {
        this.pushExampleDownTreeString(
          context,
          __isDefaultExample,
          example,
          cardNode.pairs
        );
        this.pushExampleDownTreeBoolean(
          context,
          __isDefaultExample,
          example,
          false,
          cardNode.flashcards
        );
        this.pushExampleDownTreeBoolean(
          context,
          __isDefaultExample,
          example,
          false,
          cardNode.definitions
        );
        this.pushExampleDownTreeBoolean(
          context,
          __isDefaultExample,
          example,
          true,
          cardNode.choices
        );
        this.pushExampleDownTreeBoolean(
          context,
          __isDefaultExample,
          example,
          false,
          cardNode.responses,
          cardNode.statements,
          cardNode.statement
        );
        if (cardNode.quizzes) {
          for (const quiz of cardNode.quizzes) {
            this.pushExampleDownTreeBoolean(
              context,
              __isDefaultExample,
              example,
              true,
              quiz.choices
            );
            this.pushExampleDownTreeBoolean(
              context,
              __isDefaultExample,
              example,
              false,
              quiz.responses
            );
          }
        }
        if (cardNode.matrix) {
          for (const m of cardNode.matrix) {
            this.pushExampleDownTreeString(context, __isDefaultExample, example, m.cells);
          }
        }
      }
      if (body) {
        this.pushExampleDownTreeBodyBits(context, __isDefaultExample, example, body);
      }
    }
  }
  /**
   * Set examples for boolean nodes
   *
   * @param __isDefaultExample
   * @param example
   * @param onlyCorrect
   * @param nodes
   * @returns true if any of the answers has an example, otherwise undefined
   */
  pushExampleDownTreeBoolean(_context, __isDefaultExample, example, onlyCorrect, ...nodes) {
    if (!__isDefaultExample && example == null) return;
    if (Array.isArray(nodes)) {
      for (const ds of nodes) {
        if (ds) {
          const exampleNodes = Array.isArray(ds) ? ds : [ds];
          instance7.fillBooleanExample(exampleNodes, __isDefaultExample, example, onlyCorrect);
        }
      }
    }
  }
  /**
   * Set examples for string nodes
   *
   * @param __isDefaultExample
   * @param example
   * @param nodes
   * @returns true if any of the answers has an example, otherwise undefined
   */
  pushExampleDownTreeString(context, __isDefaultExample, example, ...nodes) {
    if (!__isDefaultExample && !example) return;
    if (Array.isArray(nodes)) {
      for (const ds of nodes) {
        if (ds) {
          const exampleNodes = Array.isArray(ds) ? ds : [ds];
          instance7.fillStringExample(
            context.textFormat,
            exampleNodes,
            __isDefaultExample,
            example,
            false
          );
        }
      }
    }
  }
  pushExampleDownTreeBodyBits(context, __isDefaultExample, example, body) {
    if (!__isDefaultExample && !example) return;
    const bodyBitsJson = this.textParser.extractBodyBits(body?.body);
    for (const part of bodyBitsJson) {
      if (part) {
        switch (part.type) {
          case BodyBitType.gap: {
            const gap = part;
            instance7.fillStringExample(
              context.textFormat,
              [gap],
              __isDefaultExample,
              example,
              false
            );
            break;
          }
          case BodyBitType.mark: {
            const mark = part;
            instance7.fillBooleanExample([mark], __isDefaultExample, example, false);
            break;
          }
          case BodyBitType.select: {
            const select = part;
            instance7.fillBooleanExample(select.options, __isDefaultExample, example, true);
            break;
          }
          case BodyBitType.highlight: {
            const highlight = part;
            instance7.fillBooleanExample(highlight.texts, __isDefaultExample, example, true);
            break;
          }
        }
      }
    }
  }
  /**
   * Push a value down the tree, without overriding existing values
   *
   * This function is not type safe and should be used with care
   *
   * @param body/body[] set if the value should be passed down the body to the body bits / card body bits
   * @param bodyBitTypes body bit types to push the value down to
   * @param cardNode set if the value should be passed down the card node
   * @param path path for the value
   * @param value the value to push down
   */
  pushDownTree(_context, body, bodyBitTypes, cardNode, cardNodePath, path2, value) {
    if (value === void 0) return;
    if (cardNode && cardNodePath) {
      if (!Array.isArray(cardNodePath)) cardNodePath = [cardNodePath];
      const data = objectUtils.flatMapPath(cardNode, cardNodePath);
      for (const d of data) {
        if (d[path2] == null) {
          d[path2] = value;
        }
      }
    }
    if (body) {
      const bodyArray = Array.isArray(body) ? body : [body];
      for (const b of bodyArray) {
        if (b && b.body && bodyBitTypes) {
          const bodyBitsJson = this.textParser.extractBodyBits(b.body);
          if (bodyBitTypes && bodyBitsJson && bodyBitsJson.length > 0) {
            for (const part of bodyBitsJson) {
              if (part) {
                if (bodyBitTypes.indexOf(part.type) !== -1) {
                  const data = part;
                  if (data[path2] == null) {
                    data[path2] = value;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  parseExtraProperties(extraProperties) {
    if (!extraProperties) return void 0;
    const entries = Object.entries(extraProperties);
    if (entries.length === 0) return void 0;
    const res = {};
    for (const [key, value] of entries) {
      res[key] = instance4.asArray(value) || [value];
    }
    return res;
  }
  /**
   * Set the 'isExample' flags on the bit
   *
   * The flag is set if the bit has an example. The flag is set at each branch level up the tree from
   * where the 'example' exists.
   *
   * @param bit
   */
  setIsExampleFlags(bit) {
    const checkIsExample = (example) => {
      if (!example) return false;
      if (
        /*example.__isDefaultExample ||*/
        example.isExample || example.example != void 0
      ) {
        example.isExample = true;
        bit.isExample = true;
      } else {
        if (example === bit) {
          example.isExample = !!bit.isExample;
        } else {
          example.isExample = false;
        }
      }
      return example.isExample;
    };
    const { body, cardNode } = bit;
    const bodyBitsJson = this.textParser.extractBodyBits(body?.body);
    for (const bodyPart of bodyBitsJson) {
      switch (bodyPart.type) {
        case BodyBitType.gap:
        case BodyBitType.mark: {
          checkIsExample(bodyPart);
          break;
        }
        case BodyBitType.select: {
          const select = bodyPart;
          let hasExample = false;
          for (const option of select.options) {
            hasExample = checkIsExample(option) ? true : hasExample;
          }
          select.isExample = hasExample;
          break;
        }
        case BodyBitType.highlight: {
          const highlight = bodyPart;
          let hasExample = false;
          for (const text of highlight.texts) {
            hasExample = checkIsExample(text) ? true : hasExample;
          }
          highlight.isExample = hasExample;
          break;
        }
      }
    }
    if (cardNode) {
      for (const v of cardNode.flashcards || []) {
        checkIsExample(v);
      }
      for (const v of cardNode.definitions || []) {
        checkIsExample(v);
      }
      for (const v of cardNode.pairs || []) {
        checkIsExample(v);
      }
      for (const mx of cardNode.matrix || []) {
        let hasExample = false;
        for (const v of mx.cells || []) {
          hasExample = checkIsExample(v) ? true : hasExample;
        }
        mx.isExample = hasExample;
      }
      for (const quiz of cardNode.quizzes || []) {
        let hasExample = false;
        for (const v of quiz.responses || []) {
          hasExample = checkIsExample(v) ? true : hasExample;
        }
        for (const v of quiz.choices || []) {
          hasExample = checkIsExample(v) ? true : hasExample;
        }
        quiz.isExample = hasExample;
      }
      for (const v of cardNode.responses || []) {
        checkIsExample(v);
      }
      for (const v of cardNode.choices || []) {
        checkIsExample(v);
      }
      for (const v of cardNode.statements || []) {
        checkIsExample(v);
      }
      checkIsExample(cardNode.statement);
      for (const v of cardNode.questions || []) {
        checkIsExample(v);
      }
    }
    checkIsExample(bit.statement);
    for (const v of bit.responses || []) {
      checkIsExample(v);
    }
    for (const v of bit.choices || []) {
      checkIsExample(v);
    }
    checkIsExample(bit);
  }
  setDefaultBitValues(bit) {
    const { bitType } = bit;
    switch (bitType) {
      case BitType2.articleAi:
      case BitType2.noteAi:
      case BitType2.summaryAi:
        bit.aiGenerated = this.toAstProperty(
          bitType,
          ConfigKey.property_aiGenerated,
          true,
          void 0
        );
        break;
    }
  }
  addVersionToParserInfo(bit) {
    const parser2 = bit.parser ?? {};
    parser2.version = env.appVersion.full;
    bit.parser = parser2;
  }
};

// src/ast/writer/StringWriter.ts
var StringWriter = class {
  constructor() {
    __publicField(this, "_buffer");
    __publicField(this, "_string", "");
    __publicField(this, "endOfLineString", "\n");
    __publicField(this, "lastWrite", "");
  }
  get isSync() {
    return true;
  }
  /**
   * Get the string which has been written.
   *
   * This cannot be called until after close() has resolved its Promise.
   *
   * @returns
   */
  getString() {
    return this._string;
  }
  async open() {
    this.openSync();
    return Promise.resolve();
  }
  async close() {
    try {
      this.closeSync();
    } catch (e) {
      return Promise.reject(e);
    }
    return Promise.resolve();
  }
  openSync() {
    this._buffer = [];
    this._string = "";
  }
  closeSync() {
    if (!this._buffer) {
      throw new Error("open() or openSync() never called");
    }
    this._string = this._buffer.join("");
    this._buffer = [];
  }
  writeLine(value) {
    if (!this._buffer) return this;
    let line;
    if (value != null) {
      line = value + this.endOfLineString;
    } else {
      line = this.endOfLineString;
    }
    this._buffer.push(line);
    this.lastWrite = line;
    return this;
  }
  writeLines(values, delimiter) {
    if (!this._buffer) return this;
    if (!values) return this;
    let str = "";
    for (let i = 0, len = values.length; i < len; i++) {
      const value = values[i];
      this._buffer.push(value);
      str += value;
      if (delimiter && i < len - 1) {
        this._buffer.push(delimiter);
        str += delimiter;
      }
      this._buffer.push(this.endOfLineString);
      str += this.endOfLineString;
    }
    this.lastWrite = str;
    return this;
  }
  write(value) {
    if (!this._buffer) return this;
    if (value == null) return this;
    if (value) this.lastWrite = value;
    this._buffer.push(value);
    return this;
  }
  writeWhiteSpace() {
    this.write(" ");
    return this;
  }
  getLastWrite() {
    return this.lastWrite;
  }
};

// src/BitmarkParserGenerator.ts
import { Enum as Enum22 } from "@ncoderz/superenum";

// src/generator/bitmark/BitmarkGenerator.ts
import { Enum as Enum8 } from "@ncoderz/superenum";

// src/model/enum/CardSetVersion.ts
import "@ncoderz/superenum";
var CardSetVersion = {
  v1: 1,
  v2: 2
};

// src/generator/bitmark/BitmarkGenerator.ts
var DEFAULT_OPTIONS2 = {
  debugGenerationInline: false
};
var DEFAULT_SPACES_AROUND_VALUES = 1;
var MAX_SPACES_AROUND_VALUES = 10;
var BitmarkGenerator = class extends AstWalkerGenerator {
  /**
   * Generate bitmark markup from a bitmark AST
   *
   * @param writer - destination for the output
   * @param bitmarkVersion - The version of bitmark to output.
   * If not specified, the version will default to 3.
   *
   * Specifying the version will set defaults for other options.
   * - Bitmark v2:
   *   - cardSetVersion: 1
   * - Bitmark v3:
   *   - cardSetVersion: 2
   *
   * @param options - bitmark generation options
   */
  constructor(writer, options) {
    super();
    __publicField(this, "ast", new Ast());
    __publicField(this, "textGenerator");
    __publicField(this, "bitmarkVersion");
    __publicField(this, "options");
    __publicField(this, "mainWriter");
    // Used for writing the bitmark
    __publicField(this, "partWriter");
    // Used for writing parts which need to be assembled before writing to the main writer
    __publicField(this, "writer");
    // The current writer being used
    __publicField(this, "prettifySpace");
    __publicField(this, "spacesAroundValues");
    __publicField(this, "spacesAroundValuesStr");
    // State
    __publicField(this, "bitType", BitType2._error);
    __publicField(this, "textFormat", TextFormat2.bitmarkText);
    __publicField(this, "isBodyBitmarkText", false);
    __publicField(this, "isCardAllowed", false);
    __publicField(this, "firstBit", true);
    __publicField(this, "hasCardSet", false);
    __publicField(this, "hasFooter", false);
    __publicField(this, "inTag", true);
    __publicField(this, "skipTableTraversal", false);
    this.enter = this.enter.bind(this);
    this.between = this.between.bind(this);
    this.exit = this.exit.bind(this);
    this.leaf = this.leaf.bind(this);
    this.write = this.write.bind(this);
    this.bodyBitCallback = this.bodyBitCallback.bind(this);
    this.bitmarkVersion = Enum8(BitmarkVersion).fromValue(options?.bitmarkVersion) ?? DEFAULT_BITMARK_VERSION;
    this.options = {
      ...DEFAULT_OPTIONS2,
      ...options?.bitmarkOptions
    };
    this.debugGenerationInline = this.options.debugGenerationInline ?? false;
    if (this.bitmarkVersion === BitmarkVersion.v2) {
      if (this.options.cardSetVersion === void 0) {
        this.options.cardSetVersion = CardSetVersion.v1;
      }
    } else {
      if (this.options.cardSetVersion === void 0) {
        this.options.cardSetVersion = CardSetVersion.v2;
      }
    }
    this.prettifySpace = this.options.prettifyJson === true ? 2 : this.options.prettifyJson || void 0;
    this.spacesAroundValues = this.calcSpacesAroundValues();
    this.spacesAroundValuesStr = " ".repeat(this.spacesAroundValues);
    this.textGenerator = new TextGenerator(this.bitmarkVersion, {
      writeCallback: this.write,
      bodyBitCallback: this.bodyBitCallback,
      debugGenerationInline: this.debugGenerationInline
    });
    this.mainWriter = writer;
    this.partWriter = new StringWriter();
    this.writer = this.mainWriter;
    this.generateResourceHandlers();
    this.generatePropertyHandlers();
  }
  /**
   * Generate bitmark markup from bitmark AST
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    this.resetState();
    await this.writer.open();
    this.walkAndWrite(ast);
    this.writeLine();
    await this.writer.close();
  }
  /**
   * Generate text from a bitmark text AST synchronously
   *
   * @param ast bitmark text AST
   */
  generateSync(ast) {
    this.resetState();
    this.writer.openSync();
    this.walkAndWrite(ast);
    this.writer.closeSync();
  }
  resetState() {
    this.bitType = BitType2._error;
    this.textFormat = TextFormat2.bitmarkText;
    this.isBodyBitmarkText = false;
    this.isCardAllowed = false;
    this.firstBit = true;
    this.hasCardSet = false;
    this.hasFooter = false;
    this.inTag = true;
    this.printed = false;
  }
  walkAndWrite(ast) {
    this.ast.walk(ast, NodeType.bitmarkAst, this, void 0);
    this.writeLine();
  }
  //
  // NODE HANDLERS
  //
  //
  // Non-Terminal nodes (branches)
  //
  // bitmark
  // bitmarkAst -> bits -> bitsValue
  enter_bitsValue(node, _route) {
    const bit = node.value;
    const bitConfig = instance2.getBitConfig(bit.bitType);
    const bitResourcesConfig = instance2.getBitResourcesConfig(bit.bitType, bit.resourceType);
    this.bitType = bit.bitType;
    this.textFormat = bit.textFormat ?? bitConfig.textFormatDefault;
    this.isBodyBitmarkText = this.textFormat === TextFormat2.bitmarkText;
    this.isCardAllowed = this.calculateIsCardAllowed();
    this.hasCardSet = this.haveValidCardSet(bit);
    this.hasFooter = this.haveValidFooter(bit);
    if (!this.firstBit) {
      this.writeNL();
      this.writeNL();
      this.writeNL();
    }
    this.writeOPD(bit.bitLevel);
    if (bit.isCommented) this.writeString("|");
    this.writeTagKey(bit.bitType);
    if (bit.textFormat) {
      const write = this.isWriteTextFormat(bit.textFormat, bitConfig.textFormatDefault);
      if (write) {
        this.writeColon();
        this.writeTagKey(bit.textFormat);
      }
    }
    let resourceType;
    if (bitConfig.resourceAttachmentAllowed && bit.resources && bit.resources.length > 0) {
      const comboMap = bitResourcesConfig.comboResourceConfigKeysMap;
      if (bitResourcesConfig.comboResourceConfigKeysMap.size > 0) {
        for (const comboTagType of comboMap.keys()) {
          resourceType = configKeyToResourceType(comboTagType);
        }
      } else {
        resourceType = bit.resources[0].type;
      }
    }
    if (resourceType) {
      this.writeAmpersand();
      this.writeTagKey(resourceType);
    }
    this.writeCL();
    return true;
  }
  exit_bitsValue(_node, _route) {
    this.firstBit = false;
  }
  // bitmarkAst -> bits -> bitsValue -> internalComment
  enter_internalComment(node, route) {
    const internalComment = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    for (let i = 0; i < internalComment.length; i++) {
      const comment = internalComment[i];
      this.writeNL();
      this.writeProperty("internalComment", comment, route, {
        format: TagFormat.plainText,
        array: true
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> groupTag
  enter_groupTag(node, route) {
    const groupTag = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    for (const gt of groupTag) {
      const { name, tags: tags2 } = gt;
      this.writeProperty("groupTag", name, route, {
        format: TagFormat.plainText,
        writeEmpty: true
      });
      if (tags2 && tags2.length > 0) {
        for (const t of tags2) {
          this.writeProperty("tag", t, route, {
            format: TagFormat.plainText,
            writeEmpty: true,
            forceChain: true
          });
        }
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> labelTrue / labelFalse
  leaf_labelTrue(node, route) {
    const value = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const bit = parent?.value;
    if (bit) {
      const haveTrue = value != "";
      const haveFalse = bit.labelFalse && bit.labelFalse != "";
      if (haveTrue || haveFalse) {
        this.writeNL();
      }
      if (haveTrue)
        this.writeProperty("labelTrue", value, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      if (haveFalse)
        this.writeProperty("labelFalse", bit.labelFalse, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
    }
    return false;
  }
  leaf_labelFalse(_node, _route) {
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> imageSource
  enter_imageSource(node, route) {
    const imageSource = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const { url, mockupId, size, format, trim } = imageSource;
    this.writeProperty("imageSource", url, route, {
      format: TagFormat.plainText
    });
    if (url) {
      if (mockupId)
        this.writeProperty("mockupId", mockupId, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      if (size)
        this.writeProperty("size", size, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      if (format)
        this.writeProperty("format", format, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      if (instance5.isBoolean(trim))
        this.writeProperty("trim", trim, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> technicalTerm
  enter_technicalTerm(node, route) {
    const nodeValue = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const { technicalTerm, lang } = nodeValue;
    this.writeProperty("technicalTerm", technicalTerm, route, {
      format: TagFormat.plainText
    });
    if (lang != null) {
      this.writeProperty("lang", lang, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> servings
  enter_servings(node, route) {
    const nodeValue = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const {
      servings,
      unit,
      unitPlural,
      unitAbbr,
      unitAbbrPlural,
      decimalPlaces,
      disableCalculation,
      hint
    } = nodeValue;
    this.writeProperty("servings", servings, route, {
      format: TagFormat.plainText
    });
    if (unit != null) {
      this.writeProperty("unit", unit, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (unitPlural != null) {
      this.writeProperty("unitPlural", unitPlural, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (unitAbbr != null) {
      this.writeProperty("unitAbbr", unitAbbr, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (unitAbbrPlural != null) {
      this.writeProperty("unitAbbrPlural", unitAbbrPlural, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (decimalPlaces != null) {
      this.writeProperty("decimalPlaces", decimalPlaces, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (disableCalculation != null) {
      this.writeProperty("disableCalculation", disableCalculation, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (hint != null) {
      this.writeOPQ();
      this.writeTextOrValue(hint, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> person
  enter_person(node, route) {
    const person = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const { name, title, avatarImage } = person;
    this.writeProperty("person", name, route, {
      format: TagFormat.plainText
    });
    if (title) {
      this.writeProperty("title", title, route, {
        format: TagFormat.plainText,
        forceChain: true
      });
    }
    if (avatarImage) {
      this.writeResource(ResourceType.image, avatarImage.image.src);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions -> definitionsValue -> term -> text
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions -> definitionsValue -> definition -> text
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions -> definitionsValue -> alternativeDefinitions
  // -> alternativeDefinitionsValue -> text
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue -> question -> text
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue -> answer -> text
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue -> alternativeAnswers ->
  // -> alternativeAnswersValue -> text
  enter_text(node, route) {
    const parent = this.getParentNode(route);
    if (!parent || parent.key !== NodeType.term && parent.key !== NodeType.definition && parent.key !== NodeType.alternativeDefinitionsValue && parent.key !== NodeType.question && parent.key !== NodeType.answer && parent.key !== NodeType.alternativeAnswersValue) {
      return true;
    }
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  leaf_text(node, route) {
    const parent = this.getParentNode(route);
    if (!parent || parent.key !== NodeType.term && parent.key !== NodeType.definition && parent.key !== NodeType.alternativeDefinitionsValue && parent.key !== NodeType.question && parent.key !== NodeType.answer && parent.key !== NodeType.alternativeAnswersValue && parent.key !== NodeType.reason) {
      return true;
    }
    if (stringUtils.isString(node.value)) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> * -> term -> icon
  // bitmarkAst -> bits -> bitsValue -> * -> definition -> icon
  // bitmarkAst -> bits -> bitsValue -> * -> alternativeDefinitionsValue -> icon
  enter_icon(node, route) {
    const resource = node.value;
    const parent = this.getParentNode(route);
    if (!parent) return true;
    if (parent.key !== NodeType.term && parent.key !== NodeType.definition && parent.key !== NodeType.alternativeDefinitionsValue && parent.key !== NodeType.question && parent.key !== NodeType.answer && parent.key !== NodeType.alternativeAnswersValue) {
      return true;
    }
    this.writeResource(ResourceType.icon, resource.image.src);
    return true;
  }
  leaf_iconValue(node, route) {
    this.writeNL();
    this.writeProperty("icon", node.value, route, {
      format: TagFormat.plainText
    });
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> ratingLevelStart
  enter_ratingLevelStart(node, route) {
    this.enterRatingLevelStartEndCommon(node, route);
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> ratingLevelEnd
  enter_ratingLevelEnd(node, route) {
    this.enterRatingLevelStartEndCommon(node, route);
    return false;
  }
  // Common code for ratingLevelStart and ratingLevelEnd
  enterRatingLevelStartEndCommon(node, route) {
    const n = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const { level, label } = n;
    const levelKey = node.key === NodeType.ratingLevelStart ? "ratingLevelStart" : "ratingLevelEnd";
    this.writeProperty(levelKey, level, route, {
      format: TagFormat.plainText
    });
    if (label) {
      this.writeProperty("label", label, route, {
        format: TagFormat.bitmarkText,
        forceChain: true
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> markConfig
  enter_markConfig(_node, _route) {
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> markConfig -> markConfigValue
  enter_markConfigValue(node, route) {
    const markConfig = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.markConfig) return true;
    const { mark, color, emphasis } = markConfig;
    if (mark) {
      this.writeNL();
      this.writeProperty("mark", mark, route, {
        format: TagFormat.plainText
      });
      if (color) {
        this.writeProperty("color", color, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      }
      if (emphasis) {
        this.writeProperty("emphasis", emphasis, route, {
          format: TagFormat.plainText,
          forceChain: true
        });
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> partialAnswer
  enter_partialAnswer(node, route) {
    if (node.value) {
      this.writeProperty("partialAnswer", node.value, route, {
        format: TagFormat.plainText
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> partialAnswer
  leaf_partialAnswer(node, route) {
    if (node.value) {
      this.writeProperty("partialAnswer", node.value, route, {
        format: TagFormat.plainText
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> sampleSolution
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> sampleSolution
  enter_sampleSolution(node, route) {
    if (node.value) {
      this.writeProperty("sampleSolution", node.value, route, {
        format: TagFormat.plainText
      });
    }
    return false;
  }
  leaf_sampleSolution(node, route) {
    if (node.value) {
      this.writeProperty("sampleSolution", node.value, route, {
        format: TagFormat.plainText
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> reasonableNumOfChars
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> reasonableNumOfChars
  leaf_reasonableNumOfChars(node, route) {
    this.writeProperty("reasonableNumOfChars", node.value, route, {
      format: TagFormat.plainText
    });
  }
  // bitmarkAst -> bits -> bitsValue -> sourceBB
  enter_sourceBB(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return false;
    const tuples = node.value;
    if (!Array.isArray(tuples) || tuples.length === 0) return false;
    this.writeProperty(
      "sourceBB",
      tuples.map((t) => t.join(", ")),
      route,
      {
        format: TagFormat.plainText,
        array: true
      }
    );
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> additionalSolutions -> additionalSolutionsValue
  leaf_additionalSolutionsValue(node, route) {
    const parent = this.getParentNode(route, 2);
    if (parent?.key !== NodeType.questionsValue) return;
    this.writeNL();
    this.writeProperty("additionalSolutions", node.value, route, {
      format: TagFormat.plainText,
      array: true,
      writeEmpty: true
    });
  }
  // bitmarkAst -> bits -> bitsValue -> item
  enter_item(node, route) {
    const item = node.value;
    const parent = this.getParentNode(route);
    if (this.isEmptyText(item)) return false;
    if (!this.isEmptyText(parent?.value?.lead)) return true;
    if (!this.isEmptyText(parent?.value?.pageNumber)) return true;
    if (!this.isEmptyText(parent?.value?.marginNumber)) return true;
    this.writeNL_IfNotChain(route);
    this.writeOPC();
    this.writeTextOrValue(item, TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> lead
  enter_lead(node, route) {
    const lead = node.value;
    const parent = this.getParentNode(route);
    if (this.isEmptyText(lead)) return false;
    if (!this.isEmptyText(parent?.value?.pageNumber)) return true;
    if (!this.isEmptyText(parent?.value?.marginNumber)) return true;
    this.writeNL_IfNotChain(route);
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.item ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(lead, TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> pageNumber
  enter_pageNumber(node, route) {
    const pageNumber = node.value;
    const parent = this.getParentNode(route);
    if (this.isEmptyText(pageNumber)) return false;
    if (!this.isEmptyText(parent?.value?.marginNumber)) return true;
    this.writeNL_IfNotChain(route);
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.item ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.lead ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(pageNumber ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> marginNumber
  enter_marginNumber(node, route) {
    const marginNumber = node.value;
    const parent = this.getParentNode(route);
    if (this.isEmptyText(marginNumber)) return false;
    this.writeNL_IfNotChain(route);
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.item ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.lead ?? "", TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(
      parent?.value?.pageNumber ?? "",
      TextFormat2.bitmarkText,
      TextLocation2.tag
    );
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(marginNumber, TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> body
  enter_body(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue && parent?.key !== NodeType.cardBitsValue) return true;
    this.inTag = false;
    const body = node.value;
    if (this.textFormat === TextFormat2.json) {
      const json = body.body ?? null;
      if (Array.isArray(json) || objectUtils.isObject(json)) {
        const text = JSON.stringify(json, null, this.prettifySpace);
        if (text) {
          this.writePlainTextDivider();
          this.writeNL();
          this.writeTextOrValue(text, TextFormat2.plainText, TextLocation2.body);
        }
      }
    } else if (this.isBodyBitmarkText) {
      const plainTextDividerAllowed = !(this.hasCardSet || this.hasFooter);
      this.writeNL();
      const b = Array.isArray(body.body) ? body.body : [];
      this.writeTextOrValue(b, this.textFormat, TextLocation2.body, {
        plainTextDividerAllowed
      });
    } else {
      this.writePlainTextDivider();
      this.writeNL();
      const s = stringUtils.isString(body.body) ? body.body : "";
      this.writeTextOrValue(`${s}`, TextFormat2.plainText, TextLocation2.body);
    }
    return false;
  }
  exit_body(_node, _route) {
    this.inTag = true;
  }
  bodyBitCallback(bodyBit, _index, _route) {
    const nodeType = Enum8(NodeType).fromValue(bodyBit.type) ?? NodeType.bodyBit;
    this.ast.walk(bodyBit, nodeType, this, void 0);
    return "";
  }
  // bodyBit -> gap
  enter_gap(node, _route) {
    const gap = node.value;
    if (gap.solutions && gap.solutions.length === 0) {
      this.writeOPU();
      this.writeCL();
    } else {
      for (const solution of gap.solutions) {
        this.writeOPU();
        this.writeTextOrValue(solution, TextFormat2.plainText, TextLocation2.tag);
        this.writeCL();
      }
    }
    return true;
  }
  // bodyBit -> mark
  enter_mark(node, _route) {
    const mark = node.value;
    this.writeOPE();
    this.writeTextOrValue(mark.solution, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bodyBit -> select
  enter_select(_node, _route) {
    return true;
  }
  // bodyBit -> highlight
  enter_highlight(_node, _route) {
    return true;
  }
  // bitmarkAst -> bits -> footer
  enter_footer(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const footer = node.value;
    if (this.textFormat === TextFormat2.json) {
    } else if (footer.footer && footer.footer.length > 0) {
      if (this.isBodyBitmarkText) {
        this.writeNL();
        this.writeTextOrValue("==== footer ====", TextFormat2.plainText, TextLocation2.body);
        this.writeNL();
        this.writeTextOrValue(footer.footer, this.textFormat, TextLocation2.body, {
          plainTextDividerAllowed: true
          // Always allowed for the footer.
        });
      } else {
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyParts -> bodyPartsValue -> data -> solutions
  enter_solutions(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const solutions = node.value;
    if (solutions && solutions.length === 0) {
      this.writeOPU();
      this.writeCL();
    }
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyParts -> bodyPartsValue -> data -> solution
  leaf_solution(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return;
    const solution = node.value;
    const bodyPartsValue = this.getParentNode(route, 2)?.value;
    if (bodyPartsValue?.type !== BodyBitType.mark) return;
    if (solution) {
      this.writeOPE();
      this.writeTextOrValue(solution, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyBit -> mark -> mark
  leaf_mark(node, route) {
    const root = route[0];
    if (root?.key !== NodeType.mark) return;
    const mark = node.value;
    if (mark) {
      this.writeProperty("mark", mark, route, {
        format: TagFormat.plainText
      });
    }
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyParts -> bodyPartsValue -> data -> options -> optionsValue
  enter_optionsValue(node, _route) {
    const selectOption = node.value;
    if (selectOption.isCorrect) {
      this.writeOPP();
    } else {
      this.writeOPM();
    }
    this.writeTextOrValue(selectOption.text, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyParts -> bodyPartsValue -> data -> texts -> textsValue
  enter_textsValue(node, _route) {
    const highlightText = node.value;
    if (highlightText.isCorrect) {
      this.writeOPP();
    } else {
      this.writeOPM();
    }
    this.writeTextOrValue(highlightText.text, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode
  enter_cardNode(_node, _route) {
    if (!this.isCardAllowed) return true;
    this.writeCardSetStart();
    return true;
  }
  between_cardNode(_node, _left, _right, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetCardDivider();
  }
  exit_cardNode(_node, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetEnd();
    if (this.options.cardSetVersion === CardSetVersion.v1) {
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> elements
  enter_elements(_node, _route) {
  }
  between_elements(_node, _left, _right, _route) {
    this.writeCardSetSideDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards
  between_flashcards(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue
  between_flashcardsValue(_node, _left, right, _route) {
    if (!this.isCardAllowed) return;
    if (right.key === NodeType.answer) {
      this.writeCardSetSideDivider();
    } else if (right.key === NodeType.alternativeAnswers && right.value?.length !== 0) {
      this.writeCardSetVariantDivider();
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue -> alternativeAnswers
  between_alternativeAnswers(_node, _route) {
    this.writeCardSetVariantDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions -> definitionsValue -> alternativeDefintions
  between_alternativeDefinitions(_node, _route) {
    this.writeCardSetVariantDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> statements
  enter_statements(_node, _route) {
  }
  between_statements(_node, _left, _right, _route) {
    const isTrueFalse1 = this.isOfBitType(BitType2.trueFalse1);
    if (!isTrueFalse1) {
      this.writeCardSetCardDivider();
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> statements -> statementsValue
  enter_statementsValue(node, _route) {
    const statement = node.value;
    this.writeNL();
    if (statement.isCorrect) {
      this.writeOPP();
    } else {
      this.writeOPM();
    }
    this.writeTextOrValue(statement.statement, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> choices -> choicesValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes -> quizzesValue -> choices -> choicesValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> feedbacks -> feedbacksValue -> choices -> choicesValue
  enter_choicesValue(node, _route) {
    const choice = node.value;
    this.writeNL();
    if (choice.isCorrect || choice.requireReason) {
      this.writeOPP();
    } else {
      this.writeOPM();
    }
    this.writeTextOrValue(choice.choice, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> responses -> responsesValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes -> quizzesValue -> responses -> responsesValue
  enter_responsesValue(node, _route) {
    const response = node.value;
    this.writeNL();
    if (response.isCorrect) {
      this.writeOPP();
    } else {
      this.writeOPM();
    }
    this.writeTextOrValue(response.response, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> feedbacks
  between_feedbacks(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> feedbacks -> feedbacksValue -> reason
  enter_reason(_node, _route) {
    this.writeCardSetSideDivider();
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes
  enter_quizzes(_node, _route) {
  }
  between_quizzes(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> heading
  enter_heading(_node, _route) {
  }
  between_heading(_node, _left, _right, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetSideDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> heading -> forValues
  enter_forValues(_node, _route) {
  }
  between_forValues(_node, _left, _right, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetSideDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pairs
  enter_pairs(_node, _route) {
  }
  between_pairs(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pairs -> pairsValue -> keyAudio
  enter_keyAudio(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writeResource(ResourceType.audio, resource.audio.src);
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pairs -> pairsValue -> keyImage
  enter_keyImage(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writeResource(ResourceType.image, resource.image.src);
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> matrix
  enter_matrix(_node, _route) {
  }
  between_matrix(_node, _left, _right, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pairs -> pairsValue -> values
  // bitmarkAst -> bits -> bitsValue -> cardNode -> matrix -> matrixValue -> cells -> cellsValue -> values
  enter_values(_node, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetSideDivider();
  }
  between_values(_node, _left, _right, _route) {
    if (!this.isCardAllowed) return;
    this.writeCardSetVariantDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pronunciationTable
  between_pronunciationTable(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode) return;
    if (!this.isCardAllowed) return;
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table
  enter_table(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode) return true;
    const table = node.value;
    if (!table) return true;
    if (this.writeAdvancedTable(table)) {
      return false;
    }
    return true;
  }
  between_table(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode) return;
    if (!this.isCardAllowed) return;
    this.writeCardSetCardDivider();
  }
  writeAdvancedTable(table) {
    const normalized = normalizeTableFormat(BitType2.tableExtended, table);
    const sections = [
      { key: "header", qualifier: "table-header", section: normalized.header },
      { key: "body", qualifier: void 0, section: normalized.body },
      { key: "footer", qualifier: "table-footer", section: normalized.footer }
    ];
    let rowCount = 0;
    for (const { key, qualifier, section } of sections) {
      const rows = section?.rows ?? [];
      if (rows.length === 0) {
        continue;
      }
      for (const row of rows) {
        this.writeCardSetCardDivider(qualifier);
        this.writeTableRow(row, key);
        rowCount++;
      }
    }
    return rowCount > 0;
  }
  writeTableRow(row, section) {
    const cells = row?.cells ?? [];
    if (cells.length === 0) return;
    for (let index = 0; index < cells.length; index++) {
      if (index > 0) {
        this.writeCardSetSideDivider();
      }
      this.writeNL();
      this.writeTableCell(cells[index], section);
    }
  }
  writeTableCell(cell, section) {
    const defaultCellType = section === "body" ? "td" : "th";
    const cellType = cell.title ? "th" : "td";
    if (cellType !== defaultCellType) {
      this.writeTableCellProperty("tableCellType", cellType);
      this.writeNL();
    }
    if (cell.rowspan && cell.rowspan > 1) {
      this.writeTableCellProperty("tableRowSpan", cell.rowspan);
      this.writeNL();
    }
    if (cell.colspan && cell.colspan > 1) {
      this.writeTableCellProperty("tableColSpan", cell.colspan);
      this.writeNL();
    }
    if (cell.scope) {
      this.writeTableCellProperty("tableScope", cell.scope);
      this.writeNL();
    }
    if (cell.colwidth && cell.colwidth > 0) {
      this.writeTableCellProperty("tableColWidth", cell.colwidth);
      this.writeNL();
    }
    const content = cell.content ?? instance3.EMPTY_STRING;
    const options = this.options.noBreakscaping ? { noBreakscaping: true } : void 0;
    this.writeTextOrValue(content, this.textFormat, TextLocation2.body, options);
  }
  writeTableCellProperty(name, value) {
    this.writeOPA();
    this.writeTagKey(name);
    this.writeColon();
    this.writeTextOrValue(`${value}`, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> data
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pronunciationTable -> data
  between_data(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.table && parent?.key !== NodeType.pronunciationTable) return;
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> columns
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList -> columns
  between_columns(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    const parentKey = parent?.key;
    if (parentKey !== NodeType.table && parentKey !== NodeType.captionDefinitionList) return;
    if (!this.isCardAllowed) return;
    this.writeCardSetSideDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> columns -> columnsValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList -> columns -> columnsValue
  leaf_columnsValue(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  enter_columnsValue(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat2.bitmarkText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> data -> dataValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pronunciationTable -> data -> dataValue
  between_dataValue(_node, _left, _right, route) {
    const parent = this.getParentNode(route, 2);
    if (parent?.key !== NodeType.table && parent?.key !== NodeType.pronunciationTable) return;
    if (!this.isCardAllowed) return;
    this.writeCardSetSideDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> data -> dataValue -> dataValueValue
  leaf_dataValueValue(node, route) {
    const parent = this.getParentNode(route, 3);
    if (parent?.key !== NodeType.table) return;
    this.writeNL();
    this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table -> data -> dataValue -> dataValueValue
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pronunciationTable -> data -> dataValue -> dataValueValue
  enter_dataValueValue(node, route) {
    const parent = this.getParentNode(route, 3);
    if (parent?.key !== NodeType.table && parent?.key !== NodeType.pronunciationTable) return;
    if (node.value) {
      if (parent?.key === NodeType.pronunciationTable) {
        const cell = node.value;
        if (cell.title) {
          this.writeNL();
          this.writeOP();
          this.writeHash();
          this.writeTextOrValue(cell.title, TextFormat2.bitmarkText, TextLocation2.tag);
          this.writeCL();
        }
        if (cell.audio) {
          this.writeNL();
          this.writeResource(ResourceType.audio, cell.audio.audio.src);
        }
        if (cell.body) {
          this.writeNL();
          this.writeTextOrValue(cell.body, this.textFormat, TextLocation2.body);
        }
        return false;
      } else {
        this.writeNL();
        this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
      }
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList
  between_captionDefinitionList(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode) return;
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList -> definitions -> definitionsValue -> term
  leaf_term(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.definitionsValue) return true;
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList -> definitions -> definitionsValue -> definition
  leaf_definition(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.definitionsValue) return true;
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList -> definitions
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions
  between_definitions(_node, _left, _right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode && parent?.key !== NodeType.captionDefinitionList) return;
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions -> definitionsValue
  between_definitionsValue(_node, _left, right, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.definitions) return;
    if (!this.isCardAllowed) return;
    if (right.key === NodeType.definition) {
      this.writeCardSetSideDivider();
    } else if (right.key === NodeType.alternativeDefinitions && right.value?.length > 0) {
      this.writeCardSetVariantDivider();
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> questions
  enter_questions(_node, _route) {
  }
  between_questions(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> ingredients
  enter_ingredients(_node, _route) {
  }
  between_ingredients(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> ingredients -> ingredientsValue
  enter_ingredientsValue(node, route) {
    const ingredient = node.value;
    if (ingredient.title != null) {
      this.writeNL();
      this.writeOPHASH();
      this.writeTextOrValue(ingredient.title, TextFormat2.bitmarkText, TextLocation2.tag);
      this.writeCL();
    }
    if (ingredient.ingredient != null) {
      this.writeNL();
      if (ingredient.checked) {
        this.writeOPP();
      } else {
        this.writeOPM();
      }
      this.writeCL();
      if (ingredient.quantity != null) {
        this.writeOPB();
        this.writeTextOrValue(`${ingredient.quantity}`, TextFormat2.plainText, TextLocation2.tag);
        this.writeCL();
      }
      if (ingredient.unit != null)
        this.writeProperty("unit", ingredient.unit, route, {
          format: TagFormat.plainText
        });
      if (ingredient.unitPlural != null)
        this.writeProperty("unitPlural", ingredient.unitPlural, route, {
          format: TagFormat.plainText
        });
      if (ingredient.unitAbbr != null)
        this.writeProperty("unitAbbr", ingredient.unitAbbr, route, {
          format: TagFormat.plainText
        });
      if (ingredient.unitAbbrPlural != null)
        this.writeProperty("unitAbbrPlural", ingredient.unitAbbrPlural, route, {
          format: TagFormat.plainText
        });
      if (ingredient.decimalPlaces != null)
        this.writeProperty("decimalPlaces", ingredient.decimalPlaces, route, {
          format: TagFormat.plainText
        });
      if (ingredient.disableCalculation)
        this.writeProperty("disableCalculation", true, route, {
          format: TagFormat.plainText
        });
      this.writeNL();
      this.writeTextOrValue(ingredient.ingredient, this.textFormat, TextLocation2.body);
    }
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses
  enter_botResponses(_node, _route) {
  }
  between_botResponses(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> cardBits
  enter_cardBits(_node, _route) {
  }
  between_cardBits(_node, _left, _right, _route) {
    this.writeCardSetCardDivider();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses -> botResponsesValue -> item
  leaf_item(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.botResponsesValue) return;
    this.writeNL();
    this.writeOPC();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses -> botResponsesValue -> response
  leaf_response(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.botResponsesValue) return;
    this.writeNL();
    this.writeOPB();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses -> botResponsesValue -> reaction
  leaf_reaction(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.botResponsesValue) return;
    this.writeNL();
    this.writeProperty("reaction", node.value, route, {
      format: TagFormat.plainText
    });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses -> botResponsesValue -> feedback
  leaf_feedback(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.botResponsesValue) return;
    const feedback = node.value;
    if (feedback) {
      this.writeNL();
      this.writeTextOrValue(feedback, this.textFormat, TextLocation2.body);
    }
  }
  // bitmarkAst -> bits -> bitsValue -> backgroundWallpaper
  enter_backgroundWallpaper(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource(node.key, resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> imagePlaceholder
  enter_imagePlaceholder(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource(node.key, resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> previewImageValue
  enter_previewImageValue(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource("previewImage", resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> previewVideoValue
  enter_previewVideoValue(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource("previewVideo", resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> platformIcon
  enter_platformIcon(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource(node.key, resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> platformLogo
  enter_platformLogo(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource(node.key, resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> platformBackgroundImage
  enter_platformBackgroundImage(node, _route) {
    const resource = node.value;
    this.writeNL();
    this.writePropertyStyleResource(node.key, resource);
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> coverImage (when it's a resource object or property array)
  enter_coverImage(node, route) {
    const value = node.value;
    if (value && typeof value === "object" && !Array.isArray(value) && "type" in value) {
      const resource = value;
      this.writeNL();
      this.writePropertyStyleResource(node.key, resource);
      return true;
    }
    if (Array.isArray(value)) {
      if (value == null) return true;
      const parent = this.getParentNode(route);
      if (parent?.key !== NodeType.bitsValue) return true;
      const bitType = this.getBitType(route);
      if (!bitType) return true;
      const config = instance2.getBitConfig(bitType);
      const propertyConfig = instance2.getTagConfigForTag(
        config.tags,
        ConfigKey.property_coverImage
      );
      if (!propertyConfig) return true;
      this.writeNL_IfNotChain(route);
      this.writeProperty(propertyConfig.tag, value, route, {
        format: propertyConfig.format ?? TagFormat.plainText,
        array: propertyConfig.array ?? false,
        writeEmpty: true,
        ignoreFalse: propertyConfig.defaultValue === "false",
        ignoreTrue: propertyConfig.defaultValue === "true"
      });
    }
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> coverImage (when it's a property string)
  // This is called when coverImage is a simple string value
  leaf_coverImage(node, route) {
    const value = node.value;
    if (value == null) return;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return;
    const bitType = this.getBitType(route);
    if (!bitType) return;
    const config = instance2.getBitConfig(bitType);
    const propertyConfig = instance2.getTagConfigForTag(config.tags, ConfigKey.property_coverImage);
    if (!propertyConfig) return;
    this.writeNL_IfNotChain(route);
    this.writeProperty(propertyConfig.tag, node.value, route, {
      format: propertyConfig.format ?? TagFormat.plainText,
      array: propertyConfig.array ?? false,
      writeEmpty: true,
      ignoreFalse: propertyConfig.defaultValue === "false",
      ignoreTrue: propertyConfig.defaultValue === "true"
    });
  }
  // bitmarkAst -> bits -> bitsValue -> backgroundImage (when it's a resource object or property array)
  enter_backgroundImage(node, route) {
    const value = node.value;
    if (value && typeof value === "object" && !Array.isArray(value) && "type" in value) {
      const resource = value;
      this.writeNL();
      this.writePropertyStyleResource(node.key, resource);
      return true;
    }
    if (Array.isArray(value)) {
      if (value == null) return true;
      const parent = this.getParentNode(route);
      if (parent?.key !== NodeType.bitsValue) return true;
      const bitType = this.getBitType(route);
      if (!bitType) return true;
      const config = instance2.getBitConfig(bitType);
      const propertyConfig = instance2.getTagConfigForTag(
        config.tags,
        ConfigKey.property_backgroundImage
      );
      if (!propertyConfig) return true;
      this.writeNL_IfNotChain(route);
      this.writeProperty(propertyConfig.tag, value, route, {
        format: propertyConfig.format ?? TagFormat.plainText,
        array: propertyConfig.array ?? false,
        writeEmpty: true,
        ignoreFalse: propertyConfig.defaultValue === "false",
        ignoreTrue: propertyConfig.defaultValue === "true"
      });
    }
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> backgroundImage (when it's a property string)
  // This is called when backgroundImage is a simple string value
  leaf_backgroundImage(node, route) {
    const value = node.value;
    if (value == null) return;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return;
    const bitType = this.getBitType(route);
    if (!bitType) return;
    const config = instance2.getBitConfig(bitType);
    const propertyConfig = instance2.getTagConfigForTag(
      config.tags,
      ConfigKey.property_backgroundImage
    );
    if (!propertyConfig) return;
    this.writeNL_IfNotChain(route);
    this.writeProperty(propertyConfig.tag, node.value, route, {
      format: propertyConfig.format ?? TagFormat.plainText,
      array: propertyConfig.array ?? false,
      writeEmpty: true,
      ignoreFalse: propertyConfig.defaultValue === "false",
      ignoreTrue: propertyConfig.defaultValue === "true"
    });
  }
  exit_imagePlaceholder(_node, _route) {
  }
  // bitmarkAst -> bits -> bitsValue -> posterImage
  // bitmarkAst -> bits -> bitsValue -> resource -> * -> posterImage
  enter_posterImage(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key === NodeType.bitsValue) {
      const posterImage = node.value;
      if (posterImage) {
        this.writeProperty("posterImage", posterImage, route, {
          format: TagFormat.plainText
        });
      }
    } else {
      const posterImage = node.value;
      if (posterImage && posterImage.src) {
        this.writeProperty("posterImage", posterImage.src, route, {
          format: TagFormat.plainText
        });
      }
    }
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> resource -> thumbnails
  // [src1x,src2x,src3x,src4x,width,height,alt,zoomDisabled,caption]
  enter_thumbnails(node, route) {
    const thumbnails = node.value;
    if (Array.isArray(thumbnails)) {
      const thumbnailKeys = ["src1x", "src2x", "src3x", "src4x"];
      for (let i = 0; i < thumbnails.length; i++) {
        if (i === thumbnailKeys.length) break;
        const thumbnail = thumbnails[i];
        const key = thumbnailKeys[i];
        this.writeProperty(key, thumbnail.src, route, {
          format: TagFormat.plainText
        });
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> bitType
  // bitmarkAst -> bits -> bitsValue -> textFormat
  //  bitmarkAst -> bits -> level
  leaf_level(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    if (!instance2.isOfBitType(this.bitType, [BitType2.chapter])) return true;
    const level = node.value;
    const bit = parent?.value;
    if (level > 0 && bit.title == null) {
      this.writeNL();
      this.writeOP();
      for (let i = 0; i < +level; i++) this.writeHash();
      this.writeCL();
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> titleString
  leaf_titleString(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    if (node.value) {
      this.writeNL();
      this.writeProperty("title", node.value, route, {
        format: TagFormat.plainText
      });
    }
    return false;
  }
  //  bitmarkAst -> bits -> title
  enter_title(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const value = node.value;
    const title = value;
    const bit = parent?.value;
    const level = bit.level || 1;
    if (level && title) {
      this.writeNL();
      this.writeOP();
      for (let i = 0; i < +level; i++) this.writeHash();
      this.writeTextOrValue(title, TextFormat2.bitmarkText, TextLocation2.tag);
      this.writeCL();
    }
    return false;
  }
  //  bitmarkAst -> bits -> subtitle
  enter_subtitle(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const value = node.value;
    const subtitle = value;
    const level = 2;
    if (level && subtitle) {
      this.writeNL();
      this.writeOP();
      for (let i = 0; i < level; i++) this.writeHash();
      this.writeTextOrValue(subtitle, TextFormat2.bitmarkText, TextLocation2.tag);
      this.writeCL();
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> book (array)
  // bitmarkAst -> bits -> bitsValue -> book (array) -> bookValue
  enter_book(_node, _route) {
  }
  enter_bookValue(node, route) {
    const book = node.value;
    if (book) {
      this.writeNL();
      this.writeProperty("book", book.book, route, {
        format: TagFormat.plainText,
        writeEmpty: true
      });
      if (book.reference) {
        this.writeOPRANGLE();
        this.writeTextOrValue(book.reference, TextFormat2.plainText, TextLocation2.tag);
        this.writeCL();
        if (book.referenceEnd) {
          this.writeOPRANGLE();
          this.writeTextOrValue(book.referenceEnd, TextFormat2.plainText, TextLocation2.tag);
          this.writeCL();
        }
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> book (single)
  leaf_book(node, route) {
    const parent = this.getParentNode(route);
    const bit = parent?.value;
    if (bit && node.value) {
      this.writeNL();
      this.writeProperty("book", node.value, route, {
        format: TagFormat.plainText,
        writeEmpty: true
      });
      if (bit.reference) {
        this.writeOPRANGLE();
        this.writeTextOrValue(bit.reference, TextFormat2.plainText, TextLocation2.tag);
        this.writeCL();
        if (bit.referenceEnd) {
          this.writeOPRANGLE();
          this.writeTextOrValue(bit.referenceEnd, TextFormat2.plainText, TextLocation2.tag);
          this.writeCL();
        }
      }
    }
  }
  //  bitmarkAst -> bits -> bitsValue -> anchor
  leaf_anchor(node, _route) {
    if (node.value) {
      this.writeNL();
      this.writeOPDANGLE();
      this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
  }
  //  bitmarkAst -> bits -> bitsValue -> reference
  leaf_reference(node, route) {
    const parent = this.getParentNode(route);
    const bit = parent?.value;
    if (bit && node.value) {
      if (!bit.book) {
        this.writeNL();
        this.writeOPRANGLE();
        this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
        this.writeCL();
      }
    }
  }
  //  * -> itemLead --> item
  //  * -> itemLead --> lead
  //  * -> hint
  enter_hint(node, route) {
    const value = node.value;
    const text = value;
    if (!this.isEmptyText(text)) {
      this.writeNL_IfNotChain(route);
      this.writeOPQ();
      this.writeTextOrValue(text, TextFormat2.bitmarkText, TextLocation2.tag);
      this.writeCL();
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> instruction
  enter_instruction(node, route) {
    const value = node.value;
    const text = value;
    if (!this.isEmptyText(text)) {
      this.writeNL_IfNotChain(route);
      this.writeOPB();
      this.writeTextOrValue(text, TextFormat2.bitmarkText, TextLocation2.tag);
      this.writeCL();
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> lang
  leaf_lang(node, route) {
    if (!node.value) return;
    this.writeProperty("lang", node.value, route, {
      format: TagFormat.boolean
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> refAuthor
  enter_refAuthor(node, route) {
    if (!node.value) return;
    this.writeProperty("refAuthor", node.value, route, {
      format: TagFormat.plainText,
      array: true
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> refBookTitle
  leaf_refBookTitle(node, route) {
    if (!node.value) return;
    this.writeProperty("refBookTitle", node.value, route, {
      format: TagFormat.plainText,
      array: true
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> refPublisher
  enter_refPublisher(node, route) {
    if (!node.value) return;
    this.writeProperty("refPublisher", node.value, route, {
      format: TagFormat.plainText,
      array: true
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> refPublicationYear
  leaf_refPublicationYear(node, route) {
    if (!node.value) return;
    this.writeProperty("refPublicationYear", node.value, route, {
      format: TagFormat.plainText
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> citationStyle
  leaf_citationStyle(node, route) {
    if (!node.value) return;
    this.writeProperty("citationStyle", node.value, route, {
      format: TagFormat.plainText
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> isExample / example
  leaf_isExample(node, route) {
    const isExample = node.value;
    if (!isExample) return;
    const parent = this.getParentNode(route);
    const example = parent?.value.example ?? null;
    if (example != null && example !== "") {
      this.writeNL_IfNotChain(route);
      this.writeOPA();
      this.writeString("example");
      this.writeColon();
      if (example === true) {
        this.writeString(" true ");
      } else if (example === false) {
        this.writeString(" false ");
      } else if (Array.isArray(example)) {
        this.writeTextOrValue(example, TextFormat2.bitmarkText, TextLocation2.tag);
      } else {
        this.writeTextOrValue(example, TextFormat2.plainText, TextLocation2.tag);
      }
      this.writeCL();
    } else {
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> elements -> elementsValue
  enter_elementsValue(node, _route) {
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> body -> bodyValue -> gap -> solutions -> solution
  // ? -> solutions -> solution
  leaf_solutionsValue(node, route) {
    const parent = this.getParentNode(route, 2);
    if (parent?.key !== NodeType.bitsValue) return;
    if (node.value != null) {
      this.writeOPU();
      this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
  }
  // bitmarkAst -> bits -> bitsValue-> body -> bodyValue -> select -> options -> prefix
  // bitmarkAst -> bits -> bitsValue-> body -> bodyValue -> highlight -> options -> prefix
  leaf_prefix(node, _route) {
    if (node.value) {
      this.writeOPPRE();
      this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
  }
  // bitmarkAst -> bits -> bitsValue-> body -> bodyValue -> select -> options -> postfix
  // bitmarkAst -> bits -> bitsValue-> body -> bodyValue -> highlight -> options -> postfix
  leaf_postfix(node, _route) {
    if (node.value) {
      this.writeOPPOST();
      this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
      this.writeCL();
    }
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> isCaseSensitive
  leaf_isCaseSensitive(node, route) {
    this.writeProperty("isCaseSensitive", node.value, route, {
      format: TagFormat.boolean,
      writeEmpty: true,
      ignoreFalse: false,
      ignoreTrue: true
    });
  }
  // bitmarkAst -> bits -> bitsValue ->  * -> isCorrect
  // bitmarkAst -> bits -> bitsValue -> heading -> forKeys
  leaf_forKeys(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> heading -> forValues
  leaf_forValues(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> heading -> forValuesValue
  leaf_forValuesValue(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat2.plainText, TextLocation2.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> pairs -> pairsValue -> key
  // bitmarkAst -> bits -> bitsValue -> matrix -> matrixValue -> key
  leaf_key(node, _route) {
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
  }
  // bitmarkAst -> bits -> bitsValue -> pairs -> pairsValue -> values -> valuesValue
  // bitmarkAst -> bits -> bitsValue -> matrix -> matrixValue -> cells -> cellsValue -> values -> valuesValue
  leaf_valuesValue(node, _route) {
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
  }
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> question
  // bitmarkAst -> bits -> bitsValue -> cardNode -> questions -> questionsValue -> question
  enter_question(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.questionsValue) return true;
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards -> flashcardsValue -> question
  leaf_question(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.flashcardsValue) return;
    if (node.value) {
      this.writeNL();
      this.writeTextOrValue(node.value, this.textFormat, TextLocation2.body);
    }
  }
  // bitmarkAst -> bits -> bitsValue -> statements -> text
  // bitmarkAst -> bits -> bitsValue -> resource -> ...
  // bitmarkAst -> bits -> bitsValue -> resource -> posterImage -> ...
  // bitmarkAst -> bits -> bitsValue -> resource -> thumbnails -> thumbnailsValue -> ...
  // [srcAlt,src1x,src2x,src3x,src4x,width,height,alt,zoomDisabled,caption]
  leaf_srcAltValue(node, route) {
    this.writeProperty("srcAlt", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_src1x(node, route) {
    this.writeProperty("src1x", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_src2x(node, route) {
    this.writeProperty("src2x", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_src3x(node, route) {
    this.writeProperty("src3x", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_src4x(node, route) {
    this.writeProperty("src4x", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_width(node, route) {
    this.writeProperty("width", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_height(node, route) {
    this.writeProperty("height", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_alt(node, route) {
    this.writeProperty("alt", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_zoomDisabled(node, route) {
    if (instance2.isOfBitType(this.bitType, [
      BitType2.imageSeparator,
      BitType2.pageBanner,
      BitType2.imagesLogoGrave,
      BitType2.prototypeImages,
      BitType2.extractorImage
    ])) {
      this.writeProperty("zoomDisabled", node.value, route, {
        format: TagFormat.boolean,
        writeEmpty: true,
        ignoreFalse: false,
        ignoreTrue: true
      });
    } else {
      this.writeProperty("zoomDisabled", node.value, route, {
        format: TagFormat.boolean,
        writeEmpty: true,
        ignoreFalse: true,
        ignoreTrue: false
      });
    }
  }
  leaf_license(node, route) {
    this.writeProperty("license", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_copyright(node, route) {
    this.writeProperty("copyright", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_provider(_node, _route) {
  }
  leaf_showInIndex(node, route) {
    if (node.value == null) return;
    this.writeNL_IfNotChain(route);
    this.writeProperty("showInIndex", node.value, route, {
      format: TagFormat.boolean,
      ignoreFalse: true
    });
  }
  enter_caption(node, route) {
    const value = node.value;
    this.writeNL_IfNotChain(route);
    this.writeProperty("caption", value, route, {
      format: TagFormat.bitmarkText
    });
    return false;
  }
  leaf_search(node, route) {
    const value = node.value;
    if (!value) return;
    this.writeNL_IfNotChain(route);
    this.writeProperty("search", value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_selected(node, route) {
    if (node.value == null) return;
    this.writeNL_IfNotChain(route);
    this.writeProperty("selected", node.value, route, {
      format: TagFormat.boolean,
      ignoreFalse: true
    });
  }
  // bitmarkAst -> bits -> bitsValue -> resource -> ...
  // bitmarkAst -> bits -> bitsValue -> resource -> posterImage -> ...
  // bitmarkAst -> bits -> bitsValue -> resource -> thumbnails -> thumbnailsValue -> ...
  // [duration,mute,autoplay,allowSubtitles,showSubtitles]
  leaf_duration(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key === NodeType.bitsValue) {
      this.writeNL();
    }
    this.writeProperty("duration", node.value, route, {
      format: TagFormat.plainText
    });
  }
  leaf_mute(node, route) {
    this.writeProperty("mute", node.value, route, {
      format: TagFormat.boolean
    });
  }
  leaf_autoplay(node, route) {
    this.writeProperty("autoplay", node.value, route, {
      format: TagFormat.boolean
    });
  }
  leaf_allowSubtitles(node, route) {
    this.writeProperty("allowSubtitles", node.value, route, {
      format: TagFormat.boolean
    });
  }
  leaf_showSubtitles(node, route) {
    this.writeProperty("showSubtitles", node.value, route, {
      format: TagFormat.boolean
    });
  }
  //
  // Resources
  //
  //
  // Generated Node Handlers
  //
  /**
   * Generate the handlers for resources, as they are mostly the same, but not quite
   */
  generateResourceHandlers() {
    for (const tag of Enum8(ResourceType).keys()) {
      if (tag === Enum8(ResourceType).keyFromValue(ResourceType.unknown)) continue;
      const enterFuncName = `enter_${tag}`;
      if (typeof this[enterFuncName] === "function") {
        continue;
      }
      this[enterFuncName] = (node, route) => {
        const resource = node.value;
        if (resource == null) return false;
        const parent = this.getParentNode(route);
        if (parent?.key !== NodeType.resourcesValue) return true;
        const alias = Enum8(ResourceType).fromValue(parent?.value.__typeAlias);
        const type = alias ?? Enum8(ResourceType).fromValue(parent?.value.type);
        if (!type) return false;
        const url = resource.url || resource.src || resource.body || "";
        this.writeNL();
        this.writeResource(type, url);
        return true;
      };
      this[enterFuncName] = this[enterFuncName].bind(this);
    }
  }
  /**
   * Generate the handlers for properties, as they are mostly the same, but not quite
   */
  generatePropertyHandlers() {
    for (const propertyConfigKey of Enum8(PropertyKey).values()) {
      const propertyTag = configKeyToPropertyType(propertyConfigKey);
      const funcNames = [`enter_${propertyTag}`, `leaf_${propertyTag}`];
      for (const funcName of funcNames) {
        if (typeof this[funcName] === "function") {
          continue;
        }
        if (propertyTag === "example") continue;
        this[funcName] = (node, route) => {
          const value = node.value;
          if (value == null) return;
          const parent = this.getParentNode(route);
          if (parent?.key !== NodeType.bitsValue) return;
          const bitType = this.getBitType(route);
          if (!bitType) return;
          const config = instance2.getBitConfig(bitType);
          const propertyConfig = instance2.getTagConfigForTag(config.tags, propertyConfigKey);
          if (!propertyConfig) return;
          this.writeProperty(propertyConfig.tag, node.value, route, {
            format: propertyConfig.format ?? TagFormat.plainText,
            array: propertyConfig.array ?? false,
            writeEmpty: true,
            ignoreFalse: propertyConfig.defaultValue === "false",
            ignoreTrue: propertyConfig.defaultValue === "true"
          });
        };
        this[funcName] = this[funcName].bind(this);
      }
    }
  }
  // END NODE HANDLERS
  //
  // UTILITY FUNCTIONS
  //
  /**
   * Get the bit type from any node
   *
   * @param route the route to the current node
   * @returns the bit type
   */
  getBitType(route) {
    for (const node of route) {
      if (node.key === NodeType.bitsValue) {
        const n = node.value;
        return n?.bitType;
      }
    }
    return void 0;
  }
  /**
   * Check if in a chain.
   *
   * Return false if at the root of the bit or a card bit, otherwise true.
   * This is useful to determine if a newline should be written before certain properties.
   *
   * @param route
   * @returns
   */
  isChain(route) {
    const parent = this.getParentNode(route);
    if (parent?.key === NodeType.bitsValue) return false;
    if (parent?.key === NodeType.cardBitsValue) return false;
    if (parent?.key === NodeType.feedbacksValue) return false;
    if (parent?.key === NodeType.quizzesValue) return false;
    if (parent?.key === NodeType.pairsValue) return false;
    if (parent?.key === NodeType.matrixValue) return false;
    if (parent?.key === NodeType.definitionsValue) return false;
    if (parent?.key === NodeType.questionsValue) return false;
    if (parent?.key === NodeType.reason) return false;
    return true;
  }
  haveValidCardSet(bit) {
    const bitConfig = instance2.getBitConfig(bit.bitType);
    if (!bitConfig) return false;
    return !!bitConfig.cardSet;
  }
  haveValidFooter(bit) {
    const footer = bit.footer;
    if (!footer) return false;
    const textFormat = bit.textFormat;
    if (textFormat === TextFormat2.json) {
      return false;
    } else if (footer.footer && footer.footer.length > 0) {
      if (this.isBodyBitmarkText) {
        return true;
      } else {
        return false;
      }
    }
    return false;
  }
  /**
   * Calculate the number of spaces around values from the options.
   *
   * This function should only be called from the constructor.
   * Otherwise use this.spacesAroundValues.
   *
   * @returns
   */
  calcSpacesAroundValues() {
    const val = this.options.spacesAroundValues;
    let spaces = DEFAULT_SPACES_AROUND_VALUES;
    if (val != null) {
      if (val === true) {
        spaces = DEFAULT_SPACES_AROUND_VALUES;
      } else if (val === false) {
        spaces = 0;
      } else {
        spaces = val;
      }
    }
    spaces = Math.min(Math.max(spaces, 0), MAX_SPACES_AROUND_VALUES);
    return spaces;
  }
  // END UTILITY FUNCTIONS
  //
  // WRITE FUNCTIONS
  //
  /**
   * Helper function to write a tag key, breakscaping appropriately.
   * Use only to write a tag key.
   *
   * @param s
   */
  writeTagKey(s) {
    if (s != null) {
      this.write(
        instance3.breakscape(`${s}`, {
          format: TextFormat2.plainText,
          location: TextLocation2.tag
        })
      );
    }
  }
  /**
   * Write text or value, handling:
   *  - the format and location (i.e. conversion to text and breakscaping).
   *  - the spaces around values (if tag value).
   *
   * @param text
   * @param format
   * @param location
   * @param options
   */
  writeTextOrValue(text, format, location, options) {
    const isTagValue = location === TextLocation2.tag;
    const s = typeof text === "string" ? text : void 0;
    const ast = Array.isArray(text) ? text : void 0;
    const spaces = isTagValue ? this.spacesAroundValuesStr : "";
    const breakscapeFormat = format === TextFormat2.bitmarkText ? format : TextFormat2.plainText;
    const wrap = (content) => isTagValue ? `${spaces}${content}${spaces}` : content;
    const writeToPartWriter = (action) => {
      const originalWriter = this.writer;
      this.writer = this.partWriter;
      this.partWriter.openSync();
      action();
      this.partWriter.closeSync();
      const result = this.partWriter.getString().trim();
      this.writer = originalWriter;
      return result;
    };
    if (s != null) {
      const writeString = () => this.write(
        instance3.breakscape(s, {
          format: breakscapeFormat,
          location
        })
      );
      if (isTagValue) {
        const written = writeToPartWriter(writeString);
        if (written) this.write(wrap(written));
      } else {
        writeString();
      }
      return;
    }
    if (ast != null) {
      const generateAst = () => this.textGenerator.generateSync(ast, format, location, options);
      if (isTagValue) {
        const written = writeToPartWriter(generateAst);
        if (written) this.write(wrap(written));
      } else {
        generateAst();
      }
    }
  }
  writeString(s) {
    if (s != null) this.write(`${s}`);
  }
  writeOPBUL() {
    this.write("[\u2022");
  }
  writeOPESC() {
    this.write("[^");
  }
  writeOPRANGLE() {
    this.write("[\u25BA");
  }
  writeOPDANGLE() {
    this.write("[\u25BC");
  }
  writeOPD(level) {
    this.write(`[${".".repeat(level)}`);
  }
  writeOPU() {
    this.write("[_");
  }
  writeOPE() {
    this.write("[=");
  }
  writeOPB() {
    this.write("[!");
  }
  writeOPQ() {
    this.write("[?");
  }
  writeOPA() {
    this.write("[@");
  }
  writeOPP() {
    this.write("[+");
  }
  writeOPM() {
    this.write("[-");
  }
  writeOPS() {
    this.write("[\\");
  }
  writeOPR() {
    this.write("[*");
  }
  writeOPC() {
    this.write("[%");
  }
  writeOPAMP() {
    this.write("[&");
  }
  writeOPDOLLAR() {
    this.write("[$");
  }
  writeOPHASH() {
    this.write("[#");
  }
  writeOPPRE() {
    this.write("['");
  }
  writeOPPOST() {
    this.write("[");
  }
  writeOP() {
    this.write("[");
  }
  writeCL() {
    if (this.spacesAroundValues === 0) {
      if (this.writer.getLastWrite().endsWith("^")) {
        this.write(" ]");
      } else {
        this.write("]");
      }
    } else {
      this.write("]");
    }
  }
  writeAmpersand() {
    this.write("&");
  }
  writeColon() {
    this.write(":");
  }
  // protected writeDoubleColon(): void {
  //   this.write('::');
  // }
  writeHash() {
    this.write("#");
  }
  writeSpacesAroundValues() {
    this.write(this.spacesAroundValuesStr);
  }
  writePlainTextDivider() {
    this.writeNL();
    this.write("==== text ====");
  }
  writeCardSetStart() {
    this.writeNL();
    if (this.options.cardSetVersion === CardSetVersion.v1) {
      this.write("===");
    } else {
      this.write("====");
    }
  }
  writeCardSetEnd() {
    this.writeNL();
    if (this.options.cardSetVersion === CardSetVersion.v1) {
      this.write("===");
    } else {
    }
  }
  writeCardSetCardDivider(qualifier) {
    this.writeNL();
    const divider = this.getCardDividerMarker();
    this.write(divider);
    if (qualifier) {
      this.appendCardDividerQualifier(qualifier);
    }
  }
  writeCardSetSideDivider() {
    this.writeNL();
    if (this.options.cardSetVersion === CardSetVersion.v1) {
      this.write("==");
    } else {
      this.write("--");
    }
  }
  writeCardSetVariantDivider() {
    this.writeNL();
    if (this.options.cardSetVersion === CardSetVersion.v1) {
      this.write("--");
    } else {
      this.write("++");
    }
  }
  getCardDividerMarker() {
    return this.options.cardSetVersion === CardSetVersion.v1 ? "===" : "====";
  }
  appendCardDividerQualifier(qualifier) {
    const marker = this.getCardDividerMarker();
    const normalizedQualifier = instance3.breakscape(qualifier, {
      format: TextFormat2.plainText,
      location: TextLocation2.tag
    });
    this.write(" ");
    this.write(normalizedQualifier);
    this.write(" ");
    this.write(marker);
  }
  writeNL_IfNotChain(route) {
    if (!this.isChain(route)) {
      this.writeNL();
    }
  }
  writeNL() {
    if (this.options.debugGenerationInline) {
      this.write("\\n");
      return;
    }
    this.write("\n");
  }
  writePropertyStyleResource(key, resource, deprecated_writeAsProperty = false) {
    if (key && resource) {
      const resourceTag = Enum8(ResourceType).keyFromValue(resource.type) ?? "";
      const resourceData = resource[resourceTag];
      const src = resourceData ? resourceData.src || resourceData.url || resourceData.body || "" : "";
      if (deprecated_writeAsProperty) {
        this.writeOPA();
      } else {
        this.writeOPAMP();
      }
      this.writeTagKey(key);
      this.writeColon();
      this.writeTextOrValue(src, TextFormat2.plainText, TextLocation2.tag);
      if (resource.type === ResourceType.article) {
      }
      this.writeCL();
    }
  }
  writeResource(type, value) {
    if (type) {
      this.writeOPAMP();
      this.writeTagKey(type);
      this.writeColon();
      this.writeTextOrValue(value, TextFormat2.plainText, TextLocation2.tag);
      if (type === ResourceType.article) {
      }
      this.writeCL();
    }
  }
  writeProperty(name, values, route, options) {
    let valuesArray;
    if (values !== void 0) {
      const isBitmarkText = options.format === TagFormat.bitmarkText;
      if (isBitmarkText) {
        if (!options.writeEmpty && isBitmarkText && this.isEmptyText(values)) return;
        this.writeOPA();
        this.writeTagKey(name);
        this.writeColon();
        this.writeTextOrValue(values, TextFormat2.bitmarkText, TextLocation2.tag);
        this.writeCL();
      } else {
        if (!Array.isArray(values)) {
          valuesArray = [values];
        } else {
          valuesArray = values;
        }
        if (valuesArray.length > 0) {
          if (!options.array) valuesArray = valuesArray.slice(valuesArray.length - 1);
          let propertyIndex = 0;
          for (const val of valuesArray) {
            if (val !== void 0) {
              if (options.ignoreFalse && val === false) continue;
              if (options.ignoreTrue && val === true) continue;
              if (!options.writeEmpty && val === "") continue;
              if (!options.forceChain && propertyIndex === 0) this.writeNL_IfNotChain(route);
              this.writeOPA();
              this.writeTagKey(name);
              this.writeColon();
              this.writeTextOrValue(`${val}`, TextFormat2.plainText, TextLocation2.tag);
              this.writeCL();
              propertyIndex++;
            }
          }
        }
      }
    }
  }
  writeInlineDebug(key, state) {
    let tag = key;
    if (state.open) {
      tag = `<${key}>`;
    } else if (state.close) {
      tag = `</${key}>`;
    } else if (state.single) {
      tag = `<${key} />`;
    }
    this.writeString(tag);
  }
  //
  // Helper functions
  //
  isEmptyText(text) {
    if (!text) return true;
    if (Array.isArray(text)) return text.length === 0;
    return true;
  }
  isWriteTextFormat(bitsValue, textFormatDefault) {
    const isDefault = Enum8(TextFormat2).fromValue(bitsValue) === textFormatDefault;
    const writeFormat = !isDefault || this.options.explicitTextFormat;
    return !!writeFormat;
  }
  calculateIsCardAllowed() {
    return this.isBodyBitmarkText && !this.isOfBitType1() && !this.isTableBitType();
  }
  isTableBitType() {
    return this.isOfBitType([BitType2.table]);
  }
  isOfBitType1() {
    return this.isOfBitType([
      BitType2.trueFalse1,
      BitType2.multipleChoice1,
      BitType2.multipleResponse1
    ]);
  }
  isOfBitType(baseBitType) {
    return instance2.isOfBitType(this.bitType, baseBitType);
  }
  //
  // Writer interface
  //
  /**
   * Writes a string value to the output.
   * @param value - The string value to be written.
   */
  write(value) {
    this.writer.write(value);
    return this;
  }
  /**
   * Writes a new line to the output. The line is indented automatically. The line is ended with the endOfLineString.
   * @param value - The line to write. When omitted, only the endOfLineString is written.
   */
  writeLine(value) {
    this.writer.writeLine(value);
    return this;
  }
  /**
   * Writes a collection of lines to the output. Each line is indented automatically and ended with the endOfLineString.
   * @param values - The lines to write.
   * @param delimiter - An optional delimiter to be written at the end of each line, except for the last one.
   */
  writeLines(values, delimiter) {
    this.writer.writeLines(values, delimiter);
    return this;
  }
  /**
   * Writes a single whitespace character to the output.
   */
  writeWhiteSpace() {
    this.writer.writeWhiteSpace();
    return this;
  }
};

// src/generator/bitmark/BitmarkStringGenerator.ts
var BitmarkStringGenerator = class {
  /**
   * Generate bitmark markup from a bitmark AST as a string
   *
   * @param bitmarkVersion - bitmark version, use null to use latest version
   * @param options - bitmark generation options
   */
  constructor(options) {
    __publicField(this, "generator");
    __publicField(this, "writer");
    this.writer = new StringWriter();
    this.generator = new BitmarkGenerator(this.writer, options);
  }
  /**
   * Generate bitmark markup from bitmark AST as a string
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    await this.generator.generate(ast);
    return this.writer.getString();
  }
  /**
   * Generate bitmark markup from bitmark AST as a string synchronously
   *
   * @param ast bitmark AST
   */
  generateSync(ast) {
    this.generator.generateSync(ast);
    return this.writer.getString();
  }
};

// src/generator/html/HtmlTableGenerator.ts
var MARK_TO_TAG = {
  bold: "b",
  italic: "i",
  superscript: "sup",
  subscript: "sub",
  strike: "s",
  del: "del",
  ins: "ins",
  underline: "u",
  highlight: "mark",
  code: "code"
};
var INLINE_IMAGE_RE = /==([^=]*(?:=(?!=)[^=]*)*)==\|imageInline:([^|\s]+)((?:\|(?:@?[A-Za-z][A-Za-z0-9_-]*:[^|\s]+|#[^|\s]*))*)\|?/g;
var MAX_IMAGE_DIMENSION = 9999;
var HtmlTableGenerator = class {
  constructor() {
    __publicField(this, "indentUnit", "  ");
  }
  /**
   * Generate HTML table fragments from a string or JSON value.
   * @returns the concatenated `<table>` fragments, blank-line separated (empty string if none).
   */
  generate(input) {
    let data = input;
    if (typeof data === "string") {
      try {
        data = JSON.parse(data);
      } catch {
        return "";
      }
    }
    const bits = this.collectBits(data);
    const fragments = [];
    for (const bit of bits) {
      const html = this.renderTableBit(bit);
      if (html) fragments.push(html);
    }
    return fragments.join("\n\n");
  }
  // --- bit collection -------------------------------------------------------
  collectBits(data) {
    const out = [];
    const visit = (value) => {
      if (value == null || typeof value !== "object") return;
      if (Array.isArray(value)) {
        value.forEach(visit);
        return;
      }
      const obj = value;
      if ("bit" in obj && obj.bit && typeof obj.bit === "object") {
        out.push(obj.bit);
      } else if ("type" in obj) {
        out.push(obj);
      }
    };
    visit(data);
    return out;
  }
  // --- table rendering ------------------------------------------------------
  renderTableBit(bit) {
    const table = bit.table;
    if (!table) return "";
    if (bit.type !== "table" && bit.type !== "table-extended") return "";
    const extended = this.toExtended(table);
    const lines = [];
    lines.push("<table>");
    if (bit.caption != null) {
      const caption = this.cellContentToHtml(this.asTextAst(bit.caption));
      if (caption) lines.push(`${this.indentUnit}<caption>${caption}</caption>`);
    }
    this.renderSection(lines, "thead", extended.header, "header");
    this.renderSection(lines, "tbody", extended.body, "body");
    this.renderSection(lines, "tfoot", extended.footer, "footer");
    lines.push("</table>");
    return lines.join("\n");
  }
  toExtended(table) {
    const t = table;
    if (t.header || t.body || t.footer) return table;
    return convertBasicToExtendedTableFormat(table);
  }
  renderSection(lines, tag, section, key) {
    if (!section || !section.rows || section.rows.length === 0) return;
    lines.push(`${this.indentUnit}<${tag}>`);
    for (const row of section.rows) {
      lines.push(`${this.indentUnit.repeat(2)}<tr>`);
      for (const cell of row.cells ?? []) {
        lines.push(`${this.indentUnit.repeat(3)}${this.renderCell(cell, key)}`);
      }
      lines.push(`${this.indentUnit.repeat(2)}</tr>`);
    }
    lines.push(`${this.indentUnit}</${tag}>`);
  }
  renderCell(cell, _key) {
    const tag = cell.title === true ? "th" : "td";
    const attrs = [];
    if (cell.rowspan && cell.rowspan > 1) attrs.push(`rowspan="${cell.rowspan}"`);
    if (cell.colspan && cell.colspan > 1) attrs.push(`colspan="${cell.colspan}"`);
    if (cell.scope) attrs.push(`scope="${cell.scope}"`);
    if (cell.colwidth && cell.colwidth > 0) attrs.push(`width="${cell.colwidth}"`);
    const open = attrs.length > 0 ? `<${tag} ${attrs.join(" ")}>` : `<${tag}>`;
    return `${open}${this.cellContentToHtml(this.asTextAst(cell.content))}</${tag}>`;
  }
  // --- cell content (TextAst -> HTML) ---------------------------------------
  asTextAst(content) {
    if (content == null) return [];
    if (typeof content === "string") return [{ type: "text", text: content }];
    return content;
  }
  cellContentToHtml(blocks) {
    if (blocks.length === 0) return "";
    if (blocks.length === 1 && blocks[0].type === "paragraph") {
      return this.inlineToHtml(blocks[0].content ?? []);
    }
    return blocks.map((b) => this.blockToHtml(b)).join("");
  }
  blockToHtml(node) {
    switch (node.type) {
      case "paragraph":
        return `<p>${this.inlineToHtml(node.content ?? [])}</p>`;
      case "bulletList":
      case "noBulletList":
      case "orderedList":
      case "orderedListRoman":
      case "orderedListRomanLower":
      case "letteredList":
      case "letteredListLower":
      case "taskList":
        return this.listToHtml(node);
      case "image":
      case "imageInline":
        return this.imageToHtml(node);
      case "latex":
        return this.latexToHtml(node);
      default:
        return this.inlineToHtml([node]);
    }
  }
  inlineToHtml(nodes) {
    return nodes.map((n) => this.inlineNodeToHtml(n)).join("");
  }
  inlineNodeToHtml(node) {
    if (node.type === "hardBreak") return "<br>";
    if (node.type === "image" || node.type === "imageInline") return this.imageToHtml(node);
    if (node.type === "latex") return this.latexToHtml(node);
    if (node.type === "text") {
      return this.wrapMarks(this.textToHtml(node.text ?? ""), node.marks);
    }
    if (node.content) return this.inlineToHtml(node.content);
    return this.escapeText(node.text ?? "");
  }
  wrapMarks(text, marks) {
    if (!marks || marks.length === 0) return text;
    let open = "";
    let close = "";
    for (const mark of marks) {
      if (mark.type === "link") {
        const href = mark.attrs?.href ?? "";
        open += `<a href="${this.escapeAttr(href)}">`;
        close = `</a>${close}`;
      } else {
        const tag = MARK_TO_TAG[mark.type];
        if (tag) {
          open += `<${tag}>`;
          close = `</${tag}>${close}`;
        }
      }
    }
    return `${open}${text}${close}`;
  }
  // --- lists ----------------------------------------------------------------
  listToHtml(node) {
    const { tag, attrs } = this.listTag(node);
    const items = (node.content ?? []).map((item) => this.listItemToHtml(item, node.type === "taskList")).join("");
    return `<${tag}${attrs}>${items}</${tag}>`;
  }
  listTag(node) {
    const start = node.attrs?.start;
    const startAttr = typeof start === "number" && start !== 1 ? ` start="${start}"` : "";
    switch (node.type) {
      case "orderedList":
        return { tag: "ol", attrs: startAttr };
      case "orderedListRoman":
        return { tag: "ol", attrs: ` type="I"${startAttr}` };
      case "orderedListRomanLower":
        return { tag: "ol", attrs: ` type="i"${startAttr}` };
      case "letteredList":
        return { tag: "ol", attrs: ` type="A"${startAttr}` };
      case "letteredListLower":
        return { tag: "ol", attrs: ` type="a"${startAttr}` };
      case "noBulletList":
        return { tag: "ul", attrs: ' style="list-style:none"' };
      case "taskList":
      case "bulletList":
      default:
        return { tag: "ul", attrs: "" };
    }
  }
  listItemToHtml(item, task) {
    const inner = (item.content ?? []).map((b) => {
      if (b.type === "paragraph") return this.inlineToHtml(b.content ?? []);
      return this.blockToHtml(b);
    }).join("");
    if (task) {
      const checked = item.attrs?.checked ? " checked" : "";
      return `<li><input type="checkbox"${checked}>${inner}</li>`;
    }
    return `<li>${inner}</li>`;
  }
  // --- images ---------------------------------------------------------------
  imageToHtml(node) {
    const attrs = node.attrs ?? {};
    const parts = [];
    const src = attrs.src;
    parts.push(`src="${this.escapeAttr(typeof src === "string" ? src : "")}"`);
    if (typeof attrs.alt === "string") parts.push(`alt="${this.escapeAttr(attrs.alt)}"`);
    if (typeof attrs.title === "string") parts.push(`title="${this.escapeAttr(attrs.title)}"`);
    if (attrs.width != null) parts.push(`width="${attrs.width}"`);
    if (attrs.height != null) parts.push(`height="${attrs.height}"`);
    if (typeof attrs.class === "string" && attrs.class) {
      parts.push(`class="${this.escapeAttr(attrs.class)}"`);
    }
    return `<img ${parts.join(" ")}>`;
  }
  textToHtml(text) {
    let html = "";
    let lastIndex = 0;
    for (const match of text.matchAll(INLINE_IMAGE_RE)) {
      const index = match.index ?? 0;
      html += this.escapeText(text.slice(lastIndex, index));
      html += this.inlineImageTokenToHtml(match);
      lastIndex = index + match[0].length;
    }
    html += this.escapeText(text.slice(lastIndex));
    return html;
  }
  inlineImageTokenToHtml(match) {
    const attrs = {
      alt: match[1],
      src: match[2]
    };
    const rawAttrs = match[3] ?? "";
    for (const attr of rawAttrs.split("|")) {
      if (!attr || attr.startsWith("#")) continue;
      const colon = attr.indexOf(":");
      if (colon < 1) continue;
      const key = attr.slice(0, colon).replace(/^@/, "");
      const value = attr.slice(colon + 1);
      if (key === "width" || key === "height") {
        const n = Number.parseInt(value, 10);
        if (!Number.isNaN(n) && n > 0 && n <= MAX_IMAGE_DIMENSION) attrs[key] = n;
      } else if (key === "class" || key === "title") {
        attrs[key] = value;
      }
    }
    return this.imageToHtml({ type: "imageInline", attrs });
  }
  // --- formulas -------------------------------------------------------------
  latexToHtml(node) {
    const formula = node.attrs?.formula ?? "";
    return `<math alttext="${this.escapeAttr(formula)}"></math>`;
  }
  // --- escaping -------------------------------------------------------------
  escapeText(text) {
    return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  }
  escapeAttr(text) {
    return this.escapeText(text).replace(/"/g, "&quot;");
  }
};

// src/generator/json/JsonGenerator.ts
import { Enum as Enum9 } from "@ncoderz/superenum";
var MOVE_BODY_RECURSION_LIMIT = 5e3;
var DEFAULT_OPTIONS3 = {
  // debugGenerationInline: true,
};
var JsonGenerator = class extends AstWalkerGenerator {
  /**
   * Generate bitmark JSON from a bitmark AST
   *
   * @param writer - destination for the output
   * @param options - JSON generation options
   */
  constructor(writer, options) {
    super();
    __publicField(this, "ast", new Ast());
    __publicField(this, "textGenerator");
    __publicField(this, "bitmarkVersion");
    __publicField(this, "textParserVersion");
    __publicField(this, "textParser", new TextParser());
    // TODO - move to context
    __publicField(this, "options");
    __publicField(this, "jsonPrettifySpace");
    __publicField(this, "writer");
    // State
    __publicField(this, "json", []);
    __publicField(this, "bitWrapperJson", {});
    __publicField(this, "bitJson", {});
    __publicField(this, "textDefault", instance3.EMPTY_STRING);
    __publicField(this, "bodyDefault", instance3.EMPTY_STRING);
    __publicField(this, "bodyJson", this.bodyDefault);
    __publicField(this, "listItem");
    __publicField(this, "placeholderIndex", 0);
    this.enter = this.enter.bind(this);
    this.between = this.between.bind(this);
    this.exit = this.exit.bind(this);
    this.leaf = this.leaf.bind(this);
    this.bodyBitCallback = this.bodyBitCallback.bind(this);
    this.bitmarkVersion = Enum9(BitmarkVersion).fromValue(options?.bitmarkVersion) ?? DEFAULT_BITMARK_VERSION;
    this.textParserVersion = this.textParser.version();
    this.options = {
      ...DEFAULT_OPTIONS3,
      ...options?.jsonOptions
    };
    this.debugGenerationInline = this.options.debugGenerationInline ?? false;
    this.jsonPrettifySpace = this.options.prettify === true ? 2 : this.options.prettify || void 0;
    if (this.bitmarkVersion === BitmarkVersion.v2) {
      if (this.options.textAsPlainText === void 0) {
        this.options.textAsPlainText = true;
      }
    } else {
      if (this.options.textAsPlainText === void 0) {
        this.options.textAsPlainText = false;
      }
    }
    this.textGenerator = new TextGenerator(BitmarkVersion.v2, {
      // writeCallback: this.write,
      bodyBitCallback: this.bodyBitCallback,
      debugGenerationInline: this.debugGenerationInline
    });
    this.writer = writer;
    this.generatePropertyHandlers();
  }
  /**
   * Generate bitmark markup from bitmark AST
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    this.resetState();
    await this.writer.open();
    this.walkAndWrite(ast);
    this.write(JSON.stringify(this.json, null, this.jsonPrettifySpace));
    await this.writer.close();
  }
  /**
   * Generate text from a bitmark text AST synchronously
   *
   * @param ast bitmark text AST
   */
  generateSync(ast) {
    this.resetState();
    this.writer.openSync();
    this.walkAndWrite(ast);
    this.write(JSON.stringify(this.json, null, this.jsonPrettifySpace));
    this.writer.closeSync();
  }
  resetState() {
    this.json = [];
    this.bitWrapperJson = {};
    this.bitJson = {};
    this.textDefault = this.options.textAsPlainText ? instance3.EMPTY_STRING : [];
    this.bodyDefault = this.options.textAsPlainText ? instance3.EMPTY_STRING : [];
    this.bodyJson = this.bodyDefault;
    this.listItem = void 0;
    this.placeholderIndex = 0;
    this.printed = false;
  }
  walkAndWrite(ast) {
    this.ast.walk(ast, NodeType.bitmarkAst, this, void 0);
  }
  //
  // NODE HANDLERS
  //
  //
  // Non-Terminal nodes (branches)
  //
  // bitmark
  enter_bitmarkAst(_node, _route) {
    this.json = [];
  }
  exit_bitmarkAst(_node, _route) {
    if (this.options.textAsPlainText) {
      this.convertAllBitmarkTextsToStringsForPlainText(this.json);
    }
    this.removeTemporaryProperties(this.json);
  }
  // bitmarkAst -> bits
  // bitmarkAst -> bits -> bitsValue
  enter_bitsValue(node, _route) {
    const bit = node.value;
    this.bitWrapperJson = {
      //
    };
    this.json.push(this.bitWrapperJson);
    this.bitJson = this.createBitJson(bit);
    this.bitWrapperJson.bit = this.bitJson;
    const bitConfig = instance2.getBitConfig(bit.bitType);
    const textFormat = this.getTextFormat(_route);
    const hasRootExample = !!bitConfig.rootExampleType;
    const isBoolean = bitConfig.rootExampleType === ExampleType.boolean;
    if (hasRootExample) {
      let defaultExample;
      if (isBoolean) {
        defaultExample = true;
        if (instance2.isOfBitType(bit.bitType, BitType2.trueFalse1)) {
          if (bit.cardNode?.statement?.isCorrect !== void 0) {
            defaultExample = bit.cardNode.statement.isCorrect;
          }
        }
      } else {
        defaultExample = "true";
      }
      const exampleRes = this.toExample(bit, {
        defaultExample,
        isBoolean,
        textFormat
      });
      this.bitJson.isExample = exampleRes.isExample;
      this.bitJson.example = exampleRes.example;
    } else if (bit.isExample) {
      this.bitJson.isExample = true;
    }
    this.placeholderIndex = 0;
  }
  exit_bitsValue(_node, _route) {
    const isExample = this.bitJson.isExample;
    const example = this.bitJson.example;
    delete this.bitJson.isExample;
    delete this.bitJson.example;
    this.bitJson.isExample = isExample;
    this.bitJson.example = example;
    this.cleanBitJson(this.bitJson);
  }
  // bitmarkAst -> bits -> bitsValue -> imageSource
  enter_imageSource(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> person
  enter_person(node, route) {
    const bitType = this.getBitType(route);
    let keyOverride = "person";
    if (instance2.isOfBitType(bitType, BitType2.conversationLeft1)) {
      keyOverride = "partner";
    }
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false, keyOverride });
  }
  // bitmarkAst -> bits -> bitsValue -> ratingLevelStart
  enter_ratingLevelStart(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> ratingLevelEnd
  enter_ratingLevelEnd(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> sourceBB
  enter_sourceBB(node, route) {
    const tuples = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return false;
    if (tuples && tuples.length > 0) {
      this.bitJson.sourceBB = tuples.length === 1 ? tuples[0] : tuples;
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> productId
  enter_productId(node, route) {
    const productIds = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    const bitType = this.getBitType(route);
    if (bitType === BitType2.module) {
      this.addProperty(this.bitJson, "productId", productIds, { array: true });
    } else if (productIds.length > 0) {
      this.addProperty(this.bitJson, "productId", productIds[productIds.length - 1], {
        array: false
      });
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> isCollapsible
  /**
   * PLAN-016: `isCollapsible` is emitted only when true. `false` is the implied
   * default for every bit, so it is never written to the JSON — an absent key
   * means the bit's configured default (`true` for the deprecated
   * `*-collapsible` bit types, `false` everywhere else).
   *
   * Defining these handlers suppresses the generic property handlers that
   * `generatePropertyHandlers()` would otherwise install for this tag.
   */
  leaf_isCollapsible(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return false;
    if (node.value === true) this.bitJson.isCollapsible = true;
    return false;
  }
  enter_isCollapsible(node, route) {
    return this.leaf_isCollapsible(node, route);
  }
  // bitmarkAst -> bits -> bitsValue -> markConfig
  enter_markConfig(_node, _route) {
    return true;
  }
  // bitmarkAst -> bits -> bitsValue -> markConfig -> markConfigValue
  enter_markConfigValue(node, route) {
    const markJson = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.markConfig) return true;
    if (!this.bitJson.marks) this.bitJson.marks = [];
    this.bitJson.marks.push(markJson);
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> item
  enter_item(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> lead
  enter_lead(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> pageNumber
  enter_pageNumber(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> marginNumber
  enter_marginNumber(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> hint
  enter_hint(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> instruction
  enter_instruction(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> titleString
  leaf_titleString(node, route) {
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.bitsValue) return true;
    return this.standardHandler(node, route, [NodeType.bitsValue], {
      array: false,
      keyOverride: "title"
    });
  }
  // bitmarkAst -> bits -> bitsValue -> title
  enter_title(node, route) {
    return this.standardHandler(node, route, [NodeType.bitsValue, NodeType.cardNode], {
      array: true
    });
  }
  //  bitmarkAst -> bits -> bitsValue -> subtitle
  enter_subtitle(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  //  bitmarkAst -> bits -> bitsValue -> caption
  enter_caption(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> width
  enter_width(node, route) {
    let value = node.value;
    const bitType = this.getBitType(route);
    const parent = this.getParentNode(route);
    if (parent?.key === NodeType.bitsValue && instance2.isOfBitType(bitType, BitType2.extractorBlock)) {
      value = instance6.asNumber(value, 0) ?? 0;
    }
    return this.standardHandler(node, route, void 0, { array: false, valueOverride: value });
  }
  // bitmarkAst -> bits -> bitsValue -> height
  enter_height(node, route) {
    let value = node.value;
    const bitType = this.getBitType(route);
    const parent = this.getParentNode(route);
    if (parent?.key === NodeType.bitsValue && instance2.isOfBitType(bitType, BitType2.extractorBlock)) {
      value = instance6.asNumber(value, 0) ?? 0;
    }
    return this.standardHandler(node, route, void 0, { array: false, valueOverride: value });
  }
  // bitmarkAst -> bits -> bitsValue -> extraProperties
  enter_extraProperties(node, _route) {
    const extraProperties = node.value;
    if (!this.options.excludeUnknownProperties && extraProperties) {
      for (const [key, values] of Object.entries(extraProperties)) {
        let k = key;
        if (Object.prototype.hasOwnProperty.call(this.bitJson, key)) {
          k = `_${key}`;
        }
        this.addProperty(this.bitJson, k, values, { array: true });
      }
    }
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> cardBits -> cardBitsValue
  enter_cardBitsValue(node, route) {
    const cardBit = node.value;
    const bitType = this.getBitType(route);
    if (!bitType) return;
    let listItems;
    if (bitType === BitType2.bookReferenceList) {
      if (!this.bitJson.bookReferences) this.bitJson.bookReferences = [];
      const bookReference = {};
      this.addProperty(bookReference, "lang", cardBit.lang, { array: false });
      this.addProperty(bookReference, "refAuthor", cardBit.refAuthor ?? [], { array: true });
      this.addProperty(bookReference, "refBookTitle", cardBit.refBookTitle ?? [], { array: false });
      this.addProperty(bookReference, "refPublisher", cardBit.refPublisher ?? [], { array: true });
      this.addProperty(bookReference, "refPublicationYear", cardBit.refPublicationYear ?? [], {
        array: false
      });
      this.addProperty(bookReference, "citationStyle", cardBit.citationStyle ?? [], {
        array: false
      });
      this.bitJson.bookReferences.push(bookReference);
    } else {
      if (bitType === BitType2.pageFooter) {
        if (!this.bitJson.sections) this.bitJson.sections = [];
        listItems = this.bitJson.sections;
      } else {
        if (!this.bitJson.listItems) this.bitJson.listItems = [];
        listItems = this.bitJson.listItems;
      }
      this.listItem = {
        item: cardBit.item ?? [],
        lead: cardBit.lead ?? [],
        hint: cardBit.hint ?? [],
        instruction: cardBit.instruction ?? [],
        // ...this.toItemLeadHintInstruction(node.value),
        body: this.bodyDefault
      };
      listItems.push(this.listItem);
    }
  }
  exit_cardBitsValue(_node, _route) {
    this.listItem = void 0;
  }
  // bitmarkAst -> bits -> bitsValue -> body
  enter_body(node, route) {
    const value = node.value;
    const parent = this.getParentNode(route);
    if (!parent) return false;
    const textFormat = this.getTextFormat(route);
    const isBitmarkText = textFormat === TextFormat2.bitmarkText;
    this.bodyJson = value.body;
    if (parent.key === NodeType.bitsValue) {
      this.bitJson.body = this.bodyJson;
      const bodyIsBitmarkText = isBitmarkText && this.isBitmarkText(this.bodyJson);
      if (this.options.textAsPlainText && bodyIsBitmarkText) {
        const textBody = this.textGenerator.generateSync(
          this.bodyJson,
          textFormat,
          TextLocation2.body,
          {
            noBreakscaping: true
          }
        );
        this.bitJson.body = (textBody ?? "").trim();
      } else if (bodyIsBitmarkText) {
        this.bitJson.body = this.moveBodyBitPropertiesToAttrs(this.bodyJson);
      }
    } else if (parent.key === NodeType.cardBitsValue) {
      if (this.listItem) this.listItem.body = this.bodyJson;
    }
    return false;
  }
  bodyBitCallback(bodyBit, _index, _route) {
    const placeholder = `{${this.placeholderIndex}}`;
    this.placeholderIndex++;
    this.bitJson.placeholders = this.bitJson.placeholders ?? {};
    this.bitJson.placeholders[placeholder] = bodyBit;
    return placeholder;
  }
  /**
   * Move all properties of body bits to the attrs property.
   * This function is called recursively to process all body bits in the body tree.
   *
   * NOTE: Internally, the body bit is stored as it appears in bitmark v2, but in v3 all the properties
   * should be in the `attrs` property. The properies are only moved to 'attrs' for the JSON output
   *
   * The original body bit is not modified.
   * The function returns a new body bit with the properties moved.
   *
   * @param nodes the body to process, or the subtree to process
   * @param recursion leave as default, used for recursion only
   * @returns
   */
  moveBodyBitPropertiesToAttrs(nodes, recursion = 0) {
    if (recursion === 0) {
      nodes = structuredClone(nodes);
    } else if (recursion > MOVE_BODY_RECURSION_LIMIT) {
      throw new Error("Recursion limit exceeded");
    }
    for (const node of nodes) {
      if (node.type !== BodyBitType.text && Enum9(BodyBitType).values().includes(node.type)) {
        const bodyBit = node;
        bodyBit.attrs = {};
        for (const [key, value] of Object.entries(bodyBit)) {
          if (key === "type" || key === "attrs") continue;
          bodyBit.attrs[key] = value;
          delete bodyBit[key];
        }
      } else if (Array.isArray(node.content)) {
        recursion++;
        this.moveBodyBitPropertiesToAttrs(node.content, recursion);
      }
    }
    return nodes;
  }
  // bitmarkAst -> bits -> bitsValue -> footer
  enter_footer(node, route) {
    return this.standardHandler(node, route, void 0, {
      array: true,
      valueOverride: node.value.footer
    });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> elements
  enter_elements(node, route) {
    return this.standardHandler(node, route, void 0, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> flashcards
  enter_flashcards(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, {
      array: true,
      keyOverride: "cards"
    });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> definitions
  enter_definitions(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> statement
  enter_statement(node, route) {
    const statement = node.value;
    const parent = this.getParentNode(route);
    if (parent?.key !== NodeType.cardNode) return;
    if (statement) {
      this.bitJson.statement = statement.statement ?? [];
      this.bitJson.isCorrect = statement.isCorrect ?? false;
      this.bitJson.example = statement.example;
      this.bitJson.isExample = statement.isExample;
    }
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> statements -> statementsValue
  enter_statements(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> choices
  // X bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes -> quizzesValue -> choices
  // X bitmarkAst -> bits -> bitsValue -> cardNode -> feedbacks -> feedbacksValue -> choices
  enter_choices(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> responses
  // X bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes -> quizzesValue -> responses
  enter_responses(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> feedbacks
  enter_feedbacks(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> quizzes
  enter_quizzes(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> heading
  enter_heading(node, route) {
    return this.standardHandler(node, route, void 0, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pairs
  enter_pairs(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> matrix
  enter_matrix(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> pronunciationTable
  enter_pronunciationTable(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> table
  enter_table(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> questions
  enter_questions(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> botResponses
  enter_botResponses(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, {
      array: true,
      keyOverride: "responses"
    });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> ingredients -> ingredientsValue
  enter_ingredients(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> cardNode -> captionDefinitionList
  enter_captionDefinitionList(node, route) {
    return this.standardHandler(node, route, NodeType.cardNode, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> backgroundWallpaper
  enter_backgroundWallpaper(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> imagePlaceholder
  enter_imagePlaceholder(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> previewImage
  enter_previewImage(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> previewVideo
  enter_previewVideo(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> platformIcon
  enter_platformIcon(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> platformLogo
  enter_platformLogo(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> platformBackgroundImage
  enter_platformBackgroundImage(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> coverImage
  enter_coverImage(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, {
      array: Array.isArray(node.value)
    });
  }
  // bitmarkAst -> bits -> bitsValue -> backgroundImage
  enter_backgroundImage(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, {
      array: Array.isArray(node.value)
    });
  }
  // bitmarkAst -> bits -> bitsValue -> resources
  enter_resources(node, route) {
    const resources = node.value;
    const bitType = this.getBitType(route);
    const resourceType = this.getResourceType(route);
    if (!resources || !bitType) return true;
    let resourceJson;
    const bitConfig = instance2.getBitConfig(bitType);
    const bitResourcesConfig = instance2.getBitResourcesConfig(bitType, resourceType);
    const comboMap = bitResourcesConfig.comboResourceConfigKeysMap;
    if (comboMap.size > 0) {
      for (const [comboConfigKey, resourceConfigKeys] of comboMap.entries()) {
        const comboTagType = configKeyToResourceType(comboConfigKey);
        const wrapper = {
          type: comboTagType,
          __typeAlias: comboTagType,
          __configKey: comboConfigKey
        };
        for (const ck of resourceConfigKeys) {
          const r = resources.find((r2) => r2.__configKey === ck);
          if (r) {
            const tagConfig = instance2.getTagConfigForTag(bitConfig.tags, r.__configKey);
            if (tagConfig) {
              const key = tagConfig.jsonKey ?? tagConfig.tag;
              const configKey = tagConfig.configKey ?? r.__configKey;
              const json = r.__configKey === configKey ? r : void 0;
              if (json) {
                for (const [k, v] of Object.entries(json)) {
                  if (k !== "type") {
                    wrapper[key] = v;
                  }
                }
              }
            }
          }
        }
        resourceJson = wrapper;
      }
    } else if (instance2.isOfBitType(bitType, [
      BitType2.imagesLogoGrave,
      BitType2.prototypeImages,
      BitType2.extractorImage
    ])) {
      const images = [];
      for (const r of resources) {
        if (r.type === ResourceType.image) {
          images.push(r);
        }
      }
      if (bitType === BitType2.imagesLogoGrave) {
        this.bitJson.logos = images;
      } else {
        this.bitJson.images = images;
      }
    } else {
      if (resources.length >= 1) {
        resourceJson = resources[0];
      }
    }
    this.bitJson.resource = resourceJson;
    return false;
  }
  //  bitmarkAst -> bits -> bitsValue -> level
  leaf_level(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> book (array)
  enter_book(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: true });
  }
  // bitmarkAst -> bits -> bitsValue -> book (single)
  leaf_book(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  //  bitmarkAst -> bits -> bitsValue -> anchor
  leaf_anchor(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  //  bitmarkAst -> bits -> bitsValue -> reference
  leaf_reference(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  //  bitmarkAst -> bits -> bitsValue -> referenceEnd
  leaf_referenceEnd(node, route) {
    return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
  }
  // bitmarkAst -> bits -> bitsValue -> markup
  leaf_markup(node, _route) {
    const bitmark = node.value;
    if (bitmark) this.bitWrapperJson.bitmark = bitmark;
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> * -> internalComment
  enter_internalComment(_node, _route) {
    return false;
  }
  // bitmarkAst -> bits -> bitsValue -> parser
  enter_parser(node, route) {
    const parser2 = node.value;
    const bitType = this.getBitType(route);
    const parent = this.getParentNode(route);
    if (parser2 && bitType) {
      const { version, warnings, errors, ...parserRest } = parser2;
      const bitmarkVersion = `${this.bitmarkVersion}`;
      const textParserVersion = this.textParserVersion;
      const internalComments = this.getInternalComments(route);
      if (parent?.key === NodeType.bitsValue) {
        this.bitWrapperJson.parser = {
          version,
          bitmarkVersion,
          textParserVersion,
          internalComments,
          ...parserRest,
          warnings,
          errors
        };
        if (!this.options.enableWarnings) {
          delete this.bitWrapperJson.parser.warnings;
        }
      } else {
      }
    }
  }
  // bitmarkAst -> errors
  // protected enter_errors(node: NodeInfo, _route: NodeInfo[],
  // context: Context): void {
  //   const errors = node.value as ParserError[] | undefined;
  //   if (errors && errors.length > 0) {
  //     // Complete bit is invalid
  //     // TODO - not sure where this error can be written
  //     // this.bitWrapperJson.parser = {
  //     //   errors,
  //     // };
  //   }
  // }
  //
  // Generated Node Handlers
  //
  /**
   * Generate the handlers for properties, as they are mostly the same, but not quite
   */
  generatePropertyHandlers() {
    for (const propertyConfigKey of Enum9(PropertyKey).values()) {
      const propertyTag = configKeyToPropertyType(propertyConfigKey);
      const funcNames = [`enter_${propertyTag}`, `leaf_${propertyTag}`];
      for (const funcName of funcNames) {
        if (typeof this[funcName] === "function") {
          continue;
        }
        if (propertyTag === "example") continue;
        this[funcName] = (node, route) => {
          const value = node.value;
          if (value == null) return;
          const parent = this.getParentNode(route);
          if (parent?.key !== NodeType.bitsValue) return;
          this.bitJson[propertyTag] = value;
        };
        this[funcName] = this[funcName].bind(this);
      }
    }
  }
  // // END NODE HANDLERS
  //
  // HELPER FUNCTIONS
  //
  /**
   * Default handler for properties.
   *
   * @param node the node
   * @param route  the route to the node
   * @param parentNodeTypes the parent node types for which to handle the node
   * @returns
   */
  standardHandler(node, route, parentNodeTypes, options) {
    if (parentNodeTypes) {
      const nodeTypeArray = Array.isArray(parentNodeTypes) ? parentNodeTypes : [parentNodeTypes];
      const parent = this.getParentNode(route);
      if (!parent?.key || !nodeTypeArray.includes(parent?.key)) return true;
    }
    this.addProperty(
      this.bitJson,
      options.keyOverride ?? node.key,
      options.valueOverride ?? node.value,
      options
    );
    return false;
  }
  addProperty(target, name, values, options) {
    if (values !== void 0) {
      let finalValue;
      if (!Array.isArray(values)) values = [values];
      const valuesArr = values;
      if (!options.array && valuesArr.length >= 1) {
        finalValue = valuesArr[valuesArr.length - 1];
      } else {
        finalValue = valuesArr;
      }
      if (options.allowNull || finalValue != null) {
        target[name] = finalValue;
      }
    }
  }
  toExample(node, options) {
    const { isExample, example, __isDefaultExample } = node;
    const { defaultExample, isBoolean } = options;
    if (!isExample) {
      return {
        isExample: false,
        example: null
      };
    }
    let exampleValue;
    if (__isDefaultExample) {
      exampleValue = isBoolean ? instance5.toBoolean(defaultExample) : this.convertBreakscapedStringToJsonText(
        defaultExample,
        options.textFormat,
        TextLocation2.tag
      );
    } else {
      exampleValue = isBoolean ? instance5.toBoolean(example) : this.convertBreakscapedStringToJsonText(
        example,
        options.textFormat,
        TextLocation2.tag
      );
    }
    return {
      isExample: true,
      example: exampleValue
    };
  }
  /**
   * Get the bit type from any node
   *
   * @param route the route to the current node
   * @returns the bit type
   */
  getBitType(route) {
    for (const node of route) {
      if (node.key === NodeType.bitsValue) {
        const n = node.value;
        return n?.bitType;
      }
    }
    return void 0;
  }
  /**
   * Get the text format from any node
   *
   * @param route the route to the current node
   * @returns the text format
   */
  getTextFormat(route) {
    const bitType = this.getBitType(route);
    if (bitType) {
      const bitConfig = instance2.getBitConfig(bitType);
      for (const node of route) {
        if (node.key === NodeType.bitsValue) {
          const n = node.value;
          return Enum9(TextFormat2).fromValue(n?.textFormat) ?? bitConfig.textFormatDefault;
        }
      }
    }
    return TextFormat2.bitmarkText;
  }
  /**
   * Get the bit resourceType atttachment from any node
   *
   * @param route the route to the current node
   * @returns the bit type
   */
  getResourceType(route) {
    for (const node of route) {
      if (node.key === NodeType.bitsValue) {
        const n = node.value;
        return n?.resourceType;
      }
    }
    return void 0;
  }
  /**
   * Get the internal comments from any node
   *
   * @param route the route to the current node
   * @returns the text format
   */
  getInternalComments(route) {
    const bitType = this.getBitType(route);
    if (bitType) {
      for (const node of route) {
        if (node.key === NodeType.bitsValue) {
          const n = node.value;
          return n.internalComment;
        }
      }
    }
    return void 0;
  }
  /**
   * Convert the text from the AST to the JSON format:
   * Input:
   *  - breakscaped string
   * Output:
   *  - text: plain text
   *  - json: bitmark text JSON
   *  - Bitmark v2: breakscaped string
   *  - Bitmark v3: bitmark text JSON (TextAst)
   *
   * In the case of Bitmark v2 type texts, there is nothing to do but cast the type.
   *
   * @param text
   * @returns
   */
  convertBreakscapedStringToJsonText(text, format, textLocation) {
    if (!text) return "";
    const isBitmarkText = format === TextFormat2.bitmarkText;
    if (!isBitmarkText) {
      return text || "";
    }
    const asPlainText = this.options.textAsPlainText;
    if (asPlainText) {
      return text || "";
    }
    const textAst = this.textParser.toAst(text, {
      format,
      location: textLocation
    });
    return textAst;
  }
  /**
   * Concatenates a plain JSON text with a JsonText that may be plain (v2) or BitmarkText (v3)
   * Returns the combined text.
   *
   * @param text the text to concatenate
   * @param textPlain the plain text to concatenate
   */
  concatenatePlainTextWithJsonTexts(text, extraBreaks, textPlain) {
    if (Array.isArray(text)) {
      textPlain = textPlain.trim();
      if (textPlain) {
        const splitText = textPlain.split("\n");
        const content = [];
        for (let i = 0; i < extraBreaks; i++) {
          content.push({
            type: TextNodeType.hardBreak
          });
        }
        for (let i = 0; i < splitText.length; i++) {
          const t = splitText[i];
          if (t) {
            content.push({
              text: t,
              type: TextNodeType.text
            });
          }
          if (i < splitText.length - 1) {
            content.push({
              type: TextNodeType.hardBreak
            });
          }
        }
        const lastNode = text[text.length - 1];
        if (lastNode && lastNode.type === TextNodeType.paragraph) {
          lastNode.content = [...lastNode.content ?? [], ...content];
        } else {
          text.push({
            type: TextNodeType.paragraph,
            content,
            attrs: {}
          });
        }
      }
      return text;
    }
    return `${text ?? ""}${"\n".repeat(extraBreaks)}${textPlain ?? ""}`;
  }
  /**
   * Check if an object is bitmark text
   * The check looks for a special tag on the array (__tag: 'text')
   *
   * @param obj object that might be bitmark text
   * @returns
   */
  isBitmarkText(obj) {
    if (obj == null) return false;
    if (!Array.isArray(obj)) return false;
    if (obj.__tag === "text") return true;
    return false;
  }
  //
  // WRITE FUNCTIONS
  //
  writeInlineDebug(key, state) {
    let tag = key;
    if (state.open) {
      tag = `<${key}>`;
    } else if (state.close) {
      tag = `</${key}>`;
    } else if (state.single) {
      tag = `<${key} />`;
    }
    this.writeString(tag);
  }
  writeString(s) {
    if (s != null) this.write(`${s}`);
  }
  /**
   * Create a new bit json object.
   * - This function defines the order of the properties in the json.
   *
   * @param bit
   * @returns
   */
  createBitJson(bit) {
    const format = bit.textFormat;
    const bitJson = {
      type: bit.isCommented ? BitType2._comment : bit.bitType,
      originalType: bit.isCommented ? bit.bitType : void 0,
      format,
      bitLevel: bit.bitLevel
    };
    return bitJson;
  }
  /**
   * Remove wanted properties from bit json object.
   * - This function defines the defaults for properties in the json.
   *
   * TODO: All these defaults should come from the config and be set in the Builder.
   *
   * @param bit
   * @returns
   */
  cleanBitJson(bitJson) {
    const bitType = instance2.getBitType(bitJson.type);
    if (bitJson.originalType == null) bitJson.originalType = void 0;
    if (!bitJson.item) bitJson.item = void 0;
    if (instance2.isOfBitType(bitType, [BitType2._error, BitType2._comment])) {
      delete bitJson.format;
      delete bitJson.item;
      delete bitJson.lead;
      delete bitJson.pageNumber;
      delete bitJson.marginNumber;
      delete bitJson.hint;
      delete bitJson.instruction;
    } else {
      let isTopLevelExample = false;
      let isTopLevelExampleValue = false;
      if (instance2.isOfBitType(bitType, [BitType2.page])) {
        if (bitJson.item == null || bitJson.item?.length === 0) delete bitJson.item;
        if (bitJson.lead == null || bitJson.lead?.length === 0) delete bitJson.lead;
        if (bitJson.pageNumber == null || bitJson.pageNumber?.length === 0)
          delete bitJson.pageNumber;
        if (bitJson.marginNumber == null || bitJson.marginNumber?.length === 0)
          delete bitJson.marginNumber;
        if (bitJson.hint == null || bitJson.hint?.length === 0) delete bitJson.hint;
        if (bitJson.instruction == null || bitJson.instruction?.length === 0)
          delete bitJson.instruction;
      } else {
        if (bitJson.item == null) bitJson.item = this.textDefault;
        if (bitJson.lead == null) bitJson.lead = this.textDefault;
        if (bitJson.hint == null) bitJson.hint = this.textDefault;
        if (bitJson.instruction == null) bitJson.instruction = this.textDefault;
        if (bitJson.pageNumber == null) bitJson.pageNumber = this.textDefault;
        if (bitJson.marginNumber == null) bitJson.marginNumber = this.textDefault;
      }
      if (bitJson.body == null) bitJson.body = this.bodyDefault;
      if (instance2.isOfBitType(bitType, [BitType2.article])) {
      }
      if (instance2.isOfBitType(bitType, [BitType2.example])) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
      }
      if (instance2.isOfBitType(bitType, [
        BitType2.multipleChoice1,
        BitType2.multipleResponse1,
        BitType2.multipleChoiceText,
        BitType2.highlightText,
        BitType2.gapText,
        BitType2.gapTextInstructionGrouped,
        BitType2.clozeAndMultipleChoiceText,
        BitType2.sequence,
        BitType2.mark,
        BitType2.flashcard,
        BitType2.definitionList
      ])) {
        isTopLevelExample = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, [BitType2.sequence])) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType2.cloze)) {
        isTopLevelExample = true;
        if (instance2.isOfBitType(bitType, BitType2.clozeSolutionGrouped)) {
          if (bitJson.quizCountItems == null) bitJson.quizCountItems = true;
          if (bitJson.quizStrikethroughSolutions == null) bitJson.quizStrikethroughSolutions = true;
        } else if (instance2.isOfBitType(bitType, BitType2.clozeInstructionGrouped)) {
          if (bitJson.quizCountItems == null) bitJson.quizCountItems = true;
          if (bitJson.quizStrikethroughSolutions == null)
            bitJson.quizStrikethroughSolutions = false;
        }
      }
      if (instance2.isOfBitType(bitType, [BitType2.multipleChoice, BitType2.multipleResponse])) {
        isTopLevelExample = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (bitJson.footer == null) bitJson.footer = this.textDefault;
      }
      if (instance2.isOfBitType(bitType, BitType2.essay)) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (bitJson.partialAnswer == null) bitJson.partialAnswer = "";
      }
      if (instance2.isOfBitType(bitType, BitType2.trueFalse1)) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.isCorrect == null) bitJson.isCorrect = false;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType2.trueFalse)) {
        isTopLevelExample = true;
        if (bitJson.labelFalse == null) bitJson.labelFalse = "";
        if (bitJson.labelTrue == null) bitJson.labelTrue = "";
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType2.chapter)) {
        if (bitJson.toc == null) bitJson.toc = true;
        if (bitJson.progress == null) bitJson.progress = true;
        if (bitJson.level == null) bitJson.level = 1;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType2.interview)) {
        isTopLevelExample = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (bitJson.footer == null) bitJson.footer = this.textDefault;
        if (bitJson.questions == null) bitJson.questions = [];
      }
      if (instance2.isOfBitType(bitType, BitType2.sequence)) {
        if (bitJson.elements == null) bitJson.elements = [];
      }
      if (bitType === BitType2.matchMatrix) {
        isTopLevelExample = true;
      }
      if (instance2.isOfBitType(bitType, BitType2.match)) {
        isTopLevelExample = true;
        if (bitJson.heading == null) bitJson.heading = {};
      }
      if (instance2.isOfBitType(bitType, BitType2.learningPathBook)) {
        if (bitJson.isTracked == null) bitJson.isTracked = true;
        if (bitJson.isInfoOnly == null) bitJson.isInfoOnly = false;
      }
      const tableImageHasTable = instance2.isOfBitType(bitType, BitType2.tableImage) && !this.tableIsEmpty(bitJson.table);
      if (instance2.isOfBitType(bitType, BitType2.table) && !instance2.isOfBitType(bitType, BitType2.tableImage) || tableImageHasTable) {
        if (bitJson.tableFixedHeader == null) bitJson.tableFixedHeader = false;
        if (bitJson.tableHeaderWhitespaceNoWrap == null) {
          bitJson.tableHeaderWhitespaceNoWrap = false;
        }
        if (bitJson.tableSearch == null) bitJson.tableSearch = false;
        if (bitJson.tableSort == null) bitJson.tableSort = false;
        if (bitJson.tablePagination == null) bitJson.tablePagination = false;
        if (bitJson.tablePaginationLimit == null) bitJson.tablePaginationLimit = 0;
        if (bitJson.tableHeight == null) bitJson.tableHeight = 0;
        if (bitJson.tableWhitespaceNoWrap == null) bitJson.tableWhitespaceNoWrap = false;
        if (bitJson.tableAutoWidth == null) bitJson.tableAutoWidth = true;
        if (bitJson.tableResizableColumns == null) bitJson.tableResizableColumns = false;
        if (bitJson.tableColumnMinWidth == null) bitJson.tableColumnMinWidth = 0;
      }
      if (instance2.isOfBitType(bitType, BitType2.tableImage) && !tableImageHasTable) {
        delete bitJson.table;
      }
      if (instance2.isOfBitType(bitType, BitType2.bookReference)) {
        if (bitJson.refAuthor == null) bitJson.refAuthor = [];
        if (bitJson.refBookTitle == null) bitJson.refBookTitle = "";
        if (bitJson.refPublisher == null) bitJson.refPublisher = [];
        if (bitJson.refPublicationYear == null) bitJson.refPublicationYear = "";
      }
      if (instance2.isOfBitType(bitType, [
        BitType2.pageBanner,
        BitType2.pageBuyButton,
        BitType2.pageBuyButtonAlt,
        BitType2.pageBuyButtonPromotion,
        BitType2.pageFooter,
        BitType2.pageOpenBook,
        BitType2.pagePerson,
        BitType2.printBook,
        BitType2.pageProduct,
        BitType2.pageProductList,
        BitType2.pageProductVideo,
        BitType2.pageProductVideoList,
        BitType2.pageSectionFolder,
        BitType2.pageSubscribe,
        BitType2.pageSubpage
      ])) {
        if (bitJson.slug == null) bitJson.slug = "";
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (instance2.isOfBitType(bitType, BitType2.pageBuyButton)) {
          if (bitJson.content2Buy == null) bitJson.content2Buy = "";
        }
      }
      if (instance2.isOfBitType(bitType, BitType2.tocResource)) {
        if (bitJson.tocResource == null) bitJson.tocResource = [];
      }
      if (instance2.isOfBitType(bitType, BitType2.tocContent)) {
        if (bitJson.tocContent == null) bitJson.tocContent = [];
      }
      if (instance2.isOfBitType(bitType, BitType2.printThisChapter)) {
        if (bitJson.printParentChapterLevel == null) bitJson.printParentChapterLevel = -1;
      }
      if (instance2.isOfBitType(bitType, BitType2.book)) {
        if (bitJson.maxTocChapterLevel == null) bitJson.maxTocChapterLevel = -1;
        if (bitJson.allowPrint == null) bitJson.allowPrint = "useSpaceConfiguration";
        if (bitJson.hasPrintRestriction == null) bitJson.hasPrintRestriction = true;
        if (bitJson.enforceUpdateOverUserInput == null) bitJson.enforceUpdateOverUserInput = false;
        if (bitJson.hasMarkAsDone == null) bitJson.hasMarkAsDone = false;
        if (bitJson.processHandIn == null) bitJson.processHandIn = false;
        if (bitJson.isPublic == null) bitJson.isPublic = false;
        if (bitJson.chatWithBook == null) bitJson.chatWithBook = false;
        if (bitJson.chatWithBookBrainKey == null) bitJson.chatWithBookBrainKey = "";
      }
      if (bitType === BitType2.articleAi || bitType === BitType2.noteAi || bitType === BitType2.summaryAi) {
        if (bitJson.aiGenerated == null) bitJson.aiGenerated = true;
      }
      if (instance2.isOfBitType(bitType, [BitType2.articleResponsive, BitType2.pageArticleResponsive])) {
        if (bitJson.imageFirst == null) bitJson.imageFirst = true;
      }
      if (instance2.isOfBitType(bitType, BitType2.reviewNote)) {
        if (bitJson.resolved == null) bitJson.resolved = false;
        if (bitJson.resolvedDate == null) bitJson.resolvedDate = "";
        if (bitJson.resolvedBy == null) bitJson.resolvedBy = "";
      }
      if (instance2.isOfBitType(bitType, [
        BitType2.imagesLogoGrave,
        BitType2.prototypeImages,
        BitType2.extractorImage
      ])) {
        if (bitType === BitType2.imagesLogoGrave) {
          if (bitJson.logos == null) {
            bitJson.logos = [];
          }
        } else {
          if (bitJson.images == null) bitJson.images = [];
        }
      }
      if (instance2.isOfBitType(bitType, [BitType2.stepImageScreenshotWithPointer, BitType2.surveyMatrix])) {
        if (bitJson.pointerTop == null) bitJson.pointerTop = "";
        if (bitJson.pointerLeft == null) bitJson.pointerLeft = "";
        if (instance2.isOfBitType(bitType, [BitType2.surveyMatrix])) {
          if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
        }
      }
      if (instance2.isOfBitType(bitType, [BitType2.listItem])) {
        if (bitJson.listItemIndent == null) bitJson.listItemIndent = 0;
      }
      if (instance2.isOfBitType(bitType, BitType2.surveyRating)) {
        if (bitJson.ratingLevelStart == null) {
          bitJson.ratingLevelStart = {
            level: 0
          };
        }
        if (bitJson.ratingLevelEnd == null) {
          bitJson.ratingLevelEnd = {
            level: 0
          };
        }
      }
      if (instance2.isOfBitType(bitType, BitType2.vendorStripePricingTable)) {
        if (bitJson.stripePricingTableId == null) bitJson.stripePricingTableId = "";
        if (bitJson.stripePublishableKey == null) bitJson.stripePublishableKey = "";
        if (instance2.isOfBitType(bitType, BitType2.vendorStripePricingTablePrintOnRequest)) {
          if (bitJson.printOnRequestCustomerId == null) bitJson.printOnRequestCustomerId = "";
          if (bitJson.printOnRequestProductId == null) bitJson.printOnRequestProductId = "";
        }
      }
      if (instance2.isOfBitType(bitType, BitType2.callToAction)) {
        if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
        if (bitJson.callToActionUrl == null) bitJson.callToActionUrl = "";
      }
      if (instance2.isOfBitType(bitType, [BitType2.handInFile, BitType2.handInSystemMedia])) {
        if (bitJson.handInAcceptFileType == null) bitJson.handInAcceptFileType = [];
      }
      if (instance2.isOfBitType(bitType, BitType2.handInSubmit)) {
        if (bitJson.handInRequirement == null) bitJson.handInRequirement = [];
        if (bitJson.handInInstruction == null) bitJson.handInInstruction = "";
        if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
      }
      if (instance2.isOfBitType(bitType, BitType2.module)) {
        if (bitJson.hasBookNavigation == null) bitJson.hasBookNavigation = true;
      }
      if (instance2.isOfBitType(bitType, BitType2.container)) {
        if (bitJson.allowedBit == null) bitJson.allowedBit = [];
      }
      if (instance2.isOfBitType(bitType, BitType2.platformPath)) {
        if (bitJson.path == null) bitJson.path = "";
      }
      if (instance2.isOfBitType(bitType, BitType2.platformBrandTarget)) {
        if (bitJson.platformBrandTarget == null) bitJson.platformBrandTarget = "none";
      }
      if (instance2.isOfBitType(bitType, BitType2.platformHeader)) {
        if (bitJson.platformLogoMaxHeight == null) bitJson.platformLogoMaxHeight = 40;
      }
      if (isTopLevelExample) {
        if (bitJson.isExample == null) bitJson.isExample = false;
      } else {
        delete bitJson.isExample;
        delete bitJson.example;
      }
      if (isTopLevelExampleValue) {
        if (bitJson.example == null) bitJson.example = null;
      } else {
        delete bitJson.example;
      }
    }
    return bitJson;
  }
  tableIsEmpty(table) {
    if (!table) return true;
    if (Object.keys(table).length === 0) return true;
    const tableStandard = table;
    if (tableStandard.columns?.length === 0 && tableStandard.data?.length) {
      return true;
    }
    const tableExtended = table;
    if (tableExtended.header?.rows.length === 0 && tableExtended.body?.rows.length === 0 && tableExtended.footer?.rows.length === 0) {
      return true;
    }
    return false;
  }
  /**
   * Convert any bitmark texts to strings.
   */
  convertAllBitmarkTextsToStringsForPlainText(json) {
    if (!this.options.textAsPlainText) return;
    const obj = json;
    for (const key in obj) {
      const val = obj[key];
      if (this.isBitmarkText(val)) {
        const s = this.textGenerator.generateSync(
          val,
          TextFormat2.bitmarkText,
          TextLocation2.tag,
          {
            noBreakscaping: true
          }
        );
        obj[key] = (s ?? "").trim();
      } else if (typeof obj[key] === "object") {
        this.convertAllBitmarkTextsToStringsForPlainText(obj[key]);
      }
    }
  }
  /**
   * Remove any property with a key starting with an double underscore.
   *
   * @param json
   */
  removeTemporaryProperties(json) {
    const obj = json;
    for (const key in obj) {
      if (key.startsWith("__")) {
        delete obj[key];
      } else if (typeof obj[key] === "object") {
        this.removeTemporaryProperties(obj[key]);
      }
    }
  }
  //
  // Writer interface
  //
  /**
   * Writes a string value to the output.
   * @param value - The string value to be written.
   */
  write(value) {
    this.writer.write(value);
    return this;
  }
  /**
   * Writes a new line to the output. The line is indented automatically. The line is ended with the endOfLineString.
   * @param value - The line to write. When omitted, only the endOfLineString is written.
   */
  writeLine(value) {
    this.writer.writeLine(value);
    return this;
  }
  /**
   * Writes a collection of lines to the output. Each line is indented automatically and ended with the endOfLineString.
   * @param values - The lines to write.
   * @param delimiter - An optional delimiter to be written at the end of each line, except for the last one.
   */
  writeLines(values, delimiter) {
    this.writer.writeLines(values, delimiter);
    return this;
  }
  /**
   * Writes a single whitespace character to the output.
   */
  writeWhiteSpace() {
    this.writer.writeWhiteSpace();
    return this;
  }
};

// src/generator/json/JsonObjectGenerator.ts
var JsonObjectGenerator = class {
  /**
   * Generate bitmark JSON from a bitmark AST as a string
   *
   * @param options - JSON generation options
   */
  constructor(options) {
    __publicField(this, "generator");
    __publicField(this, "writer");
    this.writer = new StringWriter();
    this.generator = new JsonGenerator(this.writer, options);
  }
  /**
   * Generate bitmark JSON from bitmark AST as a plain JS object
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    await this.generator.generate(ast);
    return JSON.parse(this.writer.getString());
  }
  /**
   * Generate bitmark JSON from bitmark AST as a plain JS object synchronously
   *
   * @param ast bitmark AST
   */
  generateSync(ast) {
    this.generator.generateSync(ast);
    return JSON.parse(this.writer.getString());
  }
};

// src/info/InfoBuilder.ts
import { Enum as Enum10 } from "@ncoderz/superenum";
var InfoBuilder = class {
  getSupportedBits(options) {
    const includeNonDeprecated = options?.includeNonDeprecated ?? true;
    const includeDeprecated = options?.includeDeprecated ?? false;
    const supportedBits = [];
    for (const bt of Enum10(BitType2).values()) {
      if (bt === BitType2._error || bt === BitType2._comment) continue;
      const bitType = instance2.getBitType(bt);
      const bitConfig = instance2.getBitConfig(bitType);
      const inheritedBitTypes = bitConfig.inheritedBitTypes.length > 0 ? bitConfig.inheritedBitTypes : void 0;
      const include = includeNonDeprecated && !bitConfig.deprecated || includeDeprecated && bitConfig.deprecated;
      if (include) {
        supportedBits.push({
          name: bt,
          since: bitConfig.since,
          deprecated: bitConfig.deprecated,
          inheritedBitTypes
        });
      }
    }
    return supportedBits;
  }
  getSupportedBitConfigs() {
    const res = [];
    for (const bt of Enum10(BitType2).values()) {
      if (bt === BitType2._error || bt === BitType2._comment) continue;
      const bitType = instance2.getBitType(bt);
      const config = instance2.getBitConfig(bitType);
      res.push(config);
    }
    return res;
  }
};

// src/model/enum/BitmarkParserType.ts
import "@ncoderz/superenum";
var BitmarkParserType = {
  peggy: "peggy"
};

// src/model/info/enum/InfoFormat.ts
import "@ncoderz/superenum";
var InfoFormat = {
  text: "text",
  json: "json",
  pojo: "pojo"
};

// src/model/info/enum/InfoType.ts
import "@ncoderz/superenum";
var InfoType = {
  list: "list",
  // List non-deprecated bits
  deprecated: "deprecated",
  // List deprecated bits
  all: "all",
  // List all bits
  bit: "bit"
  // Get information for a bit
};

// src/parser/json/JsonParser.ts
import { Enum as Enum11 } from "@ncoderz/superenum";
var builder = new Builder();
var JsonParser = class {
  /**
   * Convert JSON to AST.
   *
   * The JSON can be a bit or a bitwrapper and can be a string or a plain JS object
   *
   * @param json - bitmark JSON as a string or a plain JS object
   * @returns bitmark AST
   */
  toAst(json) {
    const bitWrappers = this.preprocessJson(json);
    const bitsNodes = [];
    for (const bitWrapper of bitWrappers) {
      const { bit, parser: parser2 } = bitWrapper;
      const bitsNode = this.bitToAst(bit, parser2?.internalComments);
      if (bitsNode) {
        bitsNodes.push(bitsNode);
      }
    }
    const bits = bitsNodes.length > 0 ? { bits: bitsNodes } : {};
    const ast = builder.buildBitmark(bits);
    return ast;
  }
  /**
   * Preprocess bitmark JSON into a standard format (BitWrapperJson[] object) from either a bit or a bitwrapper
   * as a string or a plain JS object
   *
   * @param json - bitmark JSON as a string or a plain JS object
   * @returns bitmark JSON in a standard format (BitWrapperJson[] object)
   */
  preprocessJson(json) {
    const bitWrappers = [];
    if (stringUtils.isString(json)) {
      const str = json;
      try {
        json = JSON.parse(str);
      } catch (_e) {
        return [];
      }
    }
    const pushItemAsBitWrapper = (item) => {
      if (this.isBitWrapper(item)) {
        const w = item;
        bitWrappers.push(w);
      } else if (this.isBit(item)) {
        const b = item;
        bitWrappers.push(this.bitToBitWrapper(b));
      }
    };
    if (Array.isArray(json)) {
      for (const item of json) {
        pushItemAsBitWrapper(item);
      }
    } else {
      pushItemAsBitWrapper(json);
    }
    return bitWrappers;
  }
  /**
   * Check if a plain JS object is valid BitWrapper JSON
   *
   * @param bitWrapper - a plain JS object that might be BitWrapper JSON
   * @returns true if BitWrapper JSON, otherwise false
   */
  isBitWrapper(bitWrapper) {
    if (Object.prototype.hasOwnProperty.call(bitWrapper, "bit")) {
      const w = bitWrapper;
      return this.isBit(w.bit);
    }
    return false;
  }
  /**
   * Check if a plain JS object is valid Bit JSON
   *
   * @param bitWrapper - a plain JS object that might be Bit JSON
   * @returns true if Bit JSON, otherwise false
   */
  isBit(bit) {
    if (Object.prototype.hasOwnProperty.call(bit, "type")) {
      const b = bit;
      return instance2.getBitType(b.type) !== BitType2._error;
    }
    return false;
  }
  /**
   * Convert a Bit to BitWrapper
   *
   * @param bit a valid Bit
   * @returns the Bit wrapper in a BitWrapper
   */
  bitToBitWrapper(bit) {
    return {
      bit
    };
  }
  bitToAst(bit, internalComments) {
    const { statement, reference: referenceBit, ...bitRest } = bit;
    const isCommented = bit.type === BitType2._comment && bit.originalType !== void 0;
    const bitType = instance2.getBitType(isCommented ? bit.originalType : bit.type);
    const bitConfig = instance2.getBitConfig(bitType);
    const deprecatedTextFormat = Enum11(DeprecatedTextFormat).fromValue(bit.format);
    let _textFormat = Enum11(TextFormat2).fromValue(bit.format) ?? bitConfig.textFormatDefault;
    if (deprecatedTextFormat === DeprecatedTextFormat.bitmarkMinusMinus) {
      _textFormat = TextFormat2.bitmarkText;
    }
    const bitNode = builder.buildBit({
      ...bitRest,
      bitType,
      bitLevel: Math.max(Math.min(bit.bitLevel ?? 1, instance2.bitLevelMax), instance2.bitLevelMin),
      textFormat: bit.format,
      resourceType: this.getResourceType(bit.resource),
      isCommented,
      internalComment: internalComments,
      ...this.processTitle(bitType, bit.title),
      ...this.processReference(referenceBit),
      // reference and referenceProperty
      ...this.parseExample(bit.example),
      person: bit.partner ?? bit.person,
      markConfig: bit.marks,
      resources: this.processResources(bitType, bit.resource, bit.images, bit.logos),
      body: this.processBody(bit.body, bit.placeholders),
      flashcards: bit.cards,
      statements: this.processStatements(statement, bit.isCorrect, bit.statements, bit.example),
      responses: this.processResponses(bitType, bit.responses),
      botResponses: this.processBotResponse(bitType, bit.responses),
      cardBits: this.processListItems(
        bit.listItems ?? bit.sections ?? bit.bookReferences,
        bit.placeholders
      ),
      footer: this.processFooter(bit.footer)
    });
    return bitNode;
  }
  processStatements(statement, isCorrect, statements, example) {
    const nodes = [];
    if (statement) {
      const node = {
        statement: statement ?? "",
        isCorrect: isCorrect ?? false,
        ...this.parseExample(example)
      };
      if (node) nodes.push(node);
    }
    if (Array.isArray(statements)) for (const item of statements) nodes.push(item);
    if (nodes.length === 0) return void 0;
    return nodes;
  }
  processResponses(bitType, responses) {
    if (instance2.isOfBitType(bitType, BitType2.botActionResponse)) return void 0;
    if (!Array.isArray(responses)) return void 0;
    return responses;
  }
  processBotResponse(bitType, responses) {
    if (!instance2.isOfBitType(bitType, BitType2.botActionResponse)) return void 0;
    if (!Array.isArray(responses)) return void 0;
    return responses;
  }
  processListItems(listItems, placeholders) {
    const nodes = [];
    if (Array.isArray(listItems)) {
      for (const li of listItems) {
        const node = {
          ...li
        };
        if (node.body) {
          node.body = {
            body: node.body,
            placeholders
          };
        }
        if (node) nodes.push(node);
      }
    }
    if (nodes.length === 0) return void 0;
    return nodes;
  }
  getResourceType(resource) {
    if (resource) {
      const resourceKey = Enum11(ResourceType).fromValue(resource.type);
      return resourceKey;
    }
    return void 0;
  }
  processResources(bitType, resource, images, logos) {
    const nodes = [];
    if (resource) nodes.push(resource);
    if (instance2.isOfBitType(bitType, [BitType2.prototypeImages, BitType2.extractorImage])) {
      if (Array.isArray(images)) {
        for (const image of images) {
          if (image) nodes.push(image);
        }
      }
    }
    if (instance2.isOfBitType(bitType, [BitType2.imagesLogoGrave])) {
      if (Array.isArray(logos)) {
        for (const image of logos) {
          if (image) nodes.push(image);
        }
      }
    }
    return nodes;
  }
  processBody(body, placeholders) {
    return {
      body,
      placeholders
    };
  }
  processFooter(footerText) {
    if (!footerText) return void 0;
    return {
      footer: footerText
    };
  }
  processTitle(bitType, title) {
    if (instance2.isOfBitType(bitType, BitType2.author))
      return {
        titleString: title,
        title: void 0
      };
    return {
      titleString: void 0,
      title
    };
  }
  processReference(reference) {
    if (Array.isArray(reference) && reference.length > 0) {
      return {
        reference: void 0,
        referenceProperty: reference
      };
    }
    return {
      reference,
      referenceProperty: void 0
    };
  }
  parseExample(example) {
    if (example == null) return void 0;
    if (example === true) return { example: true };
    if (example === false) return { example: false };
    const exampleText = stringUtils.isString(example) || Array.isArray(example) ? example : void 0;
    if (exampleText) {
      return { example: exampleText };
    }
    return { example: !!example };
  }
};

// src/parser/bitmark/peg/BitmarkPegParserTypes.ts
import "@ncoderz/superenum";

// src/parser/bitmark/peg/PeggyGrammarLocation.ts
var PeggyGrammarLocation = class {
  /**
   * Create an instance.
   *
   * @param {any} source The original grammarSource.  Should be a string or
   *   have a toString() method.
   * @param {import("./peg").Location} start The starting offset for the
   *   grammar in the larger file.
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  constructor(source, start) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    __publicField(this, "source");
    __publicField(this, "start");
    this.source = source;
    this.start = start;
  }
  /**
   * Coerce to a string.
   *
   * @returns {string} The source, stringified.
   */
  toString() {
    return String(this.source);
  }
  /**
   * Return a new Location offset from the given location by the start of the
   * grammar.
   *
   * loc The location as if the start of the
   *   grammar was the start of the file.
   *  The offset location.
   */
  offset(loc) {
    return {
      line: loc.line + this.start.line - 1,
      column: loc.line === 1 ? loc.column + this.start.column - 1 : loc.column,
      offset: loc.offset + this.start.offset
    };
  }
  /**
   * If the range has a grammarSource that is a GrammarLocation, offset the
   * start of that range by the GrammarLocation.
   *
   * @param range The range to extract from.
   * @returns The offset start if possible, or the
   *   original start.
   */
  static offsetStart(range) {
    if (range.source && typeof range.source.offset === "function") {
      return range.source.offset(range.start);
    }
    return range.start;
  }
  /**
   * If the range has a grammarSource that is a GrammarLocation, offset the
   * end of that range by the GrammarLocation.
   *
   * @param range The range to extract from.
   * @returns The offset end if possible, or the
   *   original end.
   */
  static offsetEnd(range) {
    if (range.source && typeof range.source.offset === "function") {
      return range.source.offset(range.end);
    }
    return range.end;
  }
};

// src/parser/bitmark/peg/BitmarkPegParserTypes.ts
var CARD_DIVIDER_V2 = "====";
var CARD_SIDE_DIVIDER_V2 = "--";
var CARD_VARIANT_DIVIDER_V2 = "++";
var CARD_DIVIDER_V1 = "===";
var CARD_SIDE_DIVIDER_V1 = "==";
var CARD_VARIANT_DIVIDER_V1 = "--";
var TypeKey = {
  // Bit header
  TextFormat: "TextFormat",
  ResourceType: "ResourceType",
  // Tags (NOTE: ALL TAGS MUST USE THEIR CORRECT BITMARK REPRESENTATION HERE so they work with the rest of the code)
  Title: Tag.tag_title,
  Anchor: Tag.tag_anchor,
  Reference: Tag.tag_reference,
  ItemLead: Tag.tag_item,
  Instruction: Tag.tag_instruction,
  Hint: Tag.tag_hint,
  True: Tag.tag_true,
  False: Tag.tag_false,
  Gap: Tag.tag_gap,
  Mark: Tag.tag_mark,
  SampleSolution: Tag.tag_sampleSolution,
  // Generic Tags (converted to specific tags by the BitTagValidator)
  Property: "Property",
  Resource: "Resource",
  TagChain: "TagChain",
  // Text
  BodyChar: "BodyChar",
  BodyText: "BodyText",
  BodyTextPlain: "BodyTextPlain",
  // Card Set
  CardSet: "CardSet",
  Card: "Card",
  CardChar: "CardChar",
  CardText: "CardText",
  // Chains
  GapChain: "GapChain",
  TrueFalseChain: "TrueFalseChain",
  // Dividers
  PlainTextDivider: "PlainTextDivider",
  FooterDivider: "FooterDivider"
};
var ContentDepth = {
  Bit: "Bit",
  Card: "Card",
  Chain: "Chain"
};

// src/parser/bitmark/peg/BitmarkPegParserHelper.ts
import { Enum as Enum12 } from "@ncoderz/superenum";
var DEBUG_TRACE_RAW_BIT = true;
var DEBUG_TRACE_TEXT_FORMAT = false;
var DEBUG_TRACE_RESOURCE_TYPE = false;
var DEBUG_TRACE_BIT_CONTENT = false;
var DEBUG_TRACE_BIT_TAG = false;
var DEBUG_TRACE_TAGS = false;
var DEBUG_TRACE_PROPERTY_TAGS = false;
var DEBUG_TRACE_RESOURCE_TAGS = false;
var DEBUG_TRACE_TAGS_CHAIN = false;
var DEBUG_TRACE_CARD_SET = false;
var DEBUG_TRACE_CARD_SET_START = false;
var DEBUG_TRACE_CARD_SET_END = false;
var DEBUG_TRACE_CARD_LINE_OR_DIVIDER = false;
var DEBUG_TRACE_CARD_CONTENT = false;
var DEBUG_TRACE_CARD_TAGS = false;
var DEBUG_TRACE_CARD_PARSED = true;
var BitmarkPegParserHelper = class {
  constructor(options) {
    __publicField(this, "cardIndex", 0);
    __publicField(this, "cardSideIndex", 0);
    __publicField(this, "cardVariantIndex", 0);
    __publicField(this, "currentCardQualifier");
    __publicField(this, "currentSideQualifier");
    __publicField(this, "currentVariantQualifier");
    __publicField(this, "parse");
    __publicField(this, "parserText");
    __publicField(this, "parserLocation");
    this.parse = options.parse;
    this.parserText = options.parserText;
    this.parserLocation = options.parserLocation;
  }
  //
  // PARSING
  //
  handleRawBit(rawBit) {
    const rawBitTrimmed = rawBit.trim();
    if (DEBUG_TRACE_RAW_BIT) this.debugPrint("RAW BIT", rawBitTrimmed);
    if (!rawBitTrimmed) return void 0;
    const location = this.parserLocation()?.start ?? {
      line: 1,
      column: 1,
      offset: 0
    };
    const bitParserResult = this.parse(rawBit, {
      startRule: "bit",
      grammarSource: new PeggyGrammarLocation("bit", location)
    });
    if (bitParserResult.value) bitParserResult.value.markup = rawBitTrimmed;
    return bitParserResult;
  }
  handleTextFormat(value) {
    if (DEBUG_TRACE_TEXT_FORMAT) this.debugPrint(TypeKey.TextFormat, value);
    return {
      type: TypeKey.TextFormat,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleResourceType(value) {
    if (DEBUG_TRACE_RESOURCE_TYPE) this.debugPrint(TypeKey.ResourceType, value);
    return {
      type: TypeKey.ResourceType,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleBitContent(value) {
    if (DEBUG_TRACE_BIT_CONTENT) this.debugPrint("BitContent", value);
    let content = this.reduceToArrayOfTypes(value);
    content = this.mergeCharToText(content);
    return content;
  }
  //
  // Bit tags parsing
  //
  handleBitTag(value) {
    if (DEBUG_TRACE_BIT_TAG) this.debugPrint("BitTag", value);
    return value;
  }
  handleTag(type, value) {
    if (DEBUG_TRACE_TAGS) this.debugPrint(type, { value });
    return {
      type,
      key: Enum12(Tag).fromValue(type),
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handlePropertyTag(key, value) {
    if (DEBUG_TRACE_PROPERTY_TAGS) this.debugPrint(TypeKey.Property, { key, value });
    return {
      type: TypeKey.Property,
      key: `@${instance3.unbreakscape(key, {
        format: TextFormat2.plainText,
        location: TextLocation2.tag
      })}`,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleResourceTag(key, value) {
    if (DEBUG_TRACE_RESOURCE_TAGS) this.debugPrint(TypeKey.Resource, { key, value });
    const uKey = instance3.unbreakscape(key, {
      format: TextFormat2.plainText,
      location: TextLocation2.tag
    });
    const camelKey = stringUtils.kebabToCamel(uKey);
    return {
      type: TypeKey.Resource,
      key: `&${camelKey}`,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleTagChain(value) {
    if (DEBUG_TRACE_TAGS_CHAIN) this.debugPrint("TagsChain", value);
    const content = this.reduceToArrayOfTypes(value);
    let newContent = content;
    if (content.length > 1) {
      const head = content[0];
      head.chain = content.slice(1);
      newContent = [head];
    }
    return newContent;
  }
  //
  // Divider parsing
  //
  // Plain text divider
  handlePlainTextDivider(value) {
    value = this.reduceToString(value);
    return {
      type: TypeKey.PlainTextDivider,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  // Footer divider
  handleFooterDivider(value) {
    value = this.reduceToString(value);
    return {
      type: TypeKey.FooterDivider,
      value,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  //
  // Card parsing
  //
  handleCardSet(value) {
    if (DEBUG_TRACE_CARD_SET) this.debugPrint(TypeKey.CardSet, value);
    const cards = value;
    const unparsedCardSet = {
      cards: []
    };
    const cardSet = {
      cards: []
    };
    if (cards) {
      const parser2 = {
        text: this.parserText(),
        location: this.parserLocation()
      };
      for (const content of cards) {
        if (!content) continue;
        const { type, value: cardData, parser: parser3 } = content;
        if (!type || type !== TypeKey.Card) continue;
        const {
          cardIndex,
          cardSideIndex,
          cardVariantIndex: cardContentIndex,
          value: value2,
          cardQualifier,
          cardSideQualifier,
          cardVariantQualifier
        } = cardData;
        let card = unparsedCardSet.cards[cardIndex];
        if (!card) {
          card = {
            sides: [],
            qualifier: cardQualifier
          };
          unparsedCardSet.cards[cardIndex] = card;
        } else if (cardQualifier && !card.qualifier) {
          card.qualifier = cardQualifier;
        }
        let side = card.sides[cardSideIndex];
        if (!side) {
          side = {
            variants: [],
            qualifier: cardSideQualifier
          };
          card.sides[cardSideIndex] = side;
        } else if (cardSideQualifier && !side.qualifier) {
          side.qualifier = cardSideQualifier;
        }
        const variant = side.variants[cardContentIndex];
        if (!variant) {
          side.variants[cardContentIndex] = {
            value: value2,
            parser: parser3,
            qualifier: cardVariantQualifier
          };
        } else {
          side.variants[cardContentIndex].value += value2;
          if (cardVariantQualifier && !variant.qualifier) {
            variant.qualifier = cardVariantQualifier;
          }
        }
      }
      unparsedCardSet.cards = unparsedCardSet.cards.filter((card) => {
        return card.sides.some((side) => {
          return side.variants.some((variant) => {
            const trimmed = stringUtils.trimmedString(variant.value);
            return trimmed.length !== 0;
          });
        });
      });
      for (const unparsedCard of unparsedCardSet.cards) {
        const card = {
          sides: [],
          qualifier: unparsedCard.qualifier
        };
        cardSet.cards.push(card);
        for (const unparsedSide of unparsedCard.sides) {
          const side = {
            variants: [],
            qualifier: unparsedSide.qualifier
          };
          card.sides.push(side);
          for (const unparsedContent of unparsedSide.variants) {
            let location = {
              line: 1,
              column: 1,
              offset: 0
            };
            if (unparsedContent.parser.location) {
              location = unparsedContent.parser.location.start;
              const text = unparsedContent.parser.text;
              const offsetCorrection = text ? text.length : 0;
              const lineCorrection = 1;
              location.offset += offsetCorrection;
              location.line += lineCorrection;
            }
            let content = this.parse(unparsedContent.value, {
              startRule: "cardContent",
              grammarSource: new PeggyGrammarLocation("card-content", location)
            });
            content = this.reduceToArrayOfTypes(content);
            if (DEBUG_TRACE_CARD_PARSED) this.debugPrint("parsedCardContent", content);
            side.variants.push({
              parser: parser2,
              content,
              qualifier: unparsedContent.qualifier
            });
          }
        }
      }
    }
    return {
      type: TypeKey.CardSet,
      value: cardSet,
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleCardSetStart() {
    if (DEBUG_TRACE_CARD_SET_START) this.debugPrint("CardSetStart");
    this.cardIndex = -1;
    this.cardSideIndex = 0;
    this.cardVariantIndex = 0;
    this.currentCardQualifier = void 0;
    this.currentSideQualifier = void 0;
    this.currentVariantQualifier = void 0;
  }
  handleCardSetEnd() {
    if (DEBUG_TRACE_CARD_SET_END) this.debugPrint("CardSetEnd");
    this.cardIndex = 0;
    this.cardSideIndex = 0;
    this.cardVariantIndex = 0;
    this.currentCardQualifier = void 0;
    this.currentSideQualifier = void 0;
    this.currentVariantQualifier = void 0;
  }
  handleCards(value) {
    return value;
  }
  handleCardLineOrDivider(value, version) {
    if (DEBUG_TRACE_CARD_LINE_OR_DIVIDER) this.debugPrint("CardLineOrDivider", value);
    let isCardDivider = false;
    let isSideDivider = false;
    let isVariantDivider = false;
    let qualifier;
    if (Array.isArray(value) && value.length === 2) {
      const [divider, maybeQualifier] = value;
      if (typeof divider === "string") {
        value = divider;
        if (typeof maybeQualifier === "string" && maybeQualifier.trim() === maybeQualifier && maybeQualifier.length > 0) {
          qualifier = maybeQualifier;
        }
      } else {
        value = divider;
      }
    } else if (Array.isArray(value) && value.length === 1) {
      const [divider] = value;
      value = divider;
    }
    if (version === CardSetVersion.v1) {
      isCardDivider = value === CARD_DIVIDER_V1;
      isSideDivider = value === CARD_SIDE_DIVIDER_V1;
      isVariantDivider = value === CARD_VARIANT_DIVIDER_V1;
    } else {
      isCardDivider = value === CARD_DIVIDER_V2;
      isSideDivider = value === CARD_SIDE_DIVIDER_V2;
      isVariantDivider = value === CARD_VARIANT_DIVIDER_V2;
    }
    if (isCardDivider) {
      this.cardIndex++;
      this.cardSideIndex = 0;
      this.cardVariantIndex = 0;
      this.currentCardQualifier = qualifier;
      this.currentSideQualifier = void 0;
      this.currentVariantQualifier = void 0;
    } else if (isSideDivider) {
      this.cardSideIndex++;
      this.cardVariantIndex = 0;
      this.currentSideQualifier = qualifier;
      this.currentVariantQualifier = void 0;
    } else if (isVariantDivider) {
      this.cardVariantIndex++;
      this.currentVariantQualifier = qualifier;
    }
    if (this.isType(value, TypeKey.Card)) return value;
    return {
      type: TypeKey.Card,
      value: {
        cardIndex: this.cardIndex,
        cardSideIndex: this.cardSideIndex,
        cardVariantIndex: this.cardVariantIndex,
        value: "",
        cardQualifier: this.currentCardQualifier,
        cardSideQualifier: this.currentSideQualifier,
        cardVariantQualifier: this.currentVariantQualifier
      },
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleCardLine(value) {
    if (DEBUG_TRACE_CARD_LINE_OR_DIVIDER) {
      this.debugPrint(
        "CardLine",
        `(Card ${this.cardIndex}, Side ${this.cardSideIndex}, Variant: ${this.cardVariantIndex}): ${value}`
      );
    }
    return {
      type: TypeKey.Card,
      value: {
        cardIndex: this.cardIndex,
        cardSideIndex: this.cardSideIndex,
        cardVariantIndex: this.cardVariantIndex,
        value,
        cardQualifier: this.currentCardQualifier,
        cardSideQualifier: this.currentSideQualifier,
        cardVariantQualifier: this.currentVariantQualifier
      },
      parser: {
        text: this.parserText(),
        location: this.parserLocation()
      }
    };
  }
  handleCardContent(value) {
    if (DEBUG_TRACE_CARD_CONTENT) this.debugPrint("CardContent", value);
    const content = this.mergeCharToText(value);
    return content;
  }
  handleCardTags(value) {
    if (DEBUG_TRACE_CARD_TAGS) this.debugPrint("CardTags", value);
    return value;
  }
  //
  // Util functions
  //
  /**
   * Merge types:
   *  - BodyChar => BodyText
   *  - CardChar => CardText
   *
   * Any adjacent BodyChar or CardChar are merged into a single BodyText or CardText
   *
   * @param bitContent
   */
  mergeCharToText(bitContent) {
    const bc = [];
    let bodyText;
    let cardText;
    for (const c of bitContent) {
      switch (c.type) {
        case TypeKey.BodyChar: {
          if (bodyText) {
            const val = `${bodyText.value ?? ""}${c.value ?? ""}`;
            bodyText.value = val;
          } else {
            bodyText = {
              type: TypeKey.BodyText,
              value: c.value ?? "",
              parser: {
                text: this.parserText(),
                location: this.parserLocation()
              }
            };
          }
          break;
        }
        case TypeKey.CardChar: {
          if (cardText) {
            const val = `${cardText.value ?? ""}${c.value ?? ""}`;
            cardText.value = val;
          } else {
            cardText = {
              type: TypeKey.CardText,
              value: c.value ?? "",
              parser: {
                text: this.parserText(),
                location: this.parserLocation()
              }
            };
          }
          break;
        }
        default: {
          if (bodyText) {
            bc.push(bodyText);
            bodyText = void 0;
          }
          if (cardText) {
            bc.push(cardText);
            cardText = void 0;
          }
          bc.push(c);
        }
      }
    }
    if (bodyText) bc.push(bodyText);
    if (cardText) bc.push(cardText);
    return bc;
  }
  /**
   * Returns true if a value is a TypeKeyValue or TypeKey type with a type in the given types
   *
   * @param value The value to check
   * @param validType The type or types to check, or undefined to check for any type
   * @returns True if the value is a TypeKeyValue or TypeKey type with a type in the given types, otherwise False.
   */
  isType(value, validType) {
    if (!value) return false;
    const { type } = value;
    if (!validType) {
      return !!Enum12(TypeKey).fromValue(type);
    }
    if (Array.isArray(validType)) {
      return validType.indexOf(type) >= 0;
    }
    return validType === type;
  }
  /**
   * Reduce the data to type objects.
   *
   * The input data can have any structure. It will be reduced to an array of BitContent objects.
   *
   * @param data the data to reduce
   * @param validTypes types include in the reduced data
   * @param recurseIntoTypes set to true to reduce types which have array values
   * @returns an array of BitContent objects reduced from the input data
   */
  reduceToArrayOfTypes(data, validTypes, recurseIntoTypes) {
    if (!Array.isArray(data)) return [];
    const res = data.reduce((acc, content, _index) => {
      if (content == null) return acc;
      const { type, value } = content;
      if (Array.isArray(content)) {
        const subValues = this.reduceToArrayOfTypes(content, validTypes);
        acc.push(...subValues);
      } else {
        if (!this.isType(content, validTypes)) return acc;
        if (recurseIntoTypes && Array.isArray(value)) {
          const subValues = this.reduceToArrayOfTypes(value, validTypes);
          acc.push(...subValues);
        } else if (type) {
          acc.push(content);
        }
      }
      return acc;
    }, []);
    return res;
  }
  /**
   * Reduce the data to a string.
   *
   * The input data can have any nested array structure. It will be reduced to a single string.
   *
   * @param data the data to reduce
   * @returns a string concatenated from all the string values in the input data
   */
  reduceToString(data) {
    if (!Array.isArray(data)) return "";
    const res = data.reduce((acc, content, _index) => {
      if (content == null) return acc;
      if (Array.isArray(content)) {
        const subValue = this.reduceToString(content);
        acc += subValue;
      } else {
        acc += content;
      }
      return acc;
    }, []);
    return res;
  }
  /**
   * Print out data for debugging
   *
   * @param header
   * @param data
   */
  debugPrint(header, data) {
  }
};

// src/parser/bitmark/peg/BitmarkPegParserProcessor.ts
import { Enum as Enum21 } from "@ncoderz/superenum";

// src/parser/bitmark/peg/BitmarkPegParserValidator.ts
import { Enum as Enum13 } from "@ncoderz/superenum";
var COMMON_MISTAKE_STRINGS = [
  // Card divider errors
  "----",
  "\n==\n",
  "\n---\n"
  // Remark errors
  // Comment errors
];
var COMMON_STARTS_WITH_MISTAKE_STRINGS = [
  // Card divider errors
  "==\n",
  "--\n"
];
var COMMON_ENDS_WITH_MISTAKE_STRINGS = [
  // Card divider errors
  "\n==",
  "\n--"
];
var BitmarkPegParserValidator = class {
  /**
   * Validate the bit tags and tag chains.
   *
   * Any unknown tag chains will be 'unchained' and processed as individual tags.
   *
   * @param context
   * @param bitType
   * @param resourceType the resource type specified in the bit header
   * @param data the unvalidated bit content from the parser
   *
   * @returns the validated and potentially unchained bit content
   */
  validateBitTags(context, data) {
    if (!data) return [];
    const { bitConfig, bitType, resourceType } = context;
    const { tags: bitTags, cardSet: cardSetConfig } = bitConfig;
    const resourcesConfig = instance2.getBitResourcesConfig(bitType, resourceType);
    const tags2 = { ...bitTags, ...resourcesConfig.tags };
    const res = this.validateTagChainsRecursive(
      context,
      ContentDepth.Bit,
      bitType,
      data,
      tags2,
      cardSetConfig
    ).validated;
    return res;
  }
  /**
   * Check the body of the bit.
   *
   * This function also converts the body to JSON if the text format is JSON.
   *
   * @param context
   * @param bitType
   * @param _contentDepth
   * @param body
   * @returns
   */
  checkBody(context, _contentDepth, bitType, _textFormat, bodyParts) {
    if (!bodyParts) return bodyParts;
    const { bitConfig } = context;
    const { bodyAllowed } = bitConfig;
    const hasBody = bodyParts.length > 0;
    if (hasBody && !bodyAllowed) {
      context.addWarning(`Bit '${bitType}' should not have a body.`);
    }
    return bodyParts;
  }
  /**
   * Check the body part of the bit.
   *
   * @param context
   * @param _contentDepth
   * @param bitType
   * @param body
   * @returns
   */
  checkBodyPart(context, contentDepth, bodyPart) {
    if (!bodyPart) return bodyPart;
    this.checkBodyForCommonPotentialMistakes(context, contentDepth, bodyPart);
    return bodyPart;
  }
  /**
   * Check the footer of the bit.
   *
   * @param context
   * @param _contentDepth
   * @param bitType
   * @param body
   * @returns
   */
  checkFooter(context, contentDepth, footer) {
    if (!footer) return footer;
    const { bitConfig, bitType } = context;
    const { footerAllowed } = bitConfig;
    this.checkBodyForCommonPotentialMistakes(context, contentDepth, footer);
    if (!footerAllowed) {
      context.addWarning(`Bit '${bitType}' should not have a footer.`);
    }
    return footer;
  }
  /**
   * Check the footer of the bit.
   *
   * @param context
   * @param _contentDepth
   * @param bitType
   * @param cardBody
   * @returns
   */
  checkCardBody(context, _contentDepth, bitType, cardBody, cardNo, sideNo, variantNo) {
    if (!cardBody || !cardBody.body) return cardBody;
    const { bitConfig } = context;
    if (!bitConfig.cardSet) return cardBody;
    const variantConfig = instance2.getCardSetVariantConfig(bitType, sideNo, variantNo);
    if (!variantConfig) return cardBody;
    const { bodyAllowed } = variantConfig;
    const hasBody = cardBody.bodyString.length > 0;
    if (hasBody && !bodyAllowed) {
      context.addWarning(
        `Bit '${bitType}' should not have a card body at card:${cardNo + 1}, side:${sideNo + 1}, variant:${variantNo + 1}.`
      );
    }
    return cardBody;
  }
  //
  // Private
  //
  /**
   * Validate tag chains as they are encountered, expanding any tag chains that are not valid,
   * so they can be processed as individual tags.
   *
   * @param context
   * @param contentDepth
   * @param bitType
   * @param data
   * @param tags
   * @param cardSetConfig
   */
  validateTagChainsRecursive(context, contentDepth, bitType, data, tags2, cardSetConfig, chainHeadType) {
    if (!data) {
      return {
        validated: []
      };
    }
    let remaining;
    const dataOrNull = [...data];
    const validTypeKeys = this.convertTagsToTypeKeyMap(context, contentDepth, bitType, tags2);
    const addExtraValidTypeKeys = (key, maxCount, minCount) => {
      validTypeKeys.set(key, {
        maxCount,
        minCount,
        _type: key,
        _tag: key,
        _seenCount: 0
      });
    };
    if (contentDepth === ContentDepth.Bit) {
      if (cardSetConfig) addExtraValidTypeKeys(TypeKey.CardSet, 1, 0);
      addExtraValidTypeKeys(TypeKey.TextFormat, 1, 0);
      addExtraValidTypeKeys(TypeKey.BodyText, Count.infinity, 0);
      addExtraValidTypeKeys(TypeKey.BodyTextPlain, Count.infinity, 0);
      addExtraValidTypeKeys(TypeKey.PlainTextDivider, Count.infinity, 0);
      addExtraValidTypeKeys(TypeKey.FooterDivider, Count.infinity, 0);
    } else if (contentDepth === ContentDepth.Card) {
      addExtraValidTypeKeys(TypeKey.CardText, Count.infinity, 0);
    }
    for (let i = 0; i < dataOrNull.length; i++) {
      const content = dataOrNull[i];
      if (!content) continue;
      const { type, key } = content;
      let typeKey = Enum13(TypeKey).fromValue(type);
      if (!typeKey) continue;
      let validTypeKey = typeKey;
      if (typeKey === TypeKey.Property || typeKey === TypeKey.Resource) {
        validTypeKey = key;
      }
      let tagData = validTypeKeys.get(validTypeKey);
      if (!tagData && typeKey === TypeKey.Property) {
        const resourceKey = key.replace(/^@/, "&");
        if (resourceKey === ConfigKey.resource_backgroundWallpaper || resourceKey === ConfigKey.resource_imagePlaceholder || resourceKey === ConfigKey.resource_platformIcon || resourceKey === ConfigKey.resource_platformLogo || resourceKey === ConfigKey.resource_platformBackgroundImage || resourceKey === ConfigKey.resource_coverImage) {
          tagData = validTypeKeys.get(resourceKey);
          if (tagData) {
            typeKey = TypeKey.Resource;
            content.type = TypeKey.Resource;
            const warningMsg = `Falling back to '[${resourceKey}]' from '[${key}]'. Replace '[${key}]' with '[&${key}]' to avoid this warning.`;
            context.addWarning(warningMsg);
          }
        }
      }
      const { validated: validatedTagContent, warning } = this.validateSingleTag(
        context,
        contentDepth,
        bitType,
        content,
        typeKey,
        tagData,
        cardSetConfig
      );
      if (typeKey === TypeKey.Resource) chainHeadType = TypeKey.Resource;
      let addWarning = true;
      if (!validatedTagContent) {
        if (contentDepth === ContentDepth.Chain && chainHeadType !== TypeKey.Resource) {
          remaining = content;
          const restOfChain = dataOrNull.slice(i + 1);
          if (restOfChain.length > 0) {
            remaining.chain = restOfChain;
          }
          dataOrNull.splice(i);
          addWarning = false;
        } else {
          dataOrNull.splice(i, 1, null);
        }
      }
      if (warning && addWarning) {
        context.addWarning(warning.warning ?? "", warning.content, warning.previousContent);
      }
      if (contentDepth === ContentDepth.Chain && tagData && tagData.chain && validatedTagContent && !validatedTagContent.chain) {
        const restOfChain = dataOrNull.slice(i + 1);
        if (restOfChain.length > 0) {
          validatedTagContent.chain = restOfChain;
        }
        dataOrNull.splice(i + 1);
      }
      if (validatedTagContent && Array.isArray(validatedTagContent.chain) && validatedTagContent.chain.length > 0) {
        if (tagData && tagData.chain) {
          const { validated: validatedTagChainContent, remaining: remainingTagChainContent } = this.validateTagChainsRecursive(
            context,
            ContentDepth.Chain,
            bitType,
            validatedTagContent.chain,
            tagData.chain,
            void 0,
            validatedTagContent.type
          );
          if (validatedTagChainContent && validatedTagChainContent.length > 0) {
            validatedTagContent.chain = validatedTagChainContent;
          } else {
            validatedTagContent.chain = void 0;
          }
          if (remainingTagChainContent) {
            dataOrNull.splice(i + 1, 0, remainingTagChainContent);
          }
        } else {
          if (validatedTagContent.type !== TypeKey.Resource) {
            const remainingTagChainContent = validatedTagContent.chain[0];
            if (validatedTagContent.chain.length > 1) {
              remainingTagChainContent.chain = validatedTagContent.chain.slice(1);
            }
            dataOrNull.splice(i + 1, 0, remainingTagChainContent);
            validatedTagContent.chain = void 0;
          }
        }
      }
    }
    for (const data2 of validTypeKeys.values()) {
      const { minCount, _seenCount } = data2;
      if (minCount != null && _seenCount < minCount) {
        const warningMsg = `${this.getTagSignature(data2)} is required at least ${minCount} time(s)`;
        context.addWarning(warningMsg);
      }
    }
    const res = dataOrNull.filter((content) => content != null);
    return {
      validated: res,
      remaining
    };
  }
  /**
   * Validate a single bit tag
   *
   * @param context
   * @param contentDepth
   * @param bitType
   * @param content
   * @param typeKey
   * @param tagValidationData
   * @param cardSetConfig
   *
   * @returns validated tag, or undefined if the tag is invalid
   */
  validateSingleTag(context, contentDepth, bitType, content, typeKey, tagValidationData, cardSetConfig) {
    const { type, key } = content;
    const keyStr = tagValidationData ? this.getTagSignature(tagValidationData) : this.getUnknownTagSignature(type, key);
    const ignoredStr = " It will be ignored";
    let warningStr = "";
    let validatedContent;
    let warning;
    if (tagValidationData) {
      tagValidationData._seenCount++;
      switch (typeKey) {
        case TypeKey.Property: {
          const { content: c, warning: w } = this.validatePropertyTag(
            context,
            contentDepth,
            bitType,
            tagValidationData,
            content
          );
          validatedContent = c;
          warning = w;
          break;
        }
        case TypeKey.Resource: {
          const { content: c, warning: w } = this.validateResourceTag(
            context,
            contentDepth,
            bitType,
            tagValidationData,
            content
          );
          validatedContent = c;
          warning = w;
          break;
        }
        case TypeKey.CardSet: {
          const { content: c, warning: w } = this.validateCardSet(
            context,
            bitType,
            tagValidationData,
            content,
            cardSetConfig
          );
          validatedContent = c;
          warning = w;
          break;
        }
        default: {
          const { content: c, warning: w } = this.validateStandardTag(
            context,
            contentDepth,
            bitType,
            tagValidationData,
            content
          );
          validatedContent = c;
          warning = w;
        }
      }
    } else {
      switch (typeKey) {
        case TypeKey.Property: {
          warning = { extraProperty: true };
          if (contentDepth !== ContentDepth.Chain) {
            validatedContent = content;
          }
          break;
        }
        case TypeKey.Resource: {
          warning = { excessResource: true };
          validatedContent = content;
          break;
        }
        default:
          warning = { invalid: true };
      }
    }
    if (warning) {
      const previousContent = tagValidationData?._previous;
      if (warning.invalid || warning.excessResource) {
        warningStr = `${keyStr} is not valid here (incorrectly chained?).${ignoredStr}`;
      } else if (warning.tooMany != null) {
        warningStr = `${keyStr} is included more than the required ${warning.tooMany} time(s).`;
        if (warning.tooMany === 0 && typeKey === TypeKey.Resource) {
          warningStr += ` Did you forget the '${key}' in the bit header?`;
        }
        if (warning.tooMany > 0) warningStr += " The earlier ones will be ignored";
      } else if (warning.extraProperty) {
        warningStr = `${keyStr} is an unknown property. It can be excluded from the output using the 'excludeUnknownProperties' flag`;
      } else if (warning.unexpectedCardSet) {
        warningStr = `${keyStr} is not expected here.${ignoredStr}`;
      } else if (warning.unexpectedCardSideVariant) {
        warningStr = `${keyStr} has a card / side / variant that is not expected here.${ignoredStr}`;
      }
      warning.warning = warningStr;
      warning.content = validatedContent;
      warning.previousContent = previousContent;
    }
    if (tagValidationData) {
      tagValidationData._previous = content;
    }
    return {
      validated: validatedContent,
      warning
    };
  }
  /**
   * Validates a standard bit tag
   *
   * @param context
   * @param bitLevel
   * @param bitType
   * @param tagValidationData
   * @param content
   * @param seen
   *
   * @returns validated tag (plus warning message if one is generated) or undefined if the tag is invalid
   */
  validateStandardTag(_context, _contentDepth, _bitType, tagValidationData, content) {
    const validCount = tagValidationData.maxCount == null ? 1 : tagValidationData.maxCount;
    if (validCount !== Count.infinity) {
      if (tagValidationData._seenCount > validCount) {
        return {
          warning: { tooMany: validCount },
          content
          // Return content anyway when too many, as last wins
        };
      }
    }
    return {
      content
    };
  }
  /**
   * Validates a single property tag
   *
   * @param context
   * @param bitLevel
   * @param bitType
   * @param tagValidationData
   * @param content
   * @param seen
   *
   * @returns validated tag (plus warning message if one is generated) or undefined if the tag is invalid
   */
  validatePropertyTag(_context, _contentDepth, _bitType, tagValidationData, content) {
    const validCount = tagValidationData.maxCount == null ? 1 : tagValidationData.maxCount;
    if (validCount !== Count.infinity) {
      if (tagValidationData._seenCount > validCount) {
        return {
          warning: { tooMany: validCount },
          content
          // Return content anyway when too many, as last wins
        };
      }
    }
    return {
      content
    };
  }
  /**
   * Validates a single resource tag
   *
   * @param context
   * @param bitLevel
   * @param bitType
   * @param tagValidationData
   * @param content
   * @param seen
   *
   * @returns validated tag (plus warning message if one is generated) or undefined if the tag is invalid
   */
  validateResourceTag(_context, _contentDepth, _bitType, tagValidationData, content) {
    const validCount = tagValidationData.maxCount == null ? 1 : tagValidationData.maxCount;
    if (validCount !== Count.infinity) {
      if (tagValidationData._seenCount > validCount) {
        return {
          warning: { tooMany: validCount },
          content
          // Return content anyway when too many, as last wins
        };
      }
    }
    return {
      content
    };
  }
  /**
   * Validates a card set
   *
   * @param context
   * @param bitLevel
   * @param bitType
   * @param _tagValidationData
   * @param content
   * @param cardSetConfig
   * @param depth recursion depth
   *
   * @returns validated tag (plus warning message if one is generated) or undefined if the tag is invalid
   */
  validateCardSet(context, bitType, _tagValidationData, content, cardSetConfig) {
    if (!cardSetConfig || !cardSetConfig.variants || cardSetConfig.variants.length === 0) {
      return {
        warning: { unexpectedCardSet: true }
      };
    }
    const { value } = content;
    const cardSet = value;
    let warning;
    let sideIndex = 0;
    let variantIndex = 0;
    for (const card of cardSet.cards) {
      sideIndex = 0;
      for (const side of card.sides) {
        variantIndex = 0;
        for (const variant of side.variants) {
          const variantContent = variant.content;
          let validatedContent;
          const variantConfig = instance2.getCardSetVariantConfig(bitType, sideIndex, variantIndex);
          if (variantConfig) {
            const res = this.validateTagChainsRecursive(
              context,
              ContentDepth.Card,
              bitType,
              variantContent,
              variantConfig.tags
            );
            validatedContent = res.validated;
          } else {
            warning = {
              unexpectedCardSideVariant: true
            };
          }
          if (validatedContent && validatedContent.length > 0) {
            side.variants[variantIndex] = {
              parser: variant.parser,
              content: validatedContent
            };
          } else {
            side.variants[variantIndex] = {
              parser: variant.parser,
              content: []
            };
          }
          variantIndex++;
        }
        sideIndex++;
      }
    }
    return {
      content,
      warning
    };
  }
  /**
   * Check the body of the bit for patterns which indicate common potential bitmark language mistakes.
   *
   * @param context
   * @param _contentDepth
   * @param bitType
   * @param body
   * @returns
   */
  checkBodyForCommonPotentialMistakes(context, _contentDepth, body) {
    if (!body) return;
    const { bitType } = context;
    for (const mistake of COMMON_MISTAKE_STRINGS) {
      if (body.includes(mistake)) {
        context.addWarning(`Bit '${bitType}' might contain a mistake: ${mistake}`);
      }
    }
    for (const mistake of COMMON_STARTS_WITH_MISTAKE_STRINGS) {
      if (body.startsWith(mistake)) {
        context.addWarning(`Bit '${bitType}' might contain a mistake: ${mistake}`);
      }
    }
    for (const mistake of COMMON_ENDS_WITH_MISTAKE_STRINGS) {
      if (body.endsWith(mistake)) {
        context.addWarning(`Bit '${bitType}' might contain a mistake: ${mistake}`);
      }
    }
  }
  /**
   * Convert the tag data configuration to TypeKeys
   *
   * @param context
   * @param _contentDepth
   * @param bitType
   * @param tags
   * @param depth recursion depth
   * @returns
   */
  convertTagsToTypeKeyMap(_context, _contentDepth, _bitType, tags2) {
    const res = /* @__PURE__ */ new Map();
    for (const v of Object.values(tags2)) {
      const tagValidationData = {
        minCount: v.minCount,
        maxCount: v.maxCount,
        isTag: v.type === BitTagConfigKeyType.tag,
        isProperty: v.type === BitTagConfigKeyType.property,
        isResource: v.type === BitTagConfigKeyType.resource,
        chain: v.chain,
        // Private
        _configKey: v.configKey,
        _type: v.type,
        _tag: v.tag,
        _seenCount: 0,
        _previous: void 0
      };
      if (tagValidationData.isProperty) {
        res.set(v.configKey, tagValidationData);
      } else if (tagValidationData.isResource) {
        res.set(v.configKey, tagValidationData);
      } else {
        const typeKey = Enum13(TypeKey).fromValue(v.tag);
        if (typeKey) {
          res.set(typeKey, tagValidationData);
        }
      }
    }
    return res;
  }
  getTagSignature(tagValidationData) {
    switch (tagValidationData._type) {
      case BitTagConfigKeyType.tag:
      case BitTagConfigKeyType.property:
      case BitTagConfigKeyType.resource:
        return `[${tagValidationData._configKey}]`;
      default:
        return `'${tagValidationData._type}' tag '${tagValidationData._tag}'`;
    }
  }
  getUnknownTagSignature(type, tag) {
    switch (type) {
      case TypeKey.Property:
      case TypeKey.Resource:
        return `[${tag}]`;
      default:
        return tag ? `'${type}' tag '${tag}'` : `'${type}' tag`;
    }
  }
};
var instance9 = new BitmarkPegParserValidator();

// src/parser/bitmark/peg/contentProcessors/ContentProcessorUtils.ts
var ContentProcessorUtils = class {
  /**
   * Concatenates a plain JSON text with a JsonText that may be plain (v2) or BitmarkText (v3)
   * Returns the combined text.
   *
   * @param text the text to concatenate
   * @param extraBreaks extra breaks to add between concatenated texts
   * @param textPlain the plain text to concatenate
   */
  concatenatePlainTextWithAstTexts(text, extraBreaks, textPlain) {
    if (Array.isArray(text)) {
      textPlain = textPlain && textPlain.trim();
      if (textPlain) {
        const splitText = textPlain.split("\n");
        const content = [];
        for (let i = 0; i < extraBreaks; i++) {
          content.push({
            type: TextNodeType.hardBreak
          });
        }
        for (let i = 0; i < splitText.length; i++) {
          const t = splitText[i];
          if (t) {
            content.push({
              text: t,
              type: TextNodeType.text
            });
          }
          if (i < splitText.length - 1) {
            content.push({
              type: TextNodeType.hardBreak
            });
          }
        }
        const lastNode = text[text.length - 1];
        if (lastNode && lastNode.type === TextNodeType.paragraph) {
          lastNode.content = [...lastNode.content ?? [], ...content];
        } else {
          text.push({
            type: TextNodeType.paragraph,
            content,
            attrs: {}
          });
        }
      }
      return text;
    }
    return `${text ?? ""}${"\n".repeat(extraBreaks)}${textPlain ?? ""}`;
  }
};
var instance10 = new ContentProcessorUtils();

// src/parser/bitmark/peg/contentProcessors/BodyContentProcessor.ts
var textParser2 = new TextParser();
var BodyContentProcessor = class {
  process(context, contentDepth, bitType, textFormat, _tagsConfig, _target, bodyParts, isCardBody) {
    const bodyBits = [];
    let finalBody;
    let finalBodyString;
    const trimmedBodyParts = bodyParts.length > 0 ? this.trimBodyParts(bodyParts) : void 0;
    const validatedBodyParts = isCardBody ? trimmedBodyParts : instance9.checkBody(
      context,
      contentDepth,
      bitType,
      textFormat,
      trimmedBodyParts
    );
    if (textFormat === TextFormat2.json && validatedBodyParts) {
      finalBody = validatedBodyParts.reduce((acc, val) => {
        if (val.type === BodyBitType.text && val.data) {
          const bodyTextVal = val;
          return acc + (bodyTextVal.data.bodyText ?? "");
        }
        return acc;
      }, "");
      try {
        finalBody = JSON.parse(finalBody);
      } catch (_e) {
        finalBody = null;
        context.addError(`Body JSON is invalid.`);
      }
    } else {
      let bodyTextStr = "";
      let plainBodyTextStr = "";
      let bodyStr = "";
      let placeholderIndex = 0;
      const createPlaceholderKey = (i) => `[!${i}]`;
      if (validatedBodyParts) {
        for (let i = 0; i < validatedBodyParts.length; i++) {
          const bodyPart = validatedBodyParts[i];
          const isText = bodyPart.type === BodyBitType.text;
          if (isText) {
            const asText = bodyPart;
            const bodyTextPart = asText.data.bodyText;
            if (asText.data.isPlain) {
              plainBodyTextStr = instance3.concatenate(plainBodyTextStr, bodyTextPart);
            } else {
              bodyTextStr = instance3.concatenate(bodyTextStr, bodyTextPart);
            }
            bodyStr = instance3.concatenate(bodyStr, bodyTextPart);
          } else {
            const placeholderKey = createPlaceholderKey(placeholderIndex);
            bodyTextStr = instance3.concatenate(bodyTextStr, placeholderKey);
            bodyBits.push(bodyPart);
            placeholderIndex++;
          }
        }
      }
      const isBitmarkText = textFormat === TextFormat2.bitmarkText;
      const parsedBodyText = isBitmarkText ? textParser2.toAst(bodyTextStr, {
        //
        format: textFormat,
        location: TextLocation2.body
      }) : instance3.unbreakscape(bodyTextStr, {
        format: TextFormat2.plainText,
        location: TextLocation2.body
      });
      const parserPlainText = instance3.unbreakscape(plainBodyTextStr, {
        format: TextFormat2.plainText,
        location: TextLocation2.body
      });
      const newlines = stringUtils.countOccurrencesAtEnd(bodyTextStr, "\n") + stringUtils.countOccurrencesAtStart(plainBodyTextStr, "\n");
      finalBody = instance10.concatenatePlainTextWithAstTexts(
        parsedBodyText,
        newlines,
        parserPlainText
      );
      finalBodyString = instance3.unbreakscape(bodyStr, {
        format: textFormat,
        location: TextLocation2.body
      }).trim();
      const finalBodyIsAst = Array.isArray(finalBody);
      const bodyAst = finalBodyIsAst ? finalBody : void 0;
      if (bodyAst && validatedBodyParts) {
        placeholderIndex = 0;
        for (let i = 0; i < validatedBodyParts.length; i++) {
          const bodyPart = validatedBodyParts[i];
          const isText = bodyPart.type === BodyBitType.text;
          if (isText) continue;
          const bodyBit = bodyPart;
          let bodyBitJson;
          switch (bodyPart.type) {
            case BodyBitType.gap: {
              bodyBitJson = bodyBit;
              break;
            }
            case BodyBitType.mark: {
              bodyBitJson = bodyBit;
              break;
            }
            case BodyBitType.select: {
              bodyBitJson = bodyBit;
              break;
            }
            case BodyBitType.highlight: {
              bodyBitJson = bodyBit;
              break;
            }
          }
          if (bodyBitJson) {
            this.replacePlaceholderWithBodyBit(bodyAst, bodyBitJson, placeholderIndex);
          }
          placeholderIndex++;
        }
      }
    }
    return {
      body: finalBody,
      bodyBits: bodyBits.length > 0 ? bodyBits : void 0,
      bodyString: finalBodyString
    };
  }
  buildBodyText(bodyTextPart, isPlain) {
    const bodyText = {
      type: BodyBitType.text,
      data: {
        bodyText: bodyTextPart,
        isPlain
      }
    };
    return bodyText;
  }
  /**
   * Walk the body AST to find the placeholder and replace it with the body bit.
   *
   * @param bodyAst the body AST
   * @param bodyBitJson the body bit json to insert at the placeholder position
   * @param index the index of the placeholder to replace
   */
  replacePlaceholderWithBodyBit(bodyAst, bodyBitJson, index) {
    const walkRecursive = (node, parent, parentKey) => {
      if (Array.isArray(node)) {
        for (let i = 0; i < node.length; i++) {
          const child = node[i];
          const done = walkRecursive(child, node, i);
          if (done) return true;
        }
      } else {
        if (node.type === "bit" && node.index === index) {
          parent[parentKey] = bodyBitJson;
          return true;
        }
        if (node.content) {
          const done = walkRecursive(node.content, node, "content");
          if (done) return true;
        }
      }
      return false;
    };
    walkRecursive(bodyAst, null, null);
  }
  /**
   * Trim the body parts, removing any whitespace only parts at start and end of body
   *
   * @param parts the body parts to trim
   * @returns the trimmed body parts
   */
  trimBodyParts(parts) {
    let foundText = false;
    let trimmedParts = parts.reduce((acc, part) => {
      const text = part;
      if (!foundText && text.type === BodyBitType.text) {
        const t = text.data.bodyText.trimStart();
        if (t) {
          foundText = true;
          text.data.bodyText = t;
          acc.push(text);
        }
      } else {
        foundText = true;
        acc.push(part);
      }
      return acc;
    }, []);
    foundText = false;
    trimmedParts = trimmedParts.reduceRight((acc, part) => {
      const text = part;
      if (!foundText && text.type === BodyBitType.text) {
        const t = text.data.bodyText.trimEnd();
        if (t) {
          foundText = true;
          text.data.bodyText = t;
          acc.unshift(text);
        }
      } else {
        foundText = true;
        acc.unshift(part);
      }
      return acc;
    }, []);
    return trimmedParts;
  }
};
var instance11 = new BodyContentProcessor();

// src/parser/bitmark/peg/contentProcessors/CardContentProcessor.ts
function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, statementsV1, choicesV1, responsesV1) {
  const { bitConfig } = context;
  if (context.DEBUG_CARD_SET) context.debugPrint("card set", parsedCardSet);
  let result = {};
  const processedCardSet = processCardSet(context, parsedCardSet);
  const cardSetType = bitConfig.cardSet?.configKey;
  switch (cardSetType) {
    case CardSetConfigKey.flashcard:
    case CardSetConfigKey.flashcard1:
    case CardSetConfigKey.definitionList:
    case CardSetConfigKey.definitionListPlain:
      result = parseFlashcardOrDefinitionList(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.elements:
      result = parseElements(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.statements:
      result = parseStatements(context, bitType, processedCardSet, statementV1, statementsV1);
      break;
    case CardSetConfigKey.feedback:
      result = parseFeedback(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.quiz:
    case CardSetConfigKey.quizResponses:
      result = parseQuiz(context, bitType, processedCardSet, choicesV1, responsesV1);
      break;
    case CardSetConfigKey.questions:
      result = parseQuestions(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.matchPairs:
      result = parseMatchPairs(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.matchMatrix:
      result = parseMatchMatrix(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.pronunciationTable:
      result = parsePronunciationTable(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.table:
    case CardSetConfigKey.tableExtended:
      result = parseTable(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.botActionResponses:
      result = parseBotActionResponses(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.ingredients:
      result = parseIngredients(context, bitType, processedCardSet);
      break;
    case CardSetConfigKey.clozeList:
    case CardSetConfigKey.exampleBitList:
    case CardSetConfigKey.pageFooterSections:
    case CardSetConfigKey.bookReferenceList:
      result = parseCardBits(context, bitType, textFormat, processedCardSet);
      break;
    // DEPRECATED - REMOVE IN THE FUTURE
    // case CardSetConfigKey._captionDefinitionsList:
    //   // ==> captionDefinitionsList
    //   result = parseCaptionDefinitionsList(context, bitType, processedCardSet);
    //   break;
    default:
  }
  result.internalComments = processedCardSet.internalComments.length > 0 ? processedCardSet.internalComments : void 0;
  return result;
}
function processCardSet(context, parsedCardSet) {
  const processedCardSet = {
    cards: [],
    internalComments: []
  };
  if (!parsedCardSet) return processedCardSet;
  const { bitType } = context;
  let cardNo = 0;
  let sideNo = 0;
  let variantNo = 0;
  for (const card of parsedCardSet.cards) {
    const processedCard = {
      no: cardNo++,
      sides: [],
      qualifier: card.qualifier
    };
    processedCardSet.cards.push(processedCard);
    for (const side of card.sides) {
      const processedSide = {
        no: sideNo++,
        variants: [],
        qualifier: side.qualifier
      };
      processedCard.sides.push(processedSide);
      for (const variant of side.variants) {
        const { parser: parser2, content } = variant;
        const processedVariant = {
          parser: parser2,
          no: variantNo++,
          qualifier: variant.qualifier
        };
        processedSide.variants.push(processedVariant);
        const tagsConfig = instance2.getTagsConfigForCardSet(bitType, sideNo, variantNo);
        const tags2 = context.bitContentProcessor(ContentDepth.Card, tagsConfig, content);
        if (context.DEBUG_CARD_TAGS) context.debugPrint("card tags", tags2);
        tags2.cardBody = instance9.checkCardBody(
          context,
          ContentDepth.Card,
          bitType,
          tags2.cardBody,
          processedCard.no,
          processedSide.no,
          processedVariant.no
        );
        tags2.cardBodyStr = tags2.cardBody?.bodyString;
        processedVariant.data = tags2;
        if (tags2.internalComments) processedCardSet.internalComments.push(...tags2.internalComments);
      }
      variantNo = 0;
    }
    sideNo = 0;
  }
  return processedCardSet;
}
function parseFlashcardOrDefinitionList(context, bitType, cardSet) {
  const flashcards = [];
  const definitions = [];
  let questionStr = "";
  let question;
  let answer;
  let alternativeAnswers = [];
  let variantIndex = 0;
  let extraTags = {};
  let questionVariant;
  const onlyOneCardAllowed = bitType === BitType2.flashcard1;
  const headingData = extractHeadingCard(cardSet);
  for (let cardIdx = 0; cardIdx < cardSet.cards.length; cardIdx++) {
    const card = cardSet.cards[cardIdx];
    if (headingData && cardIdx === 0) continue;
    question = void 0;
    answer = void 0;
    alternativeAnswers = [];
    variantIndex = 0;
    extraTags = {};
    for (const side of card.sides) {
      for (const content of side.variants) {
        const { cardBody, resources, ...tags2 } = content.data;
        extraTags = {
          ...extraTags,
          ...tags2
        };
        const icon = resources && resources.length > 0 ? resources[0] : void 0;
        const text = cardBody?.body;
        const str = cardBody?.bodyString ?? "";
        if (variantIndex === 0) {
          questionVariant = content;
          questionStr = str;
          question = {
            text,
            icon
          };
        } else if (variantIndex === 1) {
          answer = {
            text,
            icon
          };
        } else {
          alternativeAnswers.push({
            text,
            icon
          });
        }
        variantIndex++;
      }
    }
    if (cardIdx === 0 || !onlyOneCardAllowed) {
      if (instance2.isOfBitType(bitType, [BitType2.flashcard])) {
        const fc = {
          question,
          answer,
          alternativeAnswers,
          ...extraTags
        };
        if (fc) flashcards.push(fc);
      } else {
        const dl = {
          term: question,
          definition: answer,
          alternativeDefinitions: alternativeAnswers,
          ...extraTags
        };
        if (dl) definitions.push(dl);
      }
    } else {
      context.addWarning(
        `Bit '${bitType}' should only contain one card. Ignore subsequent card: '${questionStr}'`,
        questionVariant
      );
      break;
    }
  }
  return {
    heading: headingData?.heading,
    flashcards: flashcards.length > 0 ? flashcards : void 0,
    definitions: definitions.length > 0 ? definitions : void 0
  };
}
function parseElements(_context, _bitType, cardSet) {
  const elements = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const tags2 = content.data;
        const element = tags2.cardBody?.body ?? tags2.cardBodyStr ?? instance3.EMPTY_STRING;
        elements.push(element);
      }
    }
  }
  return {
    elements: elements.length > 0 ? elements : void 0
  };
}
function parseStatements(_context, _bitType, cardSet, statementV1, statementsV1) {
  const statements = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const { statements: chainedStatements, statement: _ignore, ...tags2 } = content.data;
        if (Array.isArray(chainedStatements)) {
          for (const s of chainedStatements) {
            const statement = {
              statement: s.statement ?? [],
              isCorrect: s.isCorrect,
              item: s.item,
              lead: s.lead,
              hint: s.hint,
              instruction: s.instruction,
              example: s.example,
              ...tags2
            };
            if (statement) statements.push(statement);
          }
        }
      }
    }
  }
  if (statementV1) {
    statements.push(statementV1);
  }
  if (Array.isArray(statementsV1) && statementsV1.length > 0) {
    statements.push(...statementsV1);
  }
  return {
    statements: statements.length > 0 ? statements : void 0
  };
}
function parseFeedback(_context, _bitType, cardSet) {
  let feedback;
  const feedbacks = [];
  const headingData = extractHeadingCard(cardSet);
  for (let cardIdx = 0; cardIdx < cardSet.cards.length; cardIdx++) {
    const card = cardSet.cards[cardIdx];
    if (headingData && cardIdx === 0) continue;
    feedback = void 0;
    for (let sideIdx = 0; sideIdx < card.sides.length; sideIdx++) {
      const side = card.sides[sideIdx];
      for (const content of side.variants) {
        const tags2 = content.data;
        const { cardBody, cardBodyStr, choices: choiceTags, ...restTags } = tags2;
        if (sideIdx === 0) {
          const choices = [];
          if (choiceTags && choiceTags.length > 0) {
            for (const tf of choiceTags) {
              const { __isDefaultExample, example, ...tfTags } = tf;
              const choice = {
                choice: tfTags.choice,
                requireReason: tfTags.isCorrect,
                item: tfTags.item,
                lead: tfTags.lead,
                instruction: tfTags.instruction,
                __isDefaultExample,
                example
              };
              choices.push(choice);
            }
          }
          feedback = {
            ...tags2,
            choices
          };
          if (feedback) feedbacks.push(feedback);
        } else if (sideIdx === 1) {
          const reason = {
            text: cardBodyStr ?? "",
            __textAst: cardBody?.body,
            ...restTags
          };
          if (feedback) {
            feedback.reason = reason;
          }
        }
      }
    }
  }
  return {
    heading: headingData?.heading,
    feedbacks: feedbacks.length > 0 ? feedbacks : void 0
  };
}
function parseQuiz(_context, bitType, cardSet, choicesV1, responsesV1) {
  const quizzes = [];
  const insertChoices = instance2.isOfBitType(bitType, BitType2.multipleChoice);
  const insertResponses = instance2.isOfBitType(bitType, BitType2.multipleResponse);
  if (!insertChoices && !insertResponses) return {};
  let isDefaultExampleCard = false;
  let exampleCard;
  for (const card of cardSet.cards) {
    isDefaultExampleCard = false;
    exampleCard = void 0;
    for (const side of card.sides) {
      for (const content of side.variants) {
        const { __isDefaultExample, example, ...tags2 } = content.data;
        isDefaultExampleCard = __isDefaultExample === true ? true : isDefaultExampleCard;
        exampleCard = example ? example : exampleCard;
        if (tags2.trueFalse && tags2.trueFalse.length > 0) {
          const responsesOrChoices = [];
          for (const tf of tags2.trueFalse) {
            const { __isDefaultExample: isDefaultExampleTf, example: exampleTf, ...tfTags } = tf;
            const __isDefaultExample2 = isDefaultExampleTf || isDefaultExampleCard;
            const example2 = exampleTf || exampleCard;
            const response = {
              isCorrect: tfTags.isCorrect,
              __isDefaultExample: __isDefaultExample2,
              example: example2
            };
            if (insertResponses) response.response = tfTags.text;
            else response.choice = tfTags.text;
            if (response) responsesOrChoices.push(response);
          }
          if (insertResponses) tags2.responses = responsesOrChoices;
          else tags2.choices = responsesOrChoices;
        }
        const quiz = {
          ...tags2,
          choices: tags2.choices,
          responses: tags2.responses,
          __isDefaultExample: isDefaultExampleCard,
          __defaultExample: exampleCard
        };
        if (quiz) quizzes.push(quiz);
      }
    }
  }
  if (insertChoices && Array.isArray(choicesV1) && choicesV1.length > 0) {
    const quiz = {
      choices: choicesV1
    };
    if (quiz) quizzes.push(quiz);
  }
  if (insertResponses && Array.isArray(responsesV1) && responsesV1.length > 0) {
    const quiz = {
      responses: responsesV1
    };
    if (quiz) quizzes.push(quiz);
  }
  return {
    quizzes: quizzes.length > 0 ? quizzes : void 0
  };
}
function parseQuestions(_context, _bitType, cardSet) {
  const questions = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const tags2 = content.data;
        const q = {
          question: tags2.cardBody?.body ?? tags2.cardBodyStr ?? instance3.EMPTY_STRING,
          ...tags2
        };
        if (q) questions.push(q);
      }
    }
  }
  return {
    questions: questions.length > 0 ? questions : void 0
  };
}
function parseMatchPairs(_context, bitType, cardSet) {
  const pairs = [];
  let pairKey = void 0;
  let pairValues = [];
  let _pairValuesAst = [];
  let keyAudio = void 0;
  let keyImage = void 0;
  let extraTags = {};
  let isDefaultExampleCard = false;
  let exampleCard;
  const headingValuesIsArray = instance2.isOfBitType(bitType, BitType2.matchMatrix);
  const headingData = extractHeadingCard(cardSet, headingValuesIsArray);
  const isDefaultExampleCardSet = headingData?.isDefaultExampleCardSet ?? false;
  let exampleCardSet = headingData?.exampleCardSet;
  for (let cardIdx = 0; cardIdx < cardSet.cards.length; cardIdx++) {
    const card = cardSet.cards[cardIdx];
    if (headingData && cardIdx === 0) continue;
    pairKey = void 0;
    pairValues = [];
    _pairValuesAst = [];
    keyAudio = void 0;
    keyImage = void 0;
    extraTags = {};
    isDefaultExampleCard = false;
    exampleCard = void 0;
    for (let sideIdx = 0; sideIdx < card.sides.length; sideIdx++) {
      const side = card.sides[sideIdx];
      for (const content of side.variants) {
        const { cardBody, cardBodyStr, resources, __isDefaultExample: __isDefaultExample2, example: example2, ...tags2 } = content.data;
        isDefaultExampleCard = __isDefaultExample2 === true ? true : isDefaultExampleCard;
        exampleCard = example2 ? example2 : exampleCard;
        if (sideIdx === 0) {
          if (Array.isArray(resources) && resources.length > 0) {
            const resource = resources[0];
            if (resource.type === ResourceType.audio) {
              keyAudio = resource;
            } else if (resource.type === ResourceType.image) {
              keyImage = resource;
            }
          } else {
            pairKey = cardBodyStr;
          }
        } else {
          const value = cardBodyStr ?? "";
          const valueAst = cardBody?.body;
          pairValues.push(value);
          _pairValuesAst.push(valueAst);
          if ((isDefaultExampleCardSet || isDefaultExampleCard) && !exampleCard)
            exampleCard = valueAst;
          delete tags2.item;
          delete tags2.lead;
        }
        extraTags = {
          ...extraTags,
          ...tags2
        };
      }
    }
    if (isDefaultExampleCard) exampleCardSet = void 0;
    const __isDefaultExample = isDefaultExampleCard || isDefaultExampleCardSet;
    const example = exampleCard || exampleCardSet;
    const pair = {
      key: pairKey ?? "",
      keyAudio,
      keyImage,
      values: pairValues,
      __valuesAst: _pairValuesAst,
      ...extraTags,
      __isDefaultExample,
      example
    };
    if (pair) pairs.push(pair);
  }
  return {
    heading: headingData?.heading,
    pairs: pairs.length > 0 ? pairs : void 0
  };
}
function parseMatchMatrix(_context, _bitType, cardSet) {
  let matrixKey = void 0;
  let matrixKeyTags = void 0;
  const matrix = [];
  let matrixCells = [];
  let matrixCellValues = [];
  let _matrixCellValuesAst = [];
  let matrixCellTags = {};
  let isDefaultExampleCard = false;
  let exampleCard;
  let isDefaultExampleSide = false;
  let exampleSide;
  let isCaseSensitiveMatrix;
  let isCaseSensitiveCell;
  const headingData = extractHeadingCard(cardSet, true);
  const isDefaultExampleCardSet = headingData?.isDefaultExampleCardSet ?? false;
  let exampleCardSet = headingData?.exampleCardSet;
  for (let cardIdx = 0; cardIdx < cardSet.cards.length; cardIdx++) {
    const card = cardSet.cards[cardIdx];
    if (headingData && cardIdx === 0) continue;
    matrixKey = void 0;
    matrixKeyTags = void 0;
    matrixCells = [];
    matrixCellValues = [];
    _matrixCellValuesAst = [];
    isDefaultExampleCard = false;
    exampleCard = void 0;
    isCaseSensitiveMatrix = void 0;
    for (let sideIdx = 0; sideIdx < card.sides.length; sideIdx++) {
      const side = card.sides[sideIdx];
      matrixCellValues = [];
      _matrixCellValuesAst = [];
      matrixCellTags = {};
      isDefaultExampleSide = false;
      exampleSide = void 0;
      isCaseSensitiveCell = void 0;
      for (const content of side.variants) {
        const tags2 = content.data;
        const { cardBody, cardBodyStr, __isDefaultExample, example, isCaseSensitive, ...restTags } = tags2;
        isDefaultExampleSide = __isDefaultExample === true ? true : isDefaultExampleSide;
        exampleSide = example ? example : exampleSide;
        Object.assign(matrixCellTags, restTags);
        if (sideIdx === 0) {
          matrixKey = cardBodyStr;
          matrixKeyTags = restTags;
          isDefaultExampleCard = __isDefaultExample === true ? true : isDefaultExampleCard;
          exampleCard = example ? example : exampleCard;
          isCaseSensitiveMatrix = isCaseSensitive != null ? isCaseSensitive : isCaseSensitiveMatrix;
        } else {
          const value = cardBodyStr ?? instance3.EMPTY_STRING;
          const valueAst = cardBody?.body;
          matrixCellValues.push(value);
          _matrixCellValuesAst.push(valueAst);
          if ((isDefaultExampleCardSet || isDefaultExampleSide) && !exampleSide)
            exampleSide = valueAst;
          isCaseSensitiveCell = isCaseSensitive != null ? isCaseSensitive : isCaseSensitiveMatrix;
        }
      }
      if (sideIdx > 0) {
        if (isDefaultExampleSide) exampleCard = exampleCardSet = void 0;
        if (isDefaultExampleCard) exampleCardSet = void 0;
        const __isDefaultExample = isDefaultExampleSide || isDefaultExampleCard || isDefaultExampleCardSet;
        const example = exampleSide || exampleCard || exampleCardSet;
        const matrixCell = {
          values: matrixCellValues,
          __valuesAst: _matrixCellValuesAst,
          ...matrixCellTags,
          __isDefaultExample,
          example,
          isCaseSensitive: isCaseSensitiveCell
        };
        if (matrixCell) matrixCells.push(matrixCell);
      }
    }
    const m = {
      key: matrixKey ?? instance3.EMPTY_STRING,
      // item: matrixItem ?? Breakscape.EMPTY_STRING,
      // keyAudio,
      // keyImage,
      cells: matrixCells,
      ...matrixKeyTags
    };
    if (m) matrix.push(m);
  }
  return {
    heading: headingData?.heading,
    matrix: matrix.length > 0 ? matrix : void 0
  };
}
function parsePronunciationTable(_context, _bitType, cardSet) {
  const rows = [];
  let rowValues = [];
  for (const card of cardSet.cards) {
    rowValues = [];
    for (const side of card.sides) {
      for (const content of side.variants) {
        const tags2 = content.data;
        const { title, cardBody, resources } = tags2;
        const heading = title && title[1].titleAst;
        const audio = resources && resources.length > 0 ? resources.find((r) => r.type === ResourceType.audio) : void 0;
        const value = {
          title: heading ?? [],
          body: cardBody?.body ?? [],
          audio
        };
        rowValues.push(value);
      }
    }
    rows.push(rowValues);
  }
  const table = {
    data: rows
  };
  return { pronunciationTable: table };
}
function parseTable(context, _bitType, cardSet) {
  const sectionRows = {
    header: [],
    body: [],
    footer: []
  };
  const getNormalizedQualifier = (rawQualifier) => {
    if (!rawQualifier) return void 0;
    const normalized = rawQualifier.trim().toLowerCase();
    if (normalized === "table-header") return "header";
    if (normalized === "table-body") return "body";
    if (normalized === "table-footer") return "footer";
    context.addWarning(`Unknown table section qualifier '${rawQualifier}', defaulting to body.`);
    return void 0;
  };
  const isLegacyHeadingRow = (card, cardIndex) => {
    if (cardIndex !== 0) return false;
    return card.sides.some(
      (side) => side.variants.some((variant) => {
        const titles = variant.data.title;
        return Array.isArray(titles) && titles[1]?.titleAst;
      })
    );
  };
  const readExtraProperty = (extra, key) => {
    if (!extra) return void 0;
    const value = extra[key];
    return Array.isArray(value) ? value[0] : value;
  };
  const cleanupExtraProperties = (tags2, keys) => {
    const extra = tags2.extraProperties;
    if (!extra) return;
    for (const key of keys) {
      if (Object.prototype.hasOwnProperty.call(extra, key)) delete extra[key];
    }
    if (Object.keys(extra).length === 0) {
      delete tags2.extraProperties;
    }
  };
  const normalizeCellType = (raw, section) => {
    if (typeof raw === "string") {
      const normalized = raw.trim().toLowerCase();
      if (normalized === "th" || normalized === "td") {
        return normalized;
      }
      context.addWarning(
        `Invalid table cell type '${raw}', using default for section '${section}'.`
      );
    }
    return section === "body" ? "td" : "th";
  };
  const normalizeSpan = (raw, kind) => {
    if (raw == null) return 1;
    const numeric = instance6.asNumber(raw);
    if (numeric == null || !Number.isInteger(numeric) || numeric < 1) {
      context.addWarning(`Invalid table ${kind} '${raw}', defaulting to 1.`);
      return 1;
    }
    return numeric;
  };
  const normalizeScope = (raw) => {
    if (raw == null) return void 0;
    if (typeof raw !== "string") {
      context.addWarning(`Invalid table scope '${raw}', ignoring.`);
      return void 0;
    }
    const normalized = raw.trim().toLowerCase();
    switch (normalized) {
      case "row":
      case "col":
      case "rowgroup":
      case "colgroup":
        return normalized;
      default:
        context.addWarning(`Invalid table scope '${raw}', ignoring.`);
        return void 0;
    }
  };
  const buildCell = (variant, section) => {
    const tags2 = variant.data;
    const heading = tags2.title && tags2.title[1]?.titleAst;
    const bodyContent = tags2.cardBody?.body ?? tags2.cardBodyStr ?? instance3.EMPTY_STRING;
    const content = heading ?? bodyContent;
    const cellTypeRaw = tags2.tableCellType ?? readExtraProperty(tags2.extraProperties, "tableCellType");
    const rowSpanRaw = tags2.tableRowSpan ?? readExtraProperty(tags2.extraProperties, "tableRowSpan");
    const colSpanRaw = tags2.tableColSpan ?? readExtraProperty(tags2.extraProperties, "tableColSpan");
    const scopeRaw = tags2.tableScope ?? readExtraProperty(tags2.extraProperties, "tableScope");
    const colWidthRaw = tags2.tableColWidth ?? readExtraProperty(tags2.extraProperties, "tableColWidth");
    cleanupExtraProperties(tags2, [
      "tableCellType",
      "tableRowSpan",
      "tableColSpan",
      "tableScope",
      "tableColWidth"
    ]);
    const cellType = normalizeCellType(cellTypeRaw, section);
    const rowspan = normalizeSpan(rowSpanRaw, "rowspan");
    const colspan = normalizeSpan(colSpanRaw, "colspan");
    const scope = normalizeScope(scopeRaw);
    const colwidth = instance6.asNumber(colWidthRaw) ?? 0;
    const cell = {
      content
    };
    if (cellType === "th") cell.title = true;
    if (rowspan > 1) cell.rowspan = rowspan;
    if (colspan > 1) cell.colspan = colspan;
    if (scope) cell.scope = scope;
    if (colwidth > 0) cell.colwidth = colwidth;
    return cell;
  };
  const resolveSectionQualifier = (card) => {
    const cardQualifier = getNormalizedQualifier(card.qualifier);
    if (cardQualifier) return cardQualifier;
    for (const side of card.sides) {
      const sideQualifier = getNormalizedQualifier(side.qualifier);
      if (sideQualifier) return sideQualifier;
      for (const variant of side.variants) {
        const variantQualifier = getNormalizedQualifier(variant.qualifier);
        if (variantQualifier) return variantQualifier;
      }
    }
    return void 0;
  };
  for (let cardIdx = 0; cardIdx < cardSet.cards.length; cardIdx++) {
    const card = cardSet.cards[cardIdx];
    const qualifier = resolveSectionQualifier(card);
    const section = qualifier ? qualifier : isLegacyHeadingRow(card, cardIdx) ? "header" : "body";
    const cells = [];
    for (const side of card.sides) {
      for (const variant of side.variants) {
        cells.push(buildCell(variant, section));
      }
    }
    sectionRows[section].push({
      cells
    });
  }
  const table = {};
  const hasHeadRows = sectionRows.header.length > 0;
  const hasBodyRows = sectionRows.body.length > 0;
  const hasFootRows = sectionRows.footer.length > 0;
  if (hasHeadRows) {
    table.header = {
      rows: sectionRows.header
    };
  }
  if (hasBodyRows) {
    table.body = {
      rows: sectionRows.body
    };
  }
  if (hasFootRows) {
    table.footer = {
      rows: sectionRows.footer
    };
  }
  if (!hasHeadRows && !hasBodyRows && !hasFootRows) {
    table.header = { rows: [] };
    table.body = { rows: [] };
    table.footer = { rows: [] };
  }
  return { table };
}
function parseBotActionResponses(_context, _bitType, cardSet) {
  const botResponses = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const {
          __instructionString,
          __itemString,
          reaction,
          cardBodyStr: feedback,
          ...tags2
        } = content.data;
        const botResponse = {
          response: __instructionString ?? instance3.EMPTY_STRING,
          reaction: reaction ?? instance3.EMPTY_STRING,
          feedback: feedback ?? instance3.EMPTY_STRING,
          ...tags2,
          item: __itemString ?? instance3.EMPTY_STRING
        };
        if (botResponse) botResponses.push(botResponse);
      }
    }
  }
  return {
    botResponses: botResponses.length > 0 ? botResponses : void 0
  };
}
function parseIngredients(_context, _bitType, cardSet) {
  const ingredients = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const {
          title: titleArray,
          __instructionString,
          unit,
          unitPlural,
          unitAbbr,
          unitAbbrPlural,
          decimalPlaces,
          disableCalculation,
          cardBodyStr,
          cardBody,
          ...tags2
        } = content.data;
        const title = Array.isArray(titleArray) && titleArray.length > 0 ? titleArray[titleArray.length - 1].titleString : void 0;
        let checked = false;
        let quantity = instance6.asNumber(__instructionString);
        if (cardBody && cardBody.bodyBits) {
          const select = cardBody.bodyBits.find(
            (c) => c.type === BodyBitType.select
          );
          if (select) {
            quantity = select.__instructionString ? instance6.asNumber(select.__instructionString) : quantity;
            if (select.options && select.options.length > 0) {
              checked = select.options[0].isCorrect;
            }
          }
        }
        const ingredient = {
          title,
          checked,
          // TS compiler very weird. It doesn't recognize that cardBodyStr is a string|undefined, even if cast!
          // Casting to 'any' to avoid the error
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          ingredient: cardBodyStr,
          quantity,
          unit: unit ?? instance3.EMPTY_STRING,
          unitPlural,
          unitAbbr: unitAbbr ?? instance3.EMPTY_STRING,
          unitAbbrPlural,
          decimalPlaces: decimalPlaces ?? 1,
          disableCalculation,
          ...tags2
        };
        if (ingredient) ingredients.push(ingredient);
      }
    }
  }
  return {
    ingredients: ingredients.length > 0 ? ingredients : void 0
  };
}
function parseCardBits(_context, _bitType, _textFormat, cardSet) {
  const cardBits = [];
  for (const card of cardSet.cards) {
    for (const side of card.sides) {
      for (const content of side.variants) {
        const { cardBody: body, ...rest } = content.data;
        const cardBit = {
          body,
          ...rest
        };
        if (cardBit) cardBits.push(cardBit);
      }
    }
  }
  return {
    cardBits: cardBits.length > 0 ? cardBits : void 0
  };
}
function extractHeadingCard(cardSet, valuesIsArray = false) {
  let isHeading = false;
  let heading;
  let forKeys = void 0;
  const forValues = [];
  let isDefaultExampleCardSet = false;
  let exampleCardSet;
  if (cardSet.cards.length === 0) return void 0;
  const card = cardSet.cards[0];
  for (let sideIdx = 0; sideIdx < card.sides.length; sideIdx++) {
    const side = card.sides[sideIdx];
    if (side.variants.length === 0) continue;
    const content = side.variants[0];
    const tags2 = content.data;
    const { title, __isDefaultExample, example } = tags2;
    const heading2 = title && title[1].titleString;
    if (heading2 != null) isHeading = true;
    if (sideIdx === 0) {
      if (isHeading) {
        forKeys = heading2;
        isDefaultExampleCardSet = __isDefaultExample === true ? true : isDefaultExampleCardSet;
        exampleCardSet = example ? example : exampleCardSet;
      }
    } else {
      if (isHeading) {
        forValues.push(heading2 ?? "");
        isDefaultExampleCardSet = __isDefaultExample === true ? true : isDefaultExampleCardSet;
        exampleCardSet = example ? example : exampleCardSet;
      }
    }
  }
  if (isHeading) {
    let forValuesFinal = forValues;
    if (valuesIsArray) {
      forValuesFinal = forValues;
    } else {
      if (forValues.length >= 1) {
        forValuesFinal = forValues[forValues.length - 1];
      } else {
        forValuesFinal = "";
      }
    }
    heading = {
      forKeys: forKeys ?? "",
      forValues: forValuesFinal
    };
  }
  return heading ? {
    heading,
    isDefaultExampleCardSet,
    exampleCardSet
  } : void 0;
}

// src/parser/bitmark/peg/contentProcessors/DefaultTagContentProcessor.ts
function defaultTagContentProcessor(context, _contentDepth, _tagsConfig, content, target) {
  const { type, value } = content;
  const { textFormat } = context;
  const textParser10 = new TextParser();
  const trimmedStringValue = stringUtils.trimmedString(value);
  switch (type) {
    case TypeKey.Instruction: {
      target.instruction = textParser10.toAst(trimmedStringValue, {
        format: textFormat,
        location: TextLocation2.tag
      });
      target.__instructionString = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      });
      break;
    }
    case TypeKey.Hint: {
      target.hint = textParser10.toAst(trimmedStringValue, {
        format: textFormat,
        location: TextLocation2.tag
      });
      target.__hintString = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      });
      break;
    }
    case TypeKey.Anchor: {
      target.anchor = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      });
      break;
    }
    case TypeKey.Reference: {
      target.reference = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      });
      break;
    }
    // 16.08.2023 Deprecated, but currently still supported
    case TypeKey.SampleSolution: {
      target.sampleSolution = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      });
      target.__sampleSolutionAst = textParser10.toAst(trimmedStringValue, {
        format: textFormat,
        location: TextLocation2.tag
      });
      context.addWarning("[$...] tag is deprecated, use [@sampleSolution:...] instead", content);
      break;
    }
    default:
  }
}

// src/parser/bitmark/peg/contentProcessors/FooterContentProcessor.ts
var textParser3 = new TextParser();
var FooterContentProcessor = class {
  process(context, contentDepth, _tagsConfig, _target, footer, footerPlainText) {
    const { textFormat } = context;
    let finalFooterText;
    footer = footer.trimStart();
    footerPlainText = footerPlainText.trimEnd();
    const haveFooter = !!footer.trim();
    const haveFooterPlainText = !!footerPlainText.trim();
    let newLines = 0;
    if (haveFooter && haveFooterPlainText) {
      newLines = stringUtils.countOccurrencesAtEnd(footer, "\n") + stringUtils.countOccurrencesAtStart(footerPlainText, "\n");
    }
    if (footer || footerPlainText) {
      const isBitmarkText = textFormat === TextFormat2.bitmarkText;
      if (footer) {
        footer = instance9.checkFooter(context, contentDepth, footer);
      }
      const parsedFooterText = isBitmarkText ? textParser3.toAst(footer, {
        //
        format: textFormat,
        location: TextLocation2.body
      }) : instance3.unbreakscape(footer, {
        format: TextFormat2.plainText,
        location: TextLocation2.body
      });
      const parsedFooterPlainText = instance3.unbreakscape(footerPlainText, {
        format: TextFormat2.plainText,
        location: TextLocation2.body
      });
      finalFooterText = instance10.concatenatePlainTextWithAstTexts(
        parsedFooterText,
        newLines,
        parsedFooterPlainText
      );
    }
    return finalFooterText ? { footer: finalFooterText } : void 0;
  }
};
var instance12 = new FooterContentProcessor();

// src/parser/bitmark/peg/contentProcessors/GapChainContentProcessor.ts
import { Enum as Enum14 } from "@ncoderz/superenum";

// src/parser/bitmark/peg/contentProcessors/ClozeTagContentProcessor.ts
var textParser4 = new TextParser();
function clozeTagContentProcessor(context, _contentDepth, _tagsConfig, content, target) {
  const { textFormat } = context;
  const { value } = content;
  const solutions = target.solutions;
  const solutionsAst = target.__solutionsAst;
  if (!solutions || !solutionsAst) return;
  if (stringUtils.isString(value)) {
    const trimmedStringValue = stringUtils.trimmedString(value);
    solutions.push(
      instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat2.bitmarkText,
        location: TextLocation2.tag
      })
    );
    const solutionAst = textParser4.toAst(trimmedStringValue, {
      format: textFormat,
      location: TextLocation2.tag
    });
    solutionsAst.push(solutionAst);
  }
}

// src/parser/bitmark/peg/contentProcessors/GapChainContentProcessor.ts
function gapChainContentProcessor(context, contentDepth, tagsConfig, content, target, bodyParts) {
  if (contentDepth === ContentDepth.Chain) {
    clozeTagContentProcessor(context, contentDepth, tagsConfig, content, target);
  } else {
    const gap = buildGap(context, contentDepth, tagsConfig, content);
    if (gap) bodyParts.push(gap);
  }
}
function buildGap(context, _contentDepth, tagsConfig, content) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("gap content", content);
  const gapConfig = instance2.getTagConfigForTag(tagsConfig, Enum14(Tag).fromValue(content.type));
  const chainContent = [content, ...content.chain ?? []];
  const chainTags = context.bitContentProcessor(
    ContentDepth.Chain,
    gapConfig?.chain,
    chainContent
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("gap TAGS", chainTags);
  const gap = {
    type: BodyBitType.gap,
    ...chainTags
  };
  return gap;
}

// src/parser/bitmark/peg/contentProcessors/ItemLeadChainContentProcessor.ts
import { Enum as Enum15 } from "@ncoderz/superenum";

// src/parser/bitmark/peg/contentProcessors/ItemLeadTagContentProcessor.ts
function itemLeadTagContentProcessor(context, _contentDepth, _tagsConfig, content, target) {
  const { textFormat } = context;
  const { value } = content;
  const textParser10 = new TextParser();
  const trimmedStringValue = stringUtils.trimmedString(value);
  if (!target.itemLead) target.itemLead = [];
  const text = textParser10.toAst(trimmedStringValue, {
    format: textFormat,
    location: TextLocation2.tag
  });
  target.itemLead.push(text);
  if (!target.__itemLeadString) target.__itemLeadString = [];
  target.__itemLeadString.push(
    instance3.unbreakscape(trimmedStringValue, {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    })
  );
}

// src/parser/bitmark/peg/contentProcessors/ItemLeadChainContentProcessor.ts
function flattenChainContent(content) {
  const result = [content];
  if (content.chain) {
    for (const child of content.chain) {
      result.push(...flattenChainContent(child));
    }
  }
  return result;
}
function itemLeadChainContentProcessor(context, contentDepth, tagsConfig, content, target) {
  if (contentDepth === ContentDepth.Chain) {
    itemLeadTagContentProcessor(context, contentDepth, tagsConfig, content, target);
  } else {
    buildItemLead(context, contentDepth, tagsConfig, content, target);
  }
}
function buildItemLead(context, _contentDepth, tagsConfig, content, target) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("item lead content", content);
  const itemLeadConfig = instance2.getTagConfigForTag(tagsConfig, Enum15(Tag).fromValue(content.type));
  const chainContent = flattenChainContent(content);
  const chainTags = context.bitContentProcessor(
    ContentDepth.Chain,
    itemLeadConfig?.chain,
    chainContent
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("item lead TAGS", chainTags);
  target.itemLead = chainTags.itemLead;
  target.__itemLeadString = chainTags.__itemLeadString;
}

// src/parser/bitmark/peg/contentProcessors/MarkChainContentProcessor.ts
import { Enum as Enum16 } from "@ncoderz/superenum";

// src/parser/bitmark/peg/contentProcessors/MarkTagContentProcessor.ts
function markTagContentProcessor(_context, _contentDepth, content, target) {
  const { value } = content;
  if (stringUtils.isString(value)) {
    target.solution = stringUtils.trimmedString(value);
  }
}

// src/parser/bitmark/peg/contentProcessors/MarkChainContentProcessor.ts
function markChainContentProcessor(context, contentDepth, tagsConfig, content, target, bodyParts) {
  if (contentDepth === ContentDepth.Chain) {
    markTagContentProcessor(context, ContentDepth.Chain, content, target);
  } else {
    const mark = buildMark(
      context,
      contentDepth,
      tagsConfig,
      content
    );
    if (mark) bodyParts.push(mark);
  }
}
function buildMark(context, _contentDepth, tagsConfig, content) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("mark content", content);
  const markConfig = instance2.getTagConfigForTag(tagsConfig, Enum16(Tag).fromValue(content.type));
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, [content]);
  const chainTags = context.bitContentProcessor(
    ContentDepth.Chain,
    markConfig?.chain,
    content.chain
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("mark TAGS", chainTags);
  const { solution } = tags2;
  const { mark: markType, ...rest } = chainTags;
  const mark = {
    type: BodyBitType.mark,
    solution: solution ?? "",
    mark: instance4.asSingle(markType) ?? "",
    ...rest
  };
  return mark;
}

// src/parser/bitmark/peg/contentProcessors/PropertyContentProcessor.ts
import { Enum as Enum19 } from "@ncoderz/superenum";

// src/parser/bitmark/peg/contentProcessors/BookChainContentProcessor.ts
function bookChainContentProcessor(context, contentDepth, tagsConfig, content, target) {
  if (contentDepth === ContentDepth.Chain) {
  } else {
    buildBook(context, contentDepth, tagsConfig, content, target);
  }
}
function buildBook(context, _contentDepth, tagsConfig, content, target) {
  const { bitConfig } = context;
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("book content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("book TAGS", tags2);
  const { reference, referenceEnd } = tags2;
  const book = instance3.unbreakscape(
    stringUtils.trimmedString(content.value),
    {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }
  );
  const bookConfig = bitConfig.tags[ConfigKey.property_book];
  if (bookConfig && (bookConfig.maxCount === Count.infinity || bookConfig.maxCount > 1)) {
    if (!Array.isArray(target.book)) target.book = [];
    target.book.push({
      book,
      reference: reference ?? "",
      referenceEnd: referenceEnd ?? void 0
    });
    return;
  }
  target.book = book;
  target.reference = reference;
  target.referenceEnd = referenceEnd;
}

// src/parser/bitmark/peg/contentProcessors/ExampleTagContentProcessor.ts
var textParser5 = new TextParser();
function exampleTagContentProcessor(context, _contentDepth, content, target) {
  const { bitType } = context;
  const { value } = content;
  const example = value;
  if (instance2.isOfBitType(bitType, [
    BitType2.cloze,
    BitType2.clozeAndMultipleChoiceText,
    BitType2.multipleChoiceText,
    BitType2.highlightText,
    BitType2.trueFalse,
    BitType2.trueFalse1,
    BitType2.multipleResponse,
    BitType2.multipleResponse1,
    BitType2.multipleChoice,
    BitType2.multipleChoice1
  ])) {
    handleGapOrSelectOrTrueFalseExample(context, content, example, target);
  } else if (instance2.isOfBitType(bitType, BitType2.mark)) {
    handleDefaultOnlyExample(context, content, example, target);
  } else {
    handleStandardStringExample(context, content, example, target);
  }
}
function handleGapOrSelectOrTrueFalseExample(context, content, example, target) {
  const { bitType, textFormat } = context;
  let trueFalse;
  if (Array.isArray(target.trueFalse) && target.trueFalse.length > 0) {
    trueFalse = target.trueFalse[target.trueFalse.length - 1] ?? void 0;
  }
  if (trueFalse) {
    if (example === true) {
      trueFalse.__isDefaultExample = true;
      trueFalse.example = !!trueFalse.isCorrect;
    } else {
      if (instance5.isBooleanString(example)) {
        trueFalse.example = instance5.toBoolean(example);
      } else {
        trueFalse.__isDefaultExample = true;
        trueFalse.example = void 0;
        context.addWarning(
          `Only 'true' / 'false' / default are allowed here, using default`,
          content
        );
      }
    }
  } else if (Array.isArray(target.__solutionsAst) && target.__solutionsAst.length > 0) {
    if (example === true) {
      target.example = target.__solutionsAst[target.__solutionsAst.length - 1] ?? void 0;
    } else {
      target.example = example ? textParser5.toAst(example, {
        format: textFormat,
        location: TextLocation2.tag
      }) : void 0;
    }
  } else {
    if (instance2.isOfBitType(bitType, [
      BitType2.multipleChoiceText,
      BitType2.highlightText,
      BitType2.trueFalse,
      BitType2.trueFalse1,
      BitType2.multipleResponse,
      BitType2.multipleResponse1
    ])) {
      handleStandardBooleanExample(context, content, example, target);
    } else if (instance2.isOfBitType(bitType, [
      BitType2.clozeAndMultipleChoiceText,
      BitType2.multipleChoice,
      BitType2.multipleChoice1
    ])) {
      target.__isDefaultExample = true;
      target.example = void 0;
      if (example !== true) {
        context.addWarning(
          `At this level, only default [@example] is allowed, using default`,
          content
        );
      }
    } else {
      handleStandardStringExample(context, content, example, target);
    }
  }
}
function handleDefaultOnlyExample(context, content, example, target) {
  target.__isDefaultExample = true;
  target.example = void 0;
  if (example !== true) {
    context.addWarning(`Only default [@example] is allowed, using default`, content);
  }
}
function handleStandardBooleanExample(context, content, example, target) {
  if (example === true) {
    target.__isDefaultExample = true;
    target.example = void 0;
  } else {
    const exampleStr = example ? instance3.unbreakscape(example, {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }) : void 0;
    if (instance5.isBooleanString(exampleStr)) {
      target.example = instance5.toBoolean(exampleStr);
    } else {
      target.__isDefaultExample = true;
      target.example = void 0;
      context.addWarning(
        `Only 'true' / 'false' / default are allowed here, using default`,
        content
      );
    }
  }
}
function handleStandardStringExample(context, _content, example, target) {
  const { textFormat } = context;
  if (example === true || example === "true") {
    target.__isDefaultExample = true;
    target.example = void 0;
  } else {
    target.example = example ? textParser5.toAst(example, { format: textFormat, location: TextLocation2.tag }) : void 0;
  }
}

// src/parser/bitmark/peg/contentProcessors/GroupTagChainContentProcessor.ts
function groupTagChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  const { value } = content;
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("groupTag content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("groupTag TAGS", tags2);
  const { tag } = tags2;
  const name = stringUtils.trimmedString(value) ?? "";
  const node = {
    name,
    tags: tag ?? []
  };
  if (!Array.isArray(target.groupTag)) target.groupTag = [];
  target.groupTag.push(node);
}

// src/parser/bitmark/peg/contentProcessors/ImageSourceChainContentProcessor.ts
import { Enum as Enum17 } from "@ncoderz/superenum";
function imageSourceChainContentProcessor(context, contentDepth, tagsConfig, content, target) {
  if (contentDepth === ContentDepth.Chain) {
    imageSourceTagContentProcessor(context, contentDepth, tagsConfig, content, target);
  } else {
    buildImageSource(context, contentDepth, tagsConfig, content, target);
  }
}
function imageSourceTagContentProcessor(_context, _contentDepth, _tagsConfig, content, target) {
  const { value } = content;
  const url = instance3.unbreakscape(stringUtils.trimmedString(value), {
    format: TextFormat2.bitmarkText,
    location: TextLocation2.tag
  });
  target.imageSourceUrl = url;
}
function buildImageSource(context, _contentDepth, tagsConfig, content, target) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("imageSource content", content);
  const { key: tag } = content;
  const imageSourceConfig = instance2.getTagConfigForTag(tagsConfig, Enum17(ConfigKey).fromValue(tag));
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, [content]);
  const chainTags = context.bitContentProcessor(
    ContentDepth.Chain,
    imageSourceConfig?.chain,
    content.chain
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("imageSource TAGS", chainTags);
  const { imageSourceUrl: url } = tags2;
  const { mockupId, ...rest } = chainTags;
  if (!url) {
    context.addWarning("[@imageSource] is missing the image url", content);
  }
  if (!mockupId) {
    context.addWarning("[@mockupId:xxx] is missing from [@imageSource]", content);
  }
  const imageSource = {
    url: url ?? "",
    mockupId: mockupId ?? "",
    ...rest
  };
  target.imageSource = imageSource;
}

// src/parser/bitmark/peg/contentProcessors/InternalCommentTagContentProcessor.ts
function commentTagContentProcessor(_context, _contentDepth, content, target) {
  if (!target.internalComments) return;
  const { value } = content;
  const internalComment = stringUtils.isString(value) ? stringUtils.trimmedString(value) : void 0;
  if (!internalComment) return;
  target.internalComments.push(internalComment);
}

// src/parser/bitmark/peg/contentProcessors/MarkConfigChainContentProcessor.ts
import { Enum as Enum18 } from "@ncoderz/superenum";
function markConfigChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  const { key: tag } = content;
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("mark content", content);
  const markConfig = target.markConfig;
  if (!markConfig) return;
  const markTagConfig = instance2.getTagConfigForTag(tagsConfig, Enum18(ConfigKey).fromValue(tag));
  const { mark: _ignoreMark, ...tags2 } = context.bitContentProcessor(
    ContentDepth.Chain,
    markTagConfig?.chain,
    content.chain
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("mark TAGS", tags2);
  const mark = instance3.unbreakscape(
    stringUtils.trimmedString(content.value) ?? "unknown",
    {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }
  );
  const config = {
    mark,
    emphasis: "underline",
    ...tags2
  };
  if (config) markConfig.push(config);
}

// src/parser/bitmark/peg/contentProcessors/PersonChainContentProcessor.ts
function personChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("person content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("person TAGS", tags2);
  const { propertyTitle, resources } = tags2;
  const name = instance3.unbreakscape(
    stringUtils.trimmedString(content.value),
    {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }
  );
  const title = stringUtils.trimmedString(propertyTitle);
  const avatarImage = extractAvatarImage(context, resources);
  const person = {
    name,
    title,
    avatarImage
  };
  target.person = person;
}
function extractAvatarImage(context, resources) {
  let avatarImage;
  const excessResources = [];
  if (resources) {
    for (const r of resources.reverse()) {
      if (!avatarImage && ResourceType.image === r.type) {
        avatarImage = r;
      } else {
        excessResources.push(r);
      }
    }
  }
  if (excessResources.length > 0) {
    context.addWarning(
      `${excessResources.length} excess resource(s) present in the [@person] chain.`
    );
  }
  return avatarImage;
}

// src/parser/bitmark/peg/contentProcessors/RatingLevelChainContentProcessor.ts
var textParser6 = new TextParser();
function ratingLevelChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  const { textFormat } = context;
  const { key, value } = content;
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("ratingLevel content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("ratingLevel TAGS", tags2);
  const { label } = tags2;
  const level = instance6.asNumber(value) ?? 0;
  const node = {
    level,
    label: label ? textParser6.toAst(label, { format: textFormat, location: TextLocation2.tag }) : void 0
  };
  switch (key) {
    case ConfigKey.property_ratingLevelStart:
      target.ratingLevelStart = node;
      break;
    case ConfigKey.property_ratingLevelEnd:
      target.ratingLevelEnd = node;
      break;
    default:
  }
}

// src/parser/bitmark/peg/contentProcessors/ServingsChainContentProcessor.ts
function servingsChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("servings content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("servings TAGS", tags2);
  const {
    unit,
    unitPlural,
    unitAbbr,
    unitAbbrPlural,
    decimalPlaces,
    disableCalculation,
    __hintString
  } = tags2;
  const servings = instance6.asNumber(content.value) ?? 1;
  const node = {
    servings,
    unit,
    unitPlural,
    unitAbbr,
    unitAbbrPlural,
    decimalPlaces: decimalPlaces ?? 1,
    disableCalculation,
    hint: __hintString
  };
  target.servings = node;
}

// src/parser/bitmark/peg/contentProcessors/TechnicalTermChainContentProcessor.ts
function technicalTermChainContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("technicalTerm content", content);
  const tags2 = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, content.chain);
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("technicalTerm TAGS", tags2);
  const { lang } = tags2;
  const technicalTerm = instance3.unbreakscape(
    stringUtils.trimmedString(content.value),
    {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }
  );
  const node = {
    technicalTerm,
    lang
  };
  target.technicalTerm = node;
}

// src/parser/bitmark/peg/contentProcessors/PropertyContentProcessor.ts
var textParser7 = new TextParser();
function propertyContentProcessor(context, contentDepth, tagsConfig, content, target) {
  const { value } = content;
  const { key: keyIn } = content;
  const isChain = contentDepth === ContentDepth.Chain;
  const propertyConfig = instance2.getTagConfigForTag(tagsConfig, Enum19(PropertyKey).fromValue(keyIn));
  const configKey = propertyConfig ? propertyConfig.configKey : void 0;
  let property = configKeyToPropertyType(keyIn);
  if (configKey === ConfigKey.property_internalComment) {
    commentTagContentProcessor(context, contentDepth, content, target);
    return;
  }
  if (propertyConfig) {
    if (configKey === ConfigKey.property_example) {
      exampleTagContentProcessor(context, contentDepth, content, target);
      return;
    } else if (configKey === ConfigKey.property_groupTag) {
      groupTagChainContentProcessor(context, contentDepth, propertyConfig.chain, content, target);
      return;
    } else if (configKey === ConfigKey.property_ratingLevelStart || configKey === ConfigKey.property_ratingLevelEnd) {
      ratingLevelChainContentProcessor(
        context,
        contentDepth,
        propertyConfig.chain,
        content,
        target
      );
      return;
    } else if (configKey === ConfigKey.property_technicalTerm) {
      technicalTermChainContentProcessor(
        context,
        contentDepth,
        propertyConfig.chain,
        content,
        target
      );
      return;
    } else if (configKey === ConfigKey.property_servings) {
      servingsChainContentProcessor(context, contentDepth, propertyConfig.chain, content, target);
      return;
    } else if (configKey === ConfigKey.property_person || configKey === ConfigKey.property_partner) {
      personChainContentProcessor(context, contentDepth, propertyConfig.chain, content, target);
      return;
    } else if (configKey === ConfigKey.property_imageSource) {
      imageSourceChainContentProcessor(context, contentDepth, tagsConfig, content, target);
      return;
    } else if (configKey === ConfigKey.property_book) {
      bookChainContentProcessor(context, contentDepth, propertyConfig.chain, content, target);
      return;
    } else if (configKey === ConfigKey.property_mark && !isChain) {
      markConfigChainContentProcessor(context, contentDepth, tagsConfig, content, target);
      return;
    } else if (configKey === ConfigKey.property_title && isChain) {
      property = "propertyTitle";
    }
  }
  const addProperty = (obj, tag, v, c) => {
    const processValue = (v2) => {
      if (v2 == null) return void 0;
      if (c) {
        switch (c.format) {
          // case PropertyFormat.string:
          //   return StringUtils.isString(v) ? StringUtils.string(v) : undefined;
          case TagFormat.plainText:
            return instance3.unbreakscape(
              stringUtils.isString(v2) ? stringUtils.trimmedString(v2) : void 0,
              {
                format: TextFormat2.plainText,
                location: TextLocation2.tag
              }
            );
          case TagFormat.number:
            return instance6.asNumber(
              instance3.unbreakscape(v2, {
                format: TextFormat2.plainText,
                location: TextLocation2.tag
              })
            );
          case TagFormat.boolean:
            return instance5.toBoolean(
              instance3.unbreakscape(v2, {
                format: TextFormat2.plainText,
                location: TextLocation2.tag
              }),
              true
            );
          case TagFormat.invertedBoolean:
            return !instance5.toBoolean(
              instance3.unbreakscape(v2, {
                format: TextFormat2.plainText,
                location: TextLocation2.tag
              }),
              true
            );
          case TagFormat.bitmarkText:
            v2 = stringUtils.isString(v2) ? v2 : "";
            return textParser7.toAst(v2, {
              format: TextFormat2.bitmarkText,
              location: TextLocation2.tag
            });
          case TagFormat.coordinates:
            const coordRaw = instance3.unbreakscape(v2, {
              format: TextFormat2.plainText,
              location: TextLocation2.tag
            });
            if (coordRaw == null) return void 0;
            const coordParts = coordRaw.split(",").map((p) => instance6.asNumber(p.trim()));
            if (coordParts.length !== 4 || coordParts.some((n) => n == null)) {
              return void 0;
            }
            return coordParts;
        }
      }
      return instance3.unbreakscape(v2, {
        format: TextFormat2.plainText,
        location: TextLocation2.tag
      });
    };
    v = processValue(v);
    if (c == null || c.array) {
      if (Object.prototype.hasOwnProperty.call(obj, tag)) {
        const originalValue = obj[tag];
        obj[tag] = [...originalValue, v];
      } else {
        obj[tag] = [v];
      }
    } else {
      obj[tag] = v;
    }
  };
  if (propertyConfig) {
    addProperty(target, property, value, propertyConfig);
  } else {
    addProperty(target.extraProperties, property, value, propertyConfig);
  }
  if (configKey === ConfigKey.property_sampleSolution) {
    addProperty(
      target,
      "__sampleSolutionAst",
      value,
      new PropertyTagConfig({
        configKey: ConfigKey.property_sampleSolution,
        tag: "sampleSolution",
        maxCount: 1,
        minCount: 1,
        chain: void 0,
        jsonKey: void 0,
        exportJsonKey: void 0,
        hasExportJsonKey: false,
        htmlKey: void 0,
        hasHtmlKey: false,
        format: TagFormat.bitmarkText,
        values: void 0,
        defaultValue: void 0,
        deprecated: void 0
      })
    );
  }
}

// src/parser/bitmark/peg/contentProcessors/ReferenceTagContentProcessor.ts
function referenceTagContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  const { value, chain } = content;
  const trimmedStringValue = stringUtils.trimmedString(value);
  target.reference = instance3.unbreakscape(trimmedStringValue, {
    format: TextFormat2.bitmarkText,
    location: TextLocation2.tag
  });
  if (Array.isArray(chain) && chain.length > 0) {
    const sub = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, chain);
    if (sub.reference != null) {
      target.referenceEnd = sub.reference;
    }
  }
}

// src/parser/bitmark/peg/contentProcessors/ResourceContentProcessor.ts
import { Enum as Enum20 } from "@ncoderz/superenum";
var resourceBuilder = new ResourceBuilder();
function buildResources(context, resourceTypeAttachment, resources) {
  const { bitType } = context;
  const filteredResources = [];
  const excessResources = [];
  const validatedResourceTypeAttachemnt = Enum20(ResourceType).fromValue(resourceTypeAttachment);
  const resourcesConfig = instance2.getBitResourcesConfig(bitType, validatedResourceTypeAttachemnt);
  const resourceAttachmentAllowed = resourcesConfig.resourceAttachmentAllowed;
  const countsMin = resourcesConfig.getCountsMin();
  const countsMax = resourcesConfig.getCountsMax();
  if (resources) {
    for (const r of resources.reverse()) {
      const configKey = r.__configKey;
      let countMin = countsMin.get(configKey) ?? 0;
      let countMax = countsMax.get(configKey) ?? 0;
      countMin = Math.max(0, countMin - 1);
      if (countMax === Count.infinity) {
        filteredResources.unshift(r);
      } else if (countMax > 0) {
        filteredResources.unshift(r);
        countMax--;
      } else {
        excessResources.unshift(r);
      }
      countsMin.set(configKey, countMin);
      countsMax.set(configKey, countMax);
      if (r.__invalid) {
        context.addWarning(`Resource type [&${r.type}] is not valid`);
      }
    }
  }
  if (!resourceAttachmentAllowed && resourceTypeAttachment) {
    const warningMsg = `Resource type [&${resourceTypeAttachment}] is specified in the bit header, but no extra resource is allowed for this bit.`;
    context.addWarning(warningMsg);
  } else if (filteredResources.length === 0 && resourceTypeAttachment) {
    context.addWarning(
      `Resource type [&${resourceTypeAttachment}] is specified in the bit header, but no such resource is present`
    );
  }
  excessResources.forEach((r) => {
    r.__excessResource = true;
    filteredResources.push(r);
  });
  return filteredResources;
}
function resourceContentProcessor(context, _contentDepth, tagsConfig, content, target) {
  const { type: _ignoreType, key, value, chain } = content;
  const resources = target.resources;
  if (!resources) return;
  const configKey = Enum20(ConfigKey).fromValue(key);
  if (!configKey) return;
  const resourceType = Enum20(ResourceType).fromKey(configKey.replace(/^&/, "")) ?? ResourceType.unknown;
  const resourceConfig = instance2.getTagConfigForTag(tagsConfig, configKey);
  const { posterImage, ...tags2 } = context.bitContentProcessor(
    ContentDepth.Chain,
    resourceConfig?.chain,
    chain
  );
  let posterImageResource;
  if (posterImage) {
    const { subConfig: posterImageChainConfig, subChain: posterImageChain } = extractSubChain(
      resourceConfig?.chain,
      chain,
      ConfigKey.property_posterImage
    );
    const { posterImage: _unused, ...posterImageTags } = context.bitContentProcessor(
      ContentDepth.Chain,
      posterImageChainConfig,
      posterImageChain
    );
    posterImageResource = resourceBuilder.resource(context, {
      type: ResourceType.image,
      value: posterImage,
      ...posterImageTags
    }).image;
  }
  const resource = resourceBuilder.resource(context, {
    type: resourceType,
    value: instance3.unbreakscape(value, {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }),
    posterImage: posterImageResource,
    ...tags2
  });
  if (resource) {
    if (configKey === ConfigKey.resource_backgroundWallpaper || configKey === ConfigKey.resource_imagePlaceholder || configKey === ConfigKey.resource_platformIcon || configKey === ConfigKey.resource_platformLogo || configKey === ConfigKey.resource_platformBackgroundImage || configKey === ConfigKey.resource_previewImage || configKey === ConfigKey.resource_previewVideo || configKey === ConfigKey.resource_coverImage || configKey === ConfigKey.resource_backgroundImage) {
      if (target.propertyStyleResources) {
        if (target.propertyStyleResources[resourceType]) {
          if (!Array.isArray(target.propertyStyleResources[resourceType])) {
            target.propertyStyleResources[resourceType] = [
              target.propertyStyleResources[resourceType]
            ];
          }
          target.propertyStyleResources[resourceType].push(resource);
        } else {
          target.propertyStyleResources[resourceType] = resource;
        }
      }
    } else {
      resources.push(resource);
    }
  }
}
function extractSubChain(config, chain, configKey) {
  let subConfig;
  if (config) {
    subConfig = config[configKey]?.chain;
  }
  const subChain = chain?.reduce(
    (acc, c) => {
      if (acc) return acc;
      const tkv = c;
      if (tkv.key === configKey) return tkv.chain;
      return void 0;
    },
    void 0
  );
  return { subConfig, subChain };
}

// src/parser/bitmark/peg/contentProcessors/TitleTagContentProcessor.ts
var textParser8 = new TextParser();
function titleTagContentProcessor(context, _contentDepth, _tagsConfig, content, target) {
  const { textFormat } = context;
  const { value } = content;
  const title = target.title;
  if (!title) return;
  const titleValue = value;
  const titleText = stringUtils.trimmedString(titleValue.title);
  const level = titleValue.level.length;
  title[level] = {
    //
    titleAst: textParser8.toAst(titleText ?? "", {
      format: textFormat,
      location: TextLocation2.tag
    }),
    titleString: titleText ?? ""
  };
}
function buildTitles(_context, bitType, title) {
  title = title ?? [];
  if (instance2.isOfBitType(bitType, BitType2.chapter)) {
    let t;
    if (title.length > 0) t = title[title.length - 1];
    return {
      title: t?.titleAst ?? [],
      titleString: t?.titleString ?? "",
      level: title.length > 0 ? title.length - 1 : void 0
    };
  } else {
    if (Array.isArray(title)) {
      return {
        title: title[1]?.titleAst ?? void 0,
        titleString: title[1]?.titleString ?? void 0,
        subtitle: title[2]?.titleAst ?? void 0,
        subtitleString: title[2]?.titleString ?? void 0
      };
    } else {
      return {
        title: void 0,
        titleString: title ?? void 0,
        subtitle: void 0,
        subtitleString: void 0
      };
    }
  }
}

// src/parser/bitmark/peg/contentProcessors/TrueFalseTagContentProcessor.ts
var textParser9 = new TextParser();
function trueFalseTagContentProcessor(context, _contentDepth, content, target) {
  const { textFormat } = context;
  const { type, value } = content;
  const trueFalse = target.trueFalse;
  if (!trueFalse) return;
  const trimmedStringValue = instance3.unbreakscape(
    stringUtils.trimmedString(value),
    {
      format: TextFormat2.bitmarkText,
      location: TextLocation2.tag
    }
  );
  const textAst = textParser9.toAst(trimmedStringValue ?? "", {
    format: textFormat,
    location: TextLocation2.tag
  });
  trueFalse.push({
    text: trimmedStringValue,
    textAst,
    isCorrect: type === TypeKey.True,
    __isDefaultExample: false
  });
}

// src/parser/bitmark/peg/contentProcessors/TrueFalseChainContentProcessor.ts
function trueFalseChainContentProcessor(context, contentDepth, tagsConfig, content, target, bodyParts) {
  if (contentDepth === ContentDepth.Chain) {
    trueFalseTagContentProcessor(context, ContentDepth.Chain, content, target);
  } else {
    buildTrueFalse(context, contentDepth, tagsConfig, content, target, bodyParts);
  }
}
function buildTrueFalse(context, _contentDepth, tagsConfig, content, target, bodyParts) {
  const { bitType } = context;
  const chainContent = [content, ...content.chain ?? []];
  const statements = target.statements;
  const choices = target.choices;
  const responses = target.responses;
  if (!statements || !choices || !responses || !bodyParts) return;
  if (instance2.isOfBitType(bitType, BitType2.trueFalse1)) {
    target.statement = buildStatement(context, tagsConfig, chainContent);
  } else if (instance2.isOfBitType(bitType, [
    BitType2.trueFalse,
    BitType2.multipleChoice,
    BitType2.multipleChoice1,
    BitType2.multipleResponse,
    BitType2.multipleResponse1,
    BitType2.feedback
  ])) {
    const tf = buildStatementsChoicesResponses(context, tagsConfig, chainContent);
    if (tf.statements) statements.push(...tf.statements);
    if (tf.choices) choices.push(...tf.choices);
    if (tf.responses) responses.push(...tf.responses);
  } else if (instance2.isOfBitType(bitType, BitType2.highlightText)) {
    const highlight = buildHighlight(context, tagsConfig, chainContent);
    if (highlight) bodyParts.push(highlight);
  } else {
    const select = buildSelect(context, tagsConfig, chainContent);
    if (select) bodyParts.push(select);
  }
}
function buildStatement(context, tagsConfig, trueFalseContent) {
  const { bitType } = context;
  if (!instance2.isOfBitType(bitType, BitType2.trueFalse1)) return void 0;
  if (context.DEBUG_CHAIN_CONTENT)
    context.debugPrint("trueFalse V1 content (statement)", trueFalseContent);
  const { trueFalse, ...tags2 } = context.bitContentProcessor(
    ContentDepth.Chain,
    tagsConfig,
    trueFalseContent
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("trueFalse V1 tags (statement)", tags2);
  let statement;
  let firstTrueFalse;
  if (trueFalse && trueFalse.length > 0) {
    firstTrueFalse = trueFalse[0];
  }
  if (firstTrueFalse) {
    const { statement: _ignore, ...tagsRest } = tags2;
    statement = { ...firstTrueFalse, statement: firstTrueFalse.text, ...tagsRest };
  }
  return statement;
}
function buildStatementsChoicesResponses(context, tagsConfig, trueFalseContent) {
  const { bitType } = context;
  const insertStatements = instance2.isOfBitType(bitType, BitType2.trueFalse);
  const insertChoices = instance2.isOfBitType(bitType, [
    BitType2.multipleChoice,
    BitType2.multipleChoice1,
    BitType2.feedback
  ]);
  const insertResponses = instance2.isOfBitType(bitType, [
    BitType2.multipleResponse,
    BitType2.multipleResponse1
  ]);
  if (!insertStatements && !insertChoices && !insertResponses) return {};
  const statements = [];
  const choices = [];
  const responses = [];
  const trueFalseContents = context.splitBitContent(trueFalseContent, [
    TypeKey.True,
    TypeKey.False
  ]);
  if (context.DEBUG_CHAIN_CONTENT) {
    context.debugPrint("trueFalse V1 content (choices/responses)", trueFalseContents);
  }
  for (const contents of trueFalseContents) {
    const { trueFalse, ...tags2 } = context.bitContentProcessor(
      ContentDepth.Chain,
      tagsConfig,
      contents
    );
    if (context.DEBUG_CHAIN_TAGS) context.debugPrint("trueFalse V1 tags (choices/responses)", tags2);
    let firstTrueFalse;
    if (trueFalse && trueFalse.length > 0) {
      firstTrueFalse = trueFalse[0];
    }
    if (firstTrueFalse) {
      if (insertStatements) {
        const { statement: _ignore, ...tagsRest } = tags2;
        const statement = {
          ...firstTrueFalse,
          statement: firstTrueFalse.textAst ?? [],
          ...tagsRest
        };
        if (statement) statements.push(statement);
      } else if (insertChoices) {
        const choice = { ...firstTrueFalse, choice: firstTrueFalse.text, ...tags2 };
        if (choice) choices.push(choice);
      } else if (insertResponses) {
        const response = { ...firstTrueFalse, response: firstTrueFalse.text, ...tags2 };
        if (response) responses.push(response);
      }
    }
  }
  const res = {};
  if (insertStatements) {
    res.statements = statements;
  } else if (insertChoices) {
    res.choices = choices;
  } else if (insertResponses) {
    res.responses = responses;
  }
  return res;
}
function buildHighlight(context, tagsConfig, highlightContent) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("highlight content", highlightContent);
  const { trueFalse, ...tags2 } = context.bitContentProcessor(
    ContentDepth.Chain,
    tagsConfig,
    highlightContent
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("highlight TAGS", { trueFalse, ...tags2 });
  const texts = [];
  if (trueFalse) {
    for (const tf of trueFalse) {
      const ht = { ...tf, isHighlighted: false };
      if (ht) texts.push(ht);
    }
  }
  const highlight = {
    type: BodyBitType.highlight,
    texts,
    ...tags2
  };
  return highlight;
}
function buildSelect(context, tagsConfig, selectContent) {
  if (context.DEBUG_CHAIN_CONTENT) context.debugPrint("select content", selectContent);
  const { trueFalse, ...tags2 } = context.bitContentProcessor(
    ContentDepth.Chain,
    tagsConfig,
    selectContent
  );
  if (context.DEBUG_CHAIN_TAGS) context.debugPrint("select TAGS", { trueFalse, ...tags2 });
  const options = [];
  if (trueFalse) {
    for (const tf of trueFalse) {
      const so = tf;
      if (so) options.push(so);
    }
  }
  const select = {
    type: BodyBitType.select,
    options,
    ...tags2
  };
  return select;
}

// src/parser/bitmark/peg/BitmarkPegParserProcessor.ts
var DEBUG_BIT_RAW = true;
var DEBUG_BIT_CONTENT_RAW = false;
var DEBUG_BIT_CONTENT = true;
var DEBUG_BIT_TAGS = true;
var DEBUG_BODY = true;
var DEBUG_FOOTER = true;
var DEBUG_CHAIN_CONTENT = true;
var DEBUG_CHAIN_TAGS = true;
var DEBUG_CARD_SET_CONTENT = true;
var DEBUG_CARD_SET = true;
var DEBUG_CARD_TAGS = true;
var builder2 = new Builder();
var BitmarkPegParserProcessor = class {
  constructor(options) {
    __publicField(this, "context");
    __publicField(this, "nonFatalWarnings", []);
    __publicField(this, "nonFatalErrors", []);
    __publicField(this, "parser", {});
    __publicField(this, "parse");
    __publicField(this, "parserText");
    __publicField(this, "parserLocation");
    this.parse = options.parse;
    this.parserText = options.parserText;
    this.parserLocation = options.parserLocation;
    this.context = {
      bitConfig: instance2.getBitConfig(BitType2._error),
      bitType: BitType2._error,
      textFormat: TextFormat2.bitmarkText,
      DEBUG_BIT_RAW,
      DEBUG_BIT_CONTENT_RAW,
      DEBUG_BIT_CONTENT,
      DEBUG_BIT_TAGS,
      DEBUG_BODY,
      DEBUG_FOOTER,
      DEBUG_CHAIN_CONTENT,
      DEBUG_CHAIN_TAGS,
      DEBUG_CARD_SET_CONTENT,
      DEBUG_CARD_SET,
      DEBUG_CARD_TAGS,
      parser: this.parser,
      parse: this.parse,
      bitContentProcessor: this.bitContentProcessor.bind(this),
      splitBitContent: this.splitBitContent.bind(this),
      addWarning: this.addWarning.bind(this),
      addError: this.addError.bind(this),
      debugPrint: this.debugPrint.bind(this),
      state: {
        //
      }
    };
  }
  // Build bits
  buildBits(rawBits) {
    const bits = [];
    let errors = [];
    for (const rawBit of rawBits) {
      if (!rawBit) continue;
      const bit = rawBit.value;
      if (bit) {
        bits.push(bit);
      } else {
        errors = errors.concat(rawBit.errors ?? []);
      }
    }
    const res = builder2.buildBitmark({
      bits,
      errors: errors.length > 0 ? errors : void 0
    });
    return res;
  }
  // Build bit
  buildBit(bitHeader, bitContent) {
    const { bitType, textFormat, resourceType, bitLevel, isCommented } = bitHeader;
    if (!bitType || instance2.isOfBitType(bitType, BitType2._error)) return this.invalidBit();
    const bitConfig = instance2.getBitConfig(bitType);
    this.context.bitConfig = bitConfig;
    this.context.bitType = bitType;
    this.context.textFormat = textFormat;
    this.context.resourceType = resourceType;
    if (DEBUG_BIT_CONTENT_RAW) this.debugPrint("BIT CONTENT RAW", bitContent);
    const isTrueFalseV1 = instance2.isOfBitType(bitType, BitType2.trueFalse1);
    const isMultipleChoiceV1 = instance2.isOfBitType(bitType, BitType2.multipleChoice1);
    const isMultipleResponseV1 = instance2.isOfBitType(bitType, BitType2.multipleResponse1);
    if (DEBUG_BIT_CONTENT) this.debugPrint("BIT CONTENT", bitContent);
    this.resetParserState();
    bitContent = this.squashUnwantedInlineBodyBits(bitContent);
    bitContent = instance9.validateBitTags(this.context, bitContent);
    const {
      body,
      footer,
      cardSet,
      title,
      statement,
      statements,
      choices,
      responses,
      propertyStyleResources,
      resources,
      posterImage,
      internalComments,
      ...tags2
    } = this.bitContentProcessor(ContentDepth.Bit, bitConfig.tags, bitContent);
    if (DEBUG_BIT_TAGS) this.debugPrint("BIT TAGS", tags2);
    if (DEBUG_BODY) this.debugPrint("BIT BODY", body);
    if (DEBUG_FOOTER) this.debugPrint("BIT FOOTER", footer);
    const titles = buildTitles(this.context, bitType, title);
    const bitSpecificCards = buildCards(
      this.context,
      bitType,
      textFormat,
      cardSet,
      statement,
      statements,
      choices,
      responses
    );
    const filteredResources = buildResources(this.context, resourceType, resources);
    const internalComment = [
      ...internalComments ?? [],
      ...bitSpecificCards.internalComments ?? []
    ];
    const warnings = this.buildBitLevelWarnings();
    const errors = this.buildBitLevelErrors();
    if (warnings) this.parser.warnings = warnings;
    if (errors) this.parser.errors = errors;
    const bit = builder2.buildBit({
      bitType,
      bitLevel,
      isCommented,
      textFormat,
      resourceType,
      ...titles,
      posterImage,
      statement: isTrueFalseV1 ? statement : void 0,
      choices: isMultipleChoiceV1 ? choices : void 0,
      responses: isMultipleResponseV1 ? responses : void 0,
      ...tags2,
      ...propertyStyleResources,
      resources: filteredResources,
      ...bitSpecificCards,
      body,
      footer,
      internalComment,
      parser: this.parser
    });
    return { value: bit };
  }
  // Build bit for data that cannot be parsed
  invalidBit(bitData) {
    if (bitData) this.addError(`Invalid bit`);
    this.parser.errors = this.buildBitLevelErrors();
    const bit = builder2.buildBit({
      bitType: instance2.getBitType(BitType2._error),
      bitLevel: 1,
      parser: this.parser
    });
    return { value: bit };
  }
  // Build bit header
  buildBitHeader(bitTypeBreakscaped, bitLevel, textFormatAndResourceType) {
    const bitType = instance3.unbreakscape(bitTypeBreakscaped, {
      format: TextFormat2.plainText,
      location: TextLocation2.tag
    });
    const validBitType = instance2.getBitType(bitType);
    const commented = instance2.isBitTypeCommented(bitType);
    if (instance2.isOfBitType(validBitType, BitType2._error)) {
      this.addError(`Invalid bit type: '${bitType}'`);
    }
    const bitConfig = instance2.getBitConfig(validBitType);
    if (bitLevel > instance2.bitLevelMax) {
      this.addWarning(
        `Bit level of ${bitLevel} too high, setting to max value of ${instance2.bitLevelMax}`
      );
      bitLevel = instance2.bitLevelMax;
    } else if (bitLevel < instance2.bitLevelMin) {
      this.addWarning(
        `Bit level of ${bitLevel} too low, setting to min value of ${instance2.bitLevelMin}`
      );
      bitLevel = instance2.bitLevelMin;
    }
    let textFormat = Enum21(TextFormat2).fromValue(textFormatAndResourceType.textFormat);
    const isInvalidTextFormat = textFormatAndResourceType.textFormat && !textFormat;
    if (isInvalidTextFormat) {
      this.addWarning(
        `Invalid text format '${textFormatAndResourceType.textFormat}', defaulting to '${BodyTextFormat.bitmarkPlusPlus}'`
      );
    }
    const deprecatedTextFormat = Enum21(DeprecatedTextFormat).fromValue(
      textFormatAndResourceType.textFormat
    );
    if (deprecatedTextFormat) {
      textFormat = TextFormat2.bitmarkText;
      this.addWarning(
        `${deprecatedTextFormat} text format is deprecated. Bit will be parsed as '${BodyTextFormat.bitmarkPlusPlus}'`
      );
    }
    textFormat = textFormat ?? bitConfig.textFormatDefault;
    const resourceType = Enum21(ResourceType).fromValue(textFormatAndResourceType.resourceType);
    if (textFormatAndResourceType.resourceType && !resourceType) {
      this.addWarning(
        `Invalid resource type '${textFormatAndResourceType.resourceType}', it will be ignored`
      );
    }
    return {
      bitType: validBitType,
      bitLevel,
      textFormat: textFormat ?? bitConfig.textFormatDefault,
      resourceType,
      isCommented: commented
    };
  }
  // Build text and resource type
  buildTextAndResourceType(value1, value2) {
    const res = {};
    const processValue = (value) => {
      if (value) {
        const val = instance3.unbreakscape(stringUtils.string(value.value), {
          format: TextFormat2.plainText,
          location: TextLocation2.tag
        });
        if (value.type === TypeKey.TextFormat) {
          res.textFormat = val;
        } else {
          res.resourceType = val;
        }
      }
    };
    processValue(value1);
    processValue(value2);
    return res;
  }
  /**
   * Reset the parser state
   *
   */
  resetParserState() {
    this.context.state = {};
  }
  /**
   * Process Type/Key/Value data, squashing inline body bits back into text for bits / body text formats that
   * do not support them, and for after the plain text tag '$$$$'.
   *
   * @param data the bit content (Type/Key/Value data)
   * @returns
   */
  squashUnwantedInlineBodyBits(data) {
    const result = [];
    if (!data) return result;
    const { textFormat } = this.context;
    const isBitmarkText = textFormat === TextFormat2.bitmarkText;
    const convertTagToTextRecursive = (tag, str = "") => {
      const { chain, parser: parser2 } = tag;
      str += parser2.text;
      if (chain) {
        for (const t of chain) {
          str = convertTagToTextRecursive(t, str);
        }
      }
      return str;
    };
    let inPlainText = false;
    for (const content of data) {
      const { type, value } = content;
      const valueStr = value ?? "";
      let atPlainTextDivider = false;
      if (!inPlainText) {
        if (type === TypeKey.BodyText && !isBitmarkText && valueStr && valueStr.trim()) {
          inPlainText = true;
        }
        if (type === TypeKey.PlainTextDivider) {
          inPlainText = true;
          atPlainTextDivider = true;
        }
      }
      if (!atPlainTextDivider) {
        if (inPlainText) {
          if (type === TypeKey.BodyText) {
            result.push({ type: TypeKey.BodyTextPlain, value });
          } else {
            const s = convertTagToTextRecursive(content);
            result.push({ type: TypeKey.BodyTextPlain, value: s });
          }
        } else {
          result.push(content);
        }
      } else {
        result.push({ type: TypeKey.BodyText, value: "\n" });
      }
    }
    return result;
  }
  /**
   * Process Type/Key/Value data, building the bit parts as AST nodes.
   *
   * @param bitType bit type
   * @param textFormat bit text format type
   * @param contentDepth bit level (in chain, in card etc)
   * @param tagsConfig tags configuration at this parser level (pass undef)
   * @param data the bit content (Type/Key/Value data)
   * @returns
   */
  bitContentProcessor(contentDepth, tagsConfig, data) {
    const result = {};
    if (!data) return result;
    const { bitType, textFormat } = this.context;
    result.title = [];
    result.solutions = [];
    result.__solutionsAst = [];
    result.statements = [];
    result.choices = [];
    result.responses = [];
    result.propertyStyleResources = {};
    result.resources = [];
    result.trueFalse = [];
    result.markConfig = [];
    result.extraProperties = {};
    result.internalComments = [];
    let inFooter = false;
    const bodyParts = [];
    let bodyTextPart = instance3.EMPTY_STRING;
    let footer = instance3.EMPTY_STRING;
    let bodyPlainText = instance3.EMPTY_STRING;
    let footerPlainText = instance3.EMPTY_STRING;
    const inBit = contentDepth === ContentDepth.Bit;
    const inCard = contentDepth === ContentDepth.Card;
    const inChain = contentDepth === ContentDepth.Chain;
    const addBodyText = () => {
      if (bodyTextPart) {
        bodyTextPart = instance9.checkBodyPart(
          this.context,
          contentDepth,
          bodyTextPart
        );
        const bodyText = instance11.buildBodyText(bodyTextPart, false);
        bodyParts.push(bodyText);
      }
      bodyTextPart = instance3.EMPTY_STRING;
    };
    for (const content of data) {
      const { type, value } = content;
      switch (type) {
        case TypeKey.ItemLead: {
          itemLeadChainContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        }
        case TypeKey.Instruction:
        case TypeKey.Hint:
        case TypeKey.Anchor:
        case TypeKey.SampleSolution:
          defaultTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        case TypeKey.Reference:
          referenceTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        case TypeKey.Title:
          titleTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        case TypeKey.Property:
          propertyContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        case TypeKey.Gap: {
          if (!inChain) addBodyText();
          gapChainContentProcessor(
            this.context,
            contentDepth,
            tagsConfig,
            content,
            result,
            bodyParts
          );
          break;
        }
        case TypeKey.Mark: {
          if (!inChain) addBodyText();
          markChainContentProcessor(
            this.context,
            contentDepth,
            tagsConfig,
            content,
            result,
            bodyParts
          );
          break;
        }
        case TypeKey.True:
        case TypeKey.False: {
          if (!inChain) addBodyText();
          trueFalseChainContentProcessor(
            this.context,
            contentDepth,
            tagsConfig,
            content,
            result,
            bodyParts
          );
          break;
        }
        case TypeKey.Resource:
          resourceContentProcessor(this.context, contentDepth, tagsConfig, content, result);
          break;
        case TypeKey.CardSet: {
          result.cardSet = value;
          inFooter = true;
          break;
        }
        case TypeKey.BodyText:
        case TypeKey.CardText: {
          if (inFooter) {
            footer = instance3.concatenate(footer, value);
          } else {
            bodyTextPart = instance3.concatenate(bodyTextPart, value);
          }
          break;
        }
        case TypeKey.BodyTextPlain: {
          if (inFooter) {
            footerPlainText = instance3.concatenate(footerPlainText, value);
          } else {
            bodyPlainText = instance3.concatenate(bodyPlainText, value);
          }
          break;
        }
        case TypeKey.FooterDivider: {
          if (inFooter) {
            footer = instance3.concatenate(footer, value);
          }
          inFooter = true;
          break;
        }
        default:
      }
    }
    addBodyText();
    const bodyPlainTextNode = bodyPlainText ? instance11.buildBodyText(bodyPlainText, true) : void 0;
    if (bodyPlainTextNode) bodyParts.push(bodyPlainTextNode);
    if (result.itemLead) {
      const l = result.itemLead.length;
      if (l > 0) result.item = result.itemLead[0];
      if (l > 1) result.lead = result.itemLead[1];
      if (l > 2) result.pageNumber = result.itemLead[2];
      if (l > 3) result.marginNumber = result.itemLead[l - 1];
    }
    if (result.__itemLeadString) {
      const l = result.__itemLeadString.length;
      if (l > 0) result.__itemString = result.__itemLeadString[0];
      if (l > 1) result.__leadString = result.__itemLeadString[1];
      if (l > 2) result.__pageNumberString = result.__itemLeadString[2];
      if (l > 3) result.__marginNumberString = result.__itemLeadString[l - 1];
    }
    if (inBit) {
      result.body = instance11.process(
        this.context,
        contentDepth,
        bitType,
        textFormat,
        tagsConfig,
        result,
        bodyParts,
        false
      );
    } else if (inCard) {
      result.cardBody = instance11.process(
        this.context,
        contentDepth,
        bitType,
        textFormat,
        tagsConfig,
        result,
        bodyParts,
        true
      );
    }
    result.footer = instance12.process(
      this.context,
      contentDepth,
      tagsConfig,
      result,
      footer,
      footerPlainText
    );
    if (Object.keys(result.extraProperties).length === 0) delete result.extraProperties;
    if (result.title.length === 0) delete result.title;
    if (result.solutions.length === 0) delete result.solutions;
    if (result.statements.length === 0) delete result.statements;
    if (result.choices.length === 0) delete result.choices;
    if (result.responses.length === 0) delete result.responses;
    if (result.trueFalse.length === 0) delete result.trueFalse;
    if (result.markConfig.length === 0) delete result.markConfig;
    if (Object.keys(result.propertyStyleResources).length === 0)
      delete result.propertyStyleResources;
    if (result.resources.length === 0) delete result.resources;
    if (result.internalComments.length === 0) delete result.internalComments;
    return result;
  }
  buildBitLevelWarnings() {
    let warnings;
    if (this.nonFatalWarnings.length > 0) {
      warnings = this.nonFatalWarnings;
      this.nonFatalWarnings = [];
    }
    return warnings;
  }
  buildBitLevelErrors() {
    let errors;
    if (this.nonFatalErrors.length > 0) {
      errors = this.nonFatalErrors;
      this.nonFatalErrors = [];
    }
    return errors;
  }
  /**
   * Split bit content into parts based on the given type keys
   *
   * @param bitContent bit content to split
   * @param types to split on
   */
  splitBitContent(bitContent, types) {
    const parts = [];
    let part = [];
    for (const c of bitContent) {
      if (types.includes(c.type)) {
        if (part.length > 0) parts.push(part);
        part = [];
      }
      part.push(c);
    }
    if (part.length > 0) parts.push(part);
    return parts;
  }
  //
  // Util functions
  //
  /**
   * Add an warning to the list of non-fatal warnings
   * @param message The error message
   * @param parserData Parser data - if not set, the current parser data will be used
   * @param parserDataOriginal Parser data of the original instance of a duplicate error
   */
  addWarning(message, parserData, parserDataOriginal) {
    const warning = {
      message,
      text: parserData?.parser.text ?? this.parserText(),
      location: parserData?.parser.location ?? this.parserLocation(),
      original: parserDataOriginal?.parser ?? void 0
    };
    if (!warning.original) delete warning.original;
    this.nonFatalWarnings.push(warning);
  }
  /**
   * Add an error to the list of non-fatal errors
   * @param message The error message
   * @param parserData Parser data - if not set, the current parser data will be used
   * @param parserDataOriginal Parser data of the original instance of a duplicate error
   */
  addError(message, parserData, parserDataOriginal) {
    const error = {
      message,
      text: parserData?.parser.text ?? this.parserText(),
      location: parserData?.parser.location ?? this.parserLocation(),
      original: parserDataOriginal?.parser ?? void 0
    };
    if (!error.original) delete error.original;
    this.nonFatalErrors.push(error);
  }
  /**
   * Print out data for debugging
   *
   * @param header
   * @param data
   */
  debugPrint(header, data) {
  }
};

// src/generated/parser/bitmark/bitmark-peggy-parser.js
var peg$SyntaxError2 = class extends SyntaxError {
  constructor(message, expected, found, location) {
    super(message);
    this.expected = expected;
    this.found = found;
    this.location = location;
    this.name = "SyntaxError";
  }
  format(sources) {
    let str = "Error: " + this.message;
    if (this.location) {
      let src = null;
      const st = sources.find((s2) => s2.source === this.location.source);
      if (st) {
        src = st.text.split(/\r\n|\n|\r/g);
      }
      const s = this.location.start;
      const offset_s = this.location.source && typeof this.location.source.offset === "function" ? this.location.source.offset(s) : s;
      const loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
      if (src) {
        const e = this.location.end;
        const filler = "".padEnd(offset_s.line.toString().length, " ");
        const line = src[s.line - 1];
        const last = s.line === e.line ? e.column : line.length + 1;
        const hatLen = last - s.column || 1;
        str += "\n --> " + loc + "\n" + filler + " |\n" + offset_s.line + " | " + line + "\n" + filler + " | " + "".padEnd(s.column - 1, " ") + "".padEnd(hatLen, "^");
      } else {
        str += "\n at " + loc;
      }
    }
    return str;
  }
  static buildMessage(expected, found) {
    function hex(ch) {
      return ch.codePointAt(0).toString(16).toUpperCase();
    }
    const nonPrintable = Object.prototype.hasOwnProperty.call(RegExp.prototype, "unicode") ? new RegExp("[\\p{C}\\p{Mn}\\p{Mc}]", "gu") : null;
    function unicodeEscape(s) {
      if (nonPrintable) {
        return s.replace(nonPrintable, (ch) => "\\u{" + hex(ch) + "}");
      }
      return s;
    }
    function literalEscape(s) {
      return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
    }
    function classEscape(s) {
      return unicodeEscape(s.replace(/\\/g, "\\\\").replace(/\]/g, "\\]").replace(/\^/g, "\\^").replace(/-/g, "\\-").replace(/\0/g, "\\0").replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/[\x00-\x0F]/g, (ch) => "\\x0" + hex(ch)).replace(/[\x10-\x1F\x7F-\x9F]/g, (ch) => "\\x" + hex(ch)));
    }
    const DESCRIBE_EXPECTATION_FNS = {
      literal(expectation) {
        return '"' + literalEscape(expectation.text) + '"';
      },
      class(expectation) {
        const escapedParts = expectation.parts.map(
          (part) => Array.isArray(part) ? classEscape(part[0]) + "-" + classEscape(part[1]) : classEscape(part)
        );
        return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]" + (expectation.unicode ? "u" : "");
      },
      any() {
        return "any character";
      },
      end() {
        return "end of input";
      },
      other(expectation) {
        return expectation.description;
      }
    };
    function describeExpectation(expectation) {
      return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
    }
    function describeExpected(expected2) {
      const descriptions = expected2.map(describeExpectation);
      descriptions.sort();
      if (descriptions.length > 0) {
        let j = 1;
        for (let i = 1; i < descriptions.length; i++) {
          if (descriptions[i - 1] !== descriptions[i]) {
            descriptions[j] = descriptions[i];
            j++;
          }
        }
        descriptions.length = j;
      }
      switch (descriptions.length) {
        case 1:
          return descriptions[0];
        case 2:
          return descriptions[0] + " or " + descriptions[1];
        default:
          return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1];
      }
    }
    function describeFound(found2) {
      return found2 ? '"' + literalEscape(found2) + '"' : "end of input";
    }
    return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
  }
};
function peg$parse2(input, options) {
  options = options !== void 0 ? options : {};
  const peg$FAILED = {};
  const peg$source = options.grammarSource;
  const peg$startRuleFunctions = {
    bitmark: peg$parsebitmark,
    bit: peg$parsebit,
    cardContent: peg$parsecardContent
  };
  let peg$startRuleFunction = peg$parsebitmark;
  const peg$c0 = "[.";
  const peg$c1 = "]";
  const peg$c2 = "[";
  const peg$c3 = ":";
  const peg$c4 = "&";
  const peg$c5 = "$$$$";
  const peg$c6 = "====";
  const peg$c7 = "text";
  const peg$c8 = "~~~~";
  const peg$c9 = "footer";
  const peg$c10 = " ";
  const peg$c11 = "--";
  const peg$c12 = "++";
  const peg$c13 = "===";
  const peg$c14 = "==";
  const peg$c15 = "[@id";
  const peg$c16 = "#";
  const peg$c17 = "[\u25BC";
  const peg$c18 = "[\u25BA";
  const peg$c19 = "[@";
  const peg$c20 = "[%";
  const peg$c21 = "[!";
  const peg$c22 = "[?";
  const peg$c23 = "[+";
  const peg$c24 = "[-";
  const peg$c25 = "[$";
  const peg$c26 = "[_";
  const peg$c27 = "[=";
  const peg$c28 = "[&";
  const peg$c29 = "^]";
  const peg$c30 = "id:";
  const peg$c31 = ".";
  const peg$c32 = "\n";
  const peg$c33 = "\r\n";
  const peg$r0 = /^[^\]]/;
  const peg$r1 = /^[a-z]/;
  const peg$r2 = /^[a-z0-9\-]/;
  const peg$r3 = /^[^:\]]/;
  const peg$r4 = /^[^&:\]]/;
  const peg$r5 = /^[^\n\r\u2028\u2029]/;
  const peg$r6 = /^[ \t]/;
  const peg$r7 = /^[\r\u2028-\u2029]/;
  const peg$r8 = /^[ \t\n\r\u2028\u2029]/;
  const peg$e0 = peg$literalExpectation("[.", false);
  const peg$e1 = peg$classExpectation(["]"], true, false, false);
  const peg$e2 = peg$literalExpectation("]", false);
  const peg$e3 = peg$literalExpectation("[", false);
  const peg$e4 = peg$literalExpectation(":", false);
  const peg$e5 = peg$literalExpectation("&", false);
  const peg$e6 = peg$anyExpectation();
  const peg$e7 = peg$literalExpectation("$$$$", false);
  const peg$e8 = peg$literalExpectation("====", false);
  const peg$e9 = peg$literalExpectation("text", false);
  const peg$e10 = peg$literalExpectation("~~~~", false);
  const peg$e11 = peg$literalExpectation("footer", false);
  const peg$e12 = peg$literalExpectation(" ", false);
  const peg$e13 = peg$literalExpectation("--", false);
  const peg$e14 = peg$literalExpectation("++", false);
  const peg$e15 = peg$literalExpectation("===", false);
  const peg$e16 = peg$literalExpectation("==", false);
  const peg$e17 = peg$classExpectation([["a", "z"]], false, false, false);
  const peg$e18 = peg$classExpectation([["a", "z"], ["0", "9"], "-"], false, false, false);
  const peg$e19 = peg$literalExpectation("[@id", false);
  const peg$e20 = peg$literalExpectation("#", false);
  const peg$e21 = peg$literalExpectation("[\u25BC", false);
  const peg$e22 = peg$literalExpectation("[\u25BA", false);
  const peg$e23 = peg$literalExpectation("[@", false);
  const peg$e24 = peg$literalExpectation("[%", false);
  const peg$e25 = peg$literalExpectation("[!", false);
  const peg$e26 = peg$literalExpectation("[?", false);
  const peg$e27 = peg$literalExpectation("[+", false);
  const peg$e28 = peg$literalExpectation("[-", false);
  const peg$e29 = peg$literalExpectation("[$", false);
  const peg$e30 = peg$literalExpectation("[_", false);
  const peg$e31 = peg$literalExpectation("[=", false);
  const peg$e32 = peg$literalExpectation("[&", false);
  const peg$e33 = peg$literalExpectation("^]", false);
  const peg$e34 = peg$literalExpectation("id:", false);
  const peg$e35 = peg$classExpectation([":", "]"], true, false, false);
  const peg$e36 = peg$literalExpectation(".", false);
  const peg$e37 = peg$classExpectation(["&", ":", "]"], true, false, false);
  const peg$e38 = peg$otherExpectation("Character");
  const peg$e39 = peg$classExpectation(["\n", "\r", "\u2028", "\u2029"], true, false, false);
  const peg$e40 = peg$otherExpectation("Blank Line");
  const peg$e41 = peg$classExpectation([" ", "	"], false, false, false);
  const peg$e42 = peg$otherExpectation("Line Terminator");
  const peg$e43 = peg$literalExpectation("\n", false);
  const peg$e44 = peg$literalExpectation("\r\n", false);
  const peg$e45 = peg$classExpectation(["\r", ["\u2028", "\u2029"]], false, false, false);
  const peg$e46 = peg$otherExpectation("Whitespace, then Line Terminator");
  const peg$e47 = peg$otherExpectation("whitespace");
  const peg$e48 = peg$classExpectation([" ", "	", "\n", "\r", "\u2028", "\u2029"], false, false, false);
  function peg$f0(firstBit, bits) {
    return processor.buildBits([firstBit, ...bits]);
  }
  function peg$f1(bit) {
    return helper.handleRawBit(bit);
  }
  function peg$f2(bit) {
    return bit;
  }
  function peg$f3(bit) {
    return helper.handleRawBit(bit);
  }
  function peg$f4(bitHeader) {
    return bitHeader;
  }
  function peg$f5(bit) {
    return bit;
  }
  function peg$f6(bitHeader, bitContent) {
    return processor.buildBit(bitHeader, bitContent);
  }
  function peg$f7(bit) {
    return processor.invalidBit(bit);
  }
  function peg$f8(bitLevel, bitType, formatAndResource) {
    return processor.buildBitHeader(bitType, bitLevel, formatAndResource);
  }
  function peg$f9(value1, value2) {
    return processor.buildTextAndResourceType(value1, value2);
  }
  function peg$f10(value) {
    return helper.handleTextFormat(value);
  }
  function peg$f11(value) {
    return helper.handleResourceType(value);
  }
  function peg$f12(value) {
    return helper.handleBitContent(value);
  }
  function peg$f13(value) {
    return helper.handleTagChain(value);
  }
  function peg$f14(value) {
    return value;
  }
  function peg$f15(value) {
    return helper.handleBitTag(value);
  }
  function peg$f16(value) {
    return { type: TypeKey.BodyChar, value };
  }
  function peg$f17(value) {
    return helper.handlePlainTextDivider(value);
  }
  function peg$f18(value) {
    return helper.handleFooterDivider(value);
  }
  function peg$f19(value) {
    return helper.handleCardSet(value[1].flat());
  }
  function peg$f20() {
    helper.handleCardSetStart();
  }
  function peg$f21() {
    helper.handleCardSetEnd();
  }
  function peg$f22(value) {
    return helper.handleCards(value);
  }
  function peg$f23(value) {
    return helper.handleCardLineOrDivider(value, 2);
  }
  function peg$f24(qualifier) {
    return ["====", qualifier];
  }
  function peg$f25() {
    return ["===="];
  }
  function peg$f26(qualifier) {
    return ["--", qualifier];
  }
  function peg$f27() {
    return ["--"];
  }
  function peg$f28(qualifier) {
    return ["++", qualifier];
  }
  function peg$f29() {
    return ["++"];
  }
  function peg$f30(value) {
    return helper.handleCardLine(value);
  }
  function peg$f31(value) {
    return helper.handleCardSet(value[1].flat());
  }
  function peg$f32() {
    helper.handleCardSetStart();
  }
  function peg$f33() {
    helper.handleCardSetEnd();
  }
  function peg$f34(value) {
    return helper.handleCards(value);
  }
  function peg$f35(value) {
    return helper.handleCardLineOrDivider(value, 1);
  }
  function peg$f36(value) {
    return helper.handleCardLine(value);
  }
  function peg$f37(value) {
    return value;
  }
  function peg$f38(value) {
    return helper.handleCardContent(value);
  }
  function peg$f39(value) {
    return { type: TypeKey.CardChar, value };
  }
  function peg$f40(value) {
    return helper.handlePropertyTag("id", value);
  }
  function peg$f41(level, title) {
    return helper.handleTag(TypeKey.Title, { level, title });
  }
  function peg$f42(value) {
    return helper.handleTag(TypeKey.Anchor, value);
  }
  function peg$f43(value) {
    return helper.handleTag(TypeKey.Reference, value);
  }
  function peg$f44(key, value) {
    return helper.handlePropertyTag(key, value);
  }
  function peg$f45(value) {
    return helper.handleTag(TypeKey.ItemLead, value);
  }
  function peg$f46(value) {
    return helper.handleTag(TypeKey.Instruction, value);
  }
  function peg$f47(value) {
    return helper.handleTag(TypeKey.Hint, value);
  }
  function peg$f48(value) {
    return helper.handleTag(TypeKey.True, value);
  }
  function peg$f49(value) {
    return helper.handleTag(TypeKey.False, value);
  }
  function peg$f50(value) {
    return helper.handleTag(TypeKey.SampleSolution, value);
  }
  function peg$f51(value) {
    return helper.handleTag(TypeKey.Gap, value);
  }
  function peg$f52(value) {
    return helper.handleTag(TypeKey.Mark, value);
  }
  function peg$f53(key, value) {
    return helper.handleResourceTag(key, value);
  }
  function peg$f54(value) {
    return value;
  }
  function peg$f55(value) {
    return value ? value.trim() : "";
  }
  function peg$f56(value) {
    return value.length;
  }
  function peg$f57(value) {
    return value ? value.trim() : null;
  }
  function peg$f58(value) {
    return value ? value.trim() : "";
  }
  function peg$f59() {
    return true;
  }
  let peg$currPos = options.peg$currPos | 0;
  let peg$savedPos = peg$currPos;
  const peg$posDetailsCache = [{ line: 1, column: 1 }];
  let peg$maxFailPos = peg$currPos;
  let peg$maxFailExpected = options.peg$maxFailExpected || [];
  let peg$silentFails = options.peg$silentFails | 0;
  let peg$result;
  if (options.startRule) {
    if (!(options.startRule in peg$startRuleFunctions)) {
      throw new Error(`Can't start parsing from rule "` + options.startRule + '".');
    }
    peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
  }
  function text() {
    return input.substring(peg$savedPos, peg$currPos);
  }
  function offset() {
    return peg$savedPos;
  }
  function range() {
    return {
      source: peg$source,
      start: peg$savedPos,
      end: peg$currPos
    };
  }
  function location() {
    return peg$computeLocation(peg$savedPos, peg$currPos);
  }
  function expected(description, location2) {
    location2 = location2 !== void 0 ? location2 : peg$computeLocation(peg$savedPos, peg$currPos);
    throw peg$buildStructuredError(
      [peg$otherExpectation(description)],
      input.substring(peg$savedPos, peg$currPos),
      location2
    );
  }
  function error(message, location2) {
    location2 = location2 !== void 0 ? location2 : peg$computeLocation(peg$savedPos, peg$currPos);
    throw peg$buildSimpleError(message, location2);
  }
  function peg$getUnicode(pos = peg$currPos) {
    const cp = input.codePointAt(pos);
    if (cp === void 0) {
      return "";
    }
    return String.fromCodePoint(cp);
  }
  function peg$literalExpectation(text2, ignoreCase) {
    return { type: "literal", text: text2, ignoreCase };
  }
  function peg$classExpectation(parts, inverted, ignoreCase, unicode) {
    return { type: "class", parts, inverted, ignoreCase, unicode };
  }
  function peg$anyExpectation() {
    return { type: "any" };
  }
  function peg$endExpectation() {
    return { type: "end" };
  }
  function peg$otherExpectation(description) {
    return { type: "other", description };
  }
  function peg$computePosDetails(pos) {
    let details = peg$posDetailsCache[pos];
    let p;
    if (details) {
      return details;
    } else {
      if (pos >= peg$posDetailsCache.length) {
        p = peg$posDetailsCache.length - 1;
      } else {
        p = pos;
        while (!peg$posDetailsCache[--p]) {
        }
      }
      details = peg$posDetailsCache[p];
      details = {
        line: details.line,
        column: details.column
      };
      while (p < pos) {
        if (input.charCodeAt(p) === 10) {
          details.line++;
          details.column = 1;
        } else {
          details.column++;
        }
        p++;
      }
      peg$posDetailsCache[pos] = details;
      return details;
    }
  }
  function peg$computeLocation(startPos, endPos, offset2) {
    const startPosDetails = peg$computePosDetails(startPos);
    const endPosDetails = peg$computePosDetails(endPos);
    const res = {
      source: peg$source,
      start: {
        offset: startPos,
        line: startPosDetails.line,
        column: startPosDetails.column
      },
      end: {
        offset: endPos,
        line: endPosDetails.line,
        column: endPosDetails.column
      }
    };
    if (offset2 && peg$source && typeof peg$source.offset === "function") {
      res.start = peg$source.offset(res.start);
      res.end = peg$source.offset(res.end);
    }
    return res;
  }
  function peg$fail(expected2) {
    if (peg$currPos < peg$maxFailPos) {
      return;
    }
    if (peg$currPos > peg$maxFailPos) {
      peg$maxFailPos = peg$currPos;
      peg$maxFailExpected = [];
    }
    peg$maxFailExpected.push(expected2);
  }
  function peg$buildSimpleError(message, location2) {
    return new peg$SyntaxError2(message, null, null, location2);
  }
  function peg$buildStructuredError(expected2, found, location2) {
    return new peg$SyntaxError2(
      peg$SyntaxError2.buildMessage(expected2, found),
      expected2,
      found,
      location2
    );
  }
  function peg$parsebitmark() {
    let s0;
    s0 = peg$parseBM_Bitmark();
    return s0;
  }
  function peg$parseBM_Bitmark() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseWS();
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseWS();
    }
    s2 = peg$parseBM_FirstBit();
    s3 = peg$parseBM_Bits();
    peg$savedPos = s0;
    s0 = peg$f0(s2, s3);
    return s0;
  }
  function peg$parseBM_FirstBit() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = [];
    s3 = peg$parseBM_BodyLine();
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      s3 = peg$parseBM_BodyLine();
    }
    s1 = input.substring(s1, peg$currPos);
    peg$savedPos = s0;
    s1 = peg$f1(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseBM_Bits() {
    let s0, s1;
    s0 = [];
    s1 = peg$parseBM_NL_Bit();
    while (s1 !== peg$FAILED) {
      s0.push(s1);
      s1 = peg$parseBM_NL_Bit();
    }
    return s0;
  }
  function peg$parseBM_NL_Bit() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseBlankLine();
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseBlankLine();
    }
    s2 = peg$parseNL();
    if (s2 === peg$FAILED) {
      s2 = null;
    }
    s3 = peg$parseBM_Bit();
    if (s3 !== peg$FAILED) {
      peg$savedPos = s0;
      s0 = peg$f2(s3);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBM_Bit() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$currPos;
    s3 = peg$parseBM_BitHeader();
    if (s3 !== peg$FAILED) {
      s4 = [];
      s5 = peg$parseBM_BodyLine();
      while (s5 !== peg$FAILED) {
        s4.push(s5);
        s5 = peg$parseBM_BodyLine();
      }
      s3 = [s3, s4];
      s2 = s3;
    } else {
      peg$currPos = s2;
      s2 = peg$FAILED;
    }
    if (s2 !== peg$FAILED) {
      s1 = input.substring(s1, peg$currPos);
    } else {
      s1 = s2;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f3(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseBM_BitHeader() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c0) {
      s1 = peg$c0;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e0);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = [];
      s3 = input.charAt(peg$currPos);
      if (peg$r0.test(s3)) {
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e1);
        }
      }
      while (s3 !== peg$FAILED) {
        s2.push(s3);
        s3 = input.charAt(peg$currPos);
        if (peg$r0.test(s3)) {
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e1);
          }
        }
      }
      if (input.charCodeAt(peg$currPos) === 93) {
        s3 = peg$c1;
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e2);
        }
      }
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f4(s1);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBM_BodyLine() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$parseNL();
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      peg$silentFails++;
      s3 = peg$parseBitHeader();
      peg$silentFails--;
      if (s3 === peg$FAILED) {
        s2 = void 0;
      } else {
        peg$currPos = s2;
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = [];
      s1 = peg$parseChar();
      if (s1 !== peg$FAILED) {
        while (s1 !== peg$FAILED) {
          s0.push(s1);
          s1 = peg$parseChar();
        }
      } else {
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parsebit() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseBlankLine();
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseBlankLine();
    }
    s2 = peg$parseBit();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseBlankLine();
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$parseBlankLine();
      }
      peg$savedPos = s0;
      s0 = peg$f5(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBit() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseBitHeader();
    if (s1 !== peg$FAILED) {
      s2 = peg$parseBitContent();
      peg$savedPos = s0;
      s0 = peg$f6(s1, s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = peg$currPos;
      s2 = peg$parseAnything();
      s1 = input.substring(s1, peg$currPos);
      peg$savedPos = s0;
      s1 = peg$f7(s1);
      s0 = s1;
    }
    return s0;
  }
  function peg$parseBitHeader() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 91) {
      s1 = peg$c2;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e3);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseBit_Level();
      if (s2 !== peg$FAILED) {
        s3 = peg$parseBit_Value();
        s4 = peg$parseTextFormatAndResourceType();
        if (input.charCodeAt(peg$currPos) === 93) {
          s5 = peg$c1;
          peg$currPos++;
        } else {
          s5 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e2);
          }
        }
        if (s5 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f8(s2, s3, s4);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseTextFormatAndResourceType() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$parseTextFormat();
    if (s1 === peg$FAILED) {
      s1 = peg$parseResourceType();
    }
    if (s1 === peg$FAILED) {
      s1 = null;
    }
    s2 = peg$parseTextFormat();
    if (s2 === peg$FAILED) {
      s2 = peg$parseResourceType();
    }
    if (s2 === peg$FAILED) {
      s2 = null;
    }
    peg$savedPos = s0;
    s0 = peg$f9(s1, s2);
    return s0;
  }
  function peg$parseTextFormat() {
    let s0, s1, s2;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 58) {
      s1 = peg$c3;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e4);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseBit_Value();
      peg$savedPos = s0;
      s0 = peg$f10(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseResourceType() {
    let s0, s1, s2;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 38) {
      s1 = peg$c4;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e5);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseBit_Value();
      peg$savedPos = s0;
      s0 = peg$f11(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseBitContent() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseDivider();
    if (s2 === peg$FAILED) {
      s2 = peg$parseCardSet_V2();
      if (s2 === peg$FAILED) {
        s2 = peg$parseCardSet_V1();
        if (s2 === peg$FAILED) {
          s2 = peg$parseTagChain();
          if (s2 === peg$FAILED) {
            s2 = peg$parseBodyChar();
          }
        }
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseDivider();
      if (s2 === peg$FAILED) {
        s2 = peg$parseCardSet_V2();
        if (s2 === peg$FAILED) {
          s2 = peg$parseCardSet_V1();
          if (s2 === peg$FAILED) {
            s2 = peg$parseTagChain();
            if (s2 === peg$FAILED) {
              s2 = peg$parseBodyChar();
            }
          }
        }
      }
    }
    peg$savedPos = s0;
    s1 = peg$f12(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseTagChain() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseBitTag();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseChainedBitTag();
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$parseChainedBitTag();
      }
      s2 = [s2, s3];
      s1 = s2;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f13(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseChainedBitTag() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseBitTag();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f14(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseBitTag() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseIDTag();
    if (s1 === peg$FAILED) {
      s1 = peg$parsePropertyTag();
      if (s1 === peg$FAILED) {
        s1 = peg$parseTitleTag();
        if (s1 === peg$FAILED) {
          s1 = peg$parseAnchorTag();
          if (s1 === peg$FAILED) {
            s1 = peg$parseReferenceTag();
            if (s1 === peg$FAILED) {
              s1 = peg$parseItemLeadTag();
              if (s1 === peg$FAILED) {
                s1 = peg$parseInstructionTag();
                if (s1 === peg$FAILED) {
                  s1 = peg$parseHintTag();
                  if (s1 === peg$FAILED) {
                    s1 = peg$parseGapTag();
                    if (s1 === peg$FAILED) {
                      s1 = peg$parseMarkTag();
                      if (s1 === peg$FAILED) {
                        s1 = peg$parseSampleSolutionTag();
                        if (s1 === peg$FAILED) {
                          s1 = peg$parseTrueTag();
                          if (s1 === peg$FAILED) {
                            s1 = peg$parseFalseTag();
                            if (s1 === peg$FAILED) {
                              s1 = peg$parseResourceTag();
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f15(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseBodyChar() {
    let s0, s1;
    s0 = peg$currPos;
    if (input.length > peg$currPos) {
      s1 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e6);
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f16(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseDivider() {
    let s0;
    s0 = peg$parseFooterDivider();
    if (s0 === peg$FAILED) {
      s0 = peg$parsePlainTextDivider();
    }
    return s0;
  }
  function peg$parsePlainTextDividerText() {
    let s0, s1, s2, s3, s4, s5;
    if (input.substr(peg$currPos, 4) === peg$c5) {
      s0 = peg$c5;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e7);
      }
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 4) === peg$c6) {
        s1 = peg$c6;
        peg$currPos += 4;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e8);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = [];
        s3 = peg$parseWS();
        if (s3 !== peg$FAILED) {
          while (s3 !== peg$FAILED) {
            s2.push(s3);
            s3 = peg$parseWS();
          }
        } else {
          s2 = peg$FAILED;
        }
        if (s2 !== peg$FAILED) {
          if (input.substr(peg$currPos, 4) === peg$c7) {
            s3 = peg$c7;
            peg$currPos += 4;
          } else {
            s3 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e9);
            }
          }
          if (s3 !== peg$FAILED) {
            s4 = [];
            s5 = peg$parseWS();
            if (s5 !== peg$FAILED) {
              while (s5 !== peg$FAILED) {
                s4.push(s5);
                s5 = peg$parseWS();
              }
            } else {
              s4 = peg$FAILED;
            }
            if (s4 !== peg$FAILED) {
              if (input.substr(peg$currPos, 4) === peg$c6) {
                s5 = peg$c6;
                peg$currPos += 4;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e8);
                }
              }
              if (s5 !== peg$FAILED) {
                s1 = [s1, s2, s3, s4, s5];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parsePlainTextDivider() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseNL();
    if (s2 !== peg$FAILED) {
      s3 = peg$parsePlainTextDividerText();
      if (s3 !== peg$FAILED) {
        s4 = peg$parseWNL();
        if (s4 !== peg$FAILED) {
          s2 = [s2, s3, s4];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f17(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseFooterDividerText() {
    let s0, s1, s2, s3, s4, s5;
    if (input.substr(peg$currPos, 4) === peg$c8) {
      s0 = peg$c8;
      peg$currPos += 4;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e10);
      }
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 4) === peg$c6) {
        s1 = peg$c6;
        peg$currPos += 4;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e8);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = [];
        s3 = peg$parseWS();
        if (s3 !== peg$FAILED) {
          while (s3 !== peg$FAILED) {
            s2.push(s3);
            s3 = peg$parseWS();
          }
        } else {
          s2 = peg$FAILED;
        }
        if (s2 !== peg$FAILED) {
          if (input.substr(peg$currPos, 6) === peg$c9) {
            s3 = peg$c9;
            peg$currPos += 6;
          } else {
            s3 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e11);
            }
          }
          if (s3 !== peg$FAILED) {
            s4 = [];
            s5 = peg$parseWS();
            if (s5 !== peg$FAILED) {
              while (s5 !== peg$FAILED) {
                s4.push(s5);
                s5 = peg$parseWS();
              }
            } else {
              s4 = peg$FAILED;
            }
            if (s4 !== peg$FAILED) {
              if (input.substr(peg$currPos, 4) === peg$c6) {
                s5 = peg$c6;
                peg$currPos += 4;
              } else {
                s5 = peg$FAILED;
                if (peg$silentFails === 0) {
                  peg$fail(peg$e8);
                }
              }
              if (s5 !== peg$FAILED) {
                s1 = [s1, s2, s3, s4, s5];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseFooterDivider() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseNL();
    if (s2 !== peg$FAILED) {
      s3 = peg$parseFooterDividerText();
      if (s3 !== peg$FAILED) {
        s4 = peg$parseWNL();
        if (s4 !== peg$FAILED) {
          s2 = [s2, s3, s4];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f18(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardSet_V2() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseCardSetStart_V2();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseCards_V2();
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$parseCards_V2();
      }
      s4 = peg$parseCardSetEnd_V2();
      if (s4 !== peg$FAILED) {
        s2 = [s2, s3, s4];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f19(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardSetStart_V2() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseWNL();
    if (s2 === peg$FAILED) {
      s2 = peg$parseNL();
    }
    if (s2 !== peg$FAILED) {
      while (s2 !== peg$FAILED) {
        s1.push(s2);
        s2 = peg$parseWNL();
        if (s2 === peg$FAILED) {
          s2 = peg$parseNL();
        }
      }
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      peg$silentFails++;
      s3 = peg$parseCardDividerLookahead_V2();
      peg$silentFails--;
      if (s3 !== peg$FAILED) {
        peg$currPos = s2;
        s2 = void 0;
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f20();
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCardSetEnd_V2() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseFooterDividerText();
    if (s2 === peg$FAILED) {
      s2 = null;
    }
    s3 = peg$currPos;
    peg$silentFails++;
    s4 = peg$parseWEOL();
    peg$silentFails--;
    if (s4 !== peg$FAILED) {
      peg$currPos = s3;
      s3 = void 0;
    } else {
      s3 = peg$FAILED;
    }
    if (s3 !== peg$FAILED) {
      s2 = [s2, s3];
      s1 = s2;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f21();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCards_V2() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseCardLineOrDivider_V2();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f22(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardLineOrDivider_V2() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseCardDividerToken_V2();
    if (s1 === peg$FAILED) {
      s1 = peg$parseCardSideDividerToken_V2();
      if (s1 === peg$FAILED) {
        s1 = peg$parseCardVariantDividerToken_V2();
        if (s1 === peg$FAILED) {
          s1 = peg$parseCardLine_V2();
        }
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f23(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardDividerToken_V2() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 4) === peg$c6) {
      s1 = peg$c6;
      peg$currPos += 4;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e8);
      }
    }
    if (s1 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 32) {
        s2 = peg$c10;
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e12);
        }
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseQualifier();
        if (s3 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 32) {
            s4 = peg$c10;
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e12);
            }
          }
          if (s4 !== peg$FAILED) {
            if (input.substr(peg$currPos, 4) === peg$c6) {
              s5 = peg$c6;
              peg$currPos += 4;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e8);
              }
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parseWNL();
              if (s6 === peg$FAILED) {
                s6 = peg$parseWEOL();
              }
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f24(s3);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 4) === peg$c6) {
        s1 = peg$c6;
        peg$currPos += 4;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e8);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = peg$parseWNL();
        if (s2 === peg$FAILED) {
          s2 = peg$parseWEOL();
        }
        if (s2 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f25();
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseCardSideDividerToken_V2() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c11) {
      s1 = peg$c11;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e13);
      }
    }
    if (s1 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 32) {
        s2 = peg$c10;
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e12);
        }
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseQualifier();
        if (s3 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 32) {
            s4 = peg$c10;
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e12);
            }
          }
          if (s4 !== peg$FAILED) {
            if (input.substr(peg$currPos, 2) === peg$c11) {
              s5 = peg$c11;
              peg$currPos += 2;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e13);
              }
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parseWNL();
              if (s6 === peg$FAILED) {
                s6 = peg$parseWEOL();
              }
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f26(s3);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 2) === peg$c11) {
        s1 = peg$c11;
        peg$currPos += 2;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e13);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = peg$parseWNL();
        if (s2 === peg$FAILED) {
          s2 = peg$parseWEOL();
        }
        if (s2 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f27();
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseCardVariantDividerToken_V2() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c12) {
      s1 = peg$c12;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e14);
      }
    }
    if (s1 !== peg$FAILED) {
      if (input.charCodeAt(peg$currPos) === 32) {
        s2 = peg$c10;
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e12);
        }
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseQualifier();
        if (s3 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 32) {
            s4 = peg$c10;
            peg$currPos++;
          } else {
            s4 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e12);
            }
          }
          if (s4 !== peg$FAILED) {
            if (input.substr(peg$currPos, 2) === peg$c12) {
              s5 = peg$c12;
              peg$currPos += 2;
            } else {
              s5 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e14);
              }
            }
            if (s5 !== peg$FAILED) {
              s6 = peg$parseWNL();
              if (s6 === peg$FAILED) {
                s6 = peg$parseWEOL();
              }
              if (s6 !== peg$FAILED) {
                peg$savedPos = s0;
                s0 = peg$f28(s3);
              } else {
                peg$currPos = s0;
                s0 = peg$FAILED;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$FAILED;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      if (input.substr(peg$currPos, 2) === peg$c12) {
        s1 = peg$c12;
        peg$currPos += 2;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e14);
        }
      }
      if (s1 !== peg$FAILED) {
        s2 = peg$parseWNL();
        if (s2 === peg$FAILED) {
          s2 = peg$parseWEOL();
        }
        if (s2 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f29();
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseCardDividerLookahead_V2() {
    let s0, s1, s2, s3, s4, s5, s6;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 4) === peg$c6) {
      s1 = peg$c6;
      peg$currPos += 4;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e8);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      if (input.charCodeAt(peg$currPos) === 32) {
        s3 = peg$c10;
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e12);
        }
      }
      if (s3 !== peg$FAILED) {
        s4 = peg$parseQualifier();
        if (s4 !== peg$FAILED) {
          if (input.charCodeAt(peg$currPos) === 32) {
            s5 = peg$c10;
            peg$currPos++;
          } else {
            s5 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e12);
            }
          }
          if (s5 !== peg$FAILED) {
            if (input.substr(peg$currPos, 4) === peg$c6) {
              s6 = peg$c6;
              peg$currPos += 4;
            } else {
              s6 = peg$FAILED;
              if (peg$silentFails === 0) {
                peg$fail(peg$e8);
              }
            }
            if (s6 !== peg$FAILED) {
              s3 = [s3, s4, s5, s6];
              s2 = s3;
            } else {
              peg$currPos = s2;
              s2 = peg$FAILED;
            }
          } else {
            peg$currPos = s2;
            s2 = peg$FAILED;
          }
        } else {
          peg$currPos = s2;
          s2 = peg$FAILED;
        }
      } else {
        peg$currPos = s2;
        s2 = peg$FAILED;
      }
      if (s2 === peg$FAILED) {
        s2 = null;
      }
      s3 = peg$parseWNL();
      if (s3 === peg$FAILED) {
        s3 = peg$parseWEOL();
      }
      if (s3 !== peg$FAILED) {
        s1 = [s1, s2, s3];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCardLine_V2() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$currPos;
    s3 = peg$parseFooterDividerText();
    if (s3 !== peg$FAILED) {
      s4 = peg$parseWEOL();
      if (s4 !== peg$FAILED) {
        s3 = [s3, s4];
        s2 = s3;
      } else {
        peg$currPos = s2;
        s2 = peg$FAILED;
      }
    } else {
      peg$currPos = s2;
      s2 = peg$FAILED;
    }
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = peg$parseLine();
      s5 = peg$parseNL();
      if (s5 !== peg$FAILED) {
        s4 = [s4, s5];
        s3 = s4;
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 === peg$FAILED) {
        s3 = peg$currPos;
        s4 = [];
        s5 = peg$parseChar();
        if (s5 !== peg$FAILED) {
          while (s5 !== peg$FAILED) {
            s4.push(s5);
            s5 = peg$parseChar();
          }
        } else {
          s4 = peg$FAILED;
        }
        if (s4 !== peg$FAILED) {
          s5 = peg$parseEOL();
          if (s5 !== peg$FAILED) {
            s4 = [s4, s5];
            s3 = s4;
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f30(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCardSet_V1() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseCardSetStart_V1();
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = peg$parseCards_V1();
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = peg$parseCards_V1();
      }
      s4 = peg$parseCardSetEnd_V1();
      if (s4 !== peg$FAILED) {
        s2 = [s2, s3, s4];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f31(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardSetStart_V1() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseWNL();
    if (s2 === peg$FAILED) {
      s2 = peg$parseNL();
    }
    if (s2 !== peg$FAILED) {
      while (s2 !== peg$FAILED) {
        s1.push(s2);
        s2 = peg$parseWNL();
        if (s2 === peg$FAILED) {
          s2 = peg$parseNL();
        }
      }
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      peg$silentFails++;
      s3 = peg$currPos;
      if (input.substr(peg$currPos, 3) === peg$c13) {
        s4 = peg$c13;
        peg$currPos += 3;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e15);
        }
      }
      if (s4 !== peg$FAILED) {
        s5 = peg$parseWNL();
        if (s5 !== peg$FAILED) {
          s4 = [s4, s5];
          s3 = s4;
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      peg$silentFails--;
      if (s3 !== peg$FAILED) {
        peg$currPos = s2;
        s2 = void 0;
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f32();
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseCardSetEnd_V1() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = peg$parseFooterDividerText();
    if (s2 === peg$FAILED) {
      s2 = null;
    }
    s3 = peg$currPos;
    peg$silentFails++;
    s4 = peg$parseWEOL();
    peg$silentFails--;
    if (s4 !== peg$FAILED) {
      peg$currPos = s3;
      s3 = void 0;
    } else {
      s3 = peg$FAILED;
    }
    if (s3 !== peg$FAILED) {
      s2 = [s2, s3];
      s1 = s2;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f33();
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCards_V1() {
    let s0, s1;
    s0 = peg$currPos;
    s1 = peg$parseCardLineOrDivider_V1();
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f34(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardLineOrDivider_V1() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$currPos;
    if (input.substr(peg$currPos, 3) === peg$c13) {
      s2 = peg$c13;
      peg$currPos += 3;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e15);
      }
    }
    if (s2 !== peg$FAILED) {
      s3 = peg$parseWNL();
      if (s3 === peg$FAILED) {
        s3 = peg$parseWEOL();
      }
      if (s3 !== peg$FAILED) {
        s2 = [s2, s3];
        s1 = s2;
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 === peg$FAILED) {
      s1 = peg$currPos;
      if (input.substr(peg$currPos, 2) === peg$c14) {
        s2 = peg$c14;
        peg$currPos += 2;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e16);
        }
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseWNL();
        if (s3 === peg$FAILED) {
          s3 = peg$parseWEOL();
        }
        if (s3 !== peg$FAILED) {
          s2 = [s2, s3];
          s1 = s2;
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
      } else {
        peg$currPos = s1;
        s1 = peg$FAILED;
      }
      if (s1 === peg$FAILED) {
        s1 = peg$currPos;
        if (input.substr(peg$currPos, 2) === peg$c11) {
          s2 = peg$c11;
          peg$currPos += 2;
        } else {
          s2 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e13);
          }
        }
        if (s2 !== peg$FAILED) {
          s3 = peg$parseWNL();
          if (s3 === peg$FAILED) {
            s3 = peg$parseWEOL();
          }
          if (s3 !== peg$FAILED) {
            s2 = [s2, s3];
            s1 = s2;
          } else {
            peg$currPos = s1;
            s1 = peg$FAILED;
          }
        } else {
          peg$currPos = s1;
          s1 = peg$FAILED;
        }
        if (s1 === peg$FAILED) {
          s1 = peg$parseCardLine_V1();
        }
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f35(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseCardLine_V1() {
    let s0, s1, s2, s3, s4, s5;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    s2 = peg$currPos;
    s3 = peg$parseFooterDividerText();
    if (s3 !== peg$FAILED) {
      s4 = peg$parseWEOL();
      if (s4 !== peg$FAILED) {
        s3 = [s3, s4];
        s2 = s3;
      } else {
        peg$currPos = s2;
        s2 = peg$FAILED;
      }
    } else {
      peg$currPos = s2;
      s2 = peg$FAILED;
    }
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = peg$currPos;
      s4 = peg$parseLine();
      s5 = peg$parseNL();
      if (s5 !== peg$FAILED) {
        s4 = [s4, s5];
        s3 = s4;
      } else {
        peg$currPos = s3;
        s3 = peg$FAILED;
      }
      if (s3 === peg$FAILED) {
        s3 = peg$currPos;
        s4 = [];
        s5 = peg$parseChar();
        if (s5 !== peg$FAILED) {
          while (s5 !== peg$FAILED) {
            s4.push(s5);
            s5 = peg$parseChar();
          }
        } else {
          s4 = peg$FAILED;
        }
        if (s4 !== peg$FAILED) {
          s5 = peg$parseEOL();
          if (s5 !== peg$FAILED) {
            s4 = [s4, s5];
            s3 = s4;
          } else {
            peg$currPos = s3;
            s3 = peg$FAILED;
          }
        } else {
          peg$currPos = s3;
          s3 = peg$FAILED;
        }
      }
      if (s3 !== peg$FAILED) {
        s2 = input.substring(s2, peg$currPos);
      } else {
        s2 = s3;
      }
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f36(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseQualifier() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    if (input.substr(peg$currPos, 6) === peg$c9) {
      s2 = peg$c9;
      peg$currPos += 6;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e11);
      }
    }
    if (s2 === peg$FAILED) {
      if (input.substr(peg$currPos, 4) === peg$c7) {
        s2 = peg$c7;
        peg$currPos += 4;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e9);
        }
      }
    }
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseQualifierName();
      if (s2 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f37(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseQualifierName() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = input.charAt(peg$currPos);
    if (peg$r1.test(s2)) {
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e17);
      }
    }
    if (s2 !== peg$FAILED) {
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r2.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e18);
        }
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = input.charAt(peg$currPos);
        if (peg$r2.test(s4)) {
          peg$currPos++;
        } else {
          s4 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e18);
          }
        }
      }
      s2 = [s2, s3];
      s1 = s2;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s0 = input.substring(s0, peg$currPos);
    } else {
      s0 = s1;
    }
    return s0;
  }
  function peg$parsecardContent() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    s2 = peg$parseTagChain();
    if (s2 === peg$FAILED) {
      s2 = peg$parseCardChar();
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = peg$parseTagChain();
      if (s2 === peg$FAILED) {
        s2 = peg$parseCardChar();
      }
    }
    peg$savedPos = s0;
    s1 = peg$f38(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseCardChar() {
    let s0, s1;
    s0 = peg$currPos;
    if (input.length > peg$currPos) {
      s1 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e6);
      }
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f39(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseIDTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 4) === peg$c15) {
      s1 = peg$c15;
      peg$currPos += 4;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e19);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseKeyValueTag_Value();
      if (s2 !== peg$FAILED) {
        s3 = peg$parseTag_Close();
        if (s3 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f40(s2);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseTitleTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 91) {
      s1 = peg$c2;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e3);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = [];
      if (input.charCodeAt(peg$currPos) === 35) {
        s3 = peg$c16;
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e20);
        }
      }
      if (s3 !== peg$FAILED) {
        while (s3 !== peg$FAILED) {
          s2.push(s3);
          if (input.charCodeAt(peg$currPos) === 35) {
            s3 = peg$c16;
            peg$currPos++;
          } else {
            s3 = peg$FAILED;
            if (peg$silentFails === 0) {
              peg$fail(peg$e20);
            }
          }
        }
      } else {
        s2 = peg$FAILED;
      }
      if (s2 !== peg$FAILED) {
        s3 = peg$parseTag_Value();
        s4 = peg$parseTag_CloseOrEOF();
        if (s4 !== peg$FAILED) {
          peg$savedPos = s0;
          s0 = peg$f41(s2, s3);
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseAnchorTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c17) {
      s1 = peg$c17;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e21);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f42(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseReferenceTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c18) {
      s1 = peg$c18;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e22);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f43(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parsePropertyTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c19) {
      s1 = peg$c19;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e23);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseKeyValueTag_Key();
      if (s2 !== peg$FAILED) {
        s3 = peg$parseKeyValueTag_Value();
        if (s3 !== peg$FAILED) {
          s4 = peg$parseTag_CloseOrEOF();
          if (s4 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f44(s2, s3);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseItemLeadTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c20) {
      s1 = peg$c20;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e24);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f45(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseInstructionTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c21) {
      s1 = peg$c21;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e25);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f46(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseHintTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c22) {
      s1 = peg$c22;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e26);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f47(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseTrueTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c23) {
      s1 = peg$c23;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e27);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f48(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseFalseTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c24) {
      s1 = peg$c24;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e28);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f49(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseSampleSolutionTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c25) {
      s1 = peg$c25;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e29);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f50(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseGapTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c26) {
      s1 = peg$c26;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e30);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f51(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseMarkTag() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c27) {
      s1 = peg$c27;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e31);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      s3 = peg$parseTag_CloseOrEOF();
      if (s3 !== peg$FAILED) {
        peg$savedPos = s0;
        s0 = peg$f52(s2);
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseResourceTag() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    if (input.substr(peg$currPos, 2) === peg$c28) {
      s1 = peg$c28;
      peg$currPos += 2;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e32);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseKeyValueTag_Key();
      if (s2 !== peg$FAILED) {
        s3 = peg$parseKeyValueTag_Value();
        if (s3 !== peg$FAILED) {
          s4 = peg$parseTag_CloseOrEOF();
          if (s4 !== peg$FAILED) {
            peg$savedPos = s0;
            s0 = peg$f53(s2, s3);
          } else {
            peg$currPos = s0;
            s0 = peg$FAILED;
          }
        } else {
          peg$currPos = s0;
          s0 = peg$FAILED;
        }
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseTag_Value() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = [];
    if (input.substr(peg$currPos, 2) === peg$c29) {
      s3 = peg$c29;
      peg$currPos += 2;
    } else {
      s3 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e33);
      }
    }
    if (s3 === peg$FAILED) {
      s3 = input.charAt(peg$currPos);
      if (peg$r0.test(s3)) {
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e1);
        }
      }
    }
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      if (input.substr(peg$currPos, 2) === peg$c29) {
        s3 = peg$c29;
        peg$currPos += 2;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e33);
        }
      }
      if (s3 === peg$FAILED) {
        s3 = input.charAt(peg$currPos);
        if (peg$r0.test(s3)) {
          peg$currPos++;
        } else {
          s3 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e1);
          }
        }
      }
    }
    s1 = input.substring(s1, peg$currPos);
    peg$savedPos = s0;
    s1 = peg$f54(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseKeyValueTag_Key() {
    let s0, s1, s2, s3, s4;
    s0 = peg$currPos;
    s1 = peg$currPos;
    peg$silentFails++;
    if (input.substr(peg$currPos, 3) === peg$c30) {
      s2 = peg$c30;
      peg$currPos += 3;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e34);
      }
    }
    peg$silentFails--;
    if (s2 === peg$FAILED) {
      s1 = void 0;
    } else {
      peg$currPos = s1;
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$currPos;
      s3 = [];
      s4 = input.charAt(peg$currPos);
      if (peg$r3.test(s4)) {
        peg$currPos++;
      } else {
        s4 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e35);
        }
      }
      while (s4 !== peg$FAILED) {
        s3.push(s4);
        s4 = input.charAt(peg$currPos);
        if (peg$r3.test(s4)) {
          peg$currPos++;
        } else {
          s4 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e35);
          }
        }
      }
      s2 = input.substring(s2, peg$currPos);
      peg$savedPos = s0;
      s0 = peg$f55(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  function peg$parseTag_Close() {
    let s0;
    if (input.charCodeAt(peg$currPos) === 93) {
      s0 = peg$c1;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e2);
      }
    }
    return s0;
  }
  function peg$parseTag_CloseOrEOF() {
    let s0, s1, s2;
    if (input.charCodeAt(peg$currPos) === 93) {
      s0 = peg$c1;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e2);
      }
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = [];
      s2 = peg$parseWS();
      while (s2 !== peg$FAILED) {
        s1.push(s2);
        s2 = peg$parseWS();
      }
      s2 = peg$parseEOF();
      if (s2 !== peg$FAILED) {
        s1 = [s1, s2];
        s0 = s1;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseBit_Level() {
    let s0, s1, s2;
    s0 = peg$currPos;
    s1 = [];
    if (input.charCodeAt(peg$currPos) === 46) {
      s2 = peg$c31;
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e36);
      }
    }
    if (s2 !== peg$FAILED) {
      while (s2 !== peg$FAILED) {
        s1.push(s2);
        if (input.charCodeAt(peg$currPos) === 46) {
          s2 = peg$c31;
          peg$currPos++;
        } else {
          s2 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e36);
          }
        }
      }
    } else {
      s1 = peg$FAILED;
    }
    if (s1 !== peg$FAILED) {
      peg$savedPos = s0;
      s1 = peg$f56(s1);
    }
    s0 = s1;
    return s0;
  }
  function peg$parseBit_Value() {
    let s0, s1, s2, s3;
    s0 = peg$currPos;
    s1 = peg$currPos;
    s2 = [];
    s3 = input.charAt(peg$currPos);
    if (peg$r4.test(s3)) {
      peg$currPos++;
    } else {
      s3 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e37);
      }
    }
    while (s3 !== peg$FAILED) {
      s2.push(s3);
      s3 = input.charAt(peg$currPos);
      if (peg$r4.test(s3)) {
        peg$currPos++;
      } else {
        s3 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e37);
        }
      }
    }
    s1 = input.substring(s1, peg$currPos);
    peg$savedPos = s0;
    s1 = peg$f57(s1);
    s0 = s1;
    return s0;
  }
  function peg$parseKeyValueTag_Value() {
    let s0, s1, s2;
    s0 = peg$currPos;
    if (input.charCodeAt(peg$currPos) === 58) {
      s1 = peg$c3;
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e4);
      }
    }
    if (s1 !== peg$FAILED) {
      s2 = peg$parseTag_Value();
      peg$savedPos = s0;
      s0 = peg$f58(s2);
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      s1 = "";
      peg$savedPos = s0;
      s1 = peg$f59();
      s0 = s1;
    }
    return s0;
  }
  function peg$parseAnything() {
    let s0, s1;
    peg$silentFails++;
    s0 = [];
    if (input.length > peg$currPos) {
      s1 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e6);
      }
    }
    while (s1 !== peg$FAILED) {
      s0.push(s1);
      if (input.length > peg$currPos) {
        s1 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e6);
        }
      }
    }
    peg$silentFails--;
    return s0;
  }
  function peg$parseChar() {
    let s0, s1;
    peg$silentFails++;
    s0 = input.charAt(peg$currPos);
    if (peg$r5.test(s0)) {
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e39);
      }
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e38);
      }
    }
    return s0;
  }
  function peg$parseLine() {
    let s0, s1;
    peg$silentFails++;
    s0 = [];
    s1 = peg$parseChar();
    while (s1 !== peg$FAILED) {
      s0.push(s1);
      s1 = peg$parseChar();
    }
    peg$silentFails--;
    return s0;
  }
  function peg$parseBlankLine() {
    let s0, s1, s2;
    peg$silentFails++;
    s0 = peg$currPos;
    s1 = [];
    s2 = input.charAt(peg$currPos);
    if (peg$r6.test(s2)) {
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e41);
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = input.charAt(peg$currPos);
      if (peg$r6.test(s2)) {
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e41);
        }
      }
    }
    s2 = peg$parseNL();
    if (s2 !== peg$FAILED) {
      s1 = [s1, s2];
      s0 = s1;
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e40);
      }
    }
    return s0;
  }
  function peg$parseNL() {
    let s0, s1;
    peg$silentFails++;
    if (input.charCodeAt(peg$currPos) === 10) {
      s0 = peg$c32;
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e43);
      }
    }
    if (s0 === peg$FAILED) {
      if (input.substr(peg$currPos, 2) === peg$c33) {
        s0 = peg$c33;
        peg$currPos += 2;
      } else {
        s0 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e44);
        }
      }
      if (s0 === peg$FAILED) {
        s0 = input.charAt(peg$currPos);
        if (peg$r7.test(s0)) {
          peg$currPos++;
        } else {
          s0 = peg$FAILED;
          if (peg$silentFails === 0) {
            peg$fail(peg$e45);
          }
        }
      }
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e42);
      }
    }
    return s0;
  }
  function peg$parseWNL() {
    let s0, s1, s2;
    peg$silentFails++;
    s0 = peg$currPos;
    s1 = [];
    s2 = input.charAt(peg$currPos);
    if (peg$r6.test(s2)) {
      peg$currPos++;
    } else {
      s2 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e41);
      }
    }
    while (s2 !== peg$FAILED) {
      s1.push(s2);
      s2 = input.charAt(peg$currPos);
      if (peg$r6.test(s2)) {
        peg$currPos++;
      } else {
        s2 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e41);
        }
      }
    }
    s2 = peg$parseNL();
    if (s2 !== peg$FAILED) {
      s1 = [s1, s2];
      s0 = s1;
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e46);
      }
    }
    return s0;
  }
  function peg$parseWS() {
    let s0, s1;
    peg$silentFails++;
    s0 = input.charAt(peg$currPos);
    if (peg$r8.test(s0)) {
      peg$currPos++;
    } else {
      s0 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e48);
      }
    }
    peg$silentFails--;
    if (s0 === peg$FAILED) {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e47);
      }
    }
    return s0;
  }
  function peg$parseEOL() {
    let s0, s1;
    s0 = peg$parseNL();
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      peg$silentFails++;
      if (input.length > peg$currPos) {
        s1 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e6);
        }
      }
      peg$silentFails--;
      if (s1 === peg$FAILED) {
        s0 = void 0;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseWEOL() {
    let s0, s1;
    s0 = peg$parseWNL();
    if (s0 === peg$FAILED) {
      s0 = peg$currPos;
      peg$silentFails++;
      if (input.length > peg$currPos) {
        s1 = input.charAt(peg$currPos);
        peg$currPos++;
      } else {
        s1 = peg$FAILED;
        if (peg$silentFails === 0) {
          peg$fail(peg$e6);
        }
      }
      peg$silentFails--;
      if (s1 === peg$FAILED) {
        s0 = void 0;
      } else {
        peg$currPos = s0;
        s0 = peg$FAILED;
      }
    }
    return s0;
  }
  function peg$parseEOF() {
    let s0, s1;
    s0 = peg$currPos;
    peg$silentFails++;
    if (input.length > peg$currPos) {
      s1 = input.charAt(peg$currPos);
      peg$currPos++;
    } else {
      s1 = peg$FAILED;
      if (peg$silentFails === 0) {
        peg$fail(peg$e6);
      }
    }
    peg$silentFails--;
    if (s1 === peg$FAILED) {
      s0 = void 0;
    } else {
      peg$currPos = s0;
      s0 = peg$FAILED;
    }
    return s0;
  }
  const helper = new BitmarkPegParserHelper({
    parse: peg$parse2,
    parserText: text,
    parserLocation
  });
  const processor = new BitmarkPegParserProcessor({
    parse: peg$parse2,
    parserText: text,
    parserLocation
  });
  function parserLocation() {
    const l = location();
    if (!l) return l;
    delete l.source;
    return l;
  }
  function peg$computeLocation(startPos, endPos, offset2) {
    offset2 = true;
    var startPosDetails = peg$computePosDetails(startPos);
    var endPosDetails = peg$computePosDetails(endPos);
    var res = {
      source: peg$source,
      start: {
        offset: startPos,
        line: startPosDetails.line,
        column: startPosDetails.column
      },
      end: {
        offset: endPos,
        line: endPosDetails.line,
        column: endPosDetails.column
      }
    };
    if (offset2 && peg$source && typeof peg$source.offset === "function") {
      res.start = peg$source.offset(res.start);
      res.end = peg$source.offset(res.end);
    }
    return res;
  }
  peg$result = peg$startRuleFunction();
  const peg$success = peg$result !== peg$FAILED && peg$currPos === input.length;
  function peg$throw() {
    if (peg$result !== peg$FAILED && peg$currPos < input.length) {
      peg$fail(peg$endExpectation());
    }
    throw peg$buildStructuredError(
      peg$maxFailExpected,
      peg$maxFailPos < input.length ? peg$getUnicode(peg$maxFailPos) : null,
      peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
    );
  }
  if (options.peg$library) {
    return (
      /** @type {any} */
      {
        peg$result,
        peg$currPos,
        peg$FAILED,
        peg$maxFailExpected,
        peg$maxFailPos,
        peg$success,
        peg$throw: peg$success ? void 0 : peg$throw
      }
    );
  }
  if (peg$success) {
    return peg$result;
  } else {
    peg$throw();
  }
}

// src/parser/bitmark/peg/BitmarkPegParser.ts
var parse2 = peg$parse2;

// src/parser/bitmark/BitmarkParser.ts
var BitmarkParser = class {
  constructor() {
    __publicField(this, "jsonParser", new JsonParser());
  }
  /**
   * Convert Bitmark markup to AST.
   *
   * The Bitmark markup should be a string.
   *
   * @param json - bitmark JSON as a string or a plain JS object
   * @param _options - unused
   * @returns bitmark AST
   */
  toAst(bitmark, _options) {
    return parse2(bitmark);
  }
};

// src/parser/html/HtmlDom.ts
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
  "area",
  "base",
  "br",
  "col",
  "embed",
  "hr",
  "img",
  "input",
  "link",
  "meta",
  "param",
  "source",
  "track",
  "wbr"
]);
var IMPLIED_CLOSE = {
  li: /* @__PURE__ */ new Set(["li"]),
  td: /* @__PURE__ */ new Set(["td", "th"]),
  th: /* @__PURE__ */ new Set(["td", "th"]),
  tr: /* @__PURE__ */ new Set(["td", "th", "tr"]),
  thead: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
  tbody: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
  tfoot: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
  p: /* @__PURE__ */ new Set(["p"]),
  option: /* @__PURE__ */ new Set(["option"])
};
var TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9:-]*)((?:\s+(?:"[^"]*"|'[^']*'|[^<>"'])*)?)(\/)?>/g;
var ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9:._-]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
function parseAttrs(raw) {
  const attrs = {};
  if (!raw) return attrs;
  ATTR_RE.lastIndex = 0;
  let m;
  while ((m = ATTR_RE.exec(raw)) !== null) {
    const name = m[1].toLowerCase();
    let value = m[2] ?? "";
    if (value.length >= 2 && (value[0] === '"' || value[0] === "'")) {
      value = value.slice(1, -1);
    }
    attrs[name] = value;
  }
  return attrs;
}
function stripNonContent(html) {
  return html.replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<!doctype[^>]*>/gi, "").replace(/<script\b[\s\S]*?<\/script>/gi, "").replace(/<style\b[\s\S]*?<\/style>/gi, "");
}
function tokenize(html) {
  const tokens = [];
  const clean = stripNonContent(html);
  TAG_RE.lastIndex = 0;
  let lastIndex = 0;
  let m;
  while ((m = TAG_RE.exec(clean)) !== null) {
    if (m.index > lastIndex) {
      tokens.push({ kind: "text", text: clean.slice(lastIndex, m.index) });
    }
    lastIndex = TAG_RE.lastIndex;
    const isClose = m[1] === "/";
    const name = m[2].toLowerCase();
    if (isClose) {
      tokens.push({ kind: "close", name });
    } else {
      tokens.push({
        kind: "open",
        name,
        attrs: parseAttrs(m[3]),
        selfClose: m[4] === "/"
      });
    }
  }
  if (lastIndex < clean.length) {
    tokens.push({ kind: "text", text: clean.slice(lastIndex) });
  }
  return tokens;
}
function parseHtml(html) {
  const root = { type: "element", name: "#root", attrs: {}, children: [] };
  const stack = [root];
  const top = () => stack[stack.length - 1];
  for (const token of tokenize(html)) {
    if (token.kind === "text") {
      if (token.text) top().children.push({ type: "text", value: token.text });
      continue;
    }
    if (token.kind === "open") {
      const name = token.name;
      const closes = IMPLIED_CLOSE[name];
      if (closes) {
        while (stack.length > 1 && closes.has(top().name)) stack.pop();
      }
      const el = {
        type: "element",
        name,
        attrs: token.attrs ?? {},
        children: []
      };
      top().children.push(el);
      if (!token.selfClose && !VOID_ELEMENTS.has(name)) {
        stack.push(el);
      }
      continue;
    }
    for (let i = stack.length - 1; i > 0; i--) {
      if (stack[i].name === token.name) {
        stack.length = i;
        break;
      }
    }
  }
  return root.children;
}
function childElements(el, names) {
  return el.children.filter((c) => c.type === "element" && names.has(c.name));
}

// src/parser/html/HtmlTableParser.ts
var MARK_TAGS = {
  b: "bold",
  strong: "bold",
  i: "italic",
  em: "italic",
  sup: "superscript",
  sub: "subscript",
  s: "strike",
  strike: "strike",
  del: "del",
  ins: "ins",
  u: "underline",
  mark: "highlight",
  code: "code"
};
var SECTION_TAGS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
var ROW_TAGS = /* @__PURE__ */ new Set(["tr"]);
var CELL_TAGS = /* @__PURE__ */ new Set(["td", "th"]);
var LI_TAGS = /* @__PURE__ */ new Set(["li"]);
var VALID_SCOPES = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
var NAMED_ENTITIES = {
  amp: "&",
  lt: "<",
  gt: ">",
  quot: '"',
  apos: "'",
  nbsp: "\xA0"
};
function decodeEntities(text) {
  return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body) => {
    if (body[0] === "#") {
      const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
      if (Number.isFinite(code) && code >= 0 && code <= 1114111) {
        try {
          return String.fromCodePoint(code);
        } catch {
          return whole;
        }
      }
      return whole;
    }
    return NAMED_ENTITIES[body] ?? whole;
  });
}
function intAttr(value) {
  if (value == null) return void 0;
  const n = Number.parseInt(value, 10);
  return Number.isInteger(n) ? n : void 0;
}
function isElement(node) {
  return node.type === "element";
}
var HtmlTableParser = class {
  constructor() {
    __publicField(this, "keepUnknownTags", false);
  }
  /**
   * Parse an HTML string and return one table bit per top-level `<table>`.
   */
  parse(html, options) {
    this.keepUnknownTags = options?.keepUnknownTags ?? false;
    const tableFormat = options?.tableFormat ?? "table-extended";
    const onWarning = options?.onWarning;
    const tables = this.collectTopLevelTables(parseHtml(html));
    const bits = [];
    for (const table of tables) {
      const bit = this.tableToBit(table, tableFormat, onWarning);
      if (bit) bits.push(bit);
    }
    return bits;
  }
  // --- table extraction -----------------------------------------------------
  /** Collect every `<table>` element, NOT descending into nested tables. */
  collectTopLevelTables(nodes) {
    const tables = [];
    const visit = (node) => {
      if (!isElement(node)) return;
      if (node.name === "table") {
        tables.push(node);
        return;
      }
      node.children.forEach(visit);
    };
    nodes.forEach(visit);
    return tables;
  }
  tableToBit(table, tableFormat, onWarning) {
    const header = [];
    const body = [];
    const footer = [];
    let caption;
    for (const child of table.children) {
      if (!isElement(child)) continue;
      if (child.name === "caption") {
        const capContent = this.inlineNodes(child.children, []);
        const trimmed = this.trimInline(capContent);
        if (trimmed.length > 0) caption = trimmed;
      } else if (SECTION_TAGS.has(child.name)) {
        const target = child.name === "thead" ? header : child.name === "tfoot" ? footer : body;
        for (const tr of childElements(child, ROW_TAGS)) {
          target.push(this.rowToCells(tr));
        }
      } else if (ROW_TAGS.has(child.name)) {
        body.push(this.rowToCells(child));
      }
    }
    if (header.length === 0 && body.length === 0 && footer.length === 0) {
      return void 0;
    }
    const tableExtended = {};
    if (header.length > 0) tableExtended.header = { rows: header };
    if (body.length > 0) tableExtended.body = { rows: body };
    if (footer.length > 0) tableExtended.footer = { rows: footer };
    let tableData = tableExtended;
    if (tableFormat === "table") {
      if (this.isLossyAsBasic(tableExtended)) {
        onWarning?.(
          "Converting an HTML table to [.table] loses rowspan/colspan/scope and multi-section structure; use --tableFormat table-extended to preserve them."
        );
      }
      tableData = convertExtendedToBasicTableFormat(tableExtended);
    }
    const bit = {
      type: tableFormat,
      format: "bitmark++",
      table: tableData
    };
    if (caption) bit.caption = caption;
    return bit;
  }
  rowToCells(tr) {
    const cells = [];
    for (const cell of childElements(tr, CELL_TAGS)) {
      cells.push(this.cellToJson(cell));
    }
    return { cells };
  }
  cellToJson(cell) {
    const json = {
      content: this.blockNodes(cell.children)
    };
    if (cell.name === "th") json.title = true;
    const rowspan = intAttr(cell.attrs.rowspan);
    if (rowspan != null && rowspan >= 2) json.rowspan = rowspan;
    const colspan = intAttr(cell.attrs.colspan);
    if (colspan != null && colspan >= 2) json.colspan = colspan;
    const scope = cell.attrs.scope?.toLowerCase();
    if (scope && VALID_SCOPES.has(scope)) {
      json.scope = scope;
    }
    const width = this.widthFromCell(cell);
    if (width != null) json.colwidth = width;
    return json;
  }
  widthFromCell(cell) {
    const attrWidth = intAttr(cell.attrs.width);
    if (attrWidth != null && attrWidth > 0) return attrWidth;
    const style = cell.attrs.style;
    if (style) {
      const m = style.match(/width\s*:\s*(\d+)\s*px/i);
      if (m) {
        const n = Number.parseInt(m[1], 10);
        if (Number.isInteger(n) && n > 0) return n;
      }
    }
    return void 0;
  }
  isLossyAsBasic(table) {
    if (table.footer) return true;
    if (table.header && table.header.rows.length > 1) return true;
    const sections = [table.header, table.body, table.footer];
    for (const section of sections) {
      if (!section) continue;
      for (const row of section.rows) {
        for (const cell of row.cells) {
          if (cell.rowspan || cell.colspan || cell.scope) return true;
        }
      }
    }
    return false;
  }
  // --- content conversion (HTML -> TextAst) ---------------------------------
  /**
   * Convert a cell/li's child nodes into block-level TextAst nodes (paragraphs, lists, images).
   */
  blockNodes(nodes) {
    const blocks = [];
    let inline = [];
    const flush = () => {
      const trimmed = this.trimInline(inline);
      inline = [];
      if (trimmed.length === 0) return;
      if (trimmed.length === 1 && trimmed[0].type === "imageInline") {
        blocks.push({ ...trimmed[0], type: "image" });
      } else {
        blocks.push({ type: "paragraph", content: trimmed, attrs: {} });
      }
    };
    for (const node of nodes) {
      if (!isElement(node)) {
        inline.push(...this.inlineText(node.value, []));
        continue;
      }
      const name = node.name;
      if (name === "p") {
        flush();
        const content = this.trimInline(this.inlineNodes(node.children, []));
        if (content.length > 0) {
          blocks.push({ type: "paragraph", content, attrs: {} });
        }
      } else if (this.isListTag(name)) {
        flush();
        const list = this.listNode(node);
        if (list) blocks.push(list);
      } else {
        inline.push(...this.inlineFromElement(node, []));
      }
    }
    flush();
    return blocks;
  }
  /** Convert child nodes to a flat list of inline TextNodes, applying the given marks. */
  inlineNodes(nodes, marks) {
    const out = [];
    for (const node of nodes) {
      if (!isElement(node)) {
        out.push(...this.inlineText(node.value, marks));
      } else {
        out.push(...this.inlineFromElement(node, marks));
      }
    }
    return out;
  }
  inlineFromElement(el, marks) {
    const name = el.name;
    if (name === "br") {
      return [{ type: "hardBreak" }];
    }
    if (name === "img") {
      return [this.imageNode(el, true)];
    }
    if (this.isMathTag(name)) {
      return [this.latexNode(el)];
    }
    const markType = MARK_TAGS[name];
    if (markType) {
      return this.inlineNodes(el.children, [...marks, { type: markType }]);
    }
    if (name === "a") {
      const linkMark = {
        type: "link",
        attrs: {
          href: el.attrs.href ?? "",
          ...el.attrs.target ? { target: el.attrs.target } : {}
        }
      };
      return this.inlineNodes(el.children, [...marks, linkMark]);
    }
    if (this.keepUnknownTags) {
      const open = this.serializeOpenTag(el);
      const close = `</${name}>`;
      return [
        ...this.inlineText(open, marks),
        ...this.inlineNodes(el.children, marks),
        ...this.inlineText(close, marks)
      ];
    }
    return this.inlineNodes(el.children, marks);
  }
  inlineText(raw, marks) {
    const text = decodeEntities(raw).replace(/\s+/g, " ");
    if (text === "") return [];
    const node = { text, type: "text" };
    if (marks.length > 0) node.marks = marks.map((m) => ({ ...m }));
    return [node];
  }
  serializeOpenTag(el) {
    const attrs = Object.entries(el.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
    return `<${el.name}${attrs}>`;
  }
  // --- lists ----------------------------------------------------------------
  isListTag(name) {
    return name === "ul" || name === "ol";
  }
  listNode(el) {
    const items = childElements(el, LI_TAGS);
    if (items.length === 0) return void 0;
    const isTask = el.name === "ul" && items.some((li) => {
      const firstEl = li.children.find(isElement);
      return firstEl?.name === "input" && firstEl.attrs.type?.toLowerCase() === "checkbox";
    });
    if (isTask) {
      return {
        type: "taskList",
        content: items.map((li) => this.taskItemNode(li))
      };
    }
    const type = this.listType(el);
    const node = {
      type,
      content: items.map((li) => this.listItemNode(li))
    };
    if (el.name === "ol") {
      const start = intAttr(el.attrs.start);
      if (start != null) {
        node.attrs = { start };
      }
    }
    return node;
  }
  listType(el) {
    if (el.name === "ol") {
      switch (el.attrs.type) {
        case "i":
          return "orderedListRomanLower";
        case "I":
          return "orderedListRoman";
        case "a":
          return "letteredListLower";
        case "A":
          return "letteredList";
        default:
          return "orderedList";
      }
    }
    const style = el.attrs.style ?? "";
    if (/list-style(-type)?\s*:\s*none/i.test(style)) return "noBulletList";
    return "bulletList";
  }
  listItemNode(li) {
    let content = this.blockNodes(li.children);
    if (content.length === 0) {
      content = [{ type: "paragraph", content: [], attrs: {} }];
    }
    return { type: "listItem", content };
  }
  taskItemNode(li) {
    const checkbox = li.children.find(
      (c) => isElement(c) && c.name === "input" && c.attrs.type?.toLowerCase() === "checkbox"
    );
    const checked = checkbox ? "checked" in checkbox.attrs : false;
    const rest = li.children.filter((c) => c !== checkbox);
    let content = this.blockNodes(rest);
    if (content.length === 0) {
      content = [{ type: "paragraph", content: [], attrs: {} }];
    }
    return { type: "taskItem", attrs: { checked }, content };
  }
  // --- images ---------------------------------------------------------------
  imageNode(el, inline) {
    const attrs = { src: el.attrs.src ?? "" };
    attrs.alt = el.attrs.alt ?? null;
    attrs.title = el.attrs.title ?? null;
    const width = intAttr(el.attrs.width);
    if (width != null) attrs.width = width;
    const height = intAttr(el.attrs.height);
    if (height != null) attrs.height = height;
    if (el.attrs.class) attrs.class = el.attrs.class;
    return { type: inline ? "imageInline" : "image", attrs };
  }
  // --- formulas -------------------------------------------------------------
  isMathTag(name) {
    return this.localName(name) === "math";
  }
  latexNode(el) {
    return {
      type: "latex",
      attrs: {
        formula: this.latexFromMath(el)
      }
    };
  }
  latexFromMath(el) {
    const alt = el.attrs.alttext ?? el.attrs.alt ?? el.attrs.title;
    if (alt != null) return decodeEntities(alt);
    const annotation = this.findLatexAnnotation(el);
    if (annotation) return this.textContent(annotation).trim();
    return this.textContent(el).trim();
  }
  findLatexAnnotation(el) {
    for (const child of el.children) {
      if (!isElement(child)) continue;
      const lowerEncoding = (child.attrs.encoding ?? "").toLowerCase();
      if (this.localName(child.name) === "annotation" && (/\btex\b/.test(lowerEncoding) || /\blatex\b/.test(lowerEncoding))) {
        return child;
      }
      const nested = this.findLatexAnnotation(child);
      if (nested) return nested;
    }
    return void 0;
  }
  textContent(el) {
    return el.children.map((child) => isElement(child) ? this.textContent(child) : decodeEntities(child.value)).join("").replace(/\s+/g, " ");
  }
  localName(name) {
    return name.includes(":") ? name.slice(name.indexOf(":") + 1) : name;
  }
  // --- whitespace -----------------------------------------------------------
  /** Trim leading/trailing whitespace from inline content (edges only). */
  trimInline(nodes) {
    const out = nodes.slice();
    const isWs = (n) => n.type === "text" && /^\s*$/.test(n.text ?? "");
    while (out.length > 0 && isWs(out[0])) out.shift();
    if (out.length > 0 && out[0].type === "text") {
      out[0] = { ...out[0], text: (out[0].text ?? "").replace(/^\s+/, "") };
    }
    while (out.length > 0 && isWs(out[out.length - 1])) out.pop();
    if (out.length > 0 && out[out.length - 1].type === "text") {
      const last = out.length - 1;
      out[last] = { ...out[last], text: (out[last].text ?? "").replace(/\s+$/, "") };
    }
    return out.filter((n) => !(n.type === "text" && n.text === ""));
  }
};

// src/BitmarkParserGenerator.ts
var Input = {
  /**
   * Input is as a string
   */
  string: "string",
  /**
   * Input is as a file path
   */
  file: "file"
};
var Output = {
  /**
   * Output bitmark string
   */
  bitmark: "bitmark",
  /**
   * Output JSON as a plain JS object, or a file
   */
  json: "json",
  /**
   * Output AST as a plain JS object, or a file
   */
  ast: "ast",
  /**
   * Output plain text as a string, or a file
   */
  text: "text"
};
var InputFormat = {
  /**
   * Input is bitmark
   */
  bitmark: "bitmark",
  /**
   * Input is bitmarkText
   */
  bitmarkText: "bitmarkText",
  /**
   * Input is plain text
   */
  plainText: "plainText",
  /**
   * Input is HTML (used by convertHtmlTable)
   */
  html: "html"
};
var BitmarkParserGenerator = class {
  constructor() {
    __publicField(this, "ast", new Ast());
    __publicField(this, "jsonParser", new JsonParser());
    __publicField(this, "bitmarkParser", new BitmarkParser());
    __publicField(this, "textParser", new TextParser());
    __publicField(this, "textGenerator", new TextGenerator());
    /**
     * Stringify / prettify a plain JS object to a JSON string, depending on the JSON options
     *
     * @param json
     * @param options
     * @param forceStringify
     * @returns
     */
    __publicField(this, "jsonStringifyPrettify", (json, options, forceStringify) => {
      const prettifySpace = options.prettify === true ? 2 : options.prettify || void 0;
      const stringify = forceStringify || options.stringify === true || prettifySpace !== void 0;
      if (stringify) {
        return JSON.stringify(json, null, prettifySpace);
      }
      return json;
    });
  }
  /**
   * Get the version of the bitmark-parser-generator library
   */
  version() {
    return env.appVersion.full;
  }
  /**
   * Get information about the bitmark-parser-generator library
   */
  info(options) {
    const opts = Object.assign({}, options);
    const builder3 = new InfoBuilder();
    let res;
    const outputString = !opts.outputFormat || opts.outputFormat === InfoFormat.text;
    const outputJson = opts.outputFormat === InfoFormat.json;
    const all = opts.type === InfoType.all;
    const deprecated = opts.type === InfoType.deprecated;
    const includeNonDeprecated = all || !deprecated;
    const includeDeprecated = all || deprecated;
    if (opts.type === InfoType.bit) {
      const bitConfigs = builder3.getSupportedBitConfigs().filter((b) => {
        if (!opts.bit) return true;
        const bitType = instance2.getBitType(opts.bit);
        return bitType === b.bitType;
      });
      if (outputString) {
        res = bitConfigs.map(
          (b) => b.toString({
            includeChains: true,
            includeConfigs: true
          })
        ).join("\n\n--------------\n\n");
      } else {
        res = bitConfigs;
      }
    } else {
      const supportedBits = builder3.getSupportedBits({
        includeNonDeprecated,
        includeDeprecated
      }).filter((b) => {
        if (!opts.bit) return true;
        const bitType = instance2.getBitType(opts.bit);
        if (b.name === bitType) return true;
      });
      if (outputString) {
        res = this.supportedBitsAsString(supportedBits);
      } else {
        res = supportedBits;
      }
    }
    if (outputJson) {
      const prettifySpace = opts.prettify === true ? 2 : opts.prettify || void 0;
      res = JSON.stringify(res, null, prettifySpace);
    }
    return res;
  }
  /**
   * Generate the new configuration for the bitmark parser.
   */
  generateConfig(options) {
    const opts = Object.assign({}, options);
    const builder3 = new ConfigBuilder();
    builder3.build(opts);
  }
  /**
   * Convert bitmark from bitmark to JSON, or JSON to bitmark.
   *
   * Input type is detected automatically and may be:
   * - string: bitmark, JSON, or AST
   * - object: JSON or AST
   * - file: bitmark, JSON, or AST
   *
   * Output type is selected automatically based on input type detection:
   * - input(JSON/AST) ==> output(bitmark)
   * - input(bitmark)  ==> output(JSON)
   *
   * Output type can be overridden to one of the following:
   * - bitmark: output bitmark string
   * - json: output JSON as a plain JS object, or a file
   * - ast: output AST as a plain JS object, or a file
   * - text: output plain text as a string, or a file
   *
   * By default, the result is returned as a string for bitmark, or a plain JS object for JSON/AST.
   *
   * The options can be used to write the output to a file and to set conversion options or override defaults.
   *
   * If both the input and output formats are the same, the input will be validated and rewritten.
   * This feature be used to upgrade bitmark or JSON to the latest version.
   *
   * @param input - bitmark or JSON or AST as a string, JSON or AST as plain JS object, or path to a file containing
   * bitmark, JSON, or AST.
   * @param options - the conversion options
   * @returns A string if converting to bitmark, a plain JS object if converting to JSON, or
   * void if writing to a file
   * @throws Error if any error occurs
   */
  convert(input, options) {
    let res;
    const opts = Object.assign({}, options);
    const jsonOptions = Object.assign({}, opts.jsonOptions);
    const outputFormat = opts.outputFormat;
    const outputBitmark = outputFormat === Output.bitmark;
    const outputJson = outputFormat === Output.json;
    const outputAst = outputFormat === Output.ast;
    const outputText = outputFormat === Output.text;
    const bitmarkParserType = BitmarkParserType.peggy;
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    let ast = this.ast.preprocessAst(inStr);
    const isAst = !!ast;
    if (!isAst) {
      ast = this.jsonParser.toAst(inStr);
    }
    const isJson = !!ast?.bits;
    const isBitmark = inputIsString && !isJson && !isAst;
    const bitmarkToBitmark = (bitmarkStr) => {
      bitmarkToAst(bitmarkStr);
      astToBitmark(res);
    };
    const bitmarkToAst = (bitmarkStr) => {
      res = this.bitmarkParser.toAst(bitmarkStr, {
        parserType: bitmarkParserType
      });
    };
    const bitmarkToJson = (bitmarkStr) => {
      if (bitmarkParserType === BitmarkParserType.peggy) {
        ast = this.bitmarkParser.toAst(bitmarkStr, {
          parserType: bitmarkParserType
        });
        if (opts.outputFile) {
          const generator = new JsonFileGenerator(opts.outputFile, opts);
          generator.generateSync(ast);
        } else {
          const generator = new JsonObjectGenerator(opts);
          const json = generator.generateSync(ast);
          res = this.jsonStringifyPrettify(json, jsonOptions);
        }
      }
    };
    const bitmarkToText = (bitmarkStr) => {
      ast = this.bitmarkParser.toAst(bitmarkStr, {
        parserType: bitmarkParserType
      });
      const jsonGenerator = new JsonObjectGenerator(opts);
      const json = jsonGenerator.generateSync(ast);
      const textGenerator = new PlainTextGenerator();
      const str = textGenerator.generate(json);
      if (opts.outputFile) {
        fs.writeFileSync(opts.outputFile, str, {
          encoding: "utf8"
        });
      } else {
        res = str;
      }
    };
    const astToBitmark = (astJson) => {
      if (opts.outputFile) {
        const generator = new BitmarkFileGenerator(opts.outputFile, opts);
        generator.generateSync(astJson);
      } else {
        const generator = new BitmarkStringGenerator(opts);
        res = generator.generateSync(astJson);
      }
    };
    const astToAst = (astJson) => {
      res = this.jsonStringifyPrettify(astJson, jsonOptions);
    };
    const astToJson = (astJson) => {
      if (opts.outputFile) {
        const generator = new JsonFileGenerator(opts.outputFile, opts);
        generator.generateSync(astJson);
      } else {
        const generator = new JsonObjectGenerator(opts);
        const json = generator.generateSync(astJson);
        res = this.jsonStringifyPrettify(json, jsonOptions);
      }
    };
    const astToText = (astJson) => {
      const jsonGenerator = new JsonObjectGenerator(opts);
      const json = jsonGenerator.generateSync(astJson);
      const textGenerator = new PlainTextGenerator();
      const str = textGenerator.generate(json);
      if (opts.outputFile) {
        fs.writeFileSync(opts.outputFile, str, {
          encoding: "utf8"
        });
      } else {
        res = str;
      }
    };
    const jsonToBitmark = (astJson) => {
      if (opts.outputFile) {
        const generator = new BitmarkFileGenerator(opts.outputFile, opts);
        generator.generateSync(astJson);
      } else {
        const generator = new BitmarkStringGenerator(opts);
        res = generator.generateSync(astJson);
      }
    };
    const jsonToAst = (astJson) => {
      res = this.jsonStringifyPrettify(astJson, jsonOptions);
    };
    const jsonToText = (astJson) => {
      const jsonGenerator = new JsonObjectGenerator(opts);
      const json = jsonGenerator.generateSync(astJson);
      const textGenerator = new PlainTextGenerator();
      const str = textGenerator.generate(json);
      if (opts.outputFile) {
        fs.writeFileSync(opts.outputFile, str, {
          encoding: "utf8"
        });
      } else {
        res = str;
      }
    };
    const jsonToJson = (astJson) => {
      astToJson(astJson);
    };
    if (isBitmark) {
      if (outputBitmark) {
        bitmarkToBitmark(inStr);
      } else if (outputAst) {
        bitmarkToAst(inStr);
      } else if (outputText) {
        bitmarkToText(inStr);
      } else {
        bitmarkToJson(inStr);
      }
    } else if (isAst) {
      ast = ast;
      if (outputAst) {
        astToAst(ast);
      } else if (outputJson) {
        astToJson(ast);
      } else if (outputText) {
        astToText(ast);
      } else {
        astToBitmark(ast);
      }
    } else if (isJson) {
      ast = ast;
      if (outputJson) {
        jsonToJson(ast);
      } else if (outputAst) {
        jsonToAst(ast);
      } else if (outputText) {
        jsonToText(ast);
      } else {
        jsonToBitmark(ast);
      }
    } else {
      res = "";
    }
    return res;
  }
  /**
   * Upgrade bitmark or JSON, upgrading to the latest supported syntax, removing unrecognised data in the process.
   *
   * THIS FEATURE SHOULD BE USED WITH CAUTION. IT WILL POTENTIALLY DELETE DATA.
   *
   * Input type is detected automatically and may be:
   * - string: bitmark, JSON
   * - object: JSON
   * - file: bitmark, JSON
   *
   * Output type is the same as the detected input type
   *
   * By default, the result is returned as a string for bitmark, or a plain JS object for JSON.
   *
   * The options can be used to write the output to a file and to set conversion options or override defaults.
   *
   * If both the input and output formats are the same, the input will be validated and rewritten.
   *
   * @param input - bitmark or JSON or AST as a string, JSON or AST as plain JS object, or path to a file containing
   * bitmark, JSON, or AST.
   * @param options - the conversion options
   * @returns Promise that resolves to string if upgrading to bitmark, a plain JS object if converting to JSON, or
   * void if writing to a file
   * @throws Error if any error occurs
   */
  upgrade(input, options) {
    let res;
    const opts = Object.assign({}, options);
    const jsonOptions = Object.assign({}, opts.jsonOptions);
    const bitmarkParserType = opts.bitmarkParserType;
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    let ast = this.jsonParser.toAst(inStr);
    const isJson = !!ast?.bits;
    const isBitmark = inputIsString && !isJson;
    const bitmarkToBitmark = (bitmarkStr) => {
      const astJson = this.bitmarkParser.toAst(bitmarkStr, {
        parserType: bitmarkParserType
      });
      if (opts.outputFile) {
        const generator = new BitmarkFileGenerator(opts.outputFile, opts);
        generator.generateSync(astJson);
      } else {
        const generator = new BitmarkStringGenerator(opts);
        res = generator.generateSync(astJson);
      }
    };
    const jsonToJson = (astJson) => {
      if (opts.outputFile) {
        const generator = new JsonFileGenerator(opts.outputFile, opts);
        generator.generateSync(astJson);
      } else {
        const generator = new JsonObjectGenerator(opts);
        const json = generator.generateSync(astJson);
        res = this.jsonStringifyPrettify(json, jsonOptions);
      }
    };
    if (isBitmark) {
      bitmarkToBitmark(inStr);
    } else if (isJson) {
      ast = ast;
      jsonToJson(ast);
    } else {
      return;
    }
    return res;
  }
  /**
   * Create a bitmark AST (Abstract Syntax Tree) from bitmark or JSON or AST
   *
   * Input type is detected automatically and may be string, object (JSON or AST), or file
   *
   * @param input the JSON or bitmark to convert to a bitmark AST
   * @param options - the create AST options
   * @returns bitmark AST
   * @throws Error if any error occurs
   */
  createAst(input, options) {
    let res;
    let inStr = input;
    const inputIsString = typeof input === "string";
    const opts = Object.assign({}, options);
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    let ast = this.ast.preprocessAst(inStr);
    const isAst = !!ast;
    if (!isAst) {
      ast = this.jsonParser.toAst(inStr);
    }
    const isJson = !!ast?.bits;
    const isBitmark = inputIsString && !isJson && !isAst;
    if (isBitmark) {
      res = this.bitmarkParser.toAst(inStr);
    } else if (isJson) {
      res = ast;
    } else {
      res = {};
    }
    return res;
  }
  /**
   * Convert bitmark text to JSON, or JSON to bitmark text.
   *
   * Input type is detected automatically and may be:
   * - string: bitmark text or JSON
   * - object: JSON
   * - file: bitmark text or JSON
   *
   * Output type is selected automatically based on input type detection:
   * - input(JSON) ==> output(bitmark text)
   * - input(bitmark text)  ==> output(JSON)
   *
   * By default, the result is returned as a string for bitmark text, or a plain JS object for JSON.
   *
   * The options can be used to write the output to a file and to set conversion options or override defaults.
   *
   * @param input - bitmark text or JSON as a string, JSON as plain JS object, or path to a file containing
   * bitmark text or JSON.
   * @param options - the conversion options
   * @returns Promise that resolves to string if converting to bitmark text, a plain JS object if converting to JSON, or
   * void if writing to a file
   * @throws Error if any error occurs
   */
  convertText(input, options) {
    let res;
    let preRes;
    const opts = Object.assign({}, options);
    const fileOptions = Object.assign({}, opts.fileOptions);
    const jsonOptions = Object.assign({}, opts.jsonOptions);
    const textFormat = Enum22(TextFormat).fromValue(opts.textFormat) ?? TextFormat.bitmarkText;
    const textLocation = opts.textLocation ?? TextLocation.body;
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    const ast = this.textParser.preprocessAst(inStr);
    const isAst = !!ast;
    if (!isAst) {
      preRes = this.textParser.toAst(inStr, {
        format: textFormat,
        location: TextLocation.body
      });
    } else {
      preRes = this.textGenerator.generateSync(ast, textFormat, textLocation);
    }
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      let strRes = preRes;
      if (!isAst) {
        strRes = this.jsonStringifyPrettify(preRes, jsonOptions, true);
      }
      const flag = fileOptions.append ? "a" : "w";
      fs.ensureDirSync(path.dirname(output));
      fs.writeFileSync(output, strRes, {
        flag
      });
    } else {
      if (!isAst) {
        res = this.jsonStringifyPrettify(preRes, jsonOptions);
      } else {
        res = preRes;
      }
    }
    return res;
  }
  extractPlainText(input, options) {
    const dataIn = input;
    const inputFormat = options?.inputFormat;
    const isString2 = typeof input === "string";
    let data;
    const parseAutomatically = () => {
      let dataOut = dataIn;
      if (typeof dataIn === "string") {
        try {
          dataOut = JSON.parse(dataIn);
        } catch (_e) {
          let isBitmark = false;
          const bitmarkData = this.convert(dataIn, {
            outputFormat: Output.json
          });
          if (bitmarkData.length > 0) {
            const isError = bitmarkData[0].bit.type === BitType._error;
            if (!isError) {
              isBitmark = true;
              dataOut = bitmarkData;
            }
          }
          if (!isBitmark) {
            dataOut = this.convertText(dataIn, {
              textFormat: TextFormat.bitmarkText
            });
          }
        }
      }
      return dataOut;
    };
    if (inputFormat === InputFormat.bitmark) {
      data = this.convert(dataIn, {
        outputFormat: Output.json
      });
    } else if (inputFormat === InputFormat.bitmarkText) {
      data = this.convertText(dataIn, {
        textFormat: TextFormat.bitmarkText
      });
    } else if (inputFormat === InputFormat.plainText) {
      if (isString2) data = String(input);
    } else {
      data = parseAutomatically();
    }
    const generator = new PlainTextGenerator();
    const res = generator.generate(data);
    return res;
  }
  /**
   * Convert between HTML tables and bitmark tables.
   *
   * Direction is auto-detected (override with `options.inputFormat`):
   * - HTML input: every `<table>` is extracted and emitted as a `[.table-extended]` (or `[.table]`)
   *   bit; output as bitmark (default), JSON, AST, or HTML (re-emit).
   * - bitmark / JSON / AST input: each `table` / `table-extended` bit is rendered as an HTML
   *   `<table>` fragment.
   *
   * Cell content is reproduced verbatim except inline conversion to bitmark marks (bold, italic,
   * superscript, etc.), with lists and images mapped to their bitmark equivalents.
   *
   * @param input - HTML, bitmark, JSON or AST string; or a parsed JSON/AST value; or a file path.
   * @param options - the conversion options
   * @returns the converted output as a string (or object for JSON/AST), or void when writing a file
   * @throws Error if any error occurs
   */
  convertHtmlTable(input, options) {
    const opts = Object.assign({}, options);
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isNode && inputIsString && fs.existsSync(inStr)) {
      inStr = fs.readFileSync(inStr, { encoding: "utf8" });
    }
    const isHtmlString = (s) => {
      const t = s.replace(/^\s+/, "");
      return t.startsWith("<") && /<table/i.test(t);
    };
    let htmlInput;
    if (opts.inputFormat === InputFormat.html) {
      htmlInput = true;
    } else if (opts.inputFormat != null) {
      htmlInput = false;
    } else {
      htmlInput = inputIsString && isHtmlString(inStr);
    }
    if (htmlInput) {
      const parser2 = new HtmlTableParser();
      const bits = parser2.parse(inStr, {
        tableFormat: opts.tableFormat ?? "table-extended",
        keepUnknownTags: opts.keepUnknownTags,
        onWarning: (m) => console.warn(m)
      });
      const outputFormat2 = opts.outputFormat ?? Output.bitmark;
      if (outputFormat2 === "html") {
        const json2 = this.convert(bits, { outputFormat: Output.json });
        return this.outputHtml(new HtmlTableGenerator().generate(json2), opts);
      }
      return this.convert(bits, {
        outputFormat: Enum22(Output).fromValue(outputFormat2),
        outputFile: opts.outputFile,
        fileOptions: opts.fileOptions,
        jsonOptions: opts.jsonOptions,
        bitmarkOptions: {
          ...opts.bitmarkOptions,
          ...opts.noBreakscaping ? { noBreakscaping: true } : {}
        }
      });
    }
    const outputFormat = opts.outputFormat ?? "html";
    if (outputFormat !== "html") {
      throw new Error(
        "convertHtmlTable: non-HTML input can only be converted to HTML. Use convert() for bitmark/JSON/AST conversions."
      );
    }
    const json = this.convert(inputIsString ? inStr : input, { outputFormat: Output.json });
    return this.outputHtml(new HtmlTableGenerator().generate(json), opts);
  }
  /**
   * Write an HTML string to the configured output file, or return it.
   */
  outputHtml(html, opts) {
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      const flag = opts.fileOptions?.append ? "a" : "w";
      fs.ensureDirSync(path.dirname(output));
      fs.writeFileSync(output, html, { flag });
      return;
    }
    return html;
  }
  /**
   * Breakscape bitmark text.
   *
   * Input type is detected automatically and may be:
   * - string: text
   * - file: path to a file containing text
   *
   * By default, the result is returned as a string.
   * By default, the text is breakscaped as if it was bitmark text in the body.
   * Use the options to change this behaviour.
   *
   * The options can be used to write the output to a file.
   *
   * @param input - text, or path to a file containing text.
   * @param options - the conversion options
   * @returns breakscaped string, or void if writing to a file
   * @throws Error if any error occurs
   */
  breakscapeText(input, options) {
    if (!input) return input;
    const opts = Object.assign({}, options);
    const fileOptions = Object.assign({}, opts.fileOptions);
    const textFormat = Enum22(TextFormat).fromValue(opts.textFormat) ?? TextFormat.bitmarkText;
    const textLocation = opts.textLocation ?? TextLocation.body;
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    const res = Breakscape.breakscape(inStr, {
      format: textFormat,
      location: textLocation
    });
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      const flag = fileOptions.append ? "a" : "w";
      fs.ensureDirSync(path.dirname(output));
      fs.writeFileSync(output, res, {
        flag
      });
    } else {
      return res;
    }
  }
  /**
   * Unescape (unbreakscape) bitmark text
   *
   * Input type is detected automatically and may be:
   * - string: text
   * - file: path to a file containing text
   *
   * By default, the result is returned as a string.
   * By default, the text is unbreakscaped as if it was bitmark text in the body.
   * Use the options to change this behaviour.
   *
   * The options can be used to write the output to a file.
   *
   * @param input - text, or path to a file containing text.
   * @param options - the conversion options
   * @returns unbreakscaped string, or void if writing to a file
   * @throws Error if any error occurs
   */
  unbreakscapeText(input, options) {
    if (!input) return input;
    const opts = Object.assign({}, options);
    const fileOptions = Object.assign({}, opts.fileOptions);
    const textFormat = Enum22(TextFormat).fromValue(opts.textFormat) ?? TextFormat.bitmarkText;
    const textLocation = opts.textLocation ?? TextLocation.body;
    let inStr = input;
    const inputIsString = typeof input === "string";
    if (env.isBrowser && opts.outputFile) {
      throw new Error("Cannot write to file in browser environment");
    }
    if (!opts.inputFormat || opts.inputFormat === Input.file) {
      if (env.isNode) {
        if (inputIsString && fs.existsSync(inStr)) {
          inStr = fs.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    const res = Breakscape.unbreakscape(inStr, {
      format: textFormat,
      location: textLocation
    });
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      const flag = fileOptions.append ? "a" : "w";
      fs.ensureDirSync(path.dirname(output));
      fs.writeFileSync(output, res, {
        flag
      });
    } else {
      return res;
    }
    return;
  }
  textAstToPlainText(textAst, _options) {
    const textGenerator = new TextGenerator(BitmarkVersion.v3, {
      //
    });
    const res = textGenerator.generateSync(textAst, TextFormat.bitmarkText, TextLocation.body, {
      noBreakscaping: true,
      noMarkup: true
    });
    return res;
  }
  /**
   * Get the supported bits as a formatted strings
   *
   * @param supportedBits
   * @returns
   */
  supportedBitsAsString(supportedBits) {
    let res = "";
    for (const rootBit of supportedBits) {
      res += `${rootBit.name} (since: ${rootBit.since}`;
      if (rootBit.deprecated) res += `, deprecated: ${rootBit.deprecated}`;
      res += ")\n";
    }
    return res;
  }
};

// src/generator/json/JsonStringGenerator.ts
var JsonStringGenerator = class {
  /**
   * Generate bitmark JSON from a bitmark AST as a string
   *
   * @param options - JSON generation options
   */
  constructor(options) {
    __publicField(this, "generator");
    __publicField(this, "writer");
    this.writer = new StringWriter();
    this.generator = new JsonGenerator(this.writer, options);
  }
  /**
   * Generate bitmark JSON from bitmark AST as a string
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    await this.generator.generate(ast);
    return this.writer.getString();
  }
  /**
   * Generate bitmark JSON from bitmark AST as a string synchronously
   *
   * @param ast bitmark AST
   */
  generateSync(ast) {
    this.generator.generateSync(ast);
    return this.writer.getString();
  }
};

// src/utils/BoundingBox.ts
function toBbox(arr) {
  if (!Array.isArray(arr) || arr.length !== 4) return null;
  if (!arr.every(Number.isFinite)) return null;
  return [arr[0], arr[1], arr[2], arr[3]];
}
function parseSourceBB(sourceBB) {
  if (sourceBB == null) return [];
  if (!Array.isArray(sourceBB) || sourceBB.length === 0) return [];
  const isNested = sourceBB.some(Array.isArray);
  const candidates = isNested ? sourceBB : [sourceBB];
  const out = [];
  for (const candidate of candidates) {
    const box = toBbox(candidate);
    if (box) out.push(box);
  }
  return out;
}
function boundingBoxIntersects(a, b) {
  return a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
}
function boundingBoxUnion(boxes) {
  if (boxes.length === 0) return null;
  let [x0, y0, x1, y1] = boxes[0];
  for (let i = 1; i < boxes.length; i++) {
    const b = boxes[i];
    if (b[0] < x0) x0 = b[0];
    if (b[1] < y0) y0 = b[1];
    if (b[2] > x1) x1 = b[2];
    if (b[3] > y1) y1 = b[3];
  }
  return [x0, y0, x1, y1];
}
var BoundingBoxIndex = class _BoundingBoxIndex {
  constructor(entries) {
    __publicField(this, "_entries");
    __publicField(this, "bitmarkParser", new BitmarkParserGenerator());
    this._entries = entries;
  }
  /**
   * Build an index from already-parsed bits. Bits without a parseable
   * `sourceBB` are dropped. Order of the input is preserved.
   *
   * Pass `options.sourceRL` to scope the index to a single source page —
   * bits whose `sourceRL` doesn't match (or is absent) are excluded.
   */
  static fromBits(wrappers, options) {
    const filterSourceRL = options?.sourceRL;
    const entries = [];
    for (const wrapper of wrappers) {
      if (!wrapper?.bit) continue;
      if (filterSourceRL !== void 0 && wrapper.bit.sourceRL !== filterSourceRL) {
        continue;
      }
      const boxes = parseSourceBB(wrapper.bit.sourceBB);
      if (boxes.length === 0) continue;
      entries.push({ bit: wrapper.bit, wrapper, boxes });
    }
    return new _BoundingBoxIndex(entries);
  }
  /** All bits with at least one valid `sourceBB`, in input order. */
  get entries() {
    return this._entries;
  }
  /**
   * Bits whose ANY `sourceBB` box intersects `query` (strict overlap —
   * touching-only edges do NOT count as an intersection).
   */
  intersecting(query) {
    const matches = [];
    for (const entry of this._entries) {
      if (entry.boxes.some((box) => boundingBoxIntersects(query, box))) {
        matches.push(entry);
      }
    }
    return matches;
  }
  /**
   * Flat list of every individual `sourceBB` box that intersects `query`.
   * For a multi-box bit, only the boxes that themselves intersected are
   * returned.
   */
  intersectingBoxes(query) {
    const matches = [];
    for (const entry of this._entries) {
      for (const box of entry.boxes) {
        if (boundingBoxIntersects(query, box)) matches.push(box);
      }
    }
    return matches;
  }
  /**
   * Smallest axis-aligned box that fully encloses every individual `sourceBB`
   * box that intersected the query. For a multi-box bit, only the boxes that
   * themselves intersected contribute to the union — non-intersecting boxes
   * from the same bit are ignored. Returns `null` if no box matches.
   */
  enclosingBoundingBox(query) {
    return boundingBoxUnion(this.intersectingBoxes(query));
  }
  /** Render selected bits back to bitmark text. Empty input returns `''`. */
  toBitmark(entries) {
    if (entries.length === 0) return "";
    const wrappers = entries.map((e) => e.wrapper);
    return this.bitmarkParser.convert(wrappers, {
      outputFormat: Output.bitmark,
      bitmarkOptions: { prettifyJson: true }
    });
  }
};

// src/index.ts
init();
export {
  Ast,
  BitType2 as BitType,
  BitmarkGenerator,
  BitmarkParser,
  BitmarkParserGenerator,
  BitmarkParserType,
  BitmarkStringGenerator,
  BitmarkVersion,
  BodyTextFormat,
  BoundingBoxIndex,
  Builder,
  CardSetVersion,
  HtmlTableGenerator,
  HtmlTableParser,
  InfoFormat,
  InfoType,
  Input,
  InputFormat,
  JsonGenerator,
  JsonParser,
  JsonStringGenerator,
  NodeType,
  Output,
  ResourceBuilder,
  StringWriter,
  TextMarkType,
  TextNodeType,
  parse as bitmarkTextParse
};
//# sourceMappingURL=index.js.map