UNPKG

@gmb/bitmark-parser-generator

Version:
47,212 lines 1.57 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 BitType = {
  _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 TextFormat = {
  // 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, path5) {
    if (!obj) return void 0;
    if (!path5) return void 0;
    if (!Array.isArray(path5)) path5 = [path5];
    path5 = path5.flatMap((p) => p.split("."));
    let node = obj;
    for (const key of path5) {
      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, path5, value, create) {
    if (!obj) return false;
    if (!path5) return false;
    if (!Array.isArray(path5)) path5 = [path5];
    path5 = path5.flatMap((p) => p.split("."));
    let node = obj;
    for (let i = 0; i < path5.length - 1; i++) {
      const key = path5[i];
      if (node[key] == void 0) {
        if (create) {
          node[key] = {};
        } else {
          return false;
        }
      }
      node = node[key];
    }
    node[path5[path5.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, path5) {
    if (!obj) return [];
    if (!path5) return [];
    if (!Array.isArray(path5)) path5 = [path5];
    const node = obj;
    const key = path5.shift();
    if (key == null) return node;
    const data = node[key];
    if (!data) return [];
    if (Array.isArray(data)) {
      return data.flatMap((d) => this.flatMapPath(d, path5.slice()));
    } else {
      return [this.flatMapPath(data, path5)].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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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: TextFormat.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 = {
  [BitType._error]: {
    since: "1.3.0",
    description: "An error bit, rendered when the bit cannot be interpreted"
  },
  [BitType._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]"
  },
  [BitType._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"
      }
    ]
  },
  [BitType.appFlashcards]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.appFlashcardsQuiz]: {
    since: "1.3.0",
    baseBitType: BitType.appFlashcards,
    description: "App flashcards quiz bit"
  },
  [BitType.appFlashcardsLearn]: {
    since: "1.3.0",
    baseBitType: BitType.appFlashcards,
    description: "App flashcards learn bit"
  },
  [BitType.appLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.appRag]: {
    since: "5.14.0",
    baseBitType: BitType.article,
    description: "App RAG (Retrieval-Augmented Generation) bit, equal to article"
  },
  [BitType.appSearch]: {
    since: "5.14.0",
    baseBitType: BitType.article,
    description: "App search bit, equal to article"
  },
  [BitType.author]: {
    since: "4.2.0",
    baseBitType: BitType._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.'
      }
    ]
  },
  [BitType.bookAuthor]: {
    since: "4.2.0",
    baseBitType: BitType.author,
    description: "Represents an author of a book"
  },
  [BitType.articleAuthor]: {
    since: "4.2.0",
    baseBitType: BitType.author,
    description: "Represents an author of a book"
  },
  [BitType.article]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.articleAlt]: {
    since: "1.15.0",
    baseBitType: BitType.article,
    description: "Alternative article bit"
  },
  [BitType.articleResponsive]: {
    since: "1.21.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.articleResponsiveAlt]: {
    since: "2.0.0",
    baseBitType: BitType.articleResponsive,
    description: "Alternative responsive article bit"
  },
  [BitType.standardArticleNormative]: {
    since: "1.16.0",
    baseBitType: BitType.article,
    description: "Standard normative article bit"
  },
  [BitType.standardArticleNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.article,
    description: "Standard non-normative article bit"
  },
  [BitType.smartStandardArticleNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
        }
      ]
    }
  },
  [BitType.smartStandardArticleNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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": "$"
        }
      ]
    }
  },
  [BitType.smartStandardArticleNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardArticleNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.statement]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Statement bit, used for statements in documents"
  },
  [BitType.pageArticle]: {
    since: "1.15.0",
    baseBitType: BitType.article,
    description: "Page article bit"
  },
  [BitType.pageArticleAlt]: {
    since: "1.15.0",
    baseBitType: BitType.article,
    description: "Alternative page article bit"
  },
  [BitType.pageArticleResponsive]: {
    since: "1.21.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.buttonCopyText]: {
    since: "1.4.3",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.catalogItemBook]: {
    since: "4.15.0",
    baseBitType: BitType._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
  },
  [BitType.catalogItemExternalBook]: {
    since: "4.15.0",
    baseBitType: BitType._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
  },
  [BitType.catalogItemProduct]: {
    since: "4.15.0",
    baseBitType: BitType._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
  },
  [BitType.callToAction]: {
    since: "1.15.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.callToActionSubscribe]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action subscribe bit"
  },
  [BitType.callToActionContact]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action contact bit"
  },
  [BitType.callToActionJoin]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action join bit"
  },
  [BitType.callToActionMail]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action mail bit"
  },
  [BitType.callToActionLearnMore]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action learn more bit"
  },
  [BitType.callToActionSeeMore]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action see more bit"
  },
  [BitType.callToActionWatch]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action watch bit"
  },
  [BitType.callToActionStartNow]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action start now bit"
  },
  [BitType.callToActionGetOffer]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action get offer bit"
  },
  [BitType.callToActionBookNow]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action book now bit"
  },
  [BitType.callToActionShopNow]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action shop now bit"
  },
  [BitType.callToActionGetNow]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action get now bit"
  },
  [BitType.callToActionDownload]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action download bit"
  },
  [BitType.callToActionCreateAccount]: {
    since: "1.15.0",
    baseBitType: BitType.callToAction,
    description: "Call to action create account bit"
  },
  [BitType.appBitmarkFromJavascript]: {
    since: "1.4.5",
    baseBitType: BitType._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: TextFormat.plainText
  },
  [BitType.appBitmarkFromEditor]: {
    since: "1.4.5",
    baseBitType: BitType.appBitmarkFromJavascript,
    description: "App bitmark from editor, used to create bits from the editor in the app"
  },
  [BitType.articleEmbed]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.articleLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.audio]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.audioEmbed]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.audioLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.bitAlias]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.diff]: {
    since: "3.13.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.book]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.bookEnd]: {
    since: "3.27.0",
    baseBitType: BitType.article,
    description: "End of book bit"
  },
  [BitType.bookAcknowledgements]: {
    since: "1.17.0",
    baseBitType: BitType.article,
    description: "Acknowledgements section of a book"
  },
  [BitType.bookAddendum]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Addendum section of a book"
  },
  [BitType.bookAfterword]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Afterword section of a book"
  },
  [BitType.bookAppendix]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Appendix section of a book"
  },
  [BitType.bookArticle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Article section of a book"
  },
  [BitType.bookAutherBio]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Author biography section of a book"
  },
  [BitType.bookBibliography]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Bibliography section of a book"
  },
  [BitType.bookComingSoon]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Coming soon section of a book"
  },
  [BitType.bookConclusion]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Conclusion section of a book"
  },
  [BitType.bookCopyright]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Copyright section of a book"
  },
  [BitType.bookCopyrightPermissions]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Copyright permissions section of a book"
  },
  [BitType.bookDedication]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Dedication section of a book"
  },
  [BitType.bookEndnotes]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Endnotes section of a book"
  },
  [BitType.bookEpigraph]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Epigraph section of a book"
  },
  [BitType.bookEpilogue]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Epilogue section of a book"
  },
  [BitType.bookForword]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Foreword section of a book"
  },
  [BitType.bookFrontispiece]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Frontispiece section of a book"
  },
  [BitType.bookImprint]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Imprint section of a book"
  },
  [BitType.bookIncitingIncident]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Inciting incident section of a book"
  },
  [BitType.bookIntroduction]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Introduction section of a book"
  },
  [BitType.bookListOfContributors]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "List of contributors section of a book"
  },
  [BitType.bookNotes]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Notes section of a book"
  },
  [BitType.bookPostscript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Postscript section of a book"
  },
  [BitType.bookPreface]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Preface section of a book"
  },
  [BitType.bookPrologue]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Prologue section of a book"
  },
  [BitType.bookReadMore]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Read more section of a book"
  },
  [BitType.bookReferenceList]: {
    //
    since: "3.2.0",
    baseBitType: BitType._standard,
    description: "Book reference list, used to list references in a book",
    cardSet: CardSetConfigKey.bookReferenceList
  },
  [BitType.bookRequestForABookReview]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Request for a book review section of a book"
  },
  [BitType.bookSummary]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Summary section of a book"
  },
  [BitType.bookTeaser]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Teaser section of a book"
  },
  [BitType.bookTitle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Title section of a book"
  },
  [BitType.bookCover]: {
    since: "3.27.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.bookAlias]: {
    since: "1.4.3",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.bookDiff]: {
    since: "3.10.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.bookClose]: {
    since: "1.18.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.bookReference]: {
    since: "2.2.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.botActionResponse]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action response bit, used to define responses for bot actions",
    cardSet: CardSetConfigKey.botActionResponses
  },
  [BitType.botActionSend]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.browserImage]: {
    since: "1.3.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.card1]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Card bit, used for cards in quizzes",
    quizBit: true,
    tags: [
      {
        key: ConfigKey.group_quizCommon,
        description: "Common tags for quiz bits"
      }
    ]
  },
  [BitType.question1]: {
    since: "1.3.0",
    baseBitType: BitType.card1,
    description: "Question card bit"
  },
  [BitType.survey1]: { since: "1.3.0", baseBitType: BitType.card1, description: "Survey card bit" },
  [BitType.surveyAnonymous1]: {
    since: "1.3.0",
    baseBitType: BitType.card1,
    description: "Anonymous survey card bit"
  },
  [BitType.chapter]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.clozeAndMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.coachCallToActionClozeAndMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType.clozeAndMultipleChoiceText,
    description: "Coach call to action cloze and multiple choice text bit"
  },
  [BitType.cloze]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.clozeInstructionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType.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.
    ]
  },
  [BitType.clozeSolutionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.clozeSeveral]: {
    since: "3.5.0",
    baseBitType: BitType.cloze,
    description: "Cloze several bit, used for cloze questions with multiple answers"
  },
  [BitType.gapText]: {
    since: "1.5.15",
    baseBitType: BitType.cloze,
    description: "Gap text bit, used for fill-in-the-blank questions in quizzes with text gaps"
  },
  [BitType.gapTextInstructionGrouped]: {
    since: "1.5.15",
    baseBitType: BitType.clozeInstructionGrouped,
    description: "Gap text instruction grouped bit, used for fill-in-the-blank questions with grouped instructions"
  },
  [BitType.coachSelfReflectionCloze]: {
    since: "1.3.0",
    baseBitType: BitType.cloze,
    description: "Coach self-reflection cloze bit, used for self-reflection questions in coaching"
  },
  [BitType.coachCallToActionCloze]: {
    since: "1.3.0",
    baseBitType: BitType.cloze,
    description: "Coach call to action cloze bit, used for call to action questions in coaching"
  },
  [BitType.clozeList]: {
    since: "1.4.13",
    baseBitType: BitType._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
  },
  [BitType.code]: {
    since: "1.3.0",
    baseBitType: BitType._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: TextFormat.plainText
  },
  [BitType.formula]: {
    since: "1.35.0",
    baseBitType: BitType._standard,
    description: "Formula bit, used for mathematical formulas in articles or documents",
    textFormatDefault: TextFormat.latex
  },
  [BitType.formulaImage]: {
    since: "5.23.0",
    baseBitType: BitType.formula,
    description: "Formula image bit, used for mathematical formulas rendered as images"
  },
  [BitType.smartStandardFormula]: {
    since: "3.11.0",
    baseBitType: BitType.formula,
    description: "Smart standard formula bit, used for mathematical formulas in smart standards"
  },
  [BitType.smartStandardFormulaNonNormative]: {
    since: "3.11.0",
    baseBitType: BitType.formula,
    description: "Smart standard non-normative formula bit, used for mathematical formulas in smart standards that are non-normative"
  },
  [BitType.smartStandardFormulaNormative]: {
    since: "3.11.0",
    baseBitType: BitType.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": "$"
      }
    }
  },
  [BitType.smartStandardRemarkFormula]: {
    since: "3.11.0",
    baseBitType: BitType.formula,
    description: "Smart standard remark formula bit, used for mathematical formulas in smart standards remarks"
  },
  [BitType.smartStandardRemarkFormulaNonNormative]: {
    since: "3.11.0",
    baseBitType: BitType.formula,
    description: "Smart standard non-normative remark formula bit, used for mathematical formulas in smart standards remarks that are non-normative"
  },
  [BitType.smartStandardRemarkFormulaNormative]: {
    since: "3.11.0",
    baseBitType: BitType.formula,
    description: "Smart standard normative remark formula bit, used for mathematical formulas in smart standards remarks that are normative"
  },
  [BitType.appCodeCell]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "App code cell, used to create code cells in the app editor"
  },
  [BitType.appCodeEditor]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "App code editor, used to create code editors in the app editor"
  },
  [BitType.appCodeIde]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "App code IDE, used to create code IDEs in the app editor"
  },
  [BitType.codeRuntime]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "Code runtime, used to define the runtime environment for code snippets"
  },
  [BitType.consoleLog]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "Console log, used to log messages to the console in code snippets"
  },
  [BitType.output]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "Output bit, used to display output from code snippets"
  },
  [BitType.stdout]: {
    since: "1.4.3",
    baseBitType: BitType.code,
    description: "Standard output bit, used to display standard output from code snippets"
  },
  [BitType.sandbox]: {
    since: "4.10.0",
    baseBitType: BitType.code,
    description: "A sandbox bit, used to create a sandboxed environment for code execution"
  },
  [BitType.sandboxOutputJson]: {
    since: "4.10.0",
    baseBitType: BitType.code,
    description: "A sandbox output JSON bit, used to display JSON output from sandboxed code"
  },
  [BitType.sandboxOutputMarkup]: {
    since: "4.10.0",
    baseBitType: BitType.code,
    description: "A sandbox output markup bit, used to display markup output from sandboxed code"
  },
  [BitType.sandboxOutputRender]: {
    since: "4.10.0",
    baseBitType: BitType.code,
    description: "A sandbox output render bit, used to display rendered output from sandboxed code"
  },
  [BitType.step]: {
    since: "1.5.1",
    baseBitType: BitType.article,
    description: "Step bit, used to define steps in a process or guide"
  },
  [BitType.stepImageScreenshot]: {
    since: "1.5.1",
    baseBitType: BitType.image,
    description: "Step image screenshot bit, used to display screenshots in steps"
  },
  [BitType.stepImageScreenshotWithPointer]: {
    since: "1.5.1",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.milestone]: {
    since: "1.20.0",
    baseBitType: BitType.step,
    description: "Milestone bit, used to define milestones in a process or guide"
  },
  [BitType.conversationLeft1]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.conversationLeft1Scream]: {
    since: "1.3.0",
    baseBitType: BitType.conversationLeft1,
    description: "Conversation left scream bit, used to create a conversation with a scream on the left side of the screen"
  },
  [BitType.conversationLeft1Thought]: {
    since: "1.3.0",
    baseBitType: BitType.conversationLeft1,
    description: "Conversation left thought bit, used to create a conversation with a thought on the left side of the screen"
  },
  [BitType.conversationRight1]: {
    since: "1.3.0",
    baseBitType: BitType.conversationLeft1,
    description: "Conversation right bit, used to create a conversation on the right side of the screen"
  },
  [BitType.conversationRight1Scream]: {
    since: "1.3.0",
    baseBitType: BitType.conversationLeft1,
    description: "Conversation right scream bit, used to create a conversation with a scream on the right side of the screen"
  },
  [BitType.conversationRight1Thought]: {
    since: "1.3.0",
    baseBitType: BitType.conversationLeft1,
    description: "Conversation right thought bit, used to create a conversation with a thought on the right side of the screen"
  },
  [BitType.clozeAndMultipleChoiceTextConversationLeft1]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.clozeAndMultipleChoiceTextConversationLeft1Scream]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.clozeAndMultipleChoiceTextConversationLeft1Thought]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.clozeAndMultipleChoiceTextConversationRight1]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.clozeAndMultipleChoiceTextConversationRight1Scream]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.clozeAndMultipleChoiceTextConversationRight1Thought]: {
    since: "1.3.0",
    baseBitType: BitType.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"
  },
  [BitType.advertising]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "General advertising content bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingAdvertorial]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "Advertorial content bit (sponsored article)",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingCallToAction]: {
    since: "4.2.0",
    baseBitType: BitType.callToAction,
    description: "Call-to-action for advertising",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingCallToActionMeeting]: {
    since: "4.2.0",
    baseBitType: BitType.callToAction,
    description: "Call-to-action for meeting or appointment",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingBanner]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Banner advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingSkyscraper]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Skyscraper (vertical) advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingRectangle]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Rectangle advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingFullPage]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Full-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingHalfPage]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Half-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingQuarterPage]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Quarter-page advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingClassifiedPage]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Classifieds advertisement page bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingLandscape]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Landscape format advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.advertisingPortrait]: {
    since: "4.2.0",
    baseBitType: BitType.image,
    description: "Portrait format advertisement bit",
    tags: [
      {
        key: ConfigKey.group_advertisingCommon,
        description: "Common advertising tags"
      }
    ]
  },
  [BitType.cookPreparation]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook preparation bit"
  },
  [BitType.cookStep]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook step bit"
  },
  [BitType.cookIngredients]: {
    since: "1.5.16",
    baseBitType: BitType._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
  },
  [BitType.recipe]: {
    since: "1.5.24",
    baseBitType: BitType.cookIngredients,
    description: "Recipe bit, used to define a recipe"
  },
  [BitType.cookRemark]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook remark bit, used to add remarks to recipes"
  },
  [BitType.cookVariation]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook variation bit, used to define variations of recipes"
  },
  [BitType.cookInsert]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook insert bit, used to insert additional content in recipes"
  },
  [BitType.cookNoteOnQuantity]: {
    since: "3.27.0",
    baseBitType: BitType.article,
    description: "Cook note on quantity bit, used to add notes on ingredient quantities in recipes"
  },
  [BitType.cookArrangement]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook arrangement bit, used to arrange ingredients or steps in recipes"
  },
  [BitType.cookPracticeAdvise]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook practice advice bit, used to provide practical advice in recipes"
  },
  [BitType.cookPlate]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook plate bit, used to define the presentation of a dish"
  },
  [BitType.cookRecommendation]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook recommendation bit, used to recommend dishes or ingredients"
  },
  [BitType.cookPersonalRecommendation]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook personal recommendation bit, used to provide personal recommendations for dishes or ingredients"
  },
  [BitType.cookSideDrink]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook side drink bit, used to define side drinks for dishes"
  },
  [BitType.cookSideDish]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook side dish bit, used to define side dishes for main courses"
  },
  [BitType.cookTimer]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Cook timer bit, used to set timers for cooking steps"
  },
  [BitType.document]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.documentDownload]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.leDocumentDownload]: {
    since: "3.18.0",
    baseBitType: BitType.documentDownload,
    description: "LE Document download bit, used to define downloadable documents in learning experiences"
  },
  [BitType.documentEmbed]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.documentLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.essay]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.coachSelfReflectionEssay]: {
    since: "1.3.0",
    baseBitType: BitType.essay,
    description: "Coach self-reflection essay bit"
  },
  [BitType.coachCallToActionEssay]: {
    since: "1.3.0",
    baseBitType: BitType.essay,
    description: "Coach call to action essay bit"
  },
  [BitType.example]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.exampleAlt]: {
    since: "1.16.0",
    baseBitType: BitType.example,
    description: "Alternative example bit, used to provide alternative examples in articles or books"
  },
  [BitType.standardExampleNormative]: {
    since: "1.16.0",
    baseBitType: BitType.example,
    description: "Standard normative example bit, used to provide normative examples in articles or books"
  },
  [BitType.standardExampleNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.example,
    description: "Standard non-normative example bit, used to provide non-normative examples in articles or books"
  },
  [BitType.smartStandardExampleNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardExampleNormative,
    description: "Smart standard normative example bit, used to provide normative examples in smart standards"
  },
  [BitType.smartStandardExampleNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardExampleNonNormative,
    description: "Smart standard non-normative example bit, used to provide non-normative examples in smart standards"
  },
  [BitType.smartStandardExampleNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardExampleNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.authorContentBitGenerator]: {
    since: "4.2.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.appAiPrompt]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "App AI prompt bit, used to create AI prompts in the app editor"
  },
  [BitType.aiPrompt]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "AI prompt bit, used to create AI prompts in articles or books"
  },
  [BitType.aiChat]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "AI chat bit, used to create an AI chat"
  },
  [BitType.aiEditor]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "AI editor bit, used to create an AI editor"
  },
  [BitType.aiTutor]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "AI tutor bit, used to create an AI tutor"
  },
  [BitType.aiWbt]: {
    since: "4.2.0",
    baseBitType: BitType.article,
    description: "AI WBT bit, used to create an AI WBT (Web-Based Training)"
  },
  [BitType.articleAi]: {
    since: "1.3.0",
    baseBitType: BitType.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 }
      }
    ]
  },
  [BitType.articleAttachment]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Article attachment bit, used to attach files to articles"
  },
  [BitType.assignment]: {
    since: "1.3.0",
    baseBitType: BitType.essay,
    description: "Assignment bit, used to define assignments in articles or books"
  },
  [BitType.audioTranscript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Audio transcript bit, used to provide transcripts for audio files in articles or books"
  },
  [BitType.bitmarkExample]: {
    since: "1.3.0",
    baseBitType: BitType.example,
    description: "Bitmark example bit, used to provide examples for bitmarks in articles or books"
  },
  [BitType.blogArticle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Blog article bit, used to define blog articles in articles or books"
  },
  [BitType.bug]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Bug bit, used to report bugs in articles or books"
  },
  [BitType.bugAlt]: {
    since: "1.16.0",
    baseBitType: BitType.bug,
    description: "Alternative bug bit, used to report bugs in articles or books with alternative styling"
  },
  [BitType.checklist]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Checklist bit, used to create checklists in articles or books"
  },
  [BitType.coachAudioTranscript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Coach audio transcript bit, used to provide transcripts for audio files in coaching articles or books"
  },
  [BitType.coachCallToActionChecklist]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Coach call to action checklist bit, used to create checklists in coaching articles or books"
  },
  [BitType.coachHomeRules]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Coach home rules bit, used to define home rules in coaching articles or books"
  },
  [BitType.coachVideoTranscript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Coach video transcript bit, used to provide transcripts for video files in coaching articles or books"
  },
  [BitType.correction]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Correction bit, used to provide corrections in articles or books"
  },
  [BitType.danger]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Danger bit, used to highlight dangerous or critical information in articles or books"
  },
  [BitType.dangerAlt]: {
    since: "1.16.0",
    baseBitType: BitType.article,
    description: "Alternative danger bit, used to highlight dangerous or critical information in articles or books with alternative styling"
  },
  [BitType.definitionTerm]: {
    since: "1.34.0",
    baseBitType: BitType.article,
    description: "Definition term bit, used to define terms in articles or books"
  },
  [BitType.deleted]: {
    since: "3.9.0",
    baseBitType: BitType.article,
    description: "Deleted bit, used to indicate deleted content in articles or books"
  },
  [BitType.details1]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Details bit, used to provide additional details in articles or books"
  },
  [BitType.details]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Details bit, used to provide additional details in articles or books"
  },
  [BitType.qAndA]: {
    since: "3.5.0",
    baseBitType: BitType.article,
    description: "Q&A bit, used to create question and answer sections in articles or books"
  },
  [BitType.editorial]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Editorial bit, used to provide editorial content in articles or books"
  },
  [BitType.editorNote]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Editor note bit, used to provide notes from the editor in articles or books"
  },
  [BitType.featured]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Featured bit, used to highlight featured content in articles or books"
  },
  [BitType.glossaryTerm]: {
    since: "1.33.0",
    baseBitType: BitType.article,
    description: "Glossary term bit, used to define glossary terms in articles or books"
  },
  [BitType.help]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Help bit, used to provide help or support information in articles or books"
  },
  [BitType.helpAlt]: {
    since: "1.16.0",
    baseBitType: BitType.help,
    description: "Alternative help bit, used to provide help or support information in articles or books with alternative styling"
  },
  [BitType.hint]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Hint bit, used to provide hints or tips in articles or books"
  },
  [BitType.hintAlt]: {
    since: "1.16.0",
    baseBitType: BitType.hint,
    description: "Alternative hint bit, used to provide hints or tips in articles or books with alternative styling"
  },
  [BitType.indexTerm]: {
    since: "1.33.0",
    baseBitType: BitType.article,
    description: "Index term bit, used to define index terms in articles or books"
  },
  [BitType.info]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Info bit, used to provide informational content in articles or books"
  },
  [BitType.infoAlt]: {
    since: "1.16.0",
    baseBitType: BitType.info,
    description: "Alternative info bit, used to provide informational content in articles or books with alternative styling"
  },
  [BitType.langLearningOutcomes]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language learning outcomes bit, used to define learning outcomes in language articles or books"
  },
  [BitType.langEnablingLanguageSkills]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language enabling skills bit, used to define enabling language skills in language articles or books"
  },
  [BitType.langLifeSkills]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language life skills bit, used to define life skills in language articles or books"
  },
  [BitType.langEnglishAroundWorld]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language English around the world bit, used to provide information about English usage around the world in language articles or books"
  },
  [BitType.langGoodToKnow]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language good to know bit, used to provide useful information in language articles or books"
  },
  [BitType.langLearningGoal]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language learning goal bit, used to define learning goals in language articles or books"
  },
  [BitType.langLearningStrategy]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language learning strategy bit, used to define learning strategies in language articles or books"
  },
  [BitType.langLikeALocal]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language like a local bit, used to provide tips for speaking like a local in language articles or books"
  },
  [BitType.langMaterial]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language material bit, used to provide language learning materials in language articles or books"
  },
  [BitType.langUsefulPhrases]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language useful phrases bit, used to provide useful phrases in language articles or books"
  },
  [BitType.langLevelDown]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language level down bit, used to provide information about lower language levels in language articles or books"
  },
  [BitType.langLevelUp]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language level up bit, used to provide information about higher language levels in language articles or books"
  },
  [BitType.langExtraActivity]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language extra activity bit, used to provide additional activities in language articles or books"
  },
  [BitType.langVideoScript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language video script bit, used to provide scripts for language learning videos in articles or books"
  },
  [BitType.langAudioScript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language audio script bit, used to provide scripts for language learning audio files in articles or books"
  },
  [BitType.langVocabulary]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language vocabulary bit, used to provide vocabulary lists in language articles or books"
  },
  [BitType.langHomework]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language homework bit, used to define homework assignments in language articles or books"
  },
  [BitType.langTeacherNote]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language teacher note bit, used to provide notes for teachers in language articles or books"
  },
  [BitType.langTeacherPronunciation]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Language teacher pronunciation bit, used to provide pronunciation guidance for teachers in language articles or books"
  },
  [BitType.list]: {
    since: "1.22.0",
    baseBitType: BitType.article,
    description: "List bit, used to create lists in articles or books"
  },
  [BitType.standardList]: {
    since: "1.22.0",
    baseBitType: BitType.article,
    description: "Standard list bit, used to create standard lists in articles or books"
  },
  [BitType.smartStandardList]: {
    since: "1.28.0",
    baseBitType: BitType.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" }
    }
  },
  [BitType.smartStandardListCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.message]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Message bit, used to create messages in articles or books"
  },
  [BitType.newspaperArticle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Newspaper article bit, used to define newspaper articles in articles or books"
  },
  [BitType.note]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Note bit, used to provide notes in articles or books"
  },
  [BitType.noteAlt]: {
    since: "1.16.0",
    baseBitType: BitType.note,
    description: "Alternative note bit, used to provide notes in articles or books with alternative styling"
  },
  [BitType.standardNoteNormative]: {
    since: "1.16.0",
    baseBitType: BitType.note,
    description: "Standard normative note bit, used to provide normative notes in articles or books"
  },
  [BitType.standardNoteNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.note,
    description: "Standard non-normative note bit, used to provide non-normative notes in articles or books"
  },
  [BitType.smartStandardNoteNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardNoteNormative,
    description: "Smart standard normative note bit, used to provide normative notes in smart standards"
  },
  [BitType.smartStandardNoteNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
      }
    }
  },
  [BitType.smartStandardNoteNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardNoteNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.noteAi]: {
    since: "1.3.0",
    baseBitType: BitType.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 }
      }
    ]
  },
  [BitType.notebookArticle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Notebook article bit, used to define notebook articles in articles or books"
  },
  [BitType.preparationNote]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Preparation note bit, used to provide preparation notes in articles or books"
  },
  [BitType.printThisBook]: {
    since: "5.4.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.printThisChapter]: {
    since: "5.4.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.printPageBreak]: {
    since: "5.4.0",
    baseBitType: BitType.separator,
    description: "Print page break bit, used to create page breaks for printing in articles or books"
  },
  [BitType.releaseNotesSummary]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Release notes summary bit, used to summarize release notes in articles or books"
  },
  [BitType.remark]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Remark bit, used to provide remarks in articles or books"
  },
  [BitType.remarkAlt]: {
    since: "1.16.0",
    baseBitType: BitType.remark,
    description: "Alternative remark bit, used to provide remarks in articles or books with alternative styling"
  },
  [BitType.standardRemarkNormative]: {
    since: "1.16.0",
    baseBitType: BitType.article,
    description: "Standard normative remark bit, used to provide normative remarks in articles or books"
  },
  [BitType.standardRemarkNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.article,
    description: "Standard non-normative remark bit, used to provide non-normative remarks in articles or books"
  },
  [BitType.smartStandardRemarkNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkNormative,
    description: "Smart standard normative remark bit, used to provide normative remarks in smart standards"
  },
  [BitType.smartStandardRemarkNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
      }
    }
  },
  [BitType.smartStandardRemarkNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.selfAssessment]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Self-assessment bit, used to create self-assessment sections in articles or books"
  },
  [BitType.separator]: {
    since: "1.4.15",
    baseBitType: BitType.article,
    description: "Separator bit, used to create visual separators in articles or books"
  },
  [BitType.separatorAlt]: {
    since: "1.16.0",
    baseBitType: BitType.separator,
    description: "Alternative separator bit, used to create visual separators in articles or books with alternative styling"
  },
  [BitType.sticker]: {
    since: "1.5.28",
    baseBitType: BitType.article,
    description: "Sticker bit, used to create stickers in articles or books"
  },
  [BitType.sideNote]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Side note bit, used to provide side notes in articles or books"
  },
  [BitType.summary]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Summary bit, used to provide summaries in articles or books"
  },
  [BitType.summaryAi]: {
    since: "1.3.0",
    baseBitType: BitType.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 }
      }
    ]
  },
  [BitType.videoTranscript]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Video transcript bit, used to provide transcripts for video files in articles or books"
  },
  [BitType.warning]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Warning bit, used to highlight warnings in articles or books"
  },
  [BitType.warningAlt]: {
    since: "1.16.0",
    baseBitType: BitType.warning,
    description: "Alternative warning bit, used to highlight warnings in articles or books with alternative styling"
  },
  [BitType.workbookArticle]: {
    since: "1.3.0",
    baseBitType: BitType.article,
    description: "Workbook article bit, used to define workbook articles in articles or books"
  },
  [BitType.collapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.sideNoteCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.infoCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.remarkCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.warningCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.dangerCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.noteCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.exampleCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.hintCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.bugCollapsible]: {
    since: "1.21.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.platformPath]: {
    since: "3.14.1",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.appCalculatorLatex]: {
    since: "4.10.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.container]: {
    since: "1.9.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.containerWrap]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Container wrap bit, used to wrap content in a container"
  },
  [BitType.containerNowrap]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Container nowrap bit, used to prevent wrapping of content in a container"
  },
  [BitType.containerNowrapStretch]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Container nowrap stretch bit, used to stretch content without wrapping in a container"
  },
  [BitType.containerGroup]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Container group bit, used to group multiple containers together"
  },
  [BitType.containerFolder]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Container folder bit, used to create folders for organizing bits"
  },
  [BitType.containerCarousel]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container carousel bit, used to create carousels for displaying bits"
  },
  [BitType.containerCards]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container cards bit, used to create card layouts for displaying bits"
  },
  [BitType.containerGrid]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container grid bit, used to create grid layouts for displaying bits"
  },
  [BitType.containerStack]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container stack bit, used to create stacked layouts for displaying bits"
  },
  [BitType.containerSlides]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container slides bit, used to create slide presentations for displaying bits"
  },
  [BitType.containerGallery]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container gallery bit, used to create galleries for displaying bits"
  },
  [BitType.containerScroller]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container scroller bit, used to create scrolling sections for displaying bits"
  },
  [BitType.containerTabs]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container tabs bit, used to create tabbed sections for displaying bits"
  },
  [BitType.containerAccordionTabs]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container accordion tabs bit, used to create accordion-style tabbed sections for displaying bits"
  },
  [BitType.containerFolderAll]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container folder all bit, used to create folders that can contain all types of bits"
  },
  [BitType.containerBits2]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container bits 2 bit, used to create a container for bits with additional features"
  },
  [BitType.containerCookRecipe]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container cook recipe bit, used to create recipe containers for displaying cooking recipes"
  },
  [BitType.containerNewsArticle]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container news article bit, used to create containers for news articles"
  },
  [BitType.containerPreview]: {
    since: "1.11.0",
    baseBitType: BitType.container,
    description: "Container preview bit, used to create preview sections for displaying bits"
  },
  [BitType.pageContainer]: {
    since: "1.9.0",
    baseBitType: BitType.container,
    description: "Page container bit, used to create containers for pages in articles or books"
  },
  [BitType.pageContainerWrap]: {
    since: "1.9.0",
    baseBitType: BitType.pageContainer,
    description: "Page container wrap bit, used to wrap content in a page container"
  },
  [BitType.pageContainerNowrap]: {
    since: "1.9.0",
    baseBitType: BitType.pageContainer,
    description: "Page container nowrap bit, used to prevent wrapping of content in a page container"
  },
  [BitType.pageContainerNowrapStretch]: {
    since: "1.9.0",
    baseBitType: BitType.pageContainer,
    description: "Page container nowrap stretch bit, used to stretch content without wrapping in a page container"
  },
  [BitType.pageContainerFolder]: {
    since: "1.9.0",
    baseBitType: BitType.pageContainer,
    description: "Page container folder bit, used to create folders for organizing bits in a page container"
  },
  [BitType.pageContainerGroup]: {
    since: "1.9.0",
    baseBitType: BitType.pageContainer,
    description: "Page container group bit, used to group multiple page containers together"
  },
  [BitType.metalevelExplanation]: {
    since: "1.10.0",
    baseBitType: BitType.article,
    description: "Metalevel explanation bit, used to provide explanations for metalevels in articles or books"
  },
  [BitType.module]: {
    since: "1.5.26",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.moduleProduct]: {
    since: "1.9.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.exampleList]: {
    since: "1.4.13",
    baseBitType: BitType.example,
    description: "Example list bit, used to create lists of examples in articles or books",
    cardSet: CardSetConfigKey.exampleBitList,
    rootExampleType: ExampleType.string
  },
  [BitType.extractorPage]: {
    since: "1.5.17",
    baseBitType: BitType.image,
    description: "Extractor page bit, used to extract pages from images"
  },
  [BitType.extractorPageCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.extractorPageWithBlocks]: {
    since: "1.5.21",
    baseBitType: BitType.image,
    description: "Extractor page with blocks bit, used to extract pages with blocks from images"
  },
  [BitType.extractorPageWithBlocksCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.extractorConfiguration]: {
    since: "1.7.1",
    baseBitType: BitType._standard,
    description: "Extractor configuration bit, used to configure extractors in articles or books",
    textFormatDefault: TextFormat.plainText
  },
  [BitType.extractorBlueprintConfiguration]: {
    since: "5.11.0",
    baseBitType: BitType._standard,
    description: "Extractor blueprint configuration bit, used to specify extractor blueprint configuration when extracting from blueprints",
    textFormatDefault: TextFormat.plainText
  },
  [BitType.extractorImage]: {
    since: "4.3.0",
    baseBitType: BitType._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: TextFormat.plainText
  },
  [BitType.extractorImageCollapsible]: {
    since: "4.3.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.extractorBlueprint]: {
    since: "5.12.0",
    baseBitType: BitType._standard,
    description: "Extractor blueprint bit, used to provide blueprint information about extractors",
    textFormatDefault: TextFormat.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
  },
  [BitType.extractorInformation]: {
    since: "3.8.0",
    baseBitType: BitType._standard,
    description: "Extractor information bit, used to provide information about extractors in articles or books",
    textFormatDefault: TextFormat.json
  },
  [BitType.extractorTheme]: {
    since: "5.7.0",
    baseBitType: BitType._standard,
    description: "Extractor theme bit, used to store design/theme JSON extracted from documents",
    textFormatDefault: TextFormat.json
  },
  [BitType.extractorReconcile]: {
    since: "5.35.0",
    baseBitType: BitType._standard,
    description: "Extractor reconcile bit, used to store PDF-compare reconciliation decisions (config and issues) in JSON",
    textFormatDefault: TextFormat.json
  },
  [BitType.extractorAiChat]: {
    since: "3.19.0",
    baseBitType: BitType._standard,
    description: "Extractor AI chat bit, used to create AI chat interfaces for extractors in articles or books"
  },
  [BitType.extractorBlock]: {
    since: "1.5.16",
    baseBitType: BitType._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
  },
  [BitType.extractorRepeatedText]: {
    since: "1.5.21",
    baseBitType: BitType.article,
    description: "Extractor repeated text bit, used to define repeated text in extractor pages"
  },
  [BitType.extractorRule]: {
    since: "5.17.0",
    baseBitType: BitType._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: TextFormat.plainText
  },
  [BitType.extractorPageNumber]: {
    since: "1.5.21",
    baseBitType: BitType.article,
    description: "Extractor page number bit, used to define page numbers in extractor pages"
  },
  [BitType.extractorPageNumberCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.extractorPageHeader]: {
    since: "1.5.21",
    baseBitType: BitType.article,
    description: "Extractor page header bit, used to define headers in extractor pages"
  },
  [BitType.extractorPageHeaderCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.extractorPageFooter]: {
    since: "1.5.21",
    baseBitType: BitType.article,
    description: "Extractor page footer bit, used to define footers in extractor pages"
  },
  [BitType.extractorPageFooterCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.pageOpenBook]: {
    since: "1.5.10",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.printBook]: {
    since: "5.5.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.openBookChapter]: {
    since: "4.16.0",
    baseBitType: BitType.pageOpenBook,
    description: "Open book chapter bit, derived from page-open-book, used to create chapter pages that open books"
  },
  [BitType.openBookChapterTeaser]: {
    since: "4.16.0",
    baseBitType: BitType.openBookChapter,
    description: "Open book chapter teaser bit, equal to open-book-chapter, used to create teaser chapter pages that open books"
  },
  [BitType.pageOpenBookList]: {
    since: "2.1.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.pageSubscribe]: {
    since: "1.5.10",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.assignmentList]: {
    since: "1.4.13",
    baseBitType: BitType.exampleList,
    description: "Assignment list bit, used to create lists of assignments in articles or books"
  },
  [BitType.pageFooter]: {
    since: "1.4.13",
    baseBitType: BitType.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
  },
  [BitType.legend]: {
    since: "3.12.0",
    baseBitType: BitType._standard,
    description: "Legend bit, used to provide legends in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType.smartStandardLegend]: {
    since: "3.12.0",
    baseBitType: BitType.legend,
    description: "Smart standard legend bit, used to provide smart standard legends in articles or books"
  },
  [BitType.smartStandardLegendNonNormative]: {
    since: "3.12.0",
    baseBitType: BitType.legend,
    description: "Smart standard non-normative legend bit, used to provide non-normative smart standard legends in articles or books"
  },
  [BitType.smartStandardLegendNormative]: {
    since: "3.12.0",
    baseBitType: BitType.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" }
      ]
    }
  },
  [BitType.smartStandardRemarkLegend]: {
    since: "3.12.0",
    baseBitType: BitType.legend,
    description: "Smart standard remark legend bit, used to provide smart standard remarks in legends"
  },
  [BitType.smartStandardRemarkLegendNonNormative]: {
    since: "3.12.0",
    baseBitType: BitType.legend,
    description: "Smart standard non-normative remark legend bit, used to provide non-normative smart standard remarks in legends"
  },
  [BitType.smartStandardRemarkLegendNormative]: {
    since: "3.12.0",
    baseBitType: BitType.legend,
    description: "Smart standard normative remark legend bit, used to provide normative smart standard remarks in legends"
  },
  [BitType.definitionList]: {
    since: "1.34.0",
    baseBitType: BitType._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
  },
  [BitType.definitionListCollapsible]: {
    since: "5.25.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.standardDefinitionListNormative]: {
    since: "5.24.0",
    baseBitType: BitType.definitionList,
    description: "Standard normative definition list bit, used to create normative standard definition lists in articles or books"
  },
  [BitType.standardDefinitionListNonNormative]: {
    since: "5.24.0",
    baseBitType: BitType.definitionList,
    description: "Standard non-normative definition list bit, used to create non-normative standard definition lists in articles or books"
  },
  [BitType.smartStandardDefinitionListNormative]: {
    since: "5.24.0",
    baseBitType: BitType.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" }
    }
  },
  [BitType.smartStandardDefinitionListNonNormative]: {
    since: "5.24.0",
    baseBitType: BitType.standardDefinitionListNonNormative,
    description: "Smart standard non-normative definition list bit, used to create non-normative smart standard definition lists in articles or books"
  },
  [BitType.metaSearchDefaultTerms]: {
    since: "3.12.0",
    baseBitType: BitType._standard,
    description: "Meta search default terms bit, used to define default search terms in articles",
    cardSet: CardSetConfigKey.definitionListPlain
  },
  [BitType.metaSearchDefaultTopics]: {
    since: "3.12.0",
    baseBitType: BitType.metaSearchDefaultTerms,
    description: "Meta search default topics bit, used to define default search topics in articles"
  },
  [BitType.flashcard]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.flashcard1]: {
    since: "1.3.0",
    baseBitType: BitType.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
  },
  [BitType.qAndACard]: {
    since: "3.25.0",
    baseBitType: BitType.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
  },
  [BitType.focusImage]: {
    since: "1.3.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.highlightText]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.image]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.appCreateBitsFromImage]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "App create bits from image bit, used to create bits from images in the app"
  },
  [BitType.appGetScreenshot]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "App get screenshot bit, used to capture screenshots in the app"
  },
  [BitType.detailsImage]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Details image bit, used to create detailed images in articles or books"
  },
  [BitType.figure]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Figure bit, used to create figures in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType.imageBanner]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image banner bit, used to create banners in articles or books"
  },
  [BitType.imageFigure]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image figure bit, used to create figures with images in articles or books",
    cardSet: CardSetConfigKey.definitionList
  },
  [BitType.imageFigureAlt]: {
    since: "1.16.0",
    baseBitType: BitType.imageFigure,
    description: "Alternative image figure bit, used to create figures with alternative images in articles or books"
  },
  [BitType.standardImageFigureNormative]: {
    since: "1.16.0",
    baseBitType: BitType.imageFigure,
    description: "Standard normative image figure bit, used to create normative image figures in articles or books"
  },
  [BitType.standardImageFigureNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.imageFigure,
    description: "Standard non-normative image figure bit, used to create non-normative image figures in articles or books"
  },
  [BitType.smartStandardImageFigureNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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" }
    }
  },
  [BitType.smartStandardImageFigureNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardImageFigureNonNormative,
    description: "Smart standard non-normative image figure bit, used to create smart standard non-normative image figures in articles or books"
  },
  [BitType.smartStandardImageFigureNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardImageFigureNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.imageLandscape]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image landscape bit, used to create landscape images in articles or books"
  },
  [BitType.imageMood]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image mood bit, used to create mood images in articles or books"
  },
  [BitType.imagePortrait]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image portrait bit, used to create portrait images in articles or books"
  },
  [BitType.imagePrototype]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image prototype bit, used to create prototype images in articles or books"
  },
  [BitType.imageSeparator]: {
    since: "1.4.15",
    baseBitType: BitType._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
  },
  [BitType.imageSeparatorAlt]: {
    since: "1.16.0",
    baseBitType: BitType.imageSeparator,
    description: "Alternative image separator bit, used to create alternative separators in articles or books"
  },
  [BitType.imageScreenshot]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image screenshot bit, used to create screenshots in articles or books"
  },
  [BitType.imageStyled]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image styled bit, used to create styled images in articles or books"
  },
  [BitType.imageSuperWide]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image super wide bit, used to create super wide images in articles or books"
  },
  [BitType.imageZoom]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Image zoom bit, used to create zoomable images in articles or books"
  },
  [BitType.langLifeSkillIcon]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Language life skill icon bit, used to create icons for life skills in articles or books"
  },
  [BitType.lifeSkillSticker]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Life skill sticker bit, used to create stickers for life skills in articles or books"
  },
  [BitType.pageBanner]: {
    since: "1.4.3",
    baseBitType: BitType._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
  },
  [BitType.pageHero]: {
    since: "1.11.0",
    baseBitType: BitType.pageBanner,
    description: "Page hero bit, used to create hero sections in articles or books"
  },
  [BitType.screenshot]: {
    since: "1.3.0",
    deprecated: "1.4.0",
    baseBitType: BitType.image,
    description: "Screenshot bit, used to create screenshots in articles or books"
  },
  [BitType.tableImage]: {
    since: "1.5.15",
    baseBitType: BitType.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
  },
  [BitType.tableImageAlt]: {
    since: "1.16.0",
    baseBitType: BitType.tableImage,
    description: "Alternative table image bit, used to create alternative images in tables in articles or books"
  },
  [BitType.standardTableImageNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableImage,
    description: "Standard normative table image bit, used to create normative images in tables in articles or books"
  },
  [BitType.standardTableImageNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableImage,
    description: "Standard non-normative table image bit, used to create non-normative images in tables in articles or books"
  },
  [BitType.standardRemarkTableImageNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableImage,
    description: "Standard normative remark table image bit, used to create normative remark images in tables in articles or books"
  },
  [BitType.standardRemarkTableImageNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableImage,
    description: "Standard non-normative remark table image bit, used to create non-normative remark images in tables in articles or books"
  },
  [BitType.standardTableExtendedImageNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableExtendedImage,
    description: "Standard normative extended table image bit, used to create normative extended images in tables in articles or books"
  },
  [BitType.standardTableExtendedImageNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableExtendedImage,
    description: "Standard non-normative extended table image bit, used to create non-normative extended images in tables in articles or books"
  },
  [BitType.standardRemarkTableExtendedImageNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableExtendedImage,
    description: "Standard normative remark extended table image bit, used to create normative remark extended images in tables in articles or books"
  },
  [BitType.standardRemarkTableExtendedImageNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableExtendedImage,
    description: "Standard non-normative remark extended table image bit, used to create non-normative remark extended images in tables in articles or books"
  },
  [BitType.smartStandardTableImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableImageNormative,
    description: "Smart standard normative table image bit, used to create smart standard normative images in tables in articles or books"
  },
  [BitType.smartStandardTableImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableImageNonNormative,
    description: "Smart standard non-normative table image bit, used to create smart standard non-normative images in tables in articles or books"
  },
  [BitType.smartStandardRemarkTableImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkTableImageNormative,
    description: "Smart standard normative remark table image bit, used to create smart standard normative remark images in tables in articles or books"
  },
  [BitType.smartStandardRemarkTableImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
  },
  [BitType.smartStandardTableExtendedImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableExtendedImageNormative,
    description: "Smart standard normative extended table image bit, used to create smart standard normative extended images in tables in articles or books"
  },
  [BitType.smartStandardTableExtendedImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
  },
  [BitType.smartStandardRemarkTableExtendedImageNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
  },
  [BitType.smartStandardRemarkTableExtendedImageNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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"
  },
  [BitType.smartStandardTableImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableExtendedImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableExtendedImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableExtendedImageNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableExtendedImageNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.imageLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.imageOnDevice]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.imageResponsive]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.imagesLogoGrave]: {
    since: "1.5.11",
    baseBitType: BitType._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
  },
  [BitType.prototypeImages]: {
    since: "1.6.1",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.internalLink]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.interview]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.interviewInstructionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType.interview,
    description: "Grouped interview instruction bit"
  },
  [BitType.botInterview]: {
    since: "1.3.0",
    baseBitType: BitType.interview,
    description: "Bot interview bit, used to create bot interviews in articles or books"
  },
  [BitType.brandColor]: {
    since: "4.12.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.learningPathBook]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.bookLink]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Book link bit, used to create links to books in learning paths"
  },
  [BitType.bookLinkNext]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Next book link bit, used to create links to the next book in learning paths"
  },
  [BitType.bookLinkPrev]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Previous book link bit, used to create links to the previous book in learning paths"
  },
  [BitType.learningPathClassroomEvent]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path classroom event bit, used to create classroom events in learning paths"
  },
  [BitType.learningPathClassroomTraining]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path classroom training bit, used to create classroom training in learning paths"
  },
  [BitType.learningPathClosing]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path closing bit, used to close learning paths"
  },
  [BitType.learningPathFeedback]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path feedback bit, used to provide feedback in learning paths"
  },
  [BitType.learningPathLearningGoal]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path learning goal bit, used to define learning goals in learning paths"
  },
  [BitType.learningPathLti]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path LTI bit, used to integrate LTI tools in learning paths"
  },
  [BitType.learningPathSign]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path sign bit, used to create signs in learning paths"
  },
  [BitType.learningPathStep]: {
    since: "1.3.0",
    baseBitType: BitType.learningPathBook,
    description: "Learning path step bit, used to create steps in learning paths"
  },
  [BitType.learningPathBotTraining]: {
    since: "1.3.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.learningPathExternalLink]: {
    since: "1.3.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.learningPathVideoCall]: {
    since: "1.3.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leLearningObjectives]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leVideoCall]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leClassroomEvent]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leMultiDayEvent]: {
    since: "3.32.0",
    baseBitType: BitType.leClassroomEvent,
    description: "Multi-day event bit, used to create multi-day events in learning paths"
  },
  [BitType.leCompletion]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leExternalLink]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leReadBook]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.leLearningStep]: {
    since: "1.25.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.lePreparationTask]: {
    since: "1.26.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.leRead]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Read task bit, used to create reading tasks in learning paths"
  },
  [BitType.leTask]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Task bit, used to create tasks in learning paths"
  },
  [BitType.leTodo]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Todo bit, used to create todo tasks in learning paths"
  },
  [BitType.leFollowUpTask]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Follow-up task bit, used to create follow-up tasks in learning paths"
  },
  [BitType.leFinishingTask]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Finishing task bit, used to create finishing tasks in learning paths"
  },
  [BitType.leAssignment]: {
    since: "1.27.0",
    baseBitType: BitType.lePreparationTask,
    description: "Assignment bit, used to create assignments in learning paths"
  },
  [BitType.leWatchVideoEmbed]: {
    since: "1.27.0",
    baseBitType: BitType.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
  },
  [BitType.leListenAudioEmbed]: {
    since: "1.27.0",
    baseBitType: BitType.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
  },
  [BitType.listItem]: {
    since: "1.22.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.standardListItem]: {
    since: "1.22.0",
    baseBitType: BitType.listItem,
    description: "Standard list item bit, used to create standard list items in articles or books"
  },
  [BitType.smartStandardListItem]: {
    since: "1.28.0",
    baseBitType: BitType.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"
      }
    }
  },
  [BitType.smartStandardListItemCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.mark]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.match]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.matchAll]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match all bit, used to create matching pairs with all options in articles or books"
  },
  [BitType.matchReverse]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match reverse bit, used to create matching pairs with reversed options in articles or books"
  },
  [BitType.matchAllReverse]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match all reverse bit, used to create matching pairs with all options and reversed in articles or books"
  },
  [BitType.matchSolutionGrouped]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Grouped match solution bit, used to create grouped matching solutions in articles or books"
  },
  [BitType.matchMatrix]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match matrix bit, used to create matching pairs in a matrix format in articles or books",
    cardSet: CardSetConfigKey.matchMatrix
  },
  [BitType.matchAudio]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match audio bit, used to create matching pairs with audio in articles or books",
    cardSet: CardSetConfigKey.matchAudioPairs
  },
  [BitType.matchPicture]: {
    since: "1.3.0",
    baseBitType: BitType.match,
    description: "Match picture bit, used to create matching pairs with pictures in articles or books",
    cardSet: CardSetConfigKey.matchImagePairs
  },
  [BitType.feedback]: {
    since: "3.13.0",
    baseBitType: BitType._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
  },
  [BitType.learningDocumentationFeedback]: {
    since: "3.13.0",
    baseBitType: BitType.feedback,
    description: "Feedback for learning documentation"
  },
  [BitType.handInFeedbackExpert]: {
    since: "3.30.0",
    baseBitType: BitType.feedback,
    description: "Feedback for expert hand-in"
  },
  [BitType.handInFeedbackSelf]: {
    since: "3.30.0",
    baseBitType: BitType.feedback,
    description: "Self-feedback for hand-in"
  },
  [BitType.multipleChoice1]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.coachSelfReflectionMultipleChoice1]: {
    since: "1.3.0",
    baseBitType: BitType.multipleChoice1,
    description: "Coach self-reflection multiple choice 1 bit, used for self-reflection in coaching"
  },
  [BitType.multipleChoice]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.coachSelfReflectionMultipleChoice]: {
    since: "1.3.0",
    baseBitType: BitType.multipleChoice,
    description: "Coach self-reflection multiple choice bit, used for self-reflection in coaching"
  },
  [BitType.multipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.coachCallToActionMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType.multipleChoiceText,
    description: "Coach call to action multiple choice text bit, used for self-reflection in coaching"
  },
  [BitType.coachSelfReflectionMultipleChoiceText]: {
    since: "1.3.0",
    baseBitType: BitType.multipleChoiceText,
    description: "Coach self-reflection multiple choice text bit, used for self-reflection in coaching"
  },
  [BitType.multipleResponse1]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.coachSelfReflectionMultipleResponse1]: {
    since: "1.3.0",
    baseBitType: BitType.multipleResponse1,
    description: "Coach self-reflection multiple response 1 bit, used for self-reflection in coaching"
  },
  [BitType.multipleResponse]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.coachSelfReflectionMultipleResponse]: {
    since: "1.3.0",
    baseBitType: BitType.multipleResponse,
    description: "Coach self-reflection multiple response bit, used for self-reflection in coaching"
  },
  [BitType.page]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageCollapsible]: {
    since: "1.30.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.pageCoverImage]: {
    since: "1.22.0",
    baseBitType: BitType.page,
    description: "Page cover image bit, used to create cover images for pages"
  },
  [BitType.advertisingAdvertorialPage]: {
    since: "4.5.0",
    baseBitType: BitType.page,
    description: "Advertorial Page bit, used to create advertorial pages"
  },
  [BitType.pageBuyButton]: {
    since: "1.4.3",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageBuyButtonAlt]: {
    since: "1.31.0",
    baseBitType: BitType.pageBuyButton,
    description: "Alternative page buy button bit, used to create alternative buy buttons on pages"
  },
  [BitType.pageBuyButtonPromotion]: {
    since: "1.5.11",
    baseBitType: BitType.pageBuyButton,
    description: "Page buy button for promotions, used to create buy buttons on promotional pages"
  },
  [BitType.pageSubpage]: {
    since: "1.6.6",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageShopInShop]: {
    since: "1.6.6",
    baseBitType: BitType.pageSubpage,
    description: "Page shop-in-shop bit, used to create shop-in-shop pages in articles or books"
  },
  [BitType.pageCategory]: {
    since: "1.6.6",
    baseBitType: BitType.pageSubpage,
    description: "Page category bit, used to create category pages in articles or books"
  },
  [BitType.pageAcademy]: {
    since: "1.6.6",
    baseBitType: BitType.pageSubpage,
    description: "Page academy bit, used to create academy pages in articles or books"
  },
  [BitType.pagePromotion]: {
    since: "1.6.6",
    baseBitType: BitType.pageSubpage,
    description: "Page promotion bit, used to create promotional pages in articles or books"
  },
  [BitType.pageSpecial]: {
    since: "1.6.6",
    baseBitType: BitType.pageSubpage,
    description: "Page special bit, used to create special pages in articles or books"
  },
  [BitType.pageEnd]: {
    since: "5.33.0",
    baseBitType: BitType.article,
    description: "Page end bit, used to mark the end of a page section in articles or books"
  },
  [BitType.pageStart]: {
    since: "5.33.0",
    baseBitType: BitType.article,
    description: "Page start bit, used to mark the start of a page section in articles or books"
  },
  [BitType.pagePerson]: {
    since: "1.5.16",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageProduct]: {
    since: "1.4.17",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageProductList]: {
    since: "1.4.17",
    baseBitType: BitType._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,
      // },
    ]
  },
  [BitType.pageProductVideo]: {
    since: "1.4.17",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pageProductVideoList]: {
    since: "1.4.17",
    baseBitType: BitType._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,
      // },
    ]
  },
  [BitType.pageSectionFolder]: {
    since: "1.4.17",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.photo]: {
    since: "1.3.0",
    baseBitType: BitType.image,
    description: "Photo bit, used to create photo bits in articles or books"
  },
  [BitType.platform]: {
    since: "4.12.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.platformBrandTarget]: {
    since: "4.15.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.platformHeader]: {
    since: "4.12.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.platformMain]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform main bit, used to define platform main properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformMainButton]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform main button bit, used to define platform main button properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformMainInput]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform main input bit, used to define platform main input properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformSection]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform section bit, used to define platform section properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformSectionButton]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform section button bit, used to define platform section button properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformSectionChat]: {
    since: "4.12.0",
    baseBitType: BitType._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"
          }
        ]
      }
    ]
  },
  [BitType.platformSectionHeader]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform section header bit, used to define platform section header properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformSectionInput]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform section input bit, used to define platform section input properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.platformSystemIcon]: {
    since: "4.12.0",
    baseBitType: BitType._standard,
    description: "Platform system icon bit, used to define platform system icon properties",
    tags: [
      {
        key: ConfigKey.group_platformStylesCommon,
        description: "Common platform styles"
      }
    ]
  },
  [BitType.quote]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.rating]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Rating bit, used to create rating bits in articles or books"
  },
  [BitType.coachSelfReflectionRating]: {
    since: "1.3.0",
    baseBitType: BitType.rating,
    description: "Coach self-reflection rating bit, used for self-reflection in coaching"
  },
  [BitType.releaseNote]: {
    since: "1.3.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.reviewNote]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.reviewAuthorNote]: {
    since: "1.3.0",
    baseBitType: BitType.reviewNote,
    description: "Author review note bit, used to create author notes in reviews"
  },
  [BitType.reviewCustomerNote]: {
    //
    since: "3.5.0",
    baseBitType: BitType.reviewNote,
    description: "Customer review note bit, used to create customer notes in reviews"
  },
  [BitType.reviewError]: {
    since: "5.8.0",
    baseBitType: BitType.reviewNote,
    description: "Review error note bit, used to create error notes in reviews"
  },
  [BitType.reviewErrorAi]: {
    since: "5.8.0",
    baseBitType: BitType.reviewNote,
    description: "Review error AI note bit, used to create AI error notes in reviews"
  },
  [BitType.reviewErrorTranslation]: {
    since: "5.8.0",
    baseBitType: BitType.reviewNote,
    description: "Review error translation note bit, used to create translation error notes in reviews"
  },
  [BitType.reviewReviewerNote]: {
    since: "1.3.0",
    baseBitType: BitType.reviewNote,
    description: "Reviewer review note bit, used to create reviewer notes in reviews"
  },
  [BitType.reviewRequestForReviewNote]: {
    since: "1.3.0",
    baseBitType: BitType.reviewNote,
    description: "Request for review note bit, used to create notes requesting reviews"
  },
  [BitType.reviewApprovedNote]: {
    since: "1.3.0",
    baseBitType: BitType.reviewNote,
    description: "Approved review note bit, used to create notes for approved reviews"
  },
  [BitType.sampleSolution]: {
    since: "1.3.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.sequence]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.stillImageFilm]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.stillImageFilmEmbed]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.stillImageFilmLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.surveyAnonymous]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Anonymous survey bit, used to create anonymous surveys in articles or books"
  },
  [BitType.survey]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Survey bit, used to create surveys in articles or books"
  },
  [BitType.surveyMatrix]: {
    since: "1.6.2",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.surveyMatrixMe]: {
    since: "1.6.2",
    baseBitType: BitType.surveyMatrix,
    description: "Survey matrix me bit, used to create personal survey matrices in articles or books"
  },
  [BitType.surveyRating]: {
    since: "1.6.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.surveyRatingOnce]: {
    since: "1.6.0",
    baseBitType: BitType.surveyRating,
    description: "Survey rating once bit, used to create one-time rating surveys in articles or books"
  },
  [BitType.surveyRatingDisplay]: {
    since: "1.6.0",
    baseBitType: BitType.surveyRating,
    description: "Survey rating display bit, used to display rating surveys in articles or books"
  },
  [BitType.scorm]: {
    since: "1.5.11",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.pronunciationTable]: {
    since: "3.1.0",
    baseBitType: BitType._standard,
    description: "Pronunciation table bit, used to create pronunciation tables in articles or books",
    cardSet: CardSetConfigKey.pronunciationTable
  },
  [BitType.table]: {
    since: "1.5.19",
    baseBitType: BitType._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
  },
  [BitType.tableExtended]: {
    since: "4.14.0",
    baseBitType: BitType.table,
    description: "Extended table bit, used to create complex tables with all HTML table features",
    cardSet: CardSetConfigKey.tableExtended
  },
  [BitType.tableExtendedImage]: {
    since: "5.13.0",
    baseBitType: BitType.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
  },
  [BitType.tableAlt]: {
    since: "1.16.0",
    baseBitType: BitType.table,
    description: "Alternative table bit, used to create alternative tables in articles or books"
  },
  [BitType.standardTableNormative]: {
    since: "1.16.0",
    baseBitType: BitType.table,
    description: "Standard normative table bit, used to create standard normative tables in articles or books"
  },
  [BitType.standardTableNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.table,
    description: "Standard non-normative table bit, used to create standard non-normative tables in articles or books"
  },
  [BitType.standardRemarkTableNormative]: {
    since: "1.17.0",
    baseBitType: BitType.table,
    description: "Standard normative remark table bit, used to create standard normative remark tables in articles or books"
  },
  [BitType.standardRemarkTableNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType.table,
    description: "Standard non-normative remark table bit, used to create standard non-normative remark tables in articles or books"
  },
  [BitType.standardTableExtendedNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableExtended,
    description: "Standard normative extended table bit, used to create standard normative extended tables in articles or books"
  },
  [BitType.standardTableExtendedNonNormative]: {
    since: "1.16.0",
    baseBitType: BitType.tableExtended,
    description: "Standard non-normative extended table bit, used to create standard non-normative extended tables in articles or books"
  },
  [BitType.standardRemarkTableExtendedNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableExtended,
    description: "Standard normative remark extended table bit, used to create standard normative remark extended tables in articles or books"
  },
  [BitType.standardRemarkTableExtendedNonNormative]: {
    since: "1.17.0",
    baseBitType: BitType.tableExtended,
    description: "Standard non-normative remark extended table bit, used to create standard non-normative remark extended tables in articles or books"
  },
  [BitType.smartStandardTableNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableNormative,
    description: "Smart standard normative table bit, used to create smart standard normative tables in articles or books"
  },
  [BitType.smartStandardTableNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableNonNormative,
    description: "Smart standard non-normative table bit, used to create smart standard non-normative tables in articles or books"
  },
  [BitType.smartStandardRemarkTableNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkTableNormative,
    description: "Smart standard normative remark table bit, used to create smart standard normative remark tables in articles or books"
  },
  [BitType.smartStandardRemarkTableNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkTableNonNormative,
    description: "Smart standard non-normative remark table bit, used to create smart standard non-normative remark tables in articles or books"
  },
  [BitType.smartStandardTableExtendedNormative]: {
    since: "1.28.0",
    baseBitType: BitType.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" }
    }
  },
  [BitType.smartStandardTableExtendedNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardTableExtendedNonNormative,
    description: "Smart standard non-normative extended table bit, used to create smart standard non-normative extended tables in articles or books"
  },
  [BitType.smartStandardRemarkTableExtendedNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkTableExtendedNormative,
    description: "Smart standard normative remark extended table bit, used to create smart standard normative remark extended tables in articles or books"
  },
  [BitType.smartStandardRemarkTableExtendedNonNormative]: {
    since: "1.28.0",
    baseBitType: BitType.standardRemarkTableExtendedNonNormative,
    description: "Smart standard non-normative remark extended table bit, used to create smart standard non-normative remark extended tables in articles or books"
  },
  [BitType.smartStandardTableNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableExtendedNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardTableExtendedNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableExtendedNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.smartStandardRemarkTableExtendedNonNormativeCollapsible]: {
    since: "1.28.0",
    deprecated: "5.37.0",
    baseBitType: BitType.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"
      }
    ]
  },
  [BitType.parameters]: {
    since: "1.18.0",
    baseBitType: BitType.table,
    description: "Parameters bit, used to create parameter tables in articles or books"
  },
  [BitType.toc]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Table of contents bit, used to create a table of contents in articles or books"
  },
  [BitType.tocChapter]: {
    since: "1.5.5",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.tocInline]: {
    since: "3.24.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.tocResource]: {
    since: "3.31.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.tocContent]: {
    since: "3.31.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.anchor]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Anchor bit, used to create anchors in articles or books"
  },
  [BitType.bitBookEnding]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bit book ending bit, used to create endings in articles or books"
  },
  [BitType.bitBookSummary]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bit book summary bit, used to create summaries in articles or books"
  },
  [BitType.botActionAnnounce]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action announce bit, used to create announcements in articles or books"
  },
  [BitType.botActionRatingNumber]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action rating number bit, used to create rating number actions in articles or books"
  },
  [BitType.botActionRemind]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action remind bit, used to create reminders in articles or books"
  },
  [BitType.botActionSave]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action save bit, used to create save actions in articles or books"
  },
  [BitType.botActionTrueFalse]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Bot action true/false bit, used to create true/false actions in articles or books"
  },
  [BitType.chapterSubjectMatter]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Chapter subject matter bit, used to create subject matter chapters in articles or books"
  },
  [BitType.chat]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Chat bit, used to create chat sections in articles or books"
  },
  [BitType.conclusion]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Conclusion bit, used to create conclusions in articles or books"
  },
  [BitType.conclusionAlt]: {
    since: "1.16.0",
    baseBitType: BitType.conclusion,
    description: "Alternative conclusion bit, used to create alternative conclusions in articles or books"
  },
  [BitType.documentUpload]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Document upload bit, used to create document upload sections in articles or books"
  },
  [BitType.footNote]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Footnote bit, used to create footnotes in articles or books"
  },
  [BitType.formFreeText]: {
    since: "5.3.0",
    baseBitType: BitType.interview,
    description: "Form free text bit, used to create free text forms in articles or books"
  },
  [BitType.groupBorn]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Group born bit, used to create group born sections in articles or books"
  },
  [BitType.groupDied]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Group died bit, used to create group died sections in articles or books"
  },
  [BitType.recordAudio]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Record audio bit, used to create audio recording sections in articles or books"
  },
  [BitType.recordVideo]: {
    since: "1.5.24",
    baseBitType: BitType._standard,
    description: "Record video bit, used to create video recording sections in articles or books"
  },
  [BitType.stickyNote]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Sticky note bit, used to create sticky notes in articles or books"
  },
  [BitType.takePicture]: {
    since: "1.3.0",
    baseBitType: BitType._standard,
    description: "Take picture bit, used to create picture taking sections in articles or books"
  },
  [BitType.handInAudio]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in audio bit, used to create audio submission sections in articles or books"
  },
  [BitType.handInContact]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in contact bit, used to create contact submission sections in articles or books"
  },
  [BitType.handInDocument]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in document bit, used to create document submission sections in articles or books"
  },
  [BitType.handInFile]: {
    //
    since: "3.2.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.handInFreeText]: {
    since: "5.3.0",
    baseBitType: BitType.interview,
    description: "Hand in free text bit, used to create free text submission sections in articles or books"
  },
  [BitType.handInFreeTextExpert]: {
    since: "5.3.0",
    baseBitType: BitType.interview,
    description: "Hand in free text expert bit, used to create free text expert submission sections in articles or books"
  },
  [BitType.handInLocation]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in location bit, used to create location submission sections in articles or books"
  },
  [BitType.handInPhoto]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in photo bit, used to create photo submission sections in articles or books"
  },
  [BitType.handInScan]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in scan bit, used to create scan submission sections in articles or books"
  },
  [BitType.handInSystemMedia]: {
    //
    since: "1.5.15",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.handInSubmit]: {
    //
    since: "3.2.0",
    baseBitType: BitType._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
      }
    ]
  },
  [BitType.handInVideo]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in video bit, used to create video submission sections in articles or books"
  },
  [BitType.handInVoice]: {
    since: "1.5.15",
    baseBitType: BitType._standard,
    description: "Hand in voice bit, used to create voice submission sections in articles or books"
  },
  [BitType.trueFalse1]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.trueFalse]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.vendorAmcharts5Chart]: {
    since: "1.5.8",
    baseBitType: BitType.code,
    description: "AmCharts 5 chart bit, used to embed AmCharts 5 charts in articles or books",
    textFormatDefault: TextFormat.json
  },
  [BitType.vendorDatadogDashboard]: {
    since: "5.2.0",
    baseBitType: BitType.nonProductionPrototypeIframe,
    description: "A Datadog dashboard bit, used to embed Datadog dashboards"
  },
  [BitType.vendorDatadogDashboardEmbed]: {
    since: "3.12.0",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorFormbricksEmbed]: {
    since: "3.8.0",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorFormbricksEmbedAnonymous]: {
    since: "3.9.0",
    baseBitType: BitType.vendorFormbricksEmbed,
    description: "Anonymous Formbricks embed bit, used to embed anonymous Formbricks surveys in articles or books"
  },
  [BitType.vendorFormbricksLink]: {
    since: "3.8.0",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorFormbricksLinkAnonymous]: {
    since: "3.9.0",
    baseBitType: BitType.vendorFormbricksLink,
    description: "Anonymous Formbricks link bit, used to create links to anonymous Formbricks surveys in articles or books"
  },
  [BitType.vendorHighchartsChart]: {
    since: "1.5.28",
    baseBitType: BitType.vendorAmcharts5Chart,
    description: "Highcharts chart bit, used to embed Highcharts charts in articles or books"
  },
  [BitType.vendorIframelyEmbed]: {
    since: "1.5.10",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorIframelyCard]: {
    since: "1.5.10",
    baseBitType: BitType.vendorIframelyEmbed,
    description: "Iframely card bit, used to create cards for embedded content in articles or books"
  },
  [BitType.vendorIframelyPreview]: {
    since: "1.5.10",
    baseBitType: BitType.vendorIframelyEmbed,
    description: "Iframely preview bit, used to create previews for embedded content in articles or books"
  },
  [BitType.vendorIframelyPreviewMini]: {
    since: "1.5.10",
    baseBitType: BitType.vendorIframelyEmbed,
    description: "Iframely preview mini bit, used to create mini previews for embedded content in articles or books"
  },
  [BitType.vendorJupyterOutput]: {
    since: "1.4.3",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorJupyterCellCode]: {
    since: "1.4.3",
    baseBitType: BitType.vendorJupyterOutput,
    description: "Jupyter cell code bit, used to embed Jupyter notebook cell code in articles or books"
  },
  [BitType.vendorJupyterCellMarkdown]: {
    since: "1.4.3",
    baseBitType: BitType.vendorJupyterOutput,
    description: "Jupyter cell markdown bit, used to embed Jupyter notebook cell markdown in articles or books"
  },
  [BitType.vendorJupyterCellRaw]: {
    since: "1.4.3",
    baseBitType: BitType.vendorJupyterOutput,
    description: "Jupyter cell raw bit, used to embed Jupyter notebook cell raw content in articles or books"
  },
  [BitType.vendorJupyterIpynb]: {
    since: "1.4.3",
    baseBitType: BitType.vendorJupyterOutput,
    description: "Jupyter IPYNB bit, used to embed Jupyter notebook files in articles or books"
  },
  [BitType.vendorPadletEmbed]: {
    since: "1.3.0",
    baseBitType: BitType.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: TextFormat.plainText
  },
  [BitType.vendorStripePricingTable]: {
    since: "1.20.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.vendorStripePricingTableExternal]: {
    since: "3.13.0",
    baseBitType: BitType.vendorStripePricingTable,
    description: "External Stripe pricing table bit, used to embed external Stripe pricing tables in articles or books"
  },
  [BitType.vendorStripePricingTablePrintOnRequest]: {
    since: "5.21.0",
    baseBitType: BitType.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
      }
    ]
  },
  [BitType.video]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.videoLandscape]: {
    since: "1.3.0",
    baseBitType: BitType.video,
    description: "Landscape video bit, used to embed landscape videos in articles or books"
  },
  [BitType.videoPortrait]: {
    since: "1.3.0",
    baseBitType: BitType.video,
    description: "Portrait video bit, used to embed portrait videos in articles or books"
  },
  [BitType.videoEmbed]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.videoEmbedLandscape]: {
    since: "1.3.0",
    baseBitType: BitType.videoEmbed,
    description: "Landscape video embed bit, used to embed landscape videos from external sources in articles or books"
  },
  [BitType.videoEmbedPortrait]: {
    since: "1.3.0",
    baseBitType: BitType.videoEmbed,
    description: "Portrait video embed bit, used to embed portrait videos from external sources in articles or books"
  },
  [BitType.videoLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.videoLinkLandscape]: {
    since: "1.3.0",
    baseBitType: BitType.videoLink,
    description: "Landscape video link bit, used to create links to landscape videos in articles or books"
  },
  [BitType.videoLinkPortrait]: {
    since: "1.3.0",
    baseBitType: BitType.videoLink,
    description: "Portrait video link bit, used to create links to portrait videos in articles or books"
  },
  [BitType.websiteLink]: {
    since: "1.3.0",
    baseBitType: BitType._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
  },
  [BitType.nonProductionPrototypeIframe]: {
    since: "4.20.0",
    baseBitType: BitType._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"
      }
    ]
  },
  [BitType.vendorLearndashEmbed]: {
    since: "5.0.0",
    baseBitType: BitType.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(BitType).fromValue(bitType) ?? BitType._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 !== BitType.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 ?? TextFormat.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 TextLocation = {
  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: TextLocation.body,
  v2: false
};
function isString(obj) {
  return typeof obj === "string" || obj instanceof String;
}
function selectBreakscapeRegexAndReplacer(textFormat, textLocation, v2 = false) {
  if (textLocation === TextLocation.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 === TextLocation.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 Breakscape = 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: TextFormat.bitmarkText,
  location: TextLocation.body,
  inPlaceArray: false,
  v2: false
};
var externalBreakscape = new Breakscape();
var Breakscape2 = 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 Breakscape2();

// 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, BitType.tableExtended) || instance2.isOfBitType(bitType, BitType.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", TextFormat.bitmarkText);
    __publicField(this, "textLocation", TextLocation.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 ?? TextFormat.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: TextFormat.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 === TextFormat.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 === TextLocation.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 === TextFormat.bitmarkText) {
      if (this.inBulletList) return;
      const siblingNodes = this.getSiblingNodes(route);
      const prevSiblingNodeType = siblingNodes.left?.type;
      const inBody = this.textLocation === TextLocation.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 === TextLocation.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, TextLocation.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: TextLocation.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, TextLocation.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, [
      BitType.imageSeparator,
      BitType.pageBanner,
      BitType.imagesLogoGrave,
      BitType.prototypeImages,
      BitType.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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(TextFormat).fromValue(data.textFormat) ?? bitConfig.textFormatDefault;
    if (deprecatedTextFormat === DeprecatedTextFormat.bitmarkMinusMinus) {
      textFormat = TextFormat.bitmarkText;
    }
    const context = {
      bitConfig,
      bitType,
      textFormat
    };
    const cardNode = this.buildCardNode(context, data);
    const reasonableNumOfCharsProperty = instance2.isOfBitType(data.bitType, BitType.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, TextLocation.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, TextLocation.tag, data.title),
      subtitle: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      pageNumber: this.handleJsonText(context, TextLocation.tag, data.pageNumber),
      marginNumber: this.handleJsonText(context, TextLocation.tag, data.marginNumber),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, BitType.interview)) {
      this.pushDownTree(
        context,
        void 0,
        void 0,
        cardNode,
        "questions",
        "reasonableNumOfChars",
        data.reasonableNumOfChars
      );
    }
    if (instance2.isOfBitType(node.bitType, BitType.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, "true");
    const node = {
      text: data.text ?? "",
      reasonableNumOfChars: data.reasonableNumOfChars ?? void 0,
      item: this.handleJsonText(context, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, cell.title),
            body: this.handleJsonText(context, TextLocation.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, TextLocation.tag, col)
      );
    }
    if (Array.isArray(nodeTable.data) && nodeTable.data.length > 0) {
      nodeTable.data = nodeTable.data.map(
        (row) => (row || []).map((cell) => this.handleJsonText(context, TextLocation.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, TextLocation.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, TextLocation.body, data.question),
      partialAnswer: data.partialAnswer ?? "",
      sampleSolution: data.sampleSolution ?? "",
      additionalSolutions: data.additionalSolutions ?? void 0,
      reasonableNumOfChars: data.reasonableNumOfChars ?? void 0,
      item: this.handleJsonText(context, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.title),
      checked: data.checked ?? false,
      ingredient: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.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: TextLocation.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: TextLocation.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 === TextFormat.bitmarkText;
    if (textFormat === TextFormat.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, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, [BitType.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, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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,
      TextLocation.tag,
      data?.text || data
    );
    if (textAsStrings) {
      text = this.textGenerator.generateSync(text, TextFormat.plainText, TextLocation.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, TextLocation.tag, data.statement),
      isCorrect: !!data.isCorrect,
      item: this.handleJsonText(context, TextLocation.tag, data.item),
      lead: this.handleJsonText(context, TextLocation.tag, data.lead),
      hint: this.handleJsonText(context, TextLocation.tag, data.hint),
      instruction: this.handleJsonText(context, TextLocation.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, TextLocation.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, TextLocation.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, path5, 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[path5] == null) {
          d[path5] = 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[path5] == null) {
                    data[path5] = 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 BitType.articleAi:
      case BitType.noteAi:
      case BitType.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 Enum23 } 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", BitType._error);
    __publicField(this, "textFormat", TextFormat.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 = BitType._error;
    this.textFormat = TextFormat.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 === TextFormat.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, TextFormat.plainText, TextLocation.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, TextLocation.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, TextLocation.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, TextFormat.bitmarkText, TextLocation.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 ?? "", TextFormat.bitmarkText, TextLocation.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(lead, TextFormat.bitmarkText, TextLocation.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 ?? "", TextFormat.bitmarkText, TextLocation.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.lead ?? "", TextFormat.bitmarkText, TextLocation.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(pageNumber ?? "", TextFormat.bitmarkText, TextLocation.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 ?? "", TextFormat.bitmarkText, TextLocation.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(parent?.value?.lead ?? "", TextFormat.bitmarkText, TextLocation.tag);
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(
      parent?.value?.pageNumber ?? "",
      TextFormat.bitmarkText,
      TextLocation.tag
    );
    this.writeCL();
    this.writeOPC();
    this.writeTextOrValue(marginNumber, TextFormat.bitmarkText, TextLocation.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 === TextFormat.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, TextFormat.plainText, TextLocation.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, TextLocation.body, {
        plainTextDividerAllowed
      });
    } else {
      this.writePlainTextDivider();
      this.writeNL();
      const s = stringUtils.isString(body.body) ? body.body : "";
      this.writeTextOrValue(`${s}`, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.tag);
        this.writeCL();
      }
    }
    return true;
  }
  // bodyBit -> mark
  enter_mark(node, _route) {
    const mark = node.value;
    this.writeOPE();
    this.writeTextOrValue(mark.solution, TextFormat.plainText, TextLocation.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 === TextFormat.json) {
    } else if (footer.footer && footer.footer.length > 0) {
      if (this.isBodyBitmarkText) {
        this.writeNL();
        this.writeTextOrValue("==== footer ====", TextFormat.plainText, TextLocation.body);
        this.writeNL();
        this.writeTextOrValue(footer.footer, this.textFormat, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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(BitType.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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(BitType.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, TextLocation.body, options);
  }
  writeTableCellProperty(name, value) {
    this.writeOPA();
    this.writeTagKey(name);
    this.writeColon();
    this.writeTextOrValue(`${value}`, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.tag);
    this.writeCL();
  }
  enter_columnsValue(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat.bitmarkText, TextLocation.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, TextLocation.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, TextFormat.bitmarkText, TextLocation.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, TextLocation.body);
        }
        return false;
      } else {
        this.writeNL();
        this.writeTextOrValue(node.value, this.textFormat, TextLocation.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, TextLocation.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, TextLocation.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, TextFormat.bitmarkText, TextLocation.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}`, TextFormat.plainText, TextLocation.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, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextLocation.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, [BitType.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, TextFormat.bitmarkText, TextLocation.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, TextFormat.bitmarkText, TextLocation.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, TextFormat.plainText, TextLocation.tag);
        this.writeCL();
        if (book.referenceEnd) {
          this.writeOPRANGLE();
          this.writeTextOrValue(book.referenceEnd, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.tag);
        this.writeCL();
        if (bit.referenceEnd) {
          this.writeOPRANGLE();
          this.writeTextOrValue(bit.referenceEnd, TextFormat.plainText, TextLocation.tag);
          this.writeCL();
        }
      }
    }
  }
  //  bitmarkAst -> bits -> bitsValue -> anchor
  leaf_anchor(node, _route) {
    if (node.value) {
      this.writeNL();
      this.writeOPDANGLE();
      this.writeTextOrValue(node.value, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.bitmarkText, TextLocation.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, TextFormat.bitmarkText, TextLocation.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, TextFormat.bitmarkText, TextLocation.tag);
      } else {
        this.writeTextOrValue(example, TextFormat.plainText, TextLocation.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, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.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, TextFormat.plainText, TextLocation.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> heading -> forValues
  leaf_forValues(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat.plainText, TextLocation.tag);
    this.writeCL();
  }
  // bitmarkAst -> bits -> bitsValue -> heading -> forValuesValue
  leaf_forValuesValue(node, _route) {
    this.writeNL();
    this.writeOPHASH();
    this.writeTextOrValue(node.value, TextFormat.plainText, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, TextLocation.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, [
      BitType.imageSeparator,
      BitType.pageBanner,
      BitType.imagesLogoGrave,
      BitType.prototypeImages,
      BitType.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 === TextFormat.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: TextFormat.plainText,
          location: TextLocation.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 === TextLocation.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 === TextFormat.bitmarkText ? format : TextFormat.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: TextFormat.plainText,
      location: TextLocation.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, TextFormat.plainText, TextLocation.tag);
      if (resource.type === ResourceType.article) {
      }
      this.writeCL();
    }
  }
  writeResource(type, value) {
    if (type) {
      this.writeOPAMP();
      this.writeTagKey(type);
      this.writeColon();
      this.writeTextOrValue(value, TextFormat.plainText, TextLocation.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, TextFormat.bitmarkText, TextLocation.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}`, TextFormat.plainText, TextLocation.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(TextFormat).fromValue(bitsValue) === textFormatDefault;
    const writeFormat = !isDefault || this.options.explicitTextFormat;
    return !!writeFormat;
  }
  calculateIsCardAllowed() {
    return this.isBodyBitmarkText && !this.isOfBitType1() && !this.isTableBitType();
  }
  isTableBitType() {
    return this.isOfBitType([BitType.table]);
  }
  isOfBitType1() {
    return this.isOfBitType([
      BitType.trueFalse1,
      BitType.multipleChoice1,
      BitType.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, BitType.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, BitType.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 === BitType.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, BitType.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, BitType.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 === BitType.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 === BitType.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 === TextFormat.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,
          TextLocation.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, [
      BitType.imagesLogoGrave,
      BitType.prototypeImages,
      BitType.extractorImage
    ])) {
      const images = [];
      for (const r of resources) {
        if (r.type === ResourceType.image) {
          images.push(r);
        }
      }
      if (bitType === BitType.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,
        TextLocation.tag
      );
    } else {
      exampleValue = isBoolean ? instance5.toBoolean(example) : this.convertBreakscapedStringToJsonText(
        example,
        options.textFormat,
        TextLocation.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(TextFormat).fromValue(n?.textFormat) ?? bitConfig.textFormatDefault;
        }
      }
    }
    return TextFormat.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 === TextFormat.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 ? BitType._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, [BitType._error, BitType._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, [BitType.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, [BitType.article])) {
      }
      if (instance2.isOfBitType(bitType, [BitType.example])) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
      }
      if (instance2.isOfBitType(bitType, [
        BitType.multipleChoice1,
        BitType.multipleResponse1,
        BitType.multipleChoiceText,
        BitType.highlightText,
        BitType.gapText,
        BitType.gapTextInstructionGrouped,
        BitType.clozeAndMultipleChoiceText,
        BitType.sequence,
        BitType.mark,
        BitType.flashcard,
        BitType.definitionList
      ])) {
        isTopLevelExample = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, [BitType.sequence])) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType.cloze)) {
        isTopLevelExample = true;
        if (instance2.isOfBitType(bitType, BitType.clozeSolutionGrouped)) {
          if (bitJson.quizCountItems == null) bitJson.quizCountItems = true;
          if (bitJson.quizStrikethroughSolutions == null) bitJson.quizStrikethroughSolutions = true;
        } else if (instance2.isOfBitType(bitType, BitType.clozeInstructionGrouped)) {
          if (bitJson.quizCountItems == null) bitJson.quizCountItems = true;
          if (bitJson.quizStrikethroughSolutions == null)
            bitJson.quizStrikethroughSolutions = false;
        }
      }
      if (instance2.isOfBitType(bitType, [BitType.multipleChoice, BitType.multipleResponse])) {
        isTopLevelExample = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (bitJson.footer == null) bitJson.footer = this.textDefault;
      }
      if (instance2.isOfBitType(bitType, BitType.essay)) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (bitJson.partialAnswer == null) bitJson.partialAnswer = "";
      }
      if (instance2.isOfBitType(bitType, BitType.trueFalse1)) {
        isTopLevelExample = true;
        isTopLevelExampleValue = true;
        if (bitJson.isCorrect == null) bitJson.isCorrect = false;
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
      }
      if (instance2.isOfBitType(bitType, BitType.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, BitType.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, BitType.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, BitType.sequence)) {
        if (bitJson.elements == null) bitJson.elements = [];
      }
      if (bitType === BitType.matchMatrix) {
        isTopLevelExample = true;
      }
      if (instance2.isOfBitType(bitType, BitType.match)) {
        isTopLevelExample = true;
        if (bitJson.heading == null) bitJson.heading = {};
      }
      if (instance2.isOfBitType(bitType, BitType.learningPathBook)) {
        if (bitJson.isTracked == null) bitJson.isTracked = true;
        if (bitJson.isInfoOnly == null) bitJson.isInfoOnly = false;
      }
      const tableImageHasTable = instance2.isOfBitType(bitType, BitType.tableImage) && !this.tableIsEmpty(bitJson.table);
      if (instance2.isOfBitType(bitType, BitType.table) && !instance2.isOfBitType(bitType, BitType.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, BitType.tableImage) && !tableImageHasTable) {
        delete bitJson.table;
      }
      if (instance2.isOfBitType(bitType, BitType.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, [
        BitType.pageBanner,
        BitType.pageBuyButton,
        BitType.pageBuyButtonAlt,
        BitType.pageBuyButtonPromotion,
        BitType.pageFooter,
        BitType.pageOpenBook,
        BitType.pagePerson,
        BitType.printBook,
        BitType.pageProduct,
        BitType.pageProductList,
        BitType.pageProductVideo,
        BitType.pageProductVideoList,
        BitType.pageSectionFolder,
        BitType.pageSubscribe,
        BitType.pageSubpage
      ])) {
        if (bitJson.slug == null) bitJson.slug = "";
        if (bitJson.body == null) bitJson.body = this.bodyDefault;
        if (instance2.isOfBitType(bitType, BitType.pageBuyButton)) {
          if (bitJson.content2Buy == null) bitJson.content2Buy = "";
        }
      }
      if (instance2.isOfBitType(bitType, BitType.tocResource)) {
        if (bitJson.tocResource == null) bitJson.tocResource = [];
      }
      if (instance2.isOfBitType(bitType, BitType.tocContent)) {
        if (bitJson.tocContent == null) bitJson.tocContent = [];
      }
      if (instance2.isOfBitType(bitType, BitType.printThisChapter)) {
        if (bitJson.printParentChapterLevel == null) bitJson.printParentChapterLevel = -1;
      }
      if (instance2.isOfBitType(bitType, BitType.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 === BitType.articleAi || bitType === BitType.noteAi || bitType === BitType.summaryAi) {
        if (bitJson.aiGenerated == null) bitJson.aiGenerated = true;
      }
      if (instance2.isOfBitType(bitType, [BitType.articleResponsive, BitType.pageArticleResponsive])) {
        if (bitJson.imageFirst == null) bitJson.imageFirst = true;
      }
      if (instance2.isOfBitType(bitType, BitType.reviewNote)) {
        if (bitJson.resolved == null) bitJson.resolved = false;
        if (bitJson.resolvedDate == null) bitJson.resolvedDate = "";
        if (bitJson.resolvedBy == null) bitJson.resolvedBy = "";
      }
      if (instance2.isOfBitType(bitType, [
        BitType.imagesLogoGrave,
        BitType.prototypeImages,
        BitType.extractorImage
      ])) {
        if (bitType === BitType.imagesLogoGrave) {
          if (bitJson.logos == null) {
            bitJson.logos = [];
          }
        } else {
          if (bitJson.images == null) bitJson.images = [];
        }
      }
      if (instance2.isOfBitType(bitType, [BitType.stepImageScreenshotWithPointer, BitType.surveyMatrix])) {
        if (bitJson.pointerTop == null) bitJson.pointerTop = "";
        if (bitJson.pointerLeft == null) bitJson.pointerLeft = "";
        if (instance2.isOfBitType(bitType, [BitType.surveyMatrix])) {
          if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
        }
      }
      if (instance2.isOfBitType(bitType, [BitType.listItem])) {
        if (bitJson.listItemIndent == null) bitJson.listItemIndent = 0;
      }
      if (instance2.isOfBitType(bitType, BitType.surveyRating)) {
        if (bitJson.ratingLevelStart == null) {
          bitJson.ratingLevelStart = {
            level: 0
          };
        }
        if (bitJson.ratingLevelEnd == null) {
          bitJson.ratingLevelEnd = {
            level: 0
          };
        }
      }
      if (instance2.isOfBitType(bitType, BitType.vendorStripePricingTable)) {
        if (bitJson.stripePricingTableId == null) bitJson.stripePricingTableId = "";
        if (bitJson.stripePublishableKey == null) bitJson.stripePublishableKey = "";
        if (instance2.isOfBitType(bitType, BitType.vendorStripePricingTablePrintOnRequest)) {
          if (bitJson.printOnRequestCustomerId == null) bitJson.printOnRequestCustomerId = "";
          if (bitJson.printOnRequestProductId == null) bitJson.printOnRequestProductId = "";
        }
      }
      if (instance2.isOfBitType(bitType, BitType.callToAction)) {
        if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
        if (bitJson.callToActionUrl == null) bitJson.callToActionUrl = "";
      }
      if (instance2.isOfBitType(bitType, [BitType.handInFile, BitType.handInSystemMedia])) {
        if (bitJson.handInAcceptFileType == null) bitJson.handInAcceptFileType = [];
      }
      if (instance2.isOfBitType(bitType, BitType.handInSubmit)) {
        if (bitJson.handInRequirement == null) bitJson.handInRequirement = [];
        if (bitJson.handInInstruction == null) bitJson.handInInstruction = "";
        if (bitJson.buttonCaption == null) bitJson.buttonCaption = "";
      }
      if (instance2.isOfBitType(bitType, BitType.module)) {
        if (bitJson.hasBookNavigation == null) bitJson.hasBookNavigation = true;
      }
      if (instance2.isOfBitType(bitType, BitType.container)) {
        if (bitJson.allowedBit == null) bitJson.allowedBit = [];
      }
      if (instance2.isOfBitType(bitType, BitType.platformPath)) {
        if (bitJson.path == null) bitJson.path = "";
      }
      if (instance2.isOfBitType(bitType, BitType.platformBrandTarget)) {
        if (bitJson.platformBrandTarget == null) bitJson.platformBrandTarget = "none";
      }
      if (instance2.isOfBitType(bitType, BitType.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,
          TextFormat.bitmarkText,
          TextLocation.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(BitType).values()) {
      if (bt === BitType._error || bt === BitType._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(BitType).values()) {
      if (bt === BitType._error || bt === BitType._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) !== BitType._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 === BitType._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(TextFormat).fromValue(bit.format) ?? bitConfig.textFormatDefault;
    if (deprecatedTextFormat === DeprecatedTextFormat.bitmarkMinusMinus) {
      _textFormat = TextFormat.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, BitType.botActionResponse)) return void 0;
    if (!Array.isArray(responses)) return void 0;
    return responses;
  }
  processBotResponse(bitType, responses) {
    if (!instance2.isOfBitType(bitType, BitType.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, [BitType.prototypeImages, BitType.extractorImage])) {
      if (Array.isArray(images)) {
        for (const image of images) {
          if (image) nodes.push(image);
        }
      }
    }
    if (instance2.isOfBitType(bitType, [BitType.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, BitType.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 ENABLE_DEBUG = true;
var DEBUG_DATA = true;
var DEBUG_DATA_INCLUDE_PARSER = false;
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 DEBUG = ENABLE_DEBUG && process.env.BPG_ENV === "development";
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: TextFormat.plainText,
        location: TextLocation.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: TextFormat.plainText,
      location: TextLocation.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) {
    if (DEBUG) {
      if (DEBUG_DATA) {
        if (!DEBUG_DATA_INCLUDE_PARSER) {
          if (data != void 0) {
            data = JSON.parse(JSON.stringify(data, (k, v) => k === "parser" ? void 0 : v));
          }
        }
        console.log(`----- TRACE: ${header} -----`);
        console.log(JSON.stringify(data, null, 2));
        console.log(`----- END: ${header} -----`);
      } else {
        console.log(`- TRACE: ${header}`);
      }
    }
  }
};

// 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 === TextFormat.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 === TextFormat.bitmarkText;
      const parsedBodyText = isBitmarkText ? textParser2.toAst(bodyTextStr, {
        //
        format: textFormat,
        location: TextLocation.body
      }) : instance3.unbreakscape(bodyTextStr, {
        format: TextFormat.plainText,
        location: TextLocation.body
      });
      const parserPlainText = instance3.unbreakscape(plainBodyTextStr, {
        format: TextFormat.plainText,
        location: TextLocation.body
      });
      const newlines = stringUtils.countOccurrencesAtEnd(bodyTextStr, "\n") + stringUtils.countOccurrencesAtStart(plainBodyTextStr, "\n");
      finalBody = instance10.concatenatePlainTextWithAstTexts(
        parsedBodyText,
        newlines,
        parserPlainText
      );
      finalBodyString = instance3.unbreakscape(bodyStr, {
        format: textFormat,
        location: TextLocation.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 === BitType.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, [BitType.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, BitType.multipleChoice);
  const insertResponses = instance2.isOfBitType(bitType, BitType.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, BitType.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: TextLocation.tag
      });
      target.__instructionString = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat.bitmarkText,
        location: TextLocation.tag
      });
      break;
    }
    case TypeKey.Hint: {
      target.hint = textParser10.toAst(trimmedStringValue, {
        format: textFormat,
        location: TextLocation.tag
      });
      target.__hintString = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat.bitmarkText,
        location: TextLocation.tag
      });
      break;
    }
    case TypeKey.Anchor: {
      target.anchor = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat.bitmarkText,
        location: TextLocation.tag
      });
      break;
    }
    case TypeKey.Reference: {
      target.reference = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat.bitmarkText,
        location: TextLocation.tag
      });
      break;
    }
    // 16.08.2023 Deprecated, but currently still supported
    case TypeKey.SampleSolution: {
      target.sampleSolution = instance3.unbreakscape(trimmedStringValue, {
        format: TextFormat.bitmarkText,
        location: TextLocation.tag
      });
      target.__sampleSolutionAst = textParser10.toAst(trimmedStringValue, {
        format: textFormat,
        location: TextLocation.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 === TextFormat.bitmarkText;
      if (footer) {
        footer = instance9.checkFooter(context, contentDepth, footer);
      }
      const parsedFooterText = isBitmarkText ? textParser3.toAst(footer, {
        //
        format: textFormat,
        location: TextLocation.body
      }) : instance3.unbreakscape(footer, {
        format: TextFormat.plainText,
        location: TextLocation.body
      });
      const parsedFooterPlainText = instance3.unbreakscape(footerPlainText, {
        format: TextFormat.plainText,
        location: TextLocation.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: TextFormat.bitmarkText,
        location: TextLocation.tag
      })
    );
    const solutionAst = textParser4.toAst(trimmedStringValue, {
      format: textFormat,
      location: TextLocation.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: TextLocation.tag
  });
  target.itemLead.push(text);
  if (!target.__itemLeadString) target.__itemLeadString = [];
  target.__itemLeadString.push(
    instance3.unbreakscape(trimmedStringValue, {
      format: TextFormat.bitmarkText,
      location: TextLocation.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: TextFormat.bitmarkText,
      location: TextLocation.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, [
    BitType.cloze,
    BitType.clozeAndMultipleChoiceText,
    BitType.multipleChoiceText,
    BitType.highlightText,
    BitType.trueFalse,
    BitType.trueFalse1,
    BitType.multipleResponse,
    BitType.multipleResponse1,
    BitType.multipleChoice,
    BitType.multipleChoice1
  ])) {
    handleGapOrSelectOrTrueFalseExample(context, content, example, target);
  } else if (instance2.isOfBitType(bitType, BitType.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: TextLocation.tag
      }) : void 0;
    }
  } else {
    if (instance2.isOfBitType(bitType, [
      BitType.multipleChoiceText,
      BitType.highlightText,
      BitType.trueFalse,
      BitType.trueFalse1,
      BitType.multipleResponse,
      BitType.multipleResponse1
    ])) {
      handleStandardBooleanExample(context, content, example, target);
    } else if (instance2.isOfBitType(bitType, [
      BitType.clozeAndMultipleChoiceText,
      BitType.multipleChoice,
      BitType.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: TextFormat.bitmarkText,
      location: TextLocation.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: TextLocation.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: TextFormat.bitmarkText,
    location: TextLocation.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: TextFormat.bitmarkText,
      location: TextLocation.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: TextFormat.bitmarkText,
      location: TextLocation.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: TextLocation.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: TextFormat.bitmarkText,
      location: TextLocation.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: TextFormat.plainText,
                location: TextLocation.tag
              }
            );
          case TagFormat.number:
            return instance6.asNumber(
              instance3.unbreakscape(v2, {
                format: TextFormat.plainText,
                location: TextLocation.tag
              })
            );
          case TagFormat.boolean:
            return instance5.toBoolean(
              instance3.unbreakscape(v2, {
                format: TextFormat.plainText,
                location: TextLocation.tag
              }),
              true
            );
          case TagFormat.invertedBoolean:
            return !instance5.toBoolean(
              instance3.unbreakscape(v2, {
                format: TextFormat.plainText,
                location: TextLocation.tag
              }),
              true
            );
          case TagFormat.bitmarkText:
            v2 = stringUtils.isString(v2) ? v2 : "";
            return textParser7.toAst(v2, {
              format: TextFormat.bitmarkText,
              location: TextLocation.tag
            });
          case TagFormat.coordinates:
            const coordRaw = instance3.unbreakscape(v2, {
              format: TextFormat.plainText,
              location: TextLocation.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: TextFormat.plainText,
        location: TextLocation.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: TextFormat.bitmarkText,
    location: TextLocation.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: TextFormat.bitmarkText,
      location: TextLocation.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: TextLocation.tag
    }),
    titleString: titleText ?? ""
  };
}
function buildTitles(_context, bitType, title) {
  title = title ?? [];
  if (instance2.isOfBitType(bitType, BitType.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: TextFormat.bitmarkText,
      location: TextLocation.tag
    }
  );
  const textAst = textParser9.toAst(trimmedStringValue ?? "", {
    format: textFormat,
    location: TextLocation.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, BitType.trueFalse1)) {
    target.statement = buildStatement(context, tagsConfig, chainContent);
  } else if (instance2.isOfBitType(bitType, [
    BitType.trueFalse,
    BitType.multipleChoice,
    BitType.multipleChoice1,
    BitType.multipleResponse,
    BitType.multipleResponse1,
    BitType.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, BitType.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, BitType.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, BitType.trueFalse);
  const insertChoices = instance2.isOfBitType(bitType, [
    BitType.multipleChoice,
    BitType.multipleChoice1,
    BitType.feedback
  ]);
  const insertResponses = instance2.isOfBitType(bitType, [
    BitType.multipleResponse,
    BitType.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 ENABLE_DEBUG2 = true;
var DEBUG_DATA2 = true;
var DEBUG_DATA_INCLUDE_PARSER2 = false;
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 DEBUG2 = ENABLE_DEBUG2 && process.env.BPG_ENV === "development";
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(BitType._error),
      bitType: BitType._error,
      textFormat: TextFormat.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, BitType._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, BitType.trueFalse1);
    const isMultipleChoiceV1 = instance2.isOfBitType(bitType, BitType.multipleChoice1);
    const isMultipleResponseV1 = instance2.isOfBitType(bitType, BitType.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(BitType._error),
      bitLevel: 1,
      parser: this.parser
    });
    return { value: bit };
  }
  // Build bit header
  buildBitHeader(bitTypeBreakscaped, bitLevel, textFormatAndResourceType) {
    const bitType = instance3.unbreakscape(bitTypeBreakscaped, {
      format: TextFormat.plainText,
      location: TextLocation.tag
    });
    const validBitType = instance2.getBitType(bitType);
    const commented = instance2.isBitTypeCommented(bitType);
    if (instance2.isOfBitType(validBitType, BitType._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(TextFormat).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 = TextFormat.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: TextFormat.plainText,
          location: TextLocation.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 === TextFormat.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) {
    if (DEBUG2) {
      if (DEBUG_DATA2) {
        if (!DEBUG_DATA_INCLUDE_PARSER2) {
          if (data != void 0) {
            data = JSON.parse(JSON.stringify(data, (k, v) => k === "parser" ? void 0 : v));
          }
        }
        console.log(`===== START: ${header} =====`);
        console.log(JSON.stringify(data, null, 2));
        console.log(`===== END: ${header} =====`);
      } else {
        console.log(`- DEBUG: ${header}`);
      }
    }
  }
};

// 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
import path4 from "path";
import fs4 from "fs-extra";

// src/ast/writer/FileWriter.ts
import "fs";
import path from "path";
import fs from "fs-extra";

// src/ast/writer/StreamWriter.ts
import os from "os";
var StreamWriter = class {
  constructor() {
    __publicField(this, "_stream");
    __publicField(this, "endOfLineString", os.EOL);
    __publicField(this, "lastWrite", "");
  }
  get isSync() {
    return false;
  }
  /**
   * Get the current write stream
   */
  get stream() {
    return this._stream;
  }
  /**
   * Set the current write stream
   */
  set stream(stream) {
    this._stream = stream;
  }
  openSync() {
    throw new Error("Sync operation not supported");
  }
  closeSync() {
    throw new Error("Sync operation not supported");
  }
  writeLine(value) {
    if (!this._stream) return this;
    if (value != null) {
      this._stream.write(value + this.endOfLineString);
      this.lastWrite = value + this.endOfLineString;
    } else {
      this._stream.write(this.endOfLineString);
      this.lastWrite = this.endOfLineString;
    }
    return this;
  }
  writeLines(values, delimiter) {
    if (!this._stream) return this;
    if (!values) return this;
    let str = "";
    for (let i = 0, len = values.length; i < len; i++) {
      const value = values[i];
      this._stream.write(value);
      str += value;
      if (delimiter && i < len - 1) {
        this._stream.write(delimiter);
        str += delimiter;
      }
      this._stream.write(this.endOfLineString);
      str += this.endOfLineString;
    }
    this.lastWrite = str;
    return this;
  }
  write(value) {
    if (!this._stream) return this;
    if (value == null) return this;
    if (value) this.lastWrite = value;
    this._stream.write(value);
    return this;
  }
  writeWhiteSpace() {
    this.write(" ");
    return this;
  }
  getLastWrite() {
    return this.lastWrite;
  }
};

// src/ast/writer/FileWriter.ts
var FileWriter = class extends StreamWriter {
  /**
   * Create a writer to write to a file.
   *
   * @param path - path of file to write
   * @param options - options for file writing
   */
  constructor(path5, options) {
    super();
    __publicField(this, "_path");
    __publicField(this, "_append");
    __publicField(this, "_encoding");
    const opts = Object.assign({}, options);
    this._path = path5;
    this._append = !!opts.append;
    this._encoding = opts.encoding ?? "utf8";
  }
  get path() {
    return this._path;
  }
  get append() {
    return this._append;
  }
  get encoding() {
    return this._encoding;
  }
  async open() {
    return new Promise((resolve, reject) => {
      const flags = this._append ? "a" : "w";
      fs.ensureDirSync(path.dirname(this._path.toString()));
      const stream = fs.createWriteStream(this._path, {
        flags,
        encoding: "utf8"
      });
      this.stream = stream;
      if (!stream) reject(new Error("stream undefined"));
      stream.once("open", (_fd) => {
        resolve();
      });
      stream.once("error", (e) => {
        reject(e);
      });
    });
  }
  async close() {
    return new Promise((resolve, reject) => {
      const stream = this.stream;
      if (!stream) reject(new Error("stream not set"));
      stream.once("close", (_fd) => {
        resolve();
      });
      stream.once("error", (e) => {
        reject(e);
      });
      stream.end();
    });
  }
};

// src/generator/bitmark/BitmarkFileGenerator.ts
import "fs";

// src/ast/writer/SyncFileWriter.ts
import "fs";
import path2 from "path";
import fs2 from "fs-extra";
import os2 from "os";
var SyncFileWriter = class {
  /**
   * Create a writer to write to a file.
   *
   * @param path - path of file to write
   * @param options - options for file writing
   */
  constructor(path5, options) {
    __publicField(this, "_path");
    __publicField(this, "_append");
    __publicField(this, "_encoding");
    __publicField(this, "_open");
    __publicField(this, "endOfLineString", os2.EOL);
    __publicField(this, "lastWrite", "");
    const opts = Object.assign({}, options);
    this._path = path5;
    this._append = !!opts.append;
    this._encoding = opts.encoding ?? "utf8";
    this._open = false;
  }
  get isSync() {
    return true;
  }
  get path() {
    return this._path;
  }
  get append() {
    return this._append;
  }
  get encoding() {
    return this._encoding;
  }
  async open() {
    this.openSync();
    return Promise.resolve();
  }
  async close() {
    try {
      this.closeSync();
    } catch (e) {
      return Promise.reject(e);
    }
    return Promise.resolve();
  }
  openSync() {
    const flag = this._append ? "a" : "w";
    fs2.ensureDirSync(path2.dirname(this._path.toString()));
    fs2.writeFileSync(this._path, "", {
      flag,
      encoding: this._encoding
    });
    this._open = true;
  }
  closeSync() {
    this._open = false;
  }
  writeLine(value) {
    if (!this._open) return this;
    let line;
    if (value != null) {
      line = value + this.endOfLineString;
    } else {
      line = this.endOfLineString;
    }
    fs2.appendFileSync(this._path, line, {
      encoding: this._encoding
    });
    this.lastWrite = line;
    return this;
  }
  writeLines(values, delimiter) {
    if (!this._open) return this;
    if (!values) return this;
    let str = "";
    for (let i = 0, len = values.length; i < len; i++) {
      const value = values[i];
      str += value;
      if (delimiter && i < len - 1) {
        str += delimiter;
      }
      str += this.endOfLineString;
    }
    fs2.appendFileSync(this._path, str, {
      encoding: this._encoding
    });
    this.lastWrite = str;
    return this;
  }
  write(value) {
    if (!this._open) return this;
    if (value == null) return this;
    if (value) this.lastWrite = value;
    fs2.appendFileSync(this._path, value, {
      encoding: this._encoding
    });
    return this;
  }
  writeWhiteSpace() {
    this.write(" ");
    return this;
  }
  getLastWrite() {
    return this.lastWrite;
  }
};

// src/generator/bitmark/BitmarkFileGenerator.ts
var BitmarkFileGenerator = class {
  /**
   * Generate bitmark markup from a bitmark AST as a file
   *
   * @param path - path of file to generate
   * @param options - bitmark generation options
   */
  constructor(path5, options) {
    __publicField(this, "generator");
    __publicField(this, "async");
    this.async = options?.async ?? false;
    const writer = this.async ? new FileWriter(path5, options?.fileOptions) : new SyncFileWriter(path5, options?.fileOptions);
    this.generator = new BitmarkGenerator(writer, options);
  }
  /**
   * Generate bitmark markup from bitmark AST as a file
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    return this.generator.generate(ast);
  }
  /**
   * Generate bitmark markup from bitmark AST as a file synchronously
   *
   * @param ast bitmark AST
   */
  generateSync(_ast) {
    if (this.async) {
      throw new Error("Sync operation not supported");
    }
    this.generator.generateSync(_ast);
  }
};

// src/generator/json/JsonFileGenerator.ts
import "fs";
var JsonFileGenerator = class {
  /**
   * Generate bitmark JSON from a bitmark AST as a file
   *
   * @param path - path of file to generate
   * @param bitmarkVersion - bitmark version, use null to use latest version
   * @param fileOptions - file options
   * @param bitmarkOptions - bitmark generation options
   */
  constructor(path5, options) {
    __publicField(this, "generator");
    __publicField(this, "async");
    this.async = options?.async ?? false;
    const writer = this.async ? new FileWriter(path5, options?.fileOptions) : new SyncFileWriter(path5, options?.fileOptions);
    this.generator = new JsonGenerator(writer, options);
  }
  /**
   * Generate bitmark JSON from bitmark AST as a file
   *
   * @param ast bitmark AST
   */
  async generate(ast) {
    return this.generator.generate(ast);
  }
  /**
   * Generate bitmark JSON from bitmark AST as a file synchronously
   *
   * @param ast bitmark AST
   */
  generateSync(_ast) {
    if (this.async) {
      throw new Error("Sync operation not supported");
    }
    this.generator.generateSync(_ast);
  }
};

// src/generator/plainText/PlainTextGenerator.ts
var TEXT_NODE_TYPES = new Set(Object.values(TextNodeType));
var PlainTextGenerator = class {
  /**
   * Generate plain text from a string or JSON object.
   *
   * @param input - A string (plain or JSON-encoded) or a parsed JSON value.
   * @returns The extracted plain text.
   */
  generate(input) {
    let data = input;
    if (typeof data === "string") {
      try {
        data = JSON.parse(data);
      } catch (_e) {
        return data;
      }
    }
    return this.walk(data).trim();
  }
  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------
  walk(value) {
    if (value == null) return "";
    if (typeof value === "string") return value;
    if (typeof value !== "object") return "";
    if (Array.isArray(value)) {
      if (value.length === 0) return "";
      if (this.isTextAst(value)) {
        return this.textAstToPlainText(value);
      }
      return value.map((v) => this.walk(v)).filter(Boolean).join("\n");
    }
    const obj = value;
    if (this.isTextNode(obj)) {
      return this.textNodeToPlainText(obj);
    }
    if (this.isBitWrapper(obj)) {
      return this.walk(obj["bit"]);
    }
    const parts = [];
    for (const val of Object.values(obj)) {
      if (val == null || typeof val !== "object") continue;
      const text = this.walk(val);
      if (text) parts.push(text);
    }
    return parts.join("\n");
  }
  // -- Type guards -----------------------------------------------------------
  isTextNode(obj) {
    return typeof obj["type"] === "string" && TEXT_NODE_TYPES.has(obj["type"]);
  }
  isTextAst(arr) {
    const first = arr[0];
    return typeof first === "object" && first !== null && !Array.isArray(first) && this.isTextNode(first);
  }
  isBitWrapper(obj) {
    return "bit" in obj && typeof obj["bit"] === "object" && obj["bit"] !== null && !Array.isArray(obj["bit"]);
  }
  // -- TextNode extraction ---------------------------------------------------
  textAstToPlainText(ast) {
    return ast.map((node) => this.textNodeToPlainText(node)).join("\n");
  }
  textNodeToPlainText(node) {
    const { type, text, content } = node;
    switch (type) {
      case TextNodeType.text:
        return this.textWithMarks(node);
      case TextNodeType.hardBreak:
        return "\n";
      // Block elements whose children are joined without extra separator
      case TextNodeType.paragraph:
      case TextNodeType.heading:
      case TextNodeType.section:
      case TextNodeType.gap:
      case TextNodeType.select:
      case TextNodeType.highlight:
      case TextNodeType.mark:
      case TextNodeType.codeBlock:
        return content ? content.map((c) => this.textNodeToPlainText(c)).join("") : text ?? "";
      // List items are handled by listToPlainText with indent context
      case TextNodeType.listItem:
        return content ? content.map((c) => this.textNodeToPlainText(c)).join("") : text ?? "";
      // Task item – handled by taskListToPlainText, but fallback if encountered standalone
      case TextNodeType.taskItem: {
        const checked = node.attrs?.checked ?? false;
        const prefix = checked ? "[x] " : "[ ] ";
        const itemText = content ? content.map((c) => this.textNodeToPlainText(c)).join("") : text ?? "";
        return `${prefix}${itemText}`;
      }
      // List containers – rendered with indent-aware helper
      case TextNodeType.noBulletList:
      case TextNodeType.bulletList:
      case TextNodeType.orderedList:
      case TextNodeType.orderedListRoman:
      case TextNodeType.orderedListRomanLower:
      case TextNodeType.letteredList:
      case TextNodeType.letteredListLower:
        return this.listToPlainText(node, 0);
      // Task list – rendered with indent-aware helper
      case TextNodeType.taskList:
        return this.taskListToPlainText(node, 0);
      // Images – return alt text when available
      case TextNodeType.image:
      case TextNodeType.imageInline: {
        const attrs = node.attrs;
        return attrs?.alt ?? "";
      }
      // LaTeX – return the formula source
      case TextNodeType.latex: {
        const latexAttrs = node.attrs;
        return latexAttrs?.formula ?? "";
      }
      default:
        return content ? content.map((c) => this.textNodeToPlainText(c)).join("") : text ?? "";
    }
  }
  listToPlainText(node, depth) {
    const { type, content } = node;
    if (!content || content.length === 0) return "";
    const indent = "  ".repeat(depth);
    const start = node.attrs?.start ?? 1;
    const displayStart = start < 1 ? start + 1 : start;
    return content.map((child, i) => {
      const { inline, nested } = this.splitListItemContent(child, depth);
      const prefix = this.listItemPrefix(type, displayStart + i);
      const line = `${indent}${prefix}${inline}`;
      return nested ? `${line}
${nested}` : line;
    }).join("\n");
  }
  taskListToPlainText(node, depth) {
    const { content } = node;
    if (!content || content.length === 0) return "";
    const indent = "  ".repeat(depth);
    return content.map((child) => {
      const checked = child.attrs?.checked ?? false;
      const prefix = checked ? "[x] " : "[ ] ";
      const { inline, nested } = this.splitListItemContent(child, depth);
      const line = `${indent}${prefix}${inline}`;
      return nested ? `${line}
${nested}` : line;
    }).join("\n");
  }
  splitListItemContent(item, depth) {
    const children = item.content ?? [];
    const inlineParts = [];
    const nestedParts = [];
    for (const child of children) {
      if (this.isListType(child.type)) {
        nestedParts.push(this.renderNestedList(child, depth + 1));
      } else {
        inlineParts.push(this.textNodeToPlainText(child));
      }
    }
    return {
      inline: inlineParts.join(""),
      nested: nestedParts.join("\n")
    };
  }
  isListType(type) {
    return type === TextNodeType.bulletList || type === TextNodeType.orderedList || type === TextNodeType.orderedListRoman || type === TextNodeType.orderedListRomanLower || type === TextNodeType.letteredList || type === TextNodeType.letteredListLower || type === TextNodeType.noBulletList || type === TextNodeType.taskList;
  }
  renderNestedList(node, depth) {
    if (node.type === TextNodeType.taskList) {
      return this.taskListToPlainText(node, depth);
    }
    return this.listToPlainText(node, depth);
  }
  listItemPrefix(listType, index) {
    switch (listType) {
      case TextNodeType.bulletList:
        return "\u2022 ";
      case TextNodeType.orderedList:
        return `${index}. `;
      case TextNodeType.orderedListRoman:
        return `${this.toRoman(index)}. `;
      case TextNodeType.orderedListRomanLower:
        return `${this.toRoman(index).toLowerCase()}. `;
      case TextNodeType.letteredList:
        return `${this.toLetter(index)}. `;
      case TextNodeType.letteredListLower:
        return `${this.toLetter(index).toLowerCase()}. `;
      case TextNodeType.noBulletList:
      default:
        return "";
    }
  }
  toRoman(num) {
    const romanNumerals = [
      [1e3, "M"],
      [900, "CM"],
      [500, "D"],
      [400, "CD"],
      [100, "C"],
      [90, "XC"],
      [50, "L"],
      [40, "XL"],
      [10, "X"],
      [9, "IX"],
      [5, "V"],
      [4, "IV"],
      [1, "I"]
    ];
    let result = "";
    let remaining = num;
    for (const [value, numeral] of romanNumerals) {
      while (remaining >= value) {
        result += numeral;
        remaining -= value;
      }
    }
    return result;
  }
  toLetter(num) {
    let result = "";
    let remaining = num;
    while (remaining > 0) {
      remaining--;
      result = String.fromCharCode(65 + remaining % 26) + result;
      remaining = Math.floor(remaining / 26);
    }
    return result;
  }
  textWithMarks(node) {
    const { text, marks } = node;
    const parts = [];
    const linkMark = marks?.find((m) => m.type === "link");
    const href = linkMark?.attrs?.href;
    if (text && href && text !== href) {
      const hrefBare = href.replace(/^https?:\/\//, "");
      if (text.includes(hrefBare)) {
        parts.push(text.replace(hrefBare, href));
      } else if (text.includes(href)) {
        parts.push(text);
      } else {
        parts.push(`${text} ${href}`);
      }
    } else if (text) {
      parts.push(text);
    } else if (href) {
      parts.push(href);
    }
    if (marks) {
      for (const mark of marks) {
        if (mark.type === "footnote") {
          const footnote = mark;
          if (footnote.attrs?.content) {
            const footnoteText = footnote.attrs.content.map((c) => this.textNodeToPlainText(c)).join("");
            if (footnoteText) parts.push(footnoteText);
          }
        }
      }
    }
    return parts.join(" ");
  }
};

// src/info/ConfigBuilder.ts
import path3 from "path";
import { Enum as Enum22 } from "@ncoderz/superenum";
import fs3 from "fs-extra";
var normalizeCardKey = (cardSetKey) => stringUtils.camelToKebab(cardSetKey);
var ConfigBuilder = class {
  build(options) {
    const opts = Object.assign({}, options);
    const bitConfigs = [];
    const groupConfigs = [];
    const bitGroupConfigKeys = /* @__PURE__ */ new Set();
    const bitGroupConfigs = [];
    for (const bt of Enum22(BitType).values()) {
      const bitType = instance2.getBitType(bt);
      const _bitConfig = BITS[bitType];
      if (_bitConfig?.baseBitType) {
        let currentBitType = _bitConfig.baseBitType;
        while (currentBitType) {
          bitGroupConfigKeys.add(currentBitType);
          const parentConfig = BITS[currentBitType];
          currentBitType = parentConfig?.baseBitType;
        }
      }
    }
    for (const bt of Enum22(BitType).values()) {
      const bitType = instance2.getBitType(bt);
      const _bitConfig = BITS[bitType];
      if (_bitConfig) {
        _bitConfig.bitType = bitType;
        bitConfigs.push(_bitConfig);
      }
    }
    for (const [k, g] of Object.entries(GROUPS)) {
      const g2 = g;
      let k2 = k;
      if (k.startsWith("group_")) k2 = k2.substring(6);
      k2 = /*'_' +*/
      stringUtils.camelToKebab(k2);
      g2.key = k2;
      groupConfigs.push(g2);
    }
    const outputFolder = opts.outputDir ?? "assets/config";
    const outputFolderBits = path3.join(outputFolder, "bits");
    const outputFolderGroups = path3.join(outputFolder, "groups");
    const outputFolderCards = path3.join(outputFolder, "cards");
    fs3.ensureDirSync(outputFolderBits);
    fs3.ensureDirSync(outputFolderGroups);
    fs3.ensureDirSync(outputFolderCards);
    const bitsFiles = fs3.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
    for (const file of bitsFiles) {
      fs3.removeSync(path3.join(outputFolderBits, file));
    }
    const partialsFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
    for (const file of partialsFiles) {
      fs3.removeSync(path3.join(outputFolderGroups, file));
    }
    const cardsFiles = fs3.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc"));
    for (const file of cardsFiles) {
      fs3.removeSync(path3.join(outputFolderCards, file));
    }
    const fileWrites = [];
    const tagEntriesTypeOrder = [
      BitTagConfigKeyType.tag,
      BitTagConfigKeyType.property,
      BitTagConfigKeyType.resource,
      BitTagConfigKeyType.group,
      BitTagConfigKeyType.unknown
    ];
    const exportJsonKeyErrors = [];
    const resolveGroupReferences = (groupKey) => {
      if (squashedGroups.has(groupKey)) {
        const replacements = squashedGroups.get(groupKey);
        return replacements.flatMap(
          (r) => squashedGroups.has(r.key) ? resolveGroupReferences(r.key) : [r]
        );
      }
      return [{ key: groupKey }];
    };
    const mappingKeysField = (base, json, html) => {
      const record = {};
      for (const [id, value] of Object.entries(base ?? {})) {
        if (value != null) record[id] = value;
      }
      if (json.present && json.value != null) record.json = json.value;
      if (html.present && html.value != null) record.html = html.value;
      return Object.keys(record).length > 0 ? { mappingKeys: record } : {};
    };
    const bareKeyForProperty = (key) => {
      if (key.startsWith("@") || key.startsWith("&")) return key.substring(1);
      return void 0;
    };
    const legacyIsTrivialDefault = (tag) => {
      const bare = bareKeyForProperty(tag.key);
      if (bare == null) return false;
      return tag.jsonKey == null || tag.jsonKey === bare;
    };
    const processTagEntries = (tag, pathStack) => {
      const tags2 = [];
      let tagName = tag.key;
      const tagType = typeFromConfigKey(tag.key);
      let format = "";
      let chain = void 0;
      if (tagType === BitTagConfigKeyType.tag) {
        tagName = tag.key;
        if (tag.format === TagFormat.plainText) {
          format = "string";
        } else if (tag.format === TagFormat.boolean) {
          format = "boolean";
        } else if (tag.format === TagFormat.number) {
          format = "number";
        } else {
          format = "bitmark+";
        }
      } else if (tagType === BitTagConfigKeyType.property) {
        tagName = tag.key;
        if (tag.format === TagFormat.plainText) {
          format = "string";
        } else if (tag.format === TagFormat.boolean) {
          format = "boolean";
        } else if (tag.format === TagFormat.bitmarkText) {
          format = "bitmark+";
        } else if (tag.format === TagFormat.number) {
          format = "number";
        } else if (tag.format === TagFormat.coordinates) {
          format = "coordinates";
        }
      } else if (tagType === BitTagConfigKeyType.resource) {
        format = "string";
      } else if (tagType === BitTagConfigKeyType.group) {
        let k = tag.key;
        if (k.startsWith("group_")) k = k.substring(6);
        k = stringUtils.camelToKebab(k);
        const resolvedGroups = resolveGroupReferences(k);
        const hasOuterExport = Object.prototype.hasOwnProperty.call(tag, "exportJsonKey");
        const hasOuterHtml = Object.prototype.hasOwnProperty.call(tag, "htmlKey");
        if (!hasOuterExport && tag.jsonKey != null) {
          const headingPath = [...pathStack, `tags.${tag.key}`].join(" / ");
          exportJsonKeyErrors.push(
            `Missing exportJsonKey for non-default jsonKey at ${headingPath} (legacy: \`${tag.jsonKey}\`)`
          );
        }
        for (const ref of resolvedGroups) {
          const exportJsonKeyChosen = hasOuterExport ? tag.exportJsonKey : ref.exportJsonKey;
          const hasExportChosen = hasOuterExport || !!ref.hasExportJsonKey;
          const htmlKeyChosen = hasOuterHtml ? tag.htmlKey : ref.htmlKey;
          const hasHtmlChosen = hasOuterHtml || !!ref.hasHtmlKey;
          const min = tag.minCount != null ? tag.minCount : ref.min;
          const max = tag.maxCount != null ? tag.maxCount : ref.max;
          const description = tag.description ? tag.description : ref.description;
          tags2.push({
            type: "group",
            key: ref.key,
            ...mappingKeysField(
              { ...ref.extraKeys ?? {}, ...tag.mappingKeys ?? {} },
              { value: exportJsonKeyChosen, present: hasExportChosen },
              { value: htmlKeyChosen, present: hasHtmlChosen }
            ),
            ...min != null ? { min } : {},
            ...max != null ? { max } : {},
            ...description ? { description } : {}
          });
        }
        return tags2;
      }
      if (Array.isArray(tag.chain) && tag.chain.length > 0) {
        const chainTags = [];
        const childPathStack = [...pathStack, `tags.${tag.key}`];
        for (const [, chainTag] of tag.chain.entries()) {
          chainTags.push(...processTagEntries(chainTag, childPathStack));
        }
        chain = chainTags;
      }
      const hasExport = Object.prototype.hasOwnProperty.call(tag, "exportJsonKey");
      if (!hasExport && !legacyIsTrivialDefault(tag)) {
        const headingPath = [...pathStack, `tags.${tag.key}`].join(" / ");
        exportJsonKeyErrors.push(
          `Missing exportJsonKey for non-default jsonKey at ${headingPath}` + (tag.jsonKey ? ` (legacy: \`${tag.jsonKey}\`)` : ` (symbol-mapped tag \`${tag.key}\`)`)
        );
      }
      const hasHtml = Object.prototype.hasOwnProperty.call(tag, "htmlKey");
      const t = {
        type: "tag",
        key: tagName,
        ...mappingKeysField(
          tag.mappingKeys,
          { value: tag.exportJsonKey, present: hasExport },
          { value: tag.htmlKey, present: hasHtml }
        ),
        ...format && format !== "string" ? { format } : {},
        ...tag.defaultValue != null ? { default: tag.defaultValue } : {},
        ...tag.nullable ? { nullable: true } : {},
        ...tag.minCount != null && tag.minCount !== 0 ? { min: tag.minCount } : {},
        ...tag.maxCount != null && tag.maxCount !== 1 ? { max: tag.maxCount } : {},
        description: tag.description ?? "",
        tags: chain
      };
      tags2.push(t);
      return tags2;
    };
    const cardMappingKeysField = (container, base, legacyKey, exportKey, htmlExportKey, pathHeading) => {
      const c = container;
      const hasExport = Object.prototype.hasOwnProperty.call(c, exportKey);
      if (!hasExport && c[legacyKey] != null) {
        exportJsonKeyErrors.push(
          `Missing exportJsonKey for non-default jsonKey at ${pathHeading} (legacy: \`${c[legacyKey]}\`)`
        );
      }
      return mappingKeysField(
        base,
        { value: c[exportKey], present: hasExport },
        {
          value: c[htmlExportKey],
          present: Object.prototype.hasOwnProperty.call(c, htmlExportKey)
        }
      );
    };
    const serializeCardSet = (cardSetKey) => {
      const variantBodyFormat = (variant) => variant.format ?? TextFormat.bitmarkText;
      const cardSetConfig = CARDSETS[cardSetKey];
      if (!cardSetConfig) return void 0;
      const normalizedKey = normalizeCardKey(cardSetKey);
      const variantTagEntriesTypeOrder = [
        BitTagConfigKeyType.group,
        BitTagConfigKeyType.tag,
        BitTagConfigKeyType.property,
        BitTagConfigKeyType.resource,
        BitTagConfigKeyType.unknown
      ];
      const sides = cardSetConfig.sides.map((side, sideIdx) => {
        const sidePath = `cardSets.${normalizedKey} / sides.${side.name ?? `[${sideIdx}]`}`;
        const variants = side.variants.map((variant, variantIdx) => {
          const variantPath = `${sidePath} / variants.[${variantIdx}]`;
          const variantTags = [];
          const variantTagEntries = Object.entries(variant.tags ?? []).sort((a, b) => {
            const tagA = a[1];
            const tagB = b[1];
            const typeA = typeFromConfigKey(tagA.key);
            const typeB = typeFromConfigKey(tagB.key);
            const typeOrder = variantTagEntriesTypeOrder.indexOf(typeA) - variantTagEntriesTypeOrder.indexOf(typeB);
            return typeOrder;
          });
          for (const [, variantTag] of variantTagEntries) {
            variantTags.push(...processTagEntries(variantTag, [variantPath]));
          }
          return {
            ...cardMappingKeysField(
              variant,
              void 0,
              "jsonKey",
              "exportJsonKey",
              "htmlKey",
              variantPath
            ),
            bodyFormat: variantBodyFormat(variant),
            tags: variantTags,
            ...variant.repeatCount != null && variant.repeatCount !== 1 ? { repeatCount: variant.repeatCount } : {},
            ...variant.bodyRequired ? { bodyRequired: true } : {},
            ...variant.bodyAllowed === false ? { bodyForbidden: true } : {}
          };
        });
        return {
          name: side.name,
          ...side.repeat ? { repeat: side.repeat } : {},
          ...cardMappingKeysField(
            side,
            side.mappingKeys,
            "jsonKey",
            "exportJsonKey",
            "htmlKey",
            sidePath
          ),
          variants
        };
      });
      if (cardSetConfig.sections) {
        const cards = Object.entries(cardSetConfig.sections).map(([sectionName, section]) => {
          const sectionPath = `cardSets.${normalizedKey} / cards.${sectionName}`;
          const hasSideExport = Object.prototype.hasOwnProperty.call(section, "sideExportJsonKey");
          const hasSideHtml = Object.prototype.hasOwnProperty.call(section, "sideHtmlKey");
          let cardSides = sides;
          if (hasSideExport || section.sideJsonKey != null || hasSideHtml) {
            if (!hasSideExport && section.sideJsonKey != null) {
              exportJsonKeyErrors.push(
                `Missing exportJsonKey for non-default jsonKey at ${sectionPath} / sides.* (legacy: \`${section.sideJsonKey}\`)`
              );
            }
            cardSides = sides.map((s) => {
              const record = { ...s.mappingKeys ?? {} };
              if (hasSideExport) {
                if (section.sideExportJsonKey != null) record.json = section.sideExportJsonKey;
                else delete record.json;
              }
              if (hasSideHtml) {
                if (section.sideHtmlKey != null) record.html = section.sideHtmlKey;
                else delete record.html;
              }
              return {
                name: s.name,
                ...s.repeat ? { repeat: s.repeat } : {},
                ...Object.keys(record).length > 0 ? { mappingKeys: record } : {},
                variants: s.variants
              };
            });
          }
          return {
            name: sectionName,
            ...section.isDefault ? { isDefault: true } : {},
            ...cardMappingKeysField(
              section,
              section.mappingKeys,
              "jsonKey",
              "exportJsonKey",
              "htmlKey",
              sectionPath
            ),
            // PLAN-085: per-section cardinality. Emit only when non-default
            // (treat `0` / undefined as "unbounded — omit").
            ...section.minCount != null && section.minCount !== 0 ? { min: section.minCount } : {},
            ...section.maxCount != null && section.maxCount !== 0 ? { max: section.maxCount } : {},
            sides: cardSides
          };
        });
        return {
          name: normalizedKey,
          ...mappingKeysField(
            void 0,
            { present: false },
            {
              value: cardSetConfig.htmlKey,
              present: Object.prototype.hasOwnProperty.call(cardSetConfig, "htmlKey")
            }
          ),
          cards
        };
      }
      const cardSetPath = `cardSets.${normalizedKey} / cards.default`;
      return {
        name: normalizedKey,
        cards: [
          {
            name: "default",
            isDefault: true,
            ...cardMappingKeysField(
              cardSetConfig,
              cardSetConfig.mappingKeys,
              "jsonKey",
              "exportJsonKey",
              "htmlKey",
              cardSetPath
            ),
            sides
          }
        ]
      };
    };
    for (const bt of bitGroupConfigKeys) {
      const bitType = instance2.getBitType(bt);
      const _bitConfig = BITS[bitType];
      if (_bitConfig) {
        _bitConfig.bitType = bitType;
        if (!bitGroupConfigs.some((c) => c.bitType === bitType)) {
          bitGroupConfigs.push(_bitConfig);
        }
      }
    }
    const squashedGroups = /* @__PURE__ */ new Map();
    for (const b of bitGroupConfigs) {
      const groupKey = `group-${b.bitType}`;
      const processedTags = [];
      for (const [, tag] of Object.entries(b.tags ?? [])) {
        processedTags.push(...processTagEntries(tag, []));
      }
      const allAreGroups = processedTags.every((t) => t.type === "group");
      if (allAreGroups && processedTags.length > 0) {
        const replacements = [];
        if (b.baseBitType) {
          replacements.push({ key: `group-${b.baseBitType}` });
        }
        for (const t of processedTags) {
          const ref = {
            key: t.key,
            ...t.min != null ? { min: t.min } : {},
            ...t.max != null ? { max: t.max } : {},
            ...t.description ? { description: t.description } : {}
          };
          const record = t.mappingKeys ?? {};
          if (Object.prototype.hasOwnProperty.call(record, "json")) {
            ref.exportJsonKey = record.json;
            ref.hasExportJsonKey = true;
          }
          if (Object.prototype.hasOwnProperty.call(record, "html")) {
            ref.htmlKey = record.html;
            ref.hasHtmlKey = true;
          }
          const extras = Object.fromEntries(
            Object.entries(record).filter(([id]) => id !== "json" && id !== "html")
          );
          if (Object.keys(extras).length > 0) ref.extraKeys = extras;
          replacements.push(ref);
        }
        squashedGroups.set(groupKey, replacements);
      }
    }
    for (const b of bitConfigs) {
      const resolvedBitConfig = instance2.getBitConfig(b.bitType);
      const tags2 = [];
      const tagEntries = Object.entries(b.tags ?? []).sort((a, b2) => {
        const tagA = a[1];
        const tagB = b2[1];
        const typeA = typeFromConfigKey(tagA.key);
        const typeB = typeFromConfigKey(tagB.key);
        const typeOrder = tagEntriesTypeOrder.indexOf(typeA) - tagEntriesTypeOrder.indexOf(typeB);
        return typeOrder;
      });
      const isInheritedFrom = bitGroupConfigKeys.has(b.bitType);
      const indexByKey = /* @__PURE__ */ new Map();
      const pushOrReplace = (entry) => {
        const existing = indexByKey.get(entry.key);
        if (existing !== void 0) {
          tags2[existing] = entry;
        } else {
          indexByKey.set(entry.key, tags2.length);
          tags2.push(entry);
        }
      };
      if (isInheritedFrom) {
        const resolvedGroups = resolveGroupReferences(`group-${b.bitType}`);
        for (const ref of resolvedGroups) {
          pushOrReplace({
            type: "group",
            key: ref.key,
            ...mappingKeysField(
              ref.extraKeys,
              { value: ref.exportJsonKey, present: !!ref.hasExportJsonKey },
              { value: ref.htmlKey, present: !!ref.hasHtmlKey }
            ),
            ...ref.min != null ? { min: ref.min } : {},
            ...ref.max != null ? { max: ref.max } : {},
            ...ref.description ? { description: ref.description } : {}
          });
        }
      } else {
        if (b.baseBitType) {
          const resolvedGroups = resolveGroupReferences(`group-${b.baseBitType}`);
          for (const ref of resolvedGroups) {
            pushOrReplace({
              type: "group",
              key: ref.key,
              ...mappingKeysField(
                ref.extraKeys,
                { value: ref.exportJsonKey, present: !!ref.hasExportJsonKey },
                { value: ref.htmlKey, present: !!ref.hasHtmlKey }
              ),
              ...ref.min != null ? { min: ref.min } : {},
              ...ref.max != null ? { max: ref.max } : {},
              ...ref.description ? { description: ref.description } : {}
            });
          }
        }
        for (const [_tagKey, tag] of tagEntries) {
          for (const entry of processTagEntries(tag, [])) {
            pushOrReplace(entry);
          }
        }
      }
      let cardRef;
      let current = b;
      while (current) {
        if (current.cardSet && CARDSETS[current.cardSet]) {
          cardRef = normalizeCardKey(current.cardSet);
          break;
        }
        current = current.baseBitType ? BITS[current.baseBitType] : void 0;
      }
      const bitJson = {
        name: b.bitType,
        description: b.description ?? "",
        since: resolvedBitConfig.since,
        deprecated: resolvedBitConfig.deprecated,
        bodyFormat: resolvedBitConfig.textFormatDefault ?? "bitmark--",
        ...resolvedBitConfig.bodyRequired ? { bodyRequired: true } : {},
        ...resolvedBitConfig.bodyAllowed === false ? { bodyForbidden: true } : {},
        ...resolvedBitConfig.footerRequired ? { footerRequired: true } : {},
        ...resolvedBitConfig.footerAllowed === false ? { footerForbidden: true } : {},
        ...resolvedBitConfig.resourceAttachmentAllowed === false ? { resourceAttachmentAllowed: false } : {},
        // Bit-level named key mappings (element keys for markup import),
        // carried verbatim from the raw config.
        ...mappingKeysField(b.mappingKeys, { present: false }, { present: false }),
        tags: tags2,
        ...cardRef ? { cardSet: cardRef } : {}
      };
      const output = path3.join(outputFolderBits, `${b.bitType}.jsonc`);
      const str = JSON.stringify(bitJson, null, 2);
      fileWrites.push(fs3.writeFile(output, str));
    }
    const writeGroupConfigs = (groupConfigs2) => {
      for (const g of groupConfigs2) {
        const tags2 = [];
        const groupKey = stringUtils.camelToKebab(g.key);
        const tagEntriesTypeOrder2 = [
          BitTagConfigKeyType.tag,
          BitTagConfigKeyType.property,
          BitTagConfigKeyType.resource,
          BitTagConfigKeyType.group
        ];
        const tagEntries = Object.entries(g.tags ?? []).sort((a, b) => {
          const tagA = a[1];
          const tagB = b[1];
          const typeOrder = tagEntriesTypeOrder2.indexOf(tagA.type) - tagEntriesTypeOrder2.indexOf(tagB.type);
          return typeOrder;
        });
        for (const [_tagKey, tag] of tagEntries) {
          tags2.push(...processTagEntries(tag, []));
        }
        const bitJson = {
          name: groupKey,
          description: g.description ?? "",
          deprecated: g.deprecated,
          tags: tags2
          // cards: [
          //   {
          //     card: null,
          //     side: 1,
          //     variant: null,
          //     description: '',
          //     tags: [],
          //   },
          //   {
          //     card: null,
          //     side: 2,
          //     variant: 1,
          //     description: '',
          //     tags: [],
          //   },
          //   {
          //     card: null,
          //     side: 2,
          //     variant: null,
          //     description: '',
          //     tags: [],
          //   },
          // ],
        };
        const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
        const str = JSON.stringify(bitJson, null, 2);
        fs3.writeFileSync(output, str);
      }
    };
    writeGroupConfigs(groupConfigs);
    const writeCardConfigs = () => {
      for (const cardSetKey of Object.keys(CARDSETS)) {
        const cardJson = serializeCardSet(cardSetKey);
        if (!cardJson) continue;
        const output = path3.join(outputFolderCards, `${cardJson.name}.jsonc`);
        const str = JSON.stringify(cardJson, null, 2);
        fileWrites.push(fs3.writeFile(output, str));
      }
    };
    const writeBitsAsGroupConfigs = (bitsAsGroupConfigs) => {
      for (const b of bitsAsGroupConfigs) {
        const groupKey = `group-${b.bitType}`;
        if (squashedGroups.has(groupKey)) {
          continue;
        }
        const tags2 = [];
        if (b.baseBitType) {
          const resolvedGroups = resolveGroupReferences(`group-${b.baseBitType}`);
          for (const ref of resolvedGroups) {
            tags2.push({
              type: "group",
              key: ref.key,
              ...mappingKeysField(
                ref.extraKeys,
                { value: ref.exportJsonKey, present: !!ref.hasExportJsonKey },
                { value: ref.htmlKey, present: !!ref.hasHtmlKey }
              ),
              ...ref.min != null ? { min: ref.min } : {},
              ...ref.max != null ? { max: ref.max } : {},
              ...ref.description ? { description: ref.description } : {}
            });
          }
        }
        const tagEntries = Object.entries(b.tags ?? []).sort((a, b2) => {
          const tagA = a[1];
          const tagB = b2[1];
          const typeA = typeFromConfigKey(tagA.key);
          const typeB = typeFromConfigKey(tagB.key);
          const typeOrder = tagEntriesTypeOrder.indexOf(typeA) - tagEntriesTypeOrder.indexOf(typeB);
          return typeOrder;
        });
        for (const [_tagKey, tag] of tagEntries) {
          tags2.push(...processTagEntries(tag, []));
        }
        const bitJson = {
          name: groupKey,
          description: b.description ?? "",
          deprecated: b.deprecated,
          tags: tags2
        };
        const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
        const str = JSON.stringify(bitJson, null, 2);
        fs3.writeFileSync(output, str);
      }
    };
    writeBitsAsGroupConfigs(bitGroupConfigs);
    writeCardConfigs();
    if (exportJsonKeyErrors.length > 0) {
      console.error("\n\u26A0\uFE0F  exportJsonKey validation errors:");
      for (const e of exportJsonKeyErrors) console.error(`  - ${e}`);
      throw new Error(
        `exportJsonKey validation failed: ${exportJsonKeyErrors.length} entr${exportJsonKeyErrors.length === 1 ? "y" : "ies"} with non-default jsonKey but no exportJsonKey companion.`
      );
    }
    Promise.all(fileWrites).then(() => {
      const validationErrors = this.validateConfigTree(outputFolder);
      if (validationErrors.length > 0) {
        console.error("\n\u26A0\uFE0F  Config tree validation errors:");
        for (const error of validationErrors) {
          console.error(`  - ${error}`);
        }
        throw new Error(`Config tree validation failed with ${validationErrors.length} error(s)`);
      } else {
        console.log("\u2705 Config tree validation passed");
      }
    }).catch((err) => {
      console.error("Error during config build or validation:", err);
      throw err;
    });
  }
  /**
   * Validate the config tree for missing group references
   * @param outputFolder - The folder containing the config files
   * @returns Array of validation errors, empty if valid
   */
  validateConfigTree(outputFolder) {
    const errors = [];
    const outputFolderBits = path3.join(outputFolder, "bits");
    const outputFolderGroups = path3.join(outputFolder, "groups");
    const outputFolderCards = path3.join(outputFolder, "cards");
    const availableGroups = /* @__PURE__ */ new Set();
    if (fs3.existsSync(outputFolderGroups)) {
      const groupFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
      for (const file of groupFiles) {
        const groupName = file.replace(".jsonc", "");
        availableGroups.add(groupName);
      }
    }
    const availableCards = /* @__PURE__ */ new Set();
    const cardFiles = fs3.existsSync(outputFolderCards) ? fs3.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc")) : [];
    for (const file of cardFiles) {
      const cardName = file.replace(".jsonc", "");
      availableCards.add(cardName);
    }
    const checkTags = (tags2, filePath, lineOffset) => {
      for (let i = 0; i < tags2.length; i++) {
        const tag = tags2[i];
        if (tag?.type === "group" && tag.key) {
          if (!availableGroups.has(tag.key)) {
            const line = lineOffset + i + 1;
            errors.push(`Missing group reference '${tag.key}' in ${filePath}:${line}`);
          }
        }
        if (Array.isArray(tag?.tags)) {
          checkTags(tag.tags, filePath, lineOffset + i + 1);
        }
      }
    };
    if (fs3.existsSync(outputFolderBits)) {
      const bitFiles = fs3.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
      for (const file of bitFiles) {
        const filePath = path3.join(outputFolderBits, file);
        try {
          const content = fs3.readFileSync(filePath, "utf-8");
          const config = JSON.parse(content);
          if (config.tags && Array.isArray(config.tags)) {
            checkTags(config.tags, `bits/${file}`, 15);
          }
          if (typeof config.card === "string") {
            if (!availableCards.has(config.card)) {
              errors.push(`Missing card file '${config.card}' referenced in bits/${file}`);
            }
          } else if (config.card?.sides && Array.isArray(config.card.sides)) {
            config.card.sides.forEach((side, sideIdx) => {
              const variants = side.variants;
              if (Array.isArray(variants)) {
                variants.forEach((variant, variantIdx) => {
                  const variantTags = variant.tags;
                  if (Array.isArray(variantTags)) {
                    checkTags(
                      variantTags,
                      `bits/${file} (card side ${sideIdx}, variant ${variantIdx})`,
                      15
                    );
                  }
                });
              }
            });
          }
        } catch (err) {
          errors.push(`Failed to parse ${file}: ${err}`);
        }
      }
    }
    if (fs3.existsSync(outputFolderGroups)) {
      const groupFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
      for (const file of groupFiles) {
        const filePath = path3.join(outputFolderGroups, file);
        try {
          const content = fs3.readFileSync(filePath, "utf-8");
          const config = JSON.parse(content);
          if (config.tags && Array.isArray(config.tags)) {
            checkTags(config.tags, `groups/${file}`, 15);
          }
        } catch (err) {
          errors.push(`Failed to parse ${file}: ${err}`);
        }
      }
    }
    if (cardFiles.length > 0) {
      for (const file of cardFiles) {
        const filePath = path3.join(outputFolderCards, file);
        try {
          const content = fs3.readFileSync(filePath, "utf-8");
          const config = JSON.parse(content);
          const expectedKey = file.replace(".jsonc", "");
          if (config.key && config.key !== expectedKey) {
            errors.push(`Card key mismatch in cards/${file}: expected '${expectedKey}'`);
          }
          if (Array.isArray(config.cards)) {
            for (const cardNode of config.cards) {
              if (cardNode && Array.isArray(cardNode.sides)) {
                cardNode.sides.forEach((side, sideIdx) => {
                  const variants = side.variants;
                  if (Array.isArray(variants)) {
                    variants.forEach((variant, variantIdx) => {
                      const variantTags = variant.tags;
                      if (Array.isArray(variantTags)) {
                        checkTags(
                          variantTags,
                          `cards/${file} (side ${sideIdx}, variant ${variantIdx})`,
                          10
                        );
                      }
                    });
                  }
                });
              }
            }
          }
        } catch (err) {
          errors.push(`Failed to parse cards/${file}: ${err}`);
        }
      }
    }
    return errors;
  }
};

// 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 && fs4.existsSync(inStr)) {
          inStr = fs4.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) {
        fs4.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) {
        fs4.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) {
        fs4.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 && fs4.existsSync(inStr)) {
          inStr = fs4.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 && fs4.existsSync(inStr)) {
          inStr = fs4.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 = Enum23(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 && fs4.existsSync(inStr)) {
          inStr = fs4.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";
      fs4.ensureDirSync(path4.dirname(output));
      fs4.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 && fs4.existsSync(inStr)) {
      inStr = fs4.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: Enum23(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";
      fs4.ensureDirSync(path4.dirname(output));
      fs4.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 = Enum23(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 && fs4.existsSync(inStr)) {
          inStr = fs4.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    const res = instance3.breakscape(inStr, {
      format: textFormat,
      location: textLocation
    });
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      const flag = fileOptions.append ? "a" : "w";
      fs4.ensureDirSync(path4.dirname(output));
      fs4.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 = Enum23(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 && fs4.existsSync(inStr)) {
          inStr = fs4.readFileSync(inStr, {
            encoding: "utf8"
          });
        }
      }
    }
    const res = instance3.unbreakscape(inStr, {
      format: textFormat,
      location: textLocation
    });
    if (opts.outputFile) {
      const output = opts.outputFile.toString();
      const flag = fileOptions.append ? "a" : "w";
      fs4.ensureDirSync(path4.dirname(output));
      fs4.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,
  BitType,
  BitmarkFileGenerator,
  BitmarkGenerator,
  BitmarkParser,
  BitmarkParserGenerator,
  BitmarkParserType,
  BitmarkStringGenerator,
  BitmarkVersion,
  BodyTextFormat,
  BoundingBoxIndex,
  Builder,
  CardSetVersion,
  FileWriter,
  HtmlTableGenerator,
  HtmlTableParser,
  InfoFormat,
  InfoType,
  Input,
  InputFormat,
  JsonFileGenerator,
  JsonGenerator,
  JsonParser,
  JsonStringGenerator,
  NodeType,
  Output,
  ResourceBuilder,
  StreamWriter,
  StringWriter,
  TextMarkType,
  TextNodeType,
  parse as bitmarkTextParse
};
//# sourceMappingURL=index.js.map